diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000000..e577ab3c1169 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +.gitattributes export-ignore +R-package/* export-ignore diff --git a/.gitignore b/.gitignore index 95f205301881..82d2e560237d 100644 --- a/.gitignore +++ b/.gitignore @@ -129,7 +129,7 @@ lib .DS_Store #Notebook Automated Test -!tests/nightly/test_config.txt +!tests/nightly/test_tutorial_config.txt !tests/nightly/TestNotebook # pip building tools diff --git a/.gitmodules b/.gitmodules index 08f2bc99f2aa..c1bed70a86c7 100644 --- a/.gitmodules +++ b/.gitmodules @@ -10,7 +10,9 @@ [submodule "nnvm"] path = nnvm url = https://github.com/dmlc/nnvm +[submodule "dlpack"] + path = dlpack + url = https://github.com/dmlc/dlpack [submodule "cub"] path = cub - url = https://github.com/NVlabs/cub - shallow=true + url = https://github.com/dmlc/cub.git diff --git a/.travis.yml b/.travis.yml index c8ba0b1e645b..ca5d03b5008d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -68,6 +68,8 @@ addons: - python3-numpy - python3-dev - python3-nose + - python-h5py + - python3-h5py - graphviz - libmouse-perl - pdl diff --git a/CMakeLists.txt b/CMakeLists.txt index c8260e94e9bc..dc9ca5f7bb0c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,6 +13,7 @@ mxnet_option(USE_OPENCV "Build with OpenCV support" ON) mxnet_option(USE_OPENMP "Build with Openmp support" ON) mxnet_option(USE_CUDA "Build with CUDA support" ON) mxnet_option(USE_CUDNN "Build with cudnn support" ON) # one could set CUDNN_ROOT for search path +mxnet_option(USE_LAPACK "Build with lapack support" ON IF NOT MSVC) mxnet_option(USE_MKL_IF_AVAILABLE "Use MKL if found" ON) mxnet_option(USE_MKLML_MKL "Use MKLML variant of MKL (if MKL found)" ON IF USE_MKL_IF_AVAILABLE AND UNIX AND (NOT APPLE)) mxnet_option(USE_MKL_EXPERIMENTAL "Use experimental MKL (if MKL enabled and found)" OFF) @@ -142,6 +143,12 @@ include_directories("mshadow") include_directories("cub") include_directories("nnvm/include") include_directories("dmlc-core/include") +include_directories("dlpack/include") + +# commented out until PR goes through +#if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/dlpack) +# add_subdirectory(dlpack) +#endif() if(NOT MSVC) set(BEGIN_WHOLE_ARCHIVE -Wl,--whole-archive) @@ -176,7 +183,11 @@ if(USE_OPENCV) message(STATUS "OpenCV found (${OpenCV_CONFIG_PATH})") add_definitions(-DMXNET_USE_OPENCV=1) if(NOT MSVC) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wl,--no-undefined") + if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wl,-undefined,error") + else() + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wl,--no-undefined") + endif() endif() else(USE_OPENCV) message(STATUS "OpenCV Disabled") @@ -191,8 +202,21 @@ if(USE_OPENMP) set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${OpenMP_EXE_LINKER_FLAGS}") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${OpenMP_EXE_LINKER_FLAGS}") endif() +elseif(UNIX) + list(APPEND mxnet_LINKER_LIBS pthread) +endif() + +if(USE_LAPACK) + add_definitions(-DMXNET_USE_LAPACK=1) + list(APPEND mxnet_LINKER_LIBS lapack) +else(USE_LAPACK) + # Workaround for Windows until using new Jenkinsfile. + if(USE_BLAS STREQUAL "open") + add_definitions(-DMXNET_USE_LAPACK=1) + endif() endif() + if(UNIX) find_library(RTLIB rt) if(RTLIB) @@ -329,8 +353,10 @@ if(USE_CUDA) list(APPEND mxnet_LINKER_LIBS ${CUDA_cuda_LIBRARY}) FIND_LIBRARY(CUDA_cufft_LIBRARY nvrtc "${CUDA_TOOLKIT_ROOT_DIR}/lib/x64" "${CUDA_TOOLKIT_ROOT_DIR}/lib/win32") list(APPEND mxnet_LINKER_LIBS "${CUDA_cufft_LIBRARY}/../cufft.lib") # For fft operator + FIND_LIBRARY(CUDA_cusolver_LIBRARY nvrtc "${CUDA_TOOLKIT_ROOT_DIR}/lib/x64" "${CUDA_TOOLKIT_ROOT_DIR}/lib/win32") + list(APPEND mxnet_LINKER_LIBS "${CUDA_cusolver_LIBRARY}/../cusolver.lib") # For cusolver else(MSVC) - list(APPEND mxnet_LINKER_LIBS nvrtc cuda cufft) + list(APPEND mxnet_LINKER_LIBS nvrtc cuda cufft cusolver) link_directories("${CUDA_TOOLKIT_ROOT_DIR}/lib64") endif() list(APPEND SOURCE ${cuda_objs} ${CUDA}) @@ -425,7 +451,6 @@ if(USE_PROFILER) add_definitions(-DMXNET_USE_PROFILER) endif() -# Do tests after chrpath so that we use the "real" cuda driver add_subdirectory(tests) # AUTO_INSTALL_DIR -> Optional: specify post-build install direcory @@ -458,7 +483,10 @@ if(USE_CPP_PACKAGE) add_subdirectory(cpp-package) endif() -add_subdirectory(example/image-classification/predict-cpp) +# Problems on Mac OS X: 1. librt not available 2. mxnet built as MODULE library, which can't be linked. +if(!${CMAKE_SYSTEM_NAME} STREQUAL "Darwin") + add_subdirectory(example/image-classification/predict-cpp) +endif() # ---[ Linter target if(MSVC) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index dbbb73b1361a..8cae93854e19 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -137,3 +137,12 @@ List of Contributors * [Roshani Nagmote](https://github.com/Roshrini) * [Chetan Khatri](https://github.com/chetkhatri/) * [James Liu](https://github.com/jamesliu/) +* [Nir Ben-Zvi](https://github.com/nirbenz/) +* [Arik Poznanski](https://github.com/arikpoz/) +* [Yuwen Xiong](https://github.com/Orpine/) +* [Haozhi Qi](https://github.com/Oh233/) +* [Yi Li](https://github.com/liyi14/) +* [Guodong Zhang](https://github.com/gd-zhang/) +* [Xizhou Zhu](https://github.com/einsiedler0408/) +* [Jean Kossaifi](https://github.com/JeanKossaifi/) +* [Kenta Kubo](https://github.com/kkk669/) diff --git a/DISCLAIMER b/DISCLAIMER new file mode 100644 index 000000000000..8adc57f6e6b0 --- /dev/null +++ b/DISCLAIMER @@ -0,0 +1,12 @@ +Apache MXNet (incubating) is an effort undergoing incubation at The +Apache Software Foundation (ASF), sponsored by the Apache Incubator PMC. + +Incubation is required of all newly accepted +projects until a further review indicates that the +infrastructure, communications, and decision making process have +stabilized in a manner consistent with other successful ASF +projects. + +While incubation status is not necessarily a reflection +of the completeness or stability of the code, it does indicate +that the project has yet to be fully endorsed by the ASF. diff --git a/Jenkinsfile b/Jenkinsfile index 2f4406856288..95115cf58920 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -8,34 +8,36 @@ mx_lib = 'lib/libmxnet.so, lib/libmxnet.a, dmlc-core/libdmlc.a, nnvm/lib/libnnvm docker_run = 'tests/ci_build/ci_build.sh' // timeout in minutes max_time = 60 +// assign any caught errors here +err = null +// set build status to success by default +currentBuild.result = "SUCCESS" // initialize source codes def init_git() { - checkout scm retry(5) { - timeout(time: 2, unit: 'MINUTES') { - sh 'git submodule update --init' + try { + timeout(time: 2, unit: 'MINUTES') { + checkout scm + sh 'git submodule update --init' + } + } catch (exc) { + deleteDir() + error "Failed to fetch source codes" } } } def init_git_win() { - checkout scm - retry(5) { - timeout(time: 2, unit: 'MINUTES') { - bat 'git submodule update --init' - } - } -} - -stage("Sanity Check") { - timeout(time: max_time, unit: 'MINUTES') { - node('linux') { - ws('workspace/sanity') { - init_git() - make('lint', 'cpplint rcpplint jnilint') - make('lint', 'pylint') + retry(5) { + try { + timeout(time: 2, unit: 'MINUTES') { + checkout scm + bat 'git submodule update --init' } + } catch (exc) { + deleteDir() + error "Failed to fetch source codes" } } } @@ -50,6 +52,7 @@ def make(docker_type, make_flag) { } catch (exc) { echo 'Incremental compilation failed. Fall back to build from scratch' sh "${docker_run} ${docker_type} sudo make clean" + sh "${docker_run} ${docker_type} sudo make -C amalgamation/ clean" sh "${docker_run} ${docker_type} make ${make_flag}" } } @@ -74,140 +77,14 @@ echo ${libs} | sed -e 's/,/ /g' | xargs md5sum """ } -stage('Build') { - parallel 'CPU: Openblas': { - node('linux') { - ws('workspace/build-cpu') { - init_git() - def flag = """ \ -DEV=1 \ -USE_PROFILER=1 \ -USE_CPP_PACKAGE=1 \ -USE_BLAS=openblas \ --j\$(nproc) -""" - make("cpu", flag) - pack_lib('cpu') - } - } - }, - 'GPU: CUDA7.5+cuDNN5': { - node('GPU' && 'linux') { - ws('workspace/build-gpu') { - init_git() - def flag = """ \ -DEV=1 \ -USE_PROFILER=1 \ -USE_BLAS=openblas \ -USE_CUDA=1 \ -USE_CUDA_PATH=/usr/local/cuda \ -USE_CUDNN=1 \ -USE_CPP_PACKAGE=1 \ --j\$(nproc) -""" - make('gpu', flag) - pack_lib('gpu') - stash includes: 'build/cpp-package/example/test_score', name: 'cpp_test_score' - } - } - }, - 'Amalgamation': { - node('linux') { - ws('workspace/amalgamation') { - init_git() - make('cpu', '-C amalgamation/ USE_BLAS=openblas MIN=1') - } - } - }, - 'GPU: MKLML': { - node('GPU' && 'linux') { - ws('workspace/build-mklml') { - init_git() - def flag = """ \ -DEV=1 \ -USE_PROFILER=1 \ -USE_BLAS=openblas \ -USE_MKL2017=1 \ -USE_MKL2017_EXPERIMENTAL=1 \ -USE_CUDA=1 \ -USE_CUDA_PATH=/usr/local/cuda \ -USE_CUDNN=1 \ -USE_CPP_PACKAGE=1 \ --j\$(nproc) -""" - make('mklml_gpu', flag) - pack_lib('mklml') - } - } - }, - 'CPU windows':{ - node('windows') { - ws('workspace/build-cpu') { - withEnv(['OpenBLAS_HOME=C:\\mxnet\\openblas', 'OpenCV_DIR=C:\\mxnet\\opencv_vc14', 'CUDA_PATH=C:\\CUDA\\v8.0']) { - init_git_win() - bat """mkdir build_vc14_cpu -cd build_vc14_cpu -cmake -G \"Visual Studio 14 2015 Win64\" -DUSE_CUDA=0 -DUSE_CUDNN=0 -DUSE_NVRTC=0 -DUSE_OPENCV=1 -DUSE_OPENMP=1 -DUSE_PROFILER=1 -DUSE_BLAS=open -DUSE_DIST_KVSTORE=0 ${env.WORKSPACE}""" - bat 'C:\\mxnet\\build_vc14_cpu.bat' - - bat '''rmdir /s/q pkg_vc14_gpu -mkdir pkg_vc14_cpu\\lib -mkdir pkg_vc14_cpu\\python -mkdir pkg_vc14_cpu\\include -mkdir pkg_vc14_cpu\\build -copy build_vc14_cpu\\Release\\libmxnet.lib pkg_vc14_cpu\\lib -copy build_vc14_cpu\\Release\\libmxnet.dll pkg_vc14_cpu\\build -xcopy python pkg_vc14_cpu\\python /E /I /Y -xcopy include pkg_vc14_cpu\\include /E /I /Y -xcopy dmlc-core\\include pkg_vc14_cpu\\include /E /I /Y -xcopy mshadow\\mshadow pkg_vc14_cpu\\include\\mshadow /E /I /Y -xcopy nnvm\\include pkg_vc14_cpu\\nnvm\\include /E /I /Y -del /Q *.7z -7z.exe a vc14_cpu.7z pkg_vc14_cpu\\ -''' - stash includes: 'vc14_cpu.7z', name: 'vc14_cpu' - } - } - } - }, - 'GPU windows':{ - node('windows') { - ws('workspace/build-gpu') { - withEnv(['OpenBLAS_HOME=C:\\mxnet\\openblas', 'OpenCV_DIR=C:\\mxnet\\opencv_vc14', 'CUDA_PATH=C:\\CUDA\\v8.0']) { - init_git_win() - bat """mkdir build_vc14_gpu -call "C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\bin\\x86_amd64\\vcvarsx86_amd64.bat" -cd build_vc14_gpu -cmake -G \"NMake Makefiles JOM\" -DUSE_CUDA=1 -DUSE_CUDNN=1 -DUSE_NVRTC=1 -DUSE_OPENCV=1 -DUSE_OPENMP=1 -DUSE_PROFILER=1 -DUSE_BLAS=open -DUSE_DIST_KVSTORE=0 -DCUDA_ARCH_NAME=All -DCMAKE_CXX_FLAGS_RELEASE="/FS /MD /O2 /Ob2 /DNDEBUG" -DCMAKE_BUILD_TYPE=Release ${env.WORKSPACE}""" - bat 'C:\\mxnet\\build_vc14_gpu.bat' - bat '''rmdir /s/q pkg_vc14_gpu -mkdir pkg_vc14_gpu\\lib -mkdir pkg_vc14_gpu\\python -mkdir pkg_vc14_gpu\\include -mkdir pkg_vc14_gpu\\build -copy build_vc14_gpu\\libmxnet.lib pkg_vc14_gpu\\lib -copy build_vc14_gpu\\libmxnet.dll pkg_vc14_gpu\\build -xcopy python pkg_vc14_gpu\\python /E /I /Y -xcopy include pkg_vc14_gpu\\include /E /I /Y -xcopy dmlc-core\\include pkg_vc14_gpu\\include /E /I /Y -xcopy mshadow\\mshadow pkg_vc14_gpu\\include\\mshadow /E /I /Y -xcopy nnvm\\include pkg_vc14_gpu\\nnvm\\include /E /I /Y -del /Q *.7z -7z.exe a vc14_gpu.7z pkg_vc14_gpu\\ -''' - stash includes: 'vc14_gpu.7z', name: 'vc14_gpu' - } - } - } - } -} - // Python unittest for CPU def python_ut(docker_type) { timeout(time: max_time, unit: 'MINUTES') { + sh "${docker_run} ${docker_type} find . -name '*.pyc' -type f -delete" sh "${docker_run} ${docker_type} PYTHONPATH=./python/ nosetests --with-timer --verbose tests/python/unittest" - sh "${docker_run} ${docker_type} PYTHONPATH=./python/ nosetests-3.4 --with-timer --verbose tests/python/unittest" sh "${docker_run} ${docker_type} PYTHONPATH=./python/ nosetests --with-timer --verbose tests/python/train" + sh "${docker_run} ${docker_type} find . -name '*.pyc' -type f -delete" + sh "${docker_run} ${docker_type} PYTHONPATH=./python/ nosetests-3.4 --with-timer --verbose tests/python/unittest" } } @@ -215,128 +92,357 @@ def python_ut(docker_type) { // both CPU and GPU def python_gpu_ut(docker_type) { timeout(time: max_time, unit: 'MINUTES') { + sh "${docker_run} ${docker_type} find . -name '*.pyc' -type f -delete" sh "${docker_run} ${docker_type} PYTHONPATH=./python/ nosetests --with-timer --verbose tests/python/gpu" + sh "${docker_run} ${docker_type} find . -name '*.pyc' -type f -delete" sh "${docker_run} ${docker_type} PYTHONPATH=./python/ nosetests-3.4 --with-timer --verbose tests/python/gpu" } } -stage('Unit Test') { - parallel 'Python2/3: CPU': { - node('linux') { - ws('workspace/ut-python-cpu') { - init_git() - unpack_lib('cpu') - python_ut('cpu') - } - } - }, - 'Python2/3: GPU': { - node('GPU' && 'linux') { - ws('workspace/ut-python-gpu') { - init_git() - unpack_lib('gpu', mx_lib) - python_gpu_ut('gpu') - } - } - }, - 'Python2/3: MKLML': { - node('GPU' && 'linux') { - ws('workspace/ut-python-mklml') { - init_git() - unpack_lib('mklml') - python_ut('mklml_gpu') - python_gpu_ut('mklml_gpu') +try { + stage("Sanity Check") { + timeout(time: max_time, unit: 'MINUTES') { + node('mxnetlinux') { + ws('workspace/sanity') { + init_git() + sh "python tools/license_header.py check" + make('lint', 'cpplint rcpplint jnilint') + make('lint', 'pylint') + } + } } } - }, - 'Scala: CPU': { - node('linux') { - ws('workspace/ut-scala-cpu') { - init_git() - unpack_lib('cpu') - timeout(time: max_time, unit: 'MINUTES') { - sh "${docker_run} cpu make scalapkg USE_BLAS=openblas" - sh "${docker_run} cpu make scalatest USE_BLAS=openblas" + + stage('Build') { + parallel 'CPU: Openblas': { + node('mxnetlinux') { + ws('workspace/build-cpu') { + init_git() + def flag = """ \ + DEV=1 \ + USE_PROFILER=1 \ + USE_CPP_PACKAGE=1 \ + USE_BLAS=openblas \ + -j\$(nproc) + """ + make("cpu", flag) + pack_lib('cpu') + } + } + }, + 'GPU: CUDA7.5+cuDNN5': { + node('mxnetlinux') { + ws('workspace/build-gpu') { + init_git() + def flag = """ \ + DEV=1 \ + USE_PROFILER=1 \ + USE_BLAS=openblas \ + USE_CUDA=1 \ + USE_CUDA_PATH=/usr/local/cuda \ + USE_CUDNN=1 \ + USE_CPP_PACKAGE=1 \ + -j\$(nproc) + """ + make('gpu', flag) + pack_lib('gpu') + stash includes: 'build/cpp-package/example/test_score', name: 'cpp_test_score' + } + } + }, + 'Amalgamation': { + node('mxnetlinux') { + ws('workspace/amalgamation') { + init_git() + make('cpu', '-C amalgamation/ USE_BLAS=openblas MIN=1') + } + } + }, + 'GPU: MKLML': { + node('mxnetlinux') { + ws('workspace/build-mklml') { + init_git() + def flag = """ \ + DEV=1 \ + USE_PROFILER=1 \ + USE_BLAS=openblas \ + USE_MKL2017=1 \ + USE_MKL2017_EXPERIMENTAL=1 \ + USE_CUDA=1 \ + USE_CUDA_PATH=/usr/local/cuda \ + USE_CUDNN=1 \ + USE_CPP_PACKAGE=1 \ + -j\$(nproc) + """ + make('mklml_gpu', flag) + pack_lib('mklml') + } } + }, + 'CPU windows':{ + node('mxnetwindows') { + ws('workspace/build-cpu') { + withEnv(['OpenBLAS_HOME=C:\\mxnet\\openblas', 'OpenCV_DIR=C:\\mxnet\\opencv_vc14', 'CUDA_PATH=C:\\CUDA\\v8.0']) { + init_git_win() + bat """mkdir build_vc14_cpu + cd build_vc14_cpu + cmake -G \"Visual Studio 14 2015 Win64\" -DUSE_CUDA=0 -DUSE_CUDNN=0 -DUSE_NVRTC=0 -DUSE_OPENCV=1 -DUSE_OPENMP=1 -DUSE_PROFILER=1 -DUSE_BLAS=open -DUSE_LAPACK=1 -DUSE_DIST_KVSTORE=0 ${env.WORKSPACE}""" + bat 'C:\\mxnet\\build_vc14_cpu.bat' + + bat '''rmdir /s/q pkg_vc14_cpu + mkdir pkg_vc14_cpu\\lib + mkdir pkg_vc14_cpu\\python + mkdir pkg_vc14_cpu\\include + mkdir pkg_vc14_cpu\\build + copy build_vc14_cpu\\Release\\libmxnet.lib pkg_vc14_cpu\\lib + copy build_vc14_cpu\\Release\\libmxnet.dll pkg_vc14_cpu\\build + xcopy python pkg_vc14_cpu\\python /E /I /Y + xcopy include pkg_vc14_cpu\\include /E /I /Y + xcopy dmlc-core\\include pkg_vc14_cpu\\include /E /I /Y + xcopy mshadow\\mshadow pkg_vc14_cpu\\include\\mshadow /E /I /Y + xcopy nnvm\\include pkg_vc14_cpu\\nnvm\\include /E /I /Y + del /Q *.7z + 7z.exe a vc14_cpu.7z pkg_vc14_cpu\\ + ''' + stash includes: 'vc14_cpu.7z', name: 'vc14_cpu' + } + } + } + }, + 'GPU windows':{ + node('mxnetwindows') { + ws('workspace/build-gpu') { + withEnv(['OpenBLAS_HOME=C:\\mxnet\\openblas', 'OpenCV_DIR=C:\\mxnet\\opencv_vc14', 'CUDA_PATH=C:\\CUDA\\v8.0']) { + init_git_win() + bat """mkdir build_vc14_gpu + call "C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\bin\\x86_amd64\\vcvarsx86_amd64.bat" + cd build_vc14_gpu + cmake -G \"NMake Makefiles JOM\" -DUSE_CUDA=1 -DUSE_CUDNN=1 -DUSE_NVRTC=1 -DUSE_OPENCV=1 -DUSE_OPENMP=1 -DUSE_PROFILER=1 -DUSE_BLAS=open -DUSE_LAPACK=1 -DUSE_DIST_KVSTORE=0 -DCUDA_ARCH_NAME=All -DCMAKE_CXX_FLAGS_RELEASE="/FS /MD /O2 /Ob2 /DNDEBUG" -DCMAKE_BUILD_TYPE=Release ${env.WORKSPACE}""" + bat 'C:\\mxnet\\build_vc14_gpu.bat' + bat '''rmdir /s/q pkg_vc14_gpu + mkdir pkg_vc14_gpu\\lib + mkdir pkg_vc14_gpu\\python + mkdir pkg_vc14_gpu\\include + mkdir pkg_vc14_gpu\\build + copy build_vc14_gpu\\libmxnet.lib pkg_vc14_gpu\\lib + copy build_vc14_gpu\\libmxnet.dll pkg_vc14_gpu\\build + xcopy python pkg_vc14_gpu\\python /E /I /Y + xcopy include pkg_vc14_gpu\\include /E /I /Y + xcopy dmlc-core\\include pkg_vc14_gpu\\include /E /I /Y + xcopy mshadow\\mshadow pkg_vc14_gpu\\include\\mshadow /E /I /Y + xcopy nnvm\\include pkg_vc14_gpu\\nnvm\\include /E /I /Y + del /Q *.7z + 7z.exe a vc14_gpu.7z pkg_vc14_gpu\\ + ''' + stash includes: 'vc14_gpu.7z', name: 'vc14_gpu' + } + } + } } } - }, - 'Python2/3: CPU Win':{ - node('windows') { - ws('workspace/ut-python-cpu') { - init_git_win() - unstash 'vc14_cpu' - bat '''rmdir /s/q pkg_vc14_cpu -7z x -y vc14_cpu.7z''' - bat """xcopy C:\\mxnet\\data data /E /I /Y -xcopy C:\\mxnet\\model model /E /I /Y -call activate py3 -set PYTHONPATH=${env.WORKSPACE}\\pkg_vc14_cpu\\python -C:\\mxnet\\test_cpu.bat""" - bat """xcopy C:\\mxnet\\data data /E /I /Y -xcopy C:\\mxnet\\model model /E /I /Y -call activate py2 -set PYTHONPATH=${env.WORKSPACE}\\pkg_vc14_cpu\\python -C:\\mxnet\\test_cpu.bat""" - } - } - }, - 'Python2/3: GPU Win':{ - node('windows') { - ws('workspace/ut-python-gpu') { - init_git_win() - unstash 'vc14_gpu' - bat '''rmdir /s/q pkg_vc14_gpu -7z x -y vc14_gpu.7z''' - bat """xcopy C:\\mxnet\\data data /E /I /Y -xcopy C:\\mxnet\\model model /E /I /Y -call activate py3 -set PYTHONPATH=${env.WORKSPACE}\\pkg_vc14_gpu\\python -C:\\mxnet\\test_gpu.bat""" - bat """xcopy C:\\mxnet\\data data /E /I /Y -xcopy C:\\mxnet\\model model /E /I /Y -call activate py2 -set PYTHONPATH=${env.WORKSPACE}\\pkg_vc14_gpu\\python -C:\\mxnet\\test_gpu.bat""" - } - } - } -} + stage('Unit Test') { + parallel 'Python2/3: CPU': { + node('mxnetlinux') { + ws('workspace/ut-python-cpu') { + init_git() + unpack_lib('cpu') + python_ut('cpu') + } + } + }, + 'Python2/3: GPU': { + node('mxnetlinux') { + ws('workspace/ut-python-gpu') { + init_git() + unpack_lib('gpu', mx_lib) + python_gpu_ut('gpu') + } + } + }, + 'Python2/3: MKLML': { + node('mxnetlinux') { + ws('workspace/ut-python-mklml') { + init_git() + unpack_lib('mklml') + python_ut('mklml_gpu') + python_gpu_ut('mklml_gpu') + } + } + }, + 'Scala: CPU': { + node('mxnetlinux') { + ws('workspace/ut-scala-cpu') { + init_git() + unpack_lib('cpu') + timeout(time: max_time, unit: 'MINUTES') { + sh "${docker_run} cpu make scalapkg USE_BLAS=openblas" + sh "${docker_run} cpu make scalatest USE_BLAS=openblas" + } + } + } + }, + 'Perl: CPU': { + node('mxnetlinux') { + ws('workspace/ut-perl-cpu') { + init_git() + unpack_lib('cpu') + timeout(time: max_time, unit: 'MINUTES') { + sh "${docker_run} cpu ./perl-package/test.sh" + } + } + } + }, + 'Perl: GPU': { + node('mxnetlinux') { + ws('workspace/ut-perl-gpu') { + init_git() + unpack_lib('gpu') + timeout(time: max_time, unit: 'MINUTES') { + sh "${docker_run} gpu ./perl-package/test.sh" + } + } + } + }, + 'R: CPU': { + node('mxnetlinux') { + ws('workspace/ut-r-cpu') { + init_git() + unpack_lib('cpu') + timeout(time: max_time, unit: 'MINUTES') { + sh "${docker_run} cpu rm -rf .Renviron" + sh "${docker_run} cpu mkdir -p /workspace/ut-r-cpu/site-library" + sh "${docker_run} cpu make rpkg USE_BLAS=openblas R_LIBS=/workspace/ut-r-cpu/site-library" + sh "${docker_run} cpu R CMD INSTALL --library=/workspace/ut-r-cpu/site-library mxnet_current_r.tar.gz" + sh "${docker_run} cpu make rpkgtest R_LIBS=/workspace/ut-r-cpu/site-library" + } + } + } + }, + 'R: GPU': { + node('mxnetlinux') { + ws('workspace/ut-r-gpu') { + init_git() + unpack_lib('gpu') + timeout(time: max_time, unit: 'MINUTES') { + sh "${docker_run} gpu rm -rf .Renviron" + sh "${docker_run} gpu mkdir -p /workspace/ut-r-gpu/site-library" + sh "${docker_run} gpu make rpkg USE_BLAS=openblas R_LIBS=/workspace/ut-r-gpu/site-library" + sh "${docker_run} gpu R CMD INSTALL --library=/workspace/ut-r-gpu/site-library mxnet_current_r.tar.gz" + sh "${docker_run} gpu make rpkgtest R_LIBS=/workspace/ut-r-gpu/site-library R_GPU_ENABLE=1" + } + } + } + }, + 'Python2/3: CPU Win':{ + node('mxnetwindows') { + ws('workspace/ut-python-cpu') { + init_git_win() + unstash 'vc14_cpu' + bat '''rmdir /s/q pkg_vc14_cpu + 7z x -y vc14_cpu.7z''' + bat """xcopy C:\\mxnet\\data data /E /I /Y + xcopy C:\\mxnet\\model model /E /I /Y + call activate py3 + set PYTHONPATH=${env.WORKSPACE}\\pkg_vc14_cpu\\python + del /S /Q ${env.WORKSPACE}\\pkg_vc14_cpu\\python\\*.pyc + C:\\mxnet\\test_cpu.bat""" + bat """xcopy C:\\mxnet\\data data /E /I /Y + xcopy C:\\mxnet\\model model /E /I /Y + call activate py2 + set PYTHONPATH=${env.WORKSPACE}\\pkg_vc14_cpu\\python + del /S /Q ${env.WORKSPACE}\\pkg_vc14_cpu\\python\\*.pyc + C:\\mxnet\\test_cpu.bat""" + } + } + }, + 'Python2/3: GPU Win':{ + node('mxnetwindows') { + ws('workspace/ut-python-gpu') { + init_git_win() + unstash 'vc14_gpu' + bat '''rmdir /s/q pkg_vc14_gpu + 7z x -y vc14_gpu.7z''' + bat """xcopy C:\\mxnet\\data data /E /I /Y + xcopy C:\\mxnet\\model model /E /I /Y + call activate py3 + set PYTHONPATH=${env.WORKSPACE}\\pkg_vc14_gpu\\python + del /S /Q ${env.WORKSPACE}\\pkg_vc14_gpu\\python\\*.pyc + C:\\mxnet\\test_gpu.bat""" + bat """xcopy C:\\mxnet\\data data /E /I /Y + xcopy C:\\mxnet\\model model /E /I /Y + call activate py2 + set PYTHONPATH=${env.WORKSPACE}\\pkg_vc14_gpu\\python + del /S /Q ${env.WORKSPACE}\\pkg_vc14_gpu\\python\\*.pyc + C:\\mxnet\\test_gpu.bat""" + } + } + } + } -stage('Integration Test') { - parallel 'Python': { - node('GPU' && 'linux') { - ws('workspace/it-python-gpu') { - init_git() - unpack_lib('gpu') - timeout(time: max_time, unit: 'MINUTES') { - sh "${docker_run} gpu PYTHONPATH=./python/ python example/image-classification/test_score.py" + stage('Integration Test') { + parallel 'Python': { + node('mxnetlinux') { + ws('workspace/it-python-gpu') { + init_git() + unpack_lib('gpu') + timeout(time: max_time, unit: 'MINUTES') { + sh "${docker_run} gpu PYTHONPATH=./python/ python example/image-classification/test_score.py" + } + } + } + }, + 'Caffe': { + node('mxnetlinux') { + ws('workspace/it-caffe') { + init_git() + unpack_lib('gpu') + timeout(time: max_time, unit: 'MINUTES') { + sh "${docker_run} caffe_gpu PYTHONPATH=/caffe/python:./python python tools/caffe_converter/test_converter.py" + } + } + } + }, + 'cpp-package': { + node('mxnetlinux') { + ws('workspace/it-cpp-package') { + init_git() + unpack_lib('gpu') + unstash 'cpp_test_score' + timeout(time: max_time, unit: 'MINUTES') { + sh "${docker_run} gpu cpp-package/tests/ci_test.sh" + } + } } } } - }, - 'Caffe': { - node('GPU' && 'linux') { - ws('workspace/it-caffe') { - init_git() - unpack_lib('gpu') - timeout(time: max_time, unit: 'MINUTES') { - sh "${docker_run} caffe_gpu PYTHONPATH=/caffe/python:./python python tools/caffe_converter/test_converter.py" + + stage('Deploy') { + node('mxnetlinux') { + ws('workspace/docs') { + if (env.BRANCH_NAME == "master") { + init_git() + sh "make clean" + sh "make docs" + } } } } - }, - 'cpp-package': { - node('GPU' && 'linux') { - ws('workspace/it-cpp-package') { - init_git() - unpack_lib('gpu') - unstash 'cpp_test_score' - timeout(time: max_time, unit: 'MINUTES') { - sh "${docker_run} gpu cpp-package/tests/ci_test.sh" +} catch (caughtError) { + node("mxnetlinux") { + sh "echo caught error" + err = caughtError + currentBuild.result = "FAILURE" + } +} finally { + node("mxnetlinux") { + // Only send email if master failed + if (currentBuild.result == "FAILURE" && env.BRANCH_NAME == "master") { + emailext body: 'Build for MXNet branch ${BRANCH_NAME} has broken. Please view the build at ${BUILD_URL}', replyTo: '${EMAIL}', subject: '[BUILD FAILED] Branch ${BRANCH_NAME} build ${BUILD_NUMBER}', to: '${EMAIL}' + } + // Remember to rethrow so the build is marked as failing + if (err) { + throw err } - } } - } } diff --git a/KEYS b/KEYS new file mode 100644 index 000000000000..070f38d4f78e --- /dev/null +++ b/KEYS @@ -0,0 +1,191 @@ +This file contains the PGP keys of various developers. +Please don't use them for email unless you have to. Their main +purpose is code signing. + +Examples of importing this file in your keystore: + gpg --import KEYS.txt + (need pgp and other examples here) + +Examples of adding your key to this file: + pgp -kxa and append it to this file. + (pgpk -ll && pgpk -xa ) >> this file. + (gpg --list-sigs + && gpg --armor --export ) >> this file. + +----------------------------------------------------------------------------------- +pub 4096R/D3541808 2014-01-09 +uid [ultimate] Suneel Marthi (CODE SIGNING KEY) +sig 3 D3541808 2014-01-09 Suneel Marthi (CODE SIGNING KEY) +sub 4096R/AF46E2DE 2014-01-09 +sig D3541808 2014-01-09 Suneel Marthi (CODE SIGNING KEY) + +-----BEGIN PGP PUBLIC KEY BLOCK----- +Comment: GPGTools - https://gpgtools.org + +mQINBFLPJmEBEAC9d/dUZCXeyhB0fVGmJAjdjXfLebav4VqGdNZC+M1T9C3dcVsh +X/JGme5bjJeIgVwiH5UsdNceYn1+hyxs8jXuRAWEWKP76gD+pNrp8Az0ZdBkJoAy +zCywOPtJV2PCOz7+S5ri2nUA2+1Kgcu6IlSLMmYAGO0IAmRrjBEzxy9iGaxiNGTc +LvQt/iVtIXWkKKI8yvpoJ8iFf3TGhpjgaC/h7cJP3zpy0SScmhJJASLXRsfocLv9 +sle6ndN9IPbDtRW8cL7Fk3VQlzp1ToVjmnQTyZZ6S1WafsjzCZ9hLN+k++o8VbvY +v3icY6Sy0BKz0J6KwaxTkuZ6w1K7oUkVOQboKaWFIEdO+jwrEmU+Puyd8Np8jLnF +Q0Y5GPfyMlqM3S/zaDm1t4D1eb5FLciStkxfg5wPVK6TkqB325KVD3aio5C7E7kt +aQechHxaJXCQOtCtVY4X+L4iClnMSuk+hcSc8W8MYRTSVansItK0vI9eQZXMnpan +w9/jk5rS4Gts1rHB7+kdjT3QRJmkyk6fEFT0fz5tfMC7N8waeEUhCaRW6lAoiqDW +NW1h+0UGxJw+9YcGxBC0kkt3iofNOWQWmuf/BS3DHPKT7XV/YtBHe44wW0sF5L5P +nfQUHpnA3pcZ0En6bXAvepKVZTNdOWWJqMyHV+436DA+33h45QL6lWb/GwARAQAB +tDVTdW5lZWwgTWFydGhpIChDT0RFIFNJR05JTkcgS0VZKSA8c21hcnRoaUBhcGFj +aGUub3JnPokCNwQTAQoAIQUCUs8mYQIbAwULCQgHAwUVCgkICwUWAgMBAAIeAQIX +gAAKCRC08czE01QYCOKKEAChRtHBoYNTX+RZbFO0Kl1GlN+i1Ik0shEm5ZJ56XHv +AnFx/gRK7CfZzJswWo7kf2s/dvJiFfs+rrolYVuO6E8gNhAaTEomSuvWQAMHdPcR +9G5APRKCSkbZYugElqplEbSphk78FKoFO+sml52M7Pr9jj88ApBjoFVVY8njdnNq +6DVlaDsg8YninCD78Z7PNFnRGwxyZ8Qd4Dh0rG+MUTfAWopZu6/MxpQxU7QpeVeX +SIMLg7ClFrGfXnZcszYF4dnav1aa0i7W88PAdYNPko7tC5qz5yv2ep7t2gRbcYKf +RXhYC2FHQey3wPhMKjA8V436lAqmfYnY/YdmhEy9Xq/1EdX1nHsQ7OEkfgXK14WM +F+rnqXRAl/0cwiyb41eocdg5kpZFIKgCYT02usLWxwNnd3jOCe109Ze3y3acN/G8 ++xOf9YRfNVAe6pD8H6ieRbv9gRjBmsbz9bXQCmxFnDqxNri5Me6gBAQPNmYTJD0h +jgJTK6o0vJ0pwjBLauasJsLu+1tR3Cb0dxPE+JVaTF26FCd7pM7W6KdVfod9ZfrN +cSyJ/cECc2KvYVGmTjQNVo1dYG0awBachlWnYNt+0Qx4opLsczZOLtPKtFY4BJA7 +aZoXT4Qf9yB8km7x2/cgNExVbFummToJ/IP3M39/EaryspsQQuM5Qu5Q5lZp8Qnn +ybkCDQRSzyZhARAA7bAawFzbJaghYnm6mTZyGG5hQmfAynbF6cPAE+g2SnXcNQjP +6kjYx3tSpb7rEzmjQqs46ztqdec6PIVBMhakON6z27Zz+IviAtO/TcaZHWNuCAjw +FXVQZ+tYsSeiKInttfkrQc8jXAHWwSkSjLqNpvQpBdBEX80MYkFB6ZPOeON2+/Ta +GC1H/HU2YngF0qQSmG33KKG6ezihBJdKxU6t2tsQfTlCmZW6R6MGpS9fVurYMKBk +vR+7RGZ/H6dSjWPcpxhusGg92J9uz7r5SopN1wSdyPMUCMAFGeyoxcAuBDl38quU +H/ENG3x5LDPq2aEH2AJ6yvZfIXbeJ1zmXf2cAHv+HbmvZaTSp0XIjq8Yxh8NkYEC +ZdfRWmsGLIpU16TkBijpK3Dn9MDXjHGT3V8/qfdpURtMvIaL8WFrq9ejcy/vGRFn +mCYqxIIPH+vLiMXKWtuMc61GN3ES21msKQH6IuQxxfQLyhK44L/pv7FpF4E+6LaE +8uRwAex5HIDpR1v4aJq089rRtye9VXTJJLZ7lYs0HctdZ30QbBRWT4jS9d9rj3cr +HgQ7mIGO9TAfK2kWc6AJN/EvxPWNbOwptsTUzAF/adiy9ax8C18iw7nKczC+2eN6 +UcbxXiPdytuKYK7O9A8S9e1w89GwpxYN7Xfn2o6QfpSbL9cLKiinOeV+xikAEQEA +AYkCHwQYAQoACQUCUs8mYQIbDAAKCRC08czE01QYCG7yD/471dmyOD+go8cZkdqR +3CHhjH03odtI0EJNVy4VGEC0r9paz3BWYTy18LqWYkw3ygphOIU1r8/7QK3H5Ke3 +c4yCSUxaMk5SlAJ+iVRek5TABkR8+zI+ZN5pQtqRH+ya5JxV4F/Sx5Q3KWMzpvgY +n6AgSSc3hEfkgdI7SalIeyLaLDWv+RFdGZ5JU5gD28C0G8BeH8L62x6sixZcqoGT +oy9rwkjs45/ZmmvBZhd1wLvC/au8l2Ecou6O8+8m26W8Z7vCuGKxuWn0KV3DLLWe +66uchDVlakGoMJSPIK06JWYUlE+gL0CW+U2ekt/v2qb8hGgMVET3CBAMq+bFWuJ6 +juX7hJd7wHtCFfjnFDDAkdp2IIIZAlBW6FZGv7pJ82xsW6pSAg0A7VrV6nTtMtDv +T8esOfo/t4t0gaL7bivy9DVVdATbUBcJJFpoVoe5MxiyjptveqPzIRwzt04n52Ph +ordVWAnX5AokXWTg+Glem/EWEuf7jUuZArfqCSl/sZoQdXGTjR7G4iFscispji4+ +kNjVQsItqFbgDpuc6n+GcFxlKQ7YMCnu5MVtTV01U4lFs0qy0NTUqsuR35DM4z14 +DkFmj1upWAayCoXTpKzsHBvJZPC+Wqf9Pl3O47apelg7KxU3S011YfXpVPvCTKBv +kD2o/5GKWS5QkSUEUXXY1oDiLg== +=f8kJ +-----END PGP PUBLIC KEY BLOCK----- +pub rsa4096 2017-07-12 [SC] + 406DCA257CD2BE237B79AE6BC9D353CA4AFF2E24 +uid [ultimate] Ly Nguyen (CODE SIGNING KEY) +sig 3 C9D353CA4AFF2E24 2017-07-12 Ly Nguyen (CODE SIGNING KEY) +sub rsa4096 2017-07-12 [E] +sig C9D353CA4AFF2E24 2017-07-12 Ly Nguyen (CODE SIGNING KEY) + +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mQINBFlmSIMBEADIr6FzNJ6o/owjqgqWdOtreIRuU47/uzNRZw8c2lEys2Fw+3CI +iUitkWpb7jR0BGLk+8yUk+1VGdXPuJ+zj8XWcCnCJ7TUy3Hudp/BrX7y388m9hP9 +3LP5yx+AUKbXRZiEr5EG2lyTmJBB5lmreVlRMs74Ie3uFtH6US/DVZMqULEtumcH +yCL30kKugUjfftO1mbx901kB0WpB705od3Wrde0Jd9sniMz4HkXMsd93gExh/s1H +3XApXes+yDIEILiUJRawgzgcPIuTyOq4bbafoiFd8ipZU0G7AQPtNUAnpTUtrUaJ +5CDGzOiqGUgwi+M3zwsRcW2MjDi9MyNTmlW2P6Gifzn3EaJ0EVdz4fPmIokC5h+H +6nMHqSPUEu0WA/qpirVrOiUku34lpkP0vZwb8UOyjgBCFTxDMPX70DuUmCbij1rr +vGM0rKLV+LFclEQFpnXckUnza8f/Zbk9T3yWcPQykXyi7+1Z1WJSPVkF4l8ynpDy +4DdUnLGdF8HZAGHdroi/jGVrH2NYy42XQqOZoLfk2BTGiFYpQem/Bfzo3OdEPBT7 +zpZUVqixtXbnGseL1sdHao1BdinIbvSpPOPEbObINenk65NtXWc+9YbauGkJ5kwd +opAkBmZC4IycFWkpmHecbGXJN61eYvARuXKAev7DeYH7g6Zuzp4n07rtIwARAQAB +tC5MeSBOZ3V5ZW4gKENPREUgU0lHTklORyBLRVkpIDxseG4yQGFwYWNoZS5vcmc+ +iQJOBBMBCgA4FiEEQG3KJXzSviN7ea5rydNTykr/LiQFAllmSIMCGwMFCwkIBwMF +FQoJCAsFFgIDAQACHgECF4AACgkQydNTykr/LiT2/Q//aW1qOLX7msuJDqhlHFIM +hCUZzWClljfCHMHZJooJY5YOcvzE5mVgwVdWjgAgZfgk/bFsNhuOb+jIqlatsNfI +Eg7sm6VjfHRo3pP1W7NN+CQNu5JnEEZAIVLy2gn+Eq1rQc7g2pfylVh/HV14TGon +OWbk7BfaZubGLtLJTIimHAPd+TrRsGsLnd9JiDZj0gsPPKV6HHXHgZoAeStIUPNX +13mN/WMDAAqroPPUfMEMXPbmJgNf/ukIFxsS/y8MwU32BjVCBvvh8ojN3RIgUJnX +chdjT9i/QVKi9TyoF20R7mR80x/P9CBwqKoN9+QuHjTPDuZkol4xD3jyzOsKHPwZ +CpltwdhI2JCYJzEIFtrZ0R59fXJ+8NNXZzIOqnx83qarC+eSf8cunqPS/ZBIvEJ0 +qM1adZlJiY96La10wXSjYnEc+XEw+dad3D3ChVsvDceJirelaAVrRS2Dz4ugNShy +W0cZFFUL0aCTNNJnF9sHAfexbbg06BTzSSAeYrEWLmmpjEYHXAtFyToHzk0jTUr4 +66SeIUVHIqBLk8yx1L9zQK38JS9usYj1PFJri9J6iYyqiIS7zRinoO8MIySZOOGp +Z3Q5xJbnwzjwl4frGaXg2/zyD7rfQGG3P23WOselgNWMKuYtVAA+AHo/CxLIinKk +JAMljesV3vfeawK5HHnfcgK5Ag0EWWZIgwEQAMsmr5lOFe4n9iGdTciYFXxZYSEX +ZqmtWyxNsXkih2icfohygx/YLFBSkdXSfIywS7w7+Na4OYdhp3uaRdU+yA4ianY7 +qH5guni98KtyZmsRnnjT1DgyR0pNNqAdAyfWeCglMx5SWLLtzKxHazqF0t6Jb6M/ +sAew+KdoTXsYzKb9d/R81spvefJoBopaxKLF1tijaX98RiquKLlFBD+88XP6pxSB +nwNxNybgJVlGT/RdxPiRiRj0CySuvx27i8w8Rc2HaT9CFumzdy6moz+RJbuuIjDN +QzIOpNy4+LJKSysPGh8AwRu6xCl9gnfbJ9thiFwYGZ7S3lVvS23/poI1YzLZZY+5 +XvpiiogF7j5Aj/zTTli8BI/CiNVrGKJuzeJJyLFfBMmrbysi9mV/fR8wC7xd5P9g +LjElkA4j1Xv5I47AVsILAbHLhphpxNDoKBmr1EbP/CJitEYjRmdjn4Mo6sYwMlVN +CA+rl/VMS3Nc0Iixu/Y070H3kE9IfitksiuXIJfeX5RW/uWegEO1e1dSpi+rreb8 +lvVtQk4tMUHyM16qPqO08tPGSunt6J0HiPi7J+xDwbJjJS7gNDW4AYHG5q4/dZsx +PtpcZC7zFOlFV0BwFftYnluccDhsWPc48mDmmhOe9p42irMAx6ms/Y42jgh4OmgD +bjMzKIyYFI40URGnABEBAAGJAjYEGAEKACAWIQRAbcolfNK+I3t5rmvJ01PKSv8u +JAUCWWZIgwIbDAAKCRDJ01PKSv8uJCAtD/97SuVGnCP3kbWfI/qfTTVKwuWTdbIg +rPvOjGo5F57l1PAgARt8N1ccqREbR3JwhRdsU3ewz5eDQEyEZVffPgufhqZr8liI +EP783m83VgRSMKYt6HzORX0os2BapsHHuejvlME9XpN0UG5AnvbzXDxP3wJufB1K +GkmC+rlpqfyMu60xFXzym9QuePksbdf/xXZduvLGaB1u+AYtvHp3+NGV382vat7C +xwRShVJTb8Zr9y5tA+JDqfhDDb5CepcPH6Uk2frU8aV7vZ3hmVmGcDcUddu3U9hg +L7Lcpr1E0D7xOuQ4QMAFhcDO+aB8aPv+JRkH4Y6wDFPrEgcEJ1YK6hhW5KSdslyK +QrKHKMSl+hwPmh9fKX4wC+FjMMXJ/PHtEG3N3f7/TyyO4iza5xDIJkYcyKkDXc0l +VcHLJvtjsJziMJNV3lKAeTp/uzbaJHRhLmpPHukQPnlpjfhnmsYh3wydnd03pfzQ +k6XJ4iGeSSQqtW6T14yqkCl5HDH2ms1ufhe4Os217CMXnaRbM/K6Zl4iGGozzXgd +no02+jTN3NqmUw0hUBR/9ZEn+IKmZ6f0Azsgio0M9ez1T0CCDZvo19kJw9b3VdOF +TZQhIRekaaV+bCQQxnwDOJ31bIUUpxaMdvygjq55Gri/5C75TsMNcgbhqYWLGKe2 +kRsGTxyO+fQ6/Q== +=FuXU +-----END PGP PUBLIC KEY BLOCK----- +pub rsa4096 2017-08-14 [SC] + AA3EBCC3E65A768AE3D2A64B8EF47B8720E8C549 +uid [ultimate] Naveen Swamy (CODE SIGNING KEY) +sig 3 8EF47B8720E8C549 2017-08-14 Naveen Swamy (CODE SIGNING KEY) +sub rsa4096 2017-08-14 [E] +sig 8EF47B8720E8C549 2017-08-14 Naveen Swamy (CODE SIGNING KEY) + +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mQINBFmSC4cBEADFOKHTd2QFZk94eCCh5kqDTcZk2zgu+tNb2PY0v/EVC/rEZN2O +IS+Y16gO7DQEnyreoPBe9QdwT85iCshhl80x6ojfRHztCcXADzNLPc0knhPNeRUt +feQOwbxtWmIyglQRPbeRkhQtZbceHMLT0tjpDdU2ogI1tt4OfFkCdXX2k9nxeCfQ +KKVMvK/vPFtkcLrTDPzG31XDvbJdHzKjHXVR1D88gVX23+YTZQX2ZFD4aWyix8xy +LcH1PE0oNY3Ja6YSXqgxPa+cvOslyd0HMO8EzJTfv65jEqf2CDJTxIER8ihfyjLa +GQAH8pNHZFrIDrOVNQXgNq0oG629rtFJVBb9MLTEi3zMf4aKddcE57j0aodEGXEs +eWWmULty4s/fhFb7DaEQ9TJpcMJYE89/zVP342nAMTjMAsPsW2RnaL7Q8uGDN3aT +O87ifl6LERp5CHJQxyZPm3no6WPEaI9WdoXPsz10EnzGP95zYRM/lsKEXu3ur0P3 +1xQXXfFyzvVeeor0Yyf7Oh63TJ76A+tTLiXMeFGd7xs65vh6yUHuhQZmqygFi0fI +zO8Wc1hr5LxEh0kFIKAngL0AL4ukf5Aii6wFvOj0kx6AxlsP8Jas4dQd3e1G3Apo +lij78wpeqLRPl04XTp8HNu5+wq5qj/GwNlx0SMwVT1h/2SC1cUaKi0DUuwARAQAB +tDNOYXZlZW4gU3dhbXkgKENPREUgU0lHTklORyBLRVkpIDxuc3dhbXlAYXBhY2hl +Lm9yZz6JAk4EEwEIADgCGwMCHgECF4AWIQSqPrzD5lp2iuPSpkuO9HuHIOjFSQUC +WZIMrAULCQgHAwUVCgkICwUWAgMBAAAKCRCO9HuHIOjFSRaoD/9P2ktLKFjEwm3j +sf/HDqmKd4jNHtCv/FUhzM0kb4F4gxXcnoFavDUdyLdTisEYx033Enkyv3jSBKB8 +bYxH4awmQ/47pexEPnpLPrw6Rpsbiuk8O2RLMWw2ObRATrNXg088YbBXgg4xrxXd +4tjpd8FB1TJJnsmvrAawScjwz8ZxPQTaCqxb7oyrkRJYgswPmVD2MrB4LAjxMbpW +pUkrQSxt6OEmteZXQd1Wn9UnD88YQEfaviCevo7cpsFrUHHXH9ihUI+fjihc+NpB +LW9O4gVXY0O9BOMIU4xqHvFMht0s7Tjj698xoANosvGtO7mV/OKCtEHuqQCKzP4/ +9QS9PJrci/msBd/UwYqtYggACFnAtijOT70a7PRp3zHK5um5lsIsxuGJWJutlXiB +cCrvgrdEaEXSUQsghygsUNzYzohAzYyV3FYuvaxuFwkLKewMzSOLW5DewPpZTTSa +pO+CsmiDL2RJYS2dbz84elq1FUlNZZevFmrZmtpKClOrQ/2A6lHvs/dH5Qs4Ews/ +Wl0Hwsk2ET1VbJEVjK+CZd9CwYXZBaW2ntLr88LfrbsbXg5HW9cowmMdbMq9Rb1L +4z/OaOUTp+M7nfQP9F5/6JmGICM/2RC2DYwkqrwQe+mvp6P6QNGe2z7OG19sHMyb +qDWc+N4+VcribZV3AQsdloX7Y6GscrkCDQRZkguHARAAustOuroA9Oieela+WUZP +0M9srwsH1XHpfKHgGgPAFXVQZ2YGXl9uxG73v4kat5kOdwPERPbuEYqOM/FyIs87 +8AxgQ+dh1YB7boDslubqUAbXPaxso4ZRyxDidmdR+XRi9ZZRNTYdiA+RhS7/Y3lp +Fb2Xr4xZWtqRzuNOTp1OQ51uOaFRAj/hDZJi7v73LNIocnrk8mFDCUGaHcNzUqxY +FvVkzi8fr8diM9Y1DJsTuQicJdYFQAIfFneddp2YyHTlB6IxbBLME3DJcN6pF6Eq +1pTP77Nss4voR/0RXgByZ4OeMgFudnuN+bz8mBVtr/ToWb/c8hhYBOrbBcegSXMg +gqPIk8FjYblmPqW1qUpI4fV66TIh2XT/bOoDZ8+FGRKznD2gWzeOOeq8vLG+rQN9 +ko0YMgrdqvtioD9vOd2CKpE5eZbalRjAttqC92mcURC2t/oVEB8kOdURenkOMzCN +T4MpMrzIL2x98tmiq8/wP7HDH+Yq4HSGnpHTK5INO9rmKpewiSKdLU1HKeCjF4mn +P9kfWCCz6U6bHO4vm6UQ0EgV8nM616laDWE49DFO/9WqoPzK3CanLp/Gy2pdK3CQ +R71OzB8XOMratmA5oL/c8hIZdF1i63KjLCSaQ7w6VR/j2gh61ftO0rtD8NmksphM +X25F37SwZ6ro8QQKONkhWncAEQEAAYkCNgQYAQgAIBYhBKo+vMPmWnaK49KmS470 +e4cg6MVJBQJZkguHAhsMAAoJEI70e4cg6MVJxZ0QAKCHbB2DgoED0JZ4xnADcc7t +o1Bz5SQgAWfh9eJD1Ou4cqhk9u2Bh5mX/z6UBc6ZeSsgI55NWxaZh0LiaeKqIufY +2+4a8PfuJPLQ1Q94NMMTAyA2tpIqsFk6V+5IB/heC94L3US8H3v9CvvlZyErhSsu +OVoIxM5S0f6W3vA3nX5iNUQHzRllAMkzoFmTET6ZzWskwOCjQ/qr/tasehpsYTaJ +pUWRZA7ExbIAIclnjuQM9FsMVzsaJcxqw2gbJFjVPumysz9NKOghAGzRH4JBnxpu +wAo/UH+668R1GpFDZpHFKwEdh3zXffo6Zq9lQmAJ5NTa7L5JUGuzlIF40asLG2MN +0ywDW9/oHuCDaM0tITSmRLn6v+QVApoGD89svQ6yCZ5MeqRfP+H6CSFf6fQ3E4Cu +kIoH1GBllwnRmoQrAKyR4a7OqTVm6B+LyA+jTaa79g5UjDN7qlbGQ8MR5rE/yutP +8PNCFmE/EsImQ7NREfRKqle0+mSAWqKkdg4pX5bJNbVQX2LOLgMF5LJdUtwq8ISJ +7/k9J/FTJyuqgwXvkUOq7eEehxUpvX85gzJ5tpMSN+jYgPeMWcd8mTvVgwWDd7Qu +TNxwR0b9K/mLKGh58n1vVT79QReQFQ4wWFyQkmFkL9ybG04wTKe00VDNP987nSBg +FuSamX64+S6T8IwAuP9U +=KRiV +-----END PGP PUBLIC KEY BLOCK----- diff --git a/Makefile b/Makefile index f9b6bb62de9b..33151e574ea7 100644 --- a/Makefile +++ b/Makefile @@ -13,11 +13,20 @@ endif ifndef DMLC_CORE DMLC_CORE = $(ROOTDIR)/dmlc-core endif +CORE_INC = $(wildcard $(DMLC_CORE)/include/*/*.h) ifndef NNVM_PATH NNVM_PATH = $(ROOTDIR)/nnvm endif +ifndef DLPACK_PATH + DLPACK_PATH = $(ROOTDIR)/dlpack +endif + +ifndef AMALGAMATION_PATH + AMALGAMATION_PATH = $(ROOTDIR)/amalgamation +endif + ifneq ($(USE_OPENMP), 1) export NO_OPENMP = 1 endif @@ -41,6 +50,7 @@ CFLAGS = -DMSHADOW_FORCE_STREAM $(WARNFLAGS) ifeq ($(DEV), 1) CFLAGS += -g -Werror + NVCCFLAGS += -Werror cross-execution-space-call endif # CFLAGS for debug @@ -49,12 +59,12 @@ ifeq ($(DEBUG), 1) else CFLAGS += -O3 -DNDEBUG=1 endif -CFLAGS += -I$(ROOTDIR)/mshadow/ -I$(ROOTDIR)/dmlc-core/include -fPIC -I$(NNVM_PATH)/include -Iinclude $(MSHADOW_CFLAGS) +CFLAGS += -I$(ROOTDIR)/mshadow/ -I$(ROOTDIR)/dmlc-core/include -fPIC -I$(NNVM_PATH)/include -I$(DLPACK_PATH)/include -Iinclude $(MSHADOW_CFLAGS) LDFLAGS = -pthread $(MSHADOW_LDFLAGS) $(DMLC_LDFLAGS) ifeq ($(DEBUG), 1) - NVCCFLAGS = -std=c++11 -Xcompiler -D_FORCE_INLINES -g -G -O0 -ccbin $(CXX) $(MSHADOW_NVCCFLAGS) + NVCCFLAGS += -std=c++11 -Xcompiler -D_FORCE_INLINES -g -G -O0 -ccbin $(CXX) $(MSHADOW_NVCCFLAGS) else - NVCCFLAGS = -std=c++11 -Xcompiler -D_FORCE_INLINES -g -O3 -ccbin $(CXX) $(MSHADOW_NVCCFLAGS) + NVCCFLAGS += -std=c++11 -Xcompiler -D_FORCE_INLINES -O3 -ccbin $(CXX) $(MSHADOW_NVCCFLAGS) endif # CFLAGS for profiler @@ -102,6 +112,35 @@ else endif endif +# verify existence of separate lapack library when using blas/openblas/atlas +# switch off lapack support in case it can't be found +# issue covered with this +# - for Ubuntu 14.04 or lower, lapack is not automatically installed with openblas +# - for Ubuntu, installing atlas will not automatically install the atlas provided lapack library +# silently switching lapack off instead of letting the build fail because of backward compatibility +ifeq ($(USE_LAPACK), 1) +ifeq ($(USE_BLAS),$(filter $(USE_BLAS),blas openblas atlas)) +ifeq (,$(wildcard /lib/liblapack.a)) +ifeq (,$(wildcard /usr/lib/liblapack.a)) +ifeq (,$(wildcard $(USE_LAPACK_PATH)/liblapack.a)) + USE_LAPACK = 0 +endif +endif +endif +endif +endif + +# lapack settings. +ifeq ($(USE_LAPACK), 1) + ifneq ($(USE_LAPACK_PATH), ) + LDFLAGS += -L$(USE_LAPACK_PATH) + endif + ifeq ($(USE_BLAS),$(filter $(USE_BLAS),blas openblas atlas)) + LDFLAGS += -llapack + endif + CFLAGS += -DMXNET_USE_LAPACK +endif + ifeq ($(USE_CUDNN), 1) CFLAGS += -DMSHADOW_USE_CUDNN=1 LDFLAGS += -lcudnn @@ -125,6 +164,35 @@ ifneq ($(USE_CUDA_PATH), NONE) NVCC=$(USE_CUDA_PATH)/bin/nvcc endif +# Sets 'CUDA_ARCH', which determines the GPU architectures supported +# by the compiled kernels. Users can edit the KNOWN_CUDA_ARCHS list below +# to remove archs they don't wish to support to speed compilation, or they +# can pre-set the CUDA_ARCH args in config.mk for full control. +# +# For archs in this list, nvcc will create a fat-binary that will include +# the binaries (SASS) for all architectures supported by the installed version +# of the cuda toolkit, plus the assembly (PTX) for the most recent such architecture. +# If these kernels are then run on a newer-architecture GPU, the binary will +# be JIT-compiled by the updated driver from the included PTX. +ifeq ($(USE_CUDA), 1) +ifeq ($(origin CUDA_ARCH), undefined) + KNOWN_CUDA_ARCHS := 30 35 50 52 60 61 70 + # Run nvcc on a zero-length file to check architecture-level support. + # Create args to include SASS in the fat binary for supported levels. + CUDA_ARCH := $(foreach arch,$(KNOWN_CUDA_ARCHS), \ + $(shell $(NVCC) -arch=sm_$(arch) -E --x cu /dev/null >/dev/null 2>&1 && \ + echo -gencode arch=compute_$(arch),code=sm_$(arch))) + # Convert a trailing "code=sm_NN" to "code=[sm_NN,compute_NN]" to also + # include the PTX of the most recent arch in the fat-binaries for + # forward compatibility with newer GPUs. + CUDA_ARCH := $(shell echo $(CUDA_ARCH) | sed 's/sm_\([0-9]*\)$$/[sm_\1,compute_\1]/') + # Add fat binary compression if supported by nvcc. + COMPRESS := --fatbin-options -compress-all + CUDA_ARCH += $(shell $(NVCC) -cuda $(COMPRESS) --x cu /dev/null -o /dev/null >/dev/null 2>&1 && \ + echo $(COMPRESS)) +endif +endif + # ps-lite PS_PATH=$(ROOTDIR)/ps-lite DEPS_PATH=$(shell pwd)/deps @@ -228,7 +296,7 @@ build/plugin/%.o: plugin/%.cc $(NVCC) $(NVCCFLAGS) $(CUDA_ARCH) -Xcompiler "$(CFLAGS) -Isrc/operator" -M -MT $*_gpu.o $< >$*_gpu.d $(NVCC) -c -o $@ $(NVCCFLAGS) $(CUDA_ARCH) -Xcompiler "$(CFLAGS) -Isrc/operator" $< -%.o: %.cc +%.o: %.cc $(CORE_INC) @mkdir -p $(@D) $(CXX) -std=c++11 -c $(CFLAGS) -MMD -Isrc/operator -c $< -o $@ @@ -253,9 +321,9 @@ $(DMLC_CORE)/libdmlc.a: DMLCCORE DMLCCORE: + cd $(DMLC_CORE); $(MAKE) libdmlc.a USE_SSE=$(USE_SSE) config=$(ROOTDIR)/$(config); cd $(ROOTDIR) -$(NNVM_PATH)/lib/libnnvm.a: LIBNNVM - -LIBNNVM: +NNVM_INC = $(wildcard $(NNVM_PATH)/include/*/*.h) +NNVM_SRC = $(wildcard $(NNVM_PATH)/src/*/*/*.cc $(NNVM_PATH)/src/*/*.cc $(NNVM_PATH)/src/*.cc) +$(NNVM_PATH)/lib/libnnvm.a: $(NNVM_INC) $(NNVM_SRC) + cd $(NNVM_PATH); $(MAKE) lib/libnnvm.a DMLC_CORE_PATH=$(DMLC_CORE); cd $(ROOTDIR) bin/im2rec: tools/im2rec.cc $(ALLX_DEP) @@ -297,13 +365,13 @@ doxygen: # Cython build cython: - cd python; python setup.py build_ext --inplace + cd python; python setup.py build_ext --inplace --with-cython cython2: - cd python; python2 setup.py build_ext --inplace + cd python; python2 setup.py build_ext --inplace --with-cython cython3: - cd python; python3 setup.py build_ext --inplace + cd python; python3 setup.py build_ext --inplace --with-cython cyclean: rm -rf python/mxnet/*/*.so python/mxnet/*/*.cpp @@ -315,22 +383,31 @@ rcpplint: rpkg: mkdir -p R-package/inst mkdir -p R-package/inst/libs + cp src/io/image_recordio.h R-package/src cp -rf lib/libmxnet.so R-package/inst/libs mkdir -p R-package/inst/include cp -rf include/* R-package/inst/include cp -rf dmlc-core/include/* R-package/inst/include/ cp -rf nnvm/include/* R-package/inst/include + Rscript -e "if(!require(devtools)){install.packages('devtools', repo = 'https://cloud.r-project.org/')}" + Rscript -e "library(devtools); library(methods); options(repos=c(CRAN='https://cloud.r-project.org/')); install_deps(pkg='R-package', dependencies = TRUE)" echo "import(Rcpp)" > R-package/NAMESPACE echo "import(methods)" >> R-package/NAMESPACE R CMD INSTALL R-package - Rscript -e "require(mxnet); mxnet:::mxnet.export(\"R-package\")" + Rscript -e "require(mxnet); mxnet:::mxnet.export('R-package')" rm -rf R-package/NAMESPACE - Rscript -e "require(devtools); install_version(\"roxygen2\", version = \"5.0.1\", repos = \"https://cloud.r-project.org/\", quiet = TRUE)" - Rscript -e "require(roxygen2); roxygen2::roxygenise(\"R-package\")" + Rscript -e "if (!require('roxygen2')||packageVersion('roxygen2')!= '5.0.1'){\ + devtools::install_version('roxygen2',version='5.0.1',\ + repo='https://cloud.r-project.org/',quiet=TRUE)}" + Rscript -e "require(roxygen2); roxygen2::roxygenise('R-package')" R CMD build --no-build-vignettes R-package rm -rf mxnet_current_r.tar.gz + rm -rf R-package/src/image_recordio.h mv mxnet_*.tar.gz mxnet_current_r.tar.gz +rpkgtest: + Rscript -e "require(testthat);res<-test_dir('R-package/tests/testthat');if(!testthat:::all_passed(res)){stop('Test failures', call. = FALSE)}" + scalapkg: (cd $(ROOTDIR)/scala-package; \ mvn clean package -P$(SCALA_PKG_PROFILE) -Dcxx="$(CXX)" \ @@ -366,15 +443,17 @@ clean: cyclean $(EXTRA_PACKAGES_CLEAN) cd $(DMLC_CORE); $(MAKE) clean; cd - cd $(PS_PATH); $(MAKE) clean; cd - cd $(NNVM_PATH); $(MAKE) clean; cd - + cd $(AMALGAMATION_PATH); $(MAKE) clean; cd - $(RM) -r $(patsubst %, %/*.d, $(EXTRA_OPERATORS)) $(patsubst %, %/*/*.d, $(EXTRA_OPERATORS)) $(RM) -r $(patsubst %, %/*.o, $(EXTRA_OPERATORS)) $(patsubst %, %/*/*.o, $(EXTRA_OPERATORS)) else clean: cyclean testclean $(EXTRA_PACKAGES_CLEAN) $(RM) -r build lib bin *~ */*~ */*/*~ */*/*/*~ R-package/NAMESPACE R-package/man R-package/R/mxnet_generated.R \ - R-package/inst R-package/src/*.o R-package/src/*.so mxnet_*.tar.gz + R-package/inst R-package/src/image_recordio.h R-package/src/*.o R-package/src/*.so mxnet_*.tar.gz cd $(DMLC_CORE); $(MAKE) clean; cd - cd $(PS_PATH); $(MAKE) clean; cd - cd $(NNVM_PATH); $(MAKE) clean; cd - + cd $(AMALGAMATION_PATH); $(MAKE) clean; cd - endif clean_all: clean diff --git a/NEWS.md b/NEWS.md index f29119be897e..4f1ecd15689c 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,46 @@ MXNet Change Log ================ +## 0.11.0-rc2 +### - Major Features + - Apple Core ML model converter + - Support for Keras v1.2.2 + - For more information see [full release notes](https://cwiki.apache.org/confluence/display/MXNET/v0.11.0+Release+Notes) +### - API Changes + - Added `CachedOp`. You can now cache the operators that’s called frequently with the same set of arguments to reduce overhead. + - Added sample_multinomial for sampling from multinomial distributions. + - Added `trunc` operator for rounding towards zero. + - Added linalg_gemm, linalg_potrf, ... operators for lapack support. + - Added verbose option to Initializer for printing out initialization details. + - Added DeformableConvolution to contrib from the Deformable Convolutional Networks paper. + - Added float64 support for dot and batch_dot operator. + - `allow_extra` is added to Module.set_params to ignore extra parameters. + - Added `mod` operator for modulo. + - Added `multi_precision` option to SGD optimizer to improve training with float16. Resnet50 now achieves the same accuracy when trained with float16 and gives 50% speedup on Titan XP. +### - Performance Improvements + - ImageRecordIter now stores data in pinned memory to improve GPU memcopy speed. +### - Bugfixes + - Cython interface is fixed. `make cython` and `python setup.py install --with-cython` should install the cython interface and reduce overhead in applications that use imperative/bucketing. + - Fixed various bugs in Faster-RCNN example: https://github.com/dmlc/mxnet/pull/6486 + - Fixed various bugs in SSD example. + - Fixed `out` argument not working for `zeros`, `ones`, `full`, etc. + - `expand_dims` now supports backward shape inference. + - Fixed a bug in rnn. BucketingSentenceIter that causes incorrect layout handling on multi-GPU. + - Fixed context mismatch when loading optimizer states. + - Fixed a bug in ReLU activation when using MKL. + - Fixed a few race conditions that causes crashes on shutdown. +### - Refactors + - Refactored TShape/TBlob to use int64 dimensions and DLTensor as internal storage. Getting ready for migration to DLPack. As a result TBlob::dev_mask_ and TBlob::stride_ are removed. + + +## 0.10.0 +- Overhauled documentation for commonly used Python APIs, Installation instructions, Tutorials, HowTos and MXNet Architecture. +- Updated mxnet.io for improved readability. +- Pad operator now support reflection padding. +- Fixed a memory corruption error in threadedengine. +- Added CTC loss layer to contrib package. See mx.contrib.sym.ctc_loss. +- Added new sampling operators for several distributions (normal,uniform,gamma,exponential,negative binomial). +- Added documentation for experimental RNN APIs. + ## 0.9.3 - Move symbolic API to NNVM @tqchen - Most front-end C API are backward compatible diff --git a/NOTICE b/NOTICE index 2051e3c00d53..03695607e3e9 100644 --- a/NOTICE +++ b/NOTICE @@ -1,2 +1,5 @@ -MXNet -Copyright (c) 2015-2016 by Contributors +Apache MXNET (incubating) +Copyright [2015-2017] The Apache Software Foundation + +This product includes software developed at +The Apache Software Foundation (http://www.apache.org/). diff --git a/R-package/DESCRIPTION b/R-package/DESCRIPTION index 1ad56e33daa8..e0b435513718 100644 --- a/R-package/DESCRIPTION +++ b/R-package/DESCRIPTION @@ -1,10 +1,10 @@ Package: mxnet Type: Package -Title: MXNet -Version: 0.9.5 -Date: 2015-12-23 +Title: MXNet: A Flexible and Efficient Machine Learning Library for Heterogeneous Distributed Systems +Version: 0.11.0 +Date: 2017-06-27 Author: Tianqi Chen, Qiang Kou, Tong He -Maintainer: Qiang Kou +Maintainer: Qiang Kou Repository: DMLC Description: MXNet is a deep learning framework designed for both efficiency and flexibility. It allows you to mix the flavours of deep learning programs @@ -27,6 +27,8 @@ Suggests: knitr, rmarkdown, imager +Depends: + R (>= 3.3.0) LinkingTo: Rcpp VignetteBuilder: knitr RoxygenNote: 5.0.1 diff --git a/R-package/R/callback.R b/R-package/R/callback.R index 8766df6ffe7c..a0562386ebbc 100644 --- a/R-package/R/callback.R +++ b/R-package/R/callback.R @@ -95,7 +95,7 @@ mx.callback.early.stop <- function(train.metric = NULL, eval.metric = NULL, bad. if (!is.null(env$metric)) { if (!is.null(train.metric)) { result <- env$metric$get(env$train.metric) - if (result$value < train.metric | (maximize == TRUE & result$value > train.metric)) { + if ((maximize == F & result$value < train.metric) | (maximize == TRUE & result$value > train.metric)) { return(FALSE) } } @@ -104,7 +104,7 @@ mx.callback.early.stop <- function(train.metric = NULL, eval.metric = NULL, bad. if (!is.null(eval.metric)) { if (!is.null(env$eval.metric)) { result <- env$metric$get(env$eval.metric) - if (result$value < eval.metric | (maximize == TRUE & result$value > eval.metric)) { + if ((maximize == F & result$value < eval.metric) | (maximize == TRUE & result$value > eval.metric)) { return(FALSE) } } @@ -135,7 +135,7 @@ mx.callback.early.stop <- function(train.metric = NULL, eval.metric = NULL, bad. result <- env$metric$get(env$eval.metric) - if (result$value > mx.best.score | (maximize == TRUE & result$value < mx.best.score)) { + if ((maximize == F & result$value > mx.best.score) | (maximize == TRUE & result$value < mx.best.score)) { if (mx.best.iter == bad.steps) { if (verbose) { diff --git a/R-package/R/context.R b/R-package/R/context.R index fdcb48a857d6..604224e74fa7 100644 --- a/R-package/R/context.R +++ b/R-package/R/context.R @@ -1,6 +1,6 @@ # Initialize the global context init.context.default <- function() { - assign("mx.ctx.internal.default.value", mx.cpu(), envir = .MXNetEnv) + .MXNetEnv[["mx.ctx.internal.default.value"]] <- mx.cpu() } #' Set/Get default context for array creation. @@ -11,7 +11,7 @@ init.context.default <- function() { #' @export mx.ctx.default <- function(new = NULL) { if (!is.null(new)) { - assign("mx.ctx.internal.default.value", new, envir = .MXNetEnv) + .MXNetEnv[["mx.ctx.internal.default.value"]] <- new } return (.MXNetEnv$mx.ctx.internal.default.value) } diff --git a/R-package/R/executor.R b/R-package/R/executor.R index f5d773b5c471..571708268a7f 100644 --- a/R-package/R/executor.R +++ b/R-package/R/executor.R @@ -2,7 +2,7 @@ #' with information from input shapes. #' #' @export -mx.simple.bind <- function(symbol, ctx, grad.req = "null", ...) { +mx.simple.bind <- function(symbol, ctx, grad.req = "null", fixed.param = NULL, ...) { if (!is.MXSymbol(symbol)) stop("symbol need to be MXSymbol") slist <- symbol$infer.shape(list(...)) @@ -16,7 +16,9 @@ mx.simple.bind <- function(symbol, ctx, grad.req = "null", ...) { mx.nd.zeros(shape, ctx) }, simplify = FALSE, USE.NAMES = TRUE) grad.reqs <- lapply(names(slist$arg.shapes), function(nm) { - if (!mx.util.str.endswith(nm, "label") && !mx.util.str.endswith(nm, "data")) { + if (nm %in% fixed.param) { + "null" + } else if (!endsWith(nm, "label") && !endsWith(nm, "data")) { grad.req } else { "null" diff --git a/R-package/R/initializer.R b/R-package/R/initializer.R index ab2b151be465..7a1ffb2b182a 100644 --- a/R-package/R/initializer.R +++ b/R-package/R/initializer.R @@ -4,11 +4,11 @@ #' @param shape the shape of the array to be generated. #' mx.init.internal.default <- function(name, shape, ctx, allow.unknown=FALSE) { - if (mx.util.str.endswith(name, "bias")) return (mx.nd.zeros(shape, ctx)) - if (mx.util.str.endswith(name, "gamma")) return (mx.nd.ones(shape, ctx)) - if (mx.util.str.endswith(name, "beta")) return (mx.nd.zeros(shape, ctx)) - if (mx.util.str.endswith(name, "moving_mean")) return (mx.nd.zeros(shape, ctx)) - if (mx.util.str.endswith(name, "moving_var")) return (mx.nd.ones(shape, ctx)) + if (endsWith(name, "bias")) return (mx.nd.zeros(shape, ctx)) + if (endsWith(name, "gamma")) return (mx.nd.ones(shape, ctx)) + if (endsWith(name, "beta")) return (mx.nd.zeros(shape, ctx)) + if (endsWith(name, "moving_mean")) return (mx.nd.zeros(shape, ctx)) + if (endsWith(name, "moving_var")) return (mx.nd.ones(shape, ctx)) if (allow.unknown) return(NULL) stop(paste("Unkown initialization pattern for ", name)) } @@ -20,7 +20,7 @@ mx.init.internal.default <- function(name, shape, ctx, allow.unknown=FALSE) { #' @export mx.init.uniform <- function(scale) { function(name, shape, ctx, allow.unknown=FALSE) { - if (!mx.util.str.endswith(name, "weight")) { + if (!endsWith(name, "weight")) { return (mx.init.internal.default(name, shape, ctx, allow.unknown)) } return (mx.runif(shape, -scale, scale, ctx)) @@ -34,7 +34,7 @@ mx.init.uniform <- function(scale) { #' @export mx.init.normal <- function(sd) { function(name, shape, ctx, allow.unknown=FALSE) { - if (!mx.util.str.endswith(name, "weight")) { + if (!endsWith(name, "weight")) { return (mx.init.internal.default(name, shape, ctx, allow.unknown)) } return (mx.rnorm(shape, 0, sd, ctx)) @@ -55,7 +55,7 @@ mx.init.normal <- function(sd) { mx.init.Xavier <- function(rnd_type = "uniform", factor_type = "avg", magnitude = 3){ function(name, shape, ctx, allow.unknown = FALSE){ - if (!mx.util.str.endswith(name, "weight")) { + if (!endsWith(name, "weight")) { return (mx.init.internal.default(name, shape, ctx, allow.unknown)) } diff --git a/R-package/R/io.R b/R-package/R/io.R index 10298fbaf056..9f6a60702505 100644 --- a/R-package/R/io.R +++ b/R-package/R/io.R @@ -1,14 +1,12 @@ -is.MXDataIter <- function(x) { - inherits(x, "Rcpp_MXNativeDataIter") || - inherits(x, "Rcpp_MXArrayDataIter") -} - #' Judge if an object is mx.dataiter #' #' @return Logical indicator #' #' @export -is.mx.dataiter <- is.MXDataIter +is.mx.dataiter <- function(x) { + inherits(x, "Rcpp_MXNativeDataIter") || + inherits(x, "Rcpp_MXArrayDataIter") +} #' Extract a certain field from DataIter. #' diff --git a/R-package/R/lstm.R b/R-package/R/lstm.R index 27c1c2e96eac..622388993c8c 100644 --- a/R-package/R/lstm.R +++ b/R-package/R/lstm.R @@ -181,6 +181,10 @@ lstm.inference.symbol <- function(num.lstm.layer, input.size, #' A number in [0,1) containing the dropout ratio from the last hidden layer to the output layer. #' @param optimizer string, default="sgd" #' The optimization method. +#' @param epoch.end.callback function, optional +#' The callback when iteration ends. +#' @param batch.end.callback function, optional +#' The callback when one mini-batch iteration ends. #' @param ... other parameters passing to \code{mx.lstm}/. #' @return model A trained lstm unrolled model. #' @@ -193,19 +197,29 @@ mx.lstm <- function(train.data, eval.data=NULL, num.round=10, update.period=1, initializer=mx.init.uniform(0.01), dropout=0, optimizer='sgd', + epoch.end.callback=NULL, batch.end.callback=NULL, + model, + arg.params, ...) { # check data and change data into iterator train.data <- check.data(train.data, batch.size, TRUE) eval.data <- check.data(eval.data, batch.size, FALSE) + + # get unrolled lstm symbol - rnn.sym <- lstm.unroll(num.lstm.layer=num.lstm.layer, + if(missing(model)){ + rnn.sym <- lstm.unroll(num.lstm.layer=num.lstm.layer, num.hidden=num.hidden, seq.len=seq.len, input.size=input.size, num.embed=num.embed, num.label=num.label, dropout=dropout) + } else { + rnn.sym=model$symbol + } + init.states.c <- lapply(1:num.lstm.layer, function(i) { state.c <- paste0("l", i, ".init.c") return (state.c) @@ -229,6 +243,17 @@ mx.lstm <- function(train.data, eval.data=NULL, init.states.name=init.states.name, initializer=initializer, dropout=dropout) + # restore states + if (!missing(arg.params)){ + arg.names <- names(model$rnn.exec$ref.arg.arrays) + for (k in names(arg.params)) { + if ((k %in% arg.names) && is.param.name(k) ) { + rnn.input <- list() + rnn.input[[k]] <- arg.params[[k]] + mx.exec.update.arg.arrays(model$rnn.exec, rnn.input, match.name=TRUE) + } + } + } # train lstm model model <- train.rnn( model, train.data, eval.data, @@ -236,6 +261,8 @@ mx.lstm <- function(train.data, eval.data=NULL, update.period=update.period, ctx=ctx, init.states.name=init.states.name, + epoch.end.callback=epoch.end.callback, + batch.end.callback=batch.end.callback, ...) # change model into MXFeedForwardModel model <- list(symbol=model$symbol, arg.params=model$rnn.exec$ref.arg.arrays, aux.params=model$rnn.exec$ref.aux.arrays) diff --git a/R-package/R/metric.R b/R-package/R/metric.R index 5bf4390cd614..02572f4acdc3 100644 --- a/R-package/R/metric.R +++ b/R-package/R/metric.R @@ -78,3 +78,13 @@ mx.metric.rmsle <- mx.metric.custom("rmsle", function(label, pred) { return(res) }) +#' Perplexity metric for language model +#' +#' @export +mx.metric.Perplexity <- mx.metric.custom("Perplexity", function(label, pred) { + label_probs <- as.array(mx.nd.choose.element.0index(pred, label)) + batch <- length(label_probs) + NLL <- -sum(log(pmax(1e-15, as.array(label_probs)))) / batch + Perplexity <- exp(NLL) + return(Perplexity) +}) diff --git a/R-package/R/mlp.R b/R-package/R/mlp.R index ab3b56350d1e..33134ffbf48e 100644 --- a/R-package/R/mlp.R +++ b/R-package/R/mlp.R @@ -7,7 +7,7 @@ #' @param dropout a number in [0,1) containing the dropout ratio from the last hidden layer to the output layer. #' @param activation either a single string or a vector containing the names of the activation functions. #' @param out_activation a single string containing the name of the output activation function. -#' @param device whether train on cpu (default) or gpu. +#' @param ctx whether train on cpu (default) or gpu. #' @param eval_metric the evaluation metric/ #' @param ... other parameters passing to \code{mx.model.FeedForward.create}/ #' @@ -28,7 +28,7 @@ #' @export mx.mlp <- function(data, label, hidden_node = 1, out_node, dropout = NULL, activation = "tanh", out_activation = "softmax", - device=mx.ctx.default(), ...) { + ctx = mx.ctx.default(), ...) { m <- length(hidden_node) if (!is.null(dropout)) { @@ -64,6 +64,6 @@ mx.mlp <- function(data, label, hidden_node = 1, out_node, dropout = NULL, } else { stop("Not supported yet.") } - model <- mx.model.FeedForward.create(out, X=data, y=label, ctx=device, ...) + model <- mx.model.FeedForward.create(out, X=data, y=label, ctx = ctx, ...) return(model) } diff --git a/R-package/R/model.R b/R-package/R/model.R index a654cc03cddb..2ee66242d805 100644 --- a/R-package/R/model.R +++ b/R-package/R/model.R @@ -1,15 +1,31 @@ # slice the shape on the highest dimension mx.model.slice.shape <- function(shape, nsplit) { - ndim <- length(shape) - batchsize <- shape[[ndim]] - step <- as.integer((batchsize + nsplit - 1) / nsplit) - lapply(0:(nsplit - 1), function(k) { - begin = min(k * step, batchsize) - end = min((k + 1) * step, batchsize) - s <- shape - s[[ndim]] = end - begin - return(list(begin=begin, end=end, shape=s)) - }) + if (is.numeric(shape)) { + ndim <- length(shape) + batchsize <- shape[[ndim]] + step <- as.integer((batchsize + nsplit - 1) / nsplit) + lapply(0:(nsplit - 1), function(k) { + begin = min(k * step, batchsize) + end = min((k + 1) * step, batchsize) + s <- shape + s[[ndim]] = end - begin + return(list(begin=begin, end=end, shape=s)) + }) + } else if (is.list(shape)) { + shape.names = names(shape) + ndim <- length(shape[[1]]) + batchsize <- shape[[1]][[ndim]] + step <- as.integer((batchsize + nsplit - 1) / nsplit) + lapply(0:(nsplit - 1), function(k) { + begin = min(k * step, batchsize) + end = min((k + 1) * step, batchsize) + s <- lapply(shape, function(s) { + s[[ndim]] = end - begin + return(s) + }) + return(list(begin=begin, end=end, shape=s)) + }) + } } # get the argument name of data and label @@ -17,14 +33,14 @@ mx.model.check.arguments <- function(symbol) { data <- NULL label <- NULL for (nm in arguments(symbol)) { - if (mx.util.str.endswith(nm, "data")) { + if (endsWith(nm, "data")) { if (!is.null(data)) { stop("Multiple fields contains suffix data") } else { data <- nm } } - if (mx.util.str.endswith(nm, "label")) { + if (endsWith(nm, "label")) { if (!is.null(label)) { stop("Multiple fields contains suffix label") } else { @@ -91,21 +107,27 @@ mx.model.create.kvstore <- function(kvstore, arg.params, ndevice, verbose=TRUE) } # Internal function to do multiple device training. -mx.model.train <- function(symbol, ctx, input.shape, +mx.model.train <- function(symbol, ctx, input.shape, output.shape, arg.params, aux.params, begin.round, end.round, optimizer, - train.data, eval.data, - metric, - epoch.end.callback, - batch.end.callback, - kvstore, - verbose=TRUE) { + train.data, eval.data, metric, + epoch.end.callback, batch.end.callback, + kvstore, fixed.param = NULL, verbose = TRUE) { ndevice <- length(ctx) if(verbose) message(paste0("Start training with ", ndevice, " devices")) # create the executors - sliceinfo <- mx.model.slice.shape(input.shape, ndevice) + input_slice <- mx.model.slice.shape(input.shape, ndevice) + output_slice <- mx.model.slice.shape(output.shape, ndevice) + + arg_names <- arguments(symbol) + output.names <- names(output.shape) + #label_name <- arg_names[endsWith(arg_names, "label")] train.execs <- lapply(1:ndevice, function(i) { - mx.simple.bind(symbol, ctx=ctx[[i]], data=sliceinfo[[i]]$shape, grad.req="write") + arg_lst <- list(symbol = symbol, ctx = ctx[[i]], grad.req = "write") + arg_lst <- append(arg_lst, input_slice[[i]]$shape) + arg_lst <- append(arg_lst, output_slice[[i]]$shape) + arg_lst[["fixed.param"]] = fixed.param + do.call(mx.simple.bind, arg_lst) }) # set the parameters into executors for (texec in train.execs) { @@ -131,7 +153,6 @@ mx.model.train <- function(symbol, ctx, input.shape, kvstore$init(params.index, train.execs[[1]]$ref.arg.arrays[params.index]) } # Get the input names - input.names <- mx.model.check.arguments(symbol) for (iteration in begin.round:end.round) { nbatch <- 0 @@ -142,15 +163,16 @@ mx.model.train <- function(symbol, ctx, input.shape, # Get input data slice dlist <- train.data$value() slices <- lapply(1:ndevice, function(i) { - s <- sliceinfo[[i]] - ret <- list(data=mx.nd.slice(dlist$data, s$begin, s$end), - label=mx.nd.slice(dlist$label, s$begin, s$end)) + s <- input_slice[[i]] + ret <- sapply(names(dlist), function(n) {mx.nd.slice(dlist[[n]], s$begin, s$end)}) return(ret) }) # copy data to executor for (i in 1:ndevice) { s <- slices[[i]] - names(s) <- input.names + if (endsWith(output.names, "label")) { + names(s)[endsWith(names(s), "label")] = output.names + } mx.exec.update.arg.arrays(train.execs[[i]], s, match.name=TRUE) } for (texec in train.execs) { @@ -164,6 +186,7 @@ mx.model.train <- function(symbol, ctx, input.shape, for (texec in train.execs) { mx.exec.backward(texec) } + if (!is.null(kvstore)) { # push the gradient kvstore$push(params.index, lapply(train.execs, function(texec) { @@ -192,7 +215,7 @@ mx.model.train <- function(symbol, ctx, input.shape, # Update the evaluation metrics if (!is.null(metric)) { for (i in 1 : ndevice) { - train.metric <- metric$update(slices[[i]]$label, out.preds[[i]], train.metric) + train.metric <- metric$update(slices[[i]][[length(slices[[i]])]], out.preds[[i]], train.metric) } } nbatch <- nbatch + 1 @@ -213,14 +236,15 @@ mx.model.train <- function(symbol, ctx, input.shape, while (eval.data$iter.next()) { dlist <- eval.data$value() slices <- lapply(1:ndevice, function(i) { - s <- sliceinfo[[i]] - ret <- list(data=mx.nd.slice(dlist$data, s$begin, s$end), - label=mx.nd.slice(dlist$label, s$begin, s$end)) + s <- input_slice[[i]] + ret <- sapply(names(dlist), function(n) {mx.nd.slice(dlist[[n]], s$begin, s$end)}) return(ret) }) for (i in 1:ndevice) { s <- slices[[i]] - names(s) <- input.names + if (endsWith(output.names, "label")) { + names(s)[endsWith(names(s), "label")] = output.names + } mx.exec.update.arg.arrays(train.execs[[i]], s, match.name=TRUE) } for (texec in train.execs) { @@ -231,7 +255,7 @@ mx.model.train <- function(symbol, ctx, input.shape, }) if (!is.null(metric)) { for (i in 1 : ndevice) { - eval.metric <- metric$update(slices[[i]]$label, out.preds[[i]], eval.metric) + eval.metric <- metric$update(slices[[i]][[length(slices[[i]])]] , out.preds[[i]], eval.metric) } } } @@ -258,10 +282,21 @@ mx.model.train <- function(symbol, ctx, input.shape, return(model) } -# Initialize parameters -mx.model.init.params <- function(symbol, input.shape, initializer, ctx) { +#' Parameter initialization +#' @param symbol The symbolic configuration of the neural network. +#' @param input.shape The shape of the input for the neural network. +#' @param output.shape The shape of the output for the neural network. It can be NULL. +#' @param initializer, initializer object. The initialization scheme for parameters. +#' @param ctx mx.context. The devices used to perform initialization. +#' @export +mx.model.init.params <- function(symbol, input.shape, output.shape, initializer, ctx) { if (!is.MXSymbol(symbol)) stop("symbol need to be MXSymbol") - slist <- mx.symbol.infer.shape(symbol, data=input.shape) + + arg_lst <- list(symbol = symbol) + arg_lst <- append(arg_lst, input.shape) + arg_lst <- append(arg_lst, output.shape) + + slist <- do.call(mx.symbol.infer.shape, arg_lst) if (is.null(slist)) stop("Not enough information to get shapes") arg.params <- mx.init.create(initializer, slist$arg.shapes, ctx, skip.unknown=TRUE) aux.params <- mx.init.create(initializer, slist$aux.shapes, ctx, skip.unknown=FALSE) @@ -270,7 +305,7 @@ mx.model.init.params <- function(symbol, input.shape, initializer, ctx) { # Initialize the data iter mx.model.init.iter <- function(X, y, batch.size, is.train) { - if (is.MXDataIter(X)) return(X) + if (is.mx.dataiter(X)) return(X) if (is.null(y)) { if (is.train) stop("Need to provide parameter y for training with R arrays.") shape <- dim(X) @@ -384,6 +419,17 @@ mx.model.select.layout.predict <- function(X, model) { #' Model parameter, list of name to NDArray of net's weights. #' @param aux.params list, optional #' Model parameter, list of name to NDArray of net's auxiliary states. +#' @param input.names optional +#' The names of the input symbols. +#' @param output.names optional +#' The names of the output symbols. +#' @param fixed.param +#' The parameters to be fixed during training. For these parameters, not gradients +#' will be calculated and thus no space will be allocated for the gradient. +#' @param allow.extra.params +#' Whether allow extra parameters that are not needed by symbol. +#' If this is TRUE, no error will be thrown when arg_params or aux_params +#' contain extra parameters that is not needed by the executor. #' @return model A trained mxnet model. #' #' @export @@ -395,9 +441,10 @@ function(symbol, X, y=NULL, ctx=NULL, begin.round=1, eval.data=NULL, eval.metric=NULL, epoch.end.callback=NULL, batch.end.callback=NULL, array.batch.size=128, array.layout="auto", - kvstore="local", - verbose=TRUE, - arg.params=NULL, aux.params=NULL, + kvstore = "local", verbose = TRUE, + arg.params = NULL, aux.params = NULL, + input.names=NULL, output.names = NULL, + fixed.param = NULL, allow.extra.params = FALSE, ...) { if (is.array(X) || is.matrix(X)) { if (array.layout == "auto") { @@ -412,18 +459,37 @@ function(symbol, X, y=NULL, ctx=NULL, begin.round=1, X$reset() if (!X$iter.next()) stop("Empty input") } - input.shape <- dim((X$value())$data) - params <- mx.model.init.params(symbol, input.shape, initializer, mx.cpu()) + if (is.null(input.names)) { + input.names <- "data" + } + input.shape <- sapply(input.names, function(n){dim(X$value()[[n]])}, simplify = FALSE) + if (is.null(output.names)) { + arg_names <- arguments(symbol) + output.names <- arg_names[endsWith(arg_names, "label")] + output.shape <- list() + output.shape[[output.names]] <- dim((X$value())$label) + } else { + output.shape <- sapply(output.names, function(n){dim(X$value()[[n]])}, simplify = FALSE) + } + params <- mx.model.init.params(symbol, input.shape, output.shape, initializer, mx.cpu()) if (!is.null(arg.params)) params$arg.params <- arg.params if (!is.null(aux.params)) params$aux.params <- aux.params + if (allow.extra.params) { + params$arg.params[!names(params$arg.params) %in% arguments(symbol)] <- NULL + } if (is.null(ctx)) ctx <- mx.ctx.default() if (is.mx.context(ctx)) { ctx <- list(ctx) } if (!is.list(ctx)) stop("ctx must be mx.context or list of mx.context") if (is.character(optimizer)) { - ndim <- length(input.shape) - batchsize = input.shape[[ndim]] + if (is.numeric(input.shape)) { + ndim <- length(input.shape) + batchsize = input.shape[[ndim]] + } else { + ndim <- length(input.shape[[1]]) + batchsize = input.shape[[1]][[ndim]] + } optimizer <- mx.opt.create(optimizer, rescale.grad=(1/batchsize), ...) } if (!is.null(eval.data) && !is.list(eval.data) && !is.mx.dataiter(eval.data)) { @@ -444,14 +510,15 @@ function(symbol, X, y=NULL, ctx=NULL, begin.round=1, eval.data <- mx.model.init.iter(eval.data$data, eval.data$label, batch.size=array.batch.size, is.train = TRUE) } kvstore <- mx.model.create.kvstore(kvstore, params$arg.params, length(ctx), verbose=verbose) - model <- mx.model.train(symbol, ctx, input.shape, + model <- mx.model.train(symbol, ctx, input.shape, output.shape, params$arg.params, params$aux.params, begin.round, num.round, optimizer=optimizer, train.data=X, eval.data=eval.data, metric=eval.metric, epoch.end.callback=epoch.end.callback, batch.end.callback=batch.end.callback, - kvstore=kvstore, + kvstore=kvstore, + fixed.param = fixed.param, verbose=verbose) return (model) } @@ -468,9 +535,14 @@ function(symbol, X, y=NULL, ctx=NULL, begin.round=1, #' "colmajor" means dim(X) = c(nfeatures, nexample) #' "auto" will auto detect the layout by match the feature size, #' and will report error when X is a square matrix to ask user to explicitly specify layout. -#' +#' @param allow.extra.params +#' Whether allow extra parameters that are not needed by symbol. +#' If this is TRUE, no error will be thrown when arg_params or aux_params +#' contain extra parameters that is not needed by the executor. #' @export -predict.MXFeedForwardModel <- function(model, X, ctx=NULL, array.batch.size=128, array.layout="auto") { +predict.MXFeedForwardModel <- function(model, X, ctx = NULL, array.batch.size = 128, + array.layout = "auto", allow.extra.params = FALSE) { + if (is.serialized(model)) model <- mx.unserialize(model) if (is.null(ctx)) ctx <- mx.ctx.default() if (is.array(X) || is.matrix(X)) { if (array.layout == "auto") { @@ -484,7 +556,12 @@ predict.MXFeedForwardModel <- function(model, X, ctx=NULL, array.batch.size=128, X$reset() if (!X$iter.next()) stop("Cannot predict on empty iterator") dlist = X$value() - pexec <- mx.simple.bind(model$symbol, ctx=ctx, data=dim(dlist$data), grad.req="null") + arg_lst <- list(symbol = model$symbol, ctx = ctx, data = dim(dlist$data), grad.req="null") + + pexec <- do.call(mx.simple.bind, arg_lst) + if (allow.extra.params) { + model$arg.params[!names(model$arg.params) %in% arguments(model$symbol)] <- NULL + } mx.exec.update.arg.arrays(pexec, model$arg.params, match.name=TRUE) mx.exec.update.aux.arrays(pexec, model$aux.params, match.name=TRUE) packer <- mx.nd.arraypacker() @@ -510,14 +587,14 @@ predict.MXFeedForwardModel <- function(model, X, ctx=NULL, array.batch.size=128, #' #' @export mx.model.load <- function(prefix, iteration) { - symbol <- mx.symbol.load(paste0(prefix, "-symbol.json")) - save.dict <- mx.nd.load(sprintf("%s-%04d.params", prefix, iteration)) + symbol <- mx.symbol.load(path.expand(paste0(prefix, "-symbol.json"))) + save.dict <- mx.nd.load(path.expand(sprintf("%s-%04d.params", prefix, iteration))) names <- names(save.dict) arg.index <- as.integer(mx.util.filter.null(lapply(1:length(names), function(i) { - if (mx.util.str.startswith(names[[i]], "arg:")) i else NULL + if (startsWith(names[[i]], "arg:")) i else NULL }))) aux.index <- as.integer(mx.util.filter.null(lapply(1:length(names), function(i) { - if (mx.util.str.startswith(names[[i]], "aux:")) i else NULL + if (startsWith(names[[i]], "aux:")) i else NULL }))) if (length(arg.index) != 0) { @@ -557,6 +634,55 @@ mx.model.save <- function(model, prefix, iteration) { paste0("aux:", nm) })) save.dict <- append(arg.params, aux.params) - mx.symbol.save(model$symbol, paste0(prefix, "-symbol.json")) - mx.nd.save(save.dict, sprintf("%s-%04d.params", prefix, iteration)) + mx.symbol.save(model$symbol, path.expand(paste0(prefix, "-symbol.json"))) + mx.nd.save(save.dict, path.expand(sprintf("%s-%04d.params", prefix, iteration))) +} + +#' Check if the model has been serialized into RData-compatiable format. +#' +#' @return Logical indicator +#' +#' @export +is.serialized <- function(model) { + if (!is.null(model[['is.serialized']])) { + return(model[['is.serialized']]) + } else { + return(FALSE) + } +} + +#' Serialize MXNet model into RData-compatiable format. +#' +#' @param model The mxnet model +#' +#' @export +mx.serialize <- function(model) { + if (!is.serialized(model)) { + model_rdata <- list() + model_rdata[['symbol_json']] <- model$symbol$as.json() + model_rdata[['arg.params']] <- lapply(model$arg.params, as.array) + model_rdata[['aux.params']] <- lapply(model$aux.params, as.array) + model_rdata[['is.serialized']] <- TRUE + class(model_rdata) <- "MXFeedForwardModel" + return(model_rdata) + } else { + return(model) + } +} + +#' Unserialize MXNet model from Robject. +#' +#' @param model The mxnet model loaded from RData files. +#' +#' @export +mx.unserialize <- function(model) { + if (!is.serialized(model)) { + return(model) + } else { + symbol <- mx.symbol.load.json(model$symbol_json) + arg.params <- lapply(model$arg.params, mx.nd.array) + aux.params <- lapply(model$aux.params, mx.nd.array) + model <- list(symbol=symbol, arg.params=arg.params, aux.params=aux.params) + return(structure(model, class="MXFeedForwardModel")) + } } diff --git a/R-package/R/ndarray.R b/R-package/R/ndarray.R index e30a90117455..da624b01be2d 100644 --- a/R-package/R/ndarray.R +++ b/R-package/R/ndarray.R @@ -145,7 +145,11 @@ is.mx.ndarray <- function(src.array) { #' @param e1 The second operand #' @export Ops.MXNDArray <- function(e1, e2) { - mx.nd.internal.dispatch.Ops(.Generic, e1, e2) + if (missing(e2)) { + mx.nd.internal.dispatch.Ops(.Generic, 0, e1) + } else { + mx.nd.internal.dispatch.Ops(.Generic, e1, e2) + } } #' Dimension operator overload of mx.ndarray diff --git a/R-package/R/optimizer.R b/R-package/R/optimizer.R index 820e382cb9ed..52fc1f24e5c1 100644 --- a/R-package/R/optimizer.R +++ b/R-package/R/optimizer.R @@ -32,12 +32,12 @@ mx.opt.sgd <- function(learning.rate, lr <- sgd$lr ## update count indexKey <- paste0('ik', index) - if (!exists(envir = sgd, x = indexKey)){ - assign(x = indexKey, value = 0, envir = sgd) + if (!exists(envir = sgd, x = indexKey, inherits = FALSE)){ + sgd[[indexKey]] <- 0 } else { - indexValue <- get(envir = sgd, x = indexKey) - assign(x = indexKey, value = indexValue + 1, envir = sgd) - sgd$num_update <- max(sgd$num_update, get(envir = sgd, x = indexKey)) + indexValue <- sgd[[indexKey]] + sgd[[indexKey]] <- indexValue + 1 + sgd$num_update <- max(sgd$num_update, sgd[[indexKey]]) } } grad <- grad * rescale.grad @@ -114,12 +114,12 @@ mx.opt.rmsprop <- function(learning.rate=0.002, lr <- rmsprop$lr ## update count indexKey <- paste0('ik', index) - if (!exists(envir = rmsprop, x = indexKey)){ - assign(x = indexKey, value = 0, envir = rmsprop) + if (!exists(envir = rmsprop, x = indexKey, inherits = FALSE)){ + rmsprop[[indexKey]] <- 0 } else { - indexValue <- get(envir = rmsprop, x = indexKey) - assign(x = indexKey, value = indexValue + 1, envir = rmsprop) - rmsprop$num_update <- max(rmsprop$num_update, get(envir = rmsprop, x = indexKey)) + indexValue <- rmsprop[[indexKey]] + rmsprop[[indexKey]] <- indexValue + 1 + rmsprop$num_update <- max(rmsprop$num_update, rmsprop[[indexKey]]) } } grad <- grad * rescale.grad @@ -201,23 +201,23 @@ mx.opt.adam <- function(learning.rate=0.001, lr <- adam$lr ## update count indexKey <- paste0('ik', index) - if (!exists(envir = adam, x = indexKey)){ - assign(x = indexKey, value = 0, envir = adam) + if (!exists(envir = adam, x = indexKey, inherits = FALSE)){ + adam[[indexKey]] <- 0 } else { - indexValue <- get(envir = adam, x = indexKey) - assign(x = indexKey, value = indexValue + 1, envir = adam) - adam$num_update <- max(adam$num_update, get(envir = adam, x = indexKey)) + indexValue <- adam[[indexKey]] + adam[[indexKey]] <- indexValue + 1 + adam$num_update <- max(adam$num_update, adam[[indexKey]]) } } # increment time time.key <- paste0('t', index) - if (!exists(envir = adam, x = time.key)){ - assign(x = time.key, value = 0, envir = adam) + if (!exists(envir = adam, x = time.key, inherits = FALSE)){ + adam[[time.key]] <- 0 } - t <- get(envir = adam, x = time.key) + t <- adam[[time.key]] t <- t + 1 - assign(x = time.key, value = t, envir = adam) + adam[[time.key]] <- t mean <- state$mean variance <- state$variance @@ -297,12 +297,12 @@ mx.opt.adagrad <- function(learning.rate=0.05, lr <- adagrad$lr ## update count indexKey <- paste0('ik', index) - if (!exists(envir = adagrad, x = indexKey)){ - assign(x = indexKey, value = 0, envir = adagrad) + if (!exists(envir = adagrad, x = indexKey, inherits = FALSE)){ + adagrad[[indexKey]] <- 0 } else { - indexValue <- get(envir = adagrad, x = indexKey) - assign(x = indexKey, value = indexValue + 1, envir = adagrad) - adagrad$num_update <- max(adagrad$num_update, get(envir = adagrad, x = indexKey)) + indexValue <- adagrad[[indexKey]] + adagrad[[indexKey]] <- indexValue + 1 + adagrad$num_update <- max(adagrad$num_update, adagrad[[indexKey]]) } } diff --git a/R-package/R/rnn_model.R b/R-package/R/rnn_model.R index 7a0c8be80bb8..aa4a7d03ca9b 100644 --- a/R-package/R/rnn_model.R +++ b/R-package/R/rnn_model.R @@ -3,20 +3,10 @@ is.param.name <- function(name) { grepl('gamma$', name) || grepl('beta$', name) ) } -# Initialize parameters -mx.model.init.params.rnn <- function(symbol, input.shape, initializer, ctx) { - if (!is.mx.symbol(symbol)) stop("symbol need to be MXSymbol") - slist <- symbol$infer.shape(input.shape) - if (is.null(slist)) stop("Not enough information to get shapes") - arg.params <- mx.init.create(initializer, slist$arg.shapes, ctx, skip.unknown=TRUE) - aux.params <- mx.init.create(initializer, slist$aux.shapes, ctx, skip.unknown=FALSE) - return(list(arg.params=arg.params, aux.params=aux.params)) -} - # Initialize the data iter mx.model.init.iter.rnn <- function(X, y, batch.size, is.train) { - if (is.MXDataIter(X)) return(X) - shape <- dim(data) + if (is.mx.dataiter(X)) return(X) + shape <- dim(X) if (is.null(shape)) { num.data <- length(X) } else { @@ -56,11 +46,11 @@ setup.rnn.model <- function(rnn.sym, ctx, } } } - params <- mx.model.init.params.rnn(rnn.sym, input.shapes, initializer, mx.cpu()) + params <- mx.model.init.params(rnn.sym, input.shapes, NULL, initializer, mx.cpu()) args <- input.shapes args$symbol <- rnn.sym args$ctx <- ctx - args$grad.req <- "add" + args$grad.req <- "write" rnn.exec <- do.call(mx.simple.bind, args) mx.exec.update.arg.arrays(rnn.exec, params$arg.params, match.name=TRUE) @@ -102,8 +92,16 @@ get.label <- function(label, ctx) { train.rnn <- function (model, train.data, eval.data, num.round, update.period, init.states.name, - optimizer='sgd', ctx=mx.ctx.default(), ...) { + optimizer='sgd', ctx=mx.ctx.default(), + epoch.end.callback, + batch.end.callback, + verbose=TRUE, + ...) { m <- model + + model <- list(symbol=model$symbol, arg.params=model$rnn.exec$ref.arg.arrays, + aux.params=model$rnn.exec$ref.aux.arrays) + seq.len <- m$seq.len batch.size <- m$batch.size num.rnn.layer <- m$num.rnn.layer @@ -173,6 +171,11 @@ train.rnn <- function (model, train.data, eval.data, train.nll <- train.nll + calc.nll(as.array(seq.label.probs), batch.size) nbatch <- nbatch + seq.len + + if (!is.null(batch.end.callback)) { + batch.end.callback(iteration, nbatch, environment()) + } + if ((epoch.counter %% log.period) == 0) { message(paste0("Epoch [", epoch.counter, "] Train: NLL=", train.nll / nbatch, @@ -220,6 +223,17 @@ train.rnn <- function (model, train.data, eval.data, "] Val: NLL=", val.nll / nbatch, ", Perp=", exp(val.nll / nbatch))) } + # get the model out + + + epoch_continue <- TRUE + if (!is.null(epoch.end.callback)) { + epoch_continue <- epoch.end.callback(iteration, 0, environment(), verbose = verbose) + } + + if (!epoch_continue) { + break + } } return (m) diff --git a/R-package/R/symbol.R b/R-package/R/symbol.R index d2fd67bc45c0..b97b19394209 100644 --- a/R-package/R/symbol.R +++ b/R-package/R/symbol.R @@ -32,14 +32,43 @@ mx.symbol.Group <- function(...) { #' @return out The result mx.symbol #' #' @export -mx.symbol.Concat <- function(data, num.args, dim = NULL, name = NULL) { +mx.symbol.concat <- function(data, num.args, dim = NULL, name = NULL) { data[['num.args']] <- num.args if(!is.null(dim)) data[['dim']] <- dim if(!is.null(name)) data[['name']] <- name - mx.varg.symbol.Concat(data) + mx.varg.symbol.concat(data) +} + +#' Perform an feature concat on channel dim (dim 1) over all the inputs. +#' +#' @param data list, required +#' List of tensors to concatenate +#' @param num.args int, required +#' Number of inputs to be concated. +#' @param dim int, optional, default='1' +#' the dimension to be concated. +#' @param name string, optional +#' Name of the resulting symbol. +#' @return out The result mx.symbol +#' +#' @export +mx.symbol.Concat <- function(data, num.args, dim = NULL, name = NULL) { + warning("mx.symbol.Concat is deprecated. Use mx.symbol.concat instead.") + mx.symbol.concat(data, num.args, dim, name) +} + +#' @export +mx.symbol.min <- function(e1, e2) { + if (is.mx.symbol(e1) && is.mx.symbol(e2)) { + mx.varg.symbol.internal.minimum(list(e1, e2)) + } else if (is.mx.symbol(e1)) { + mx.varg.symbol.internal.minimum_scalar(list(e1, scalar = e2)) + } else if (is.mx.symbol(e2)) { + mx.varg.symbol.internal.minimum_scalar(list(e2, scalar = e1)) + } } #' Save an mx.symbol object @@ -123,6 +152,24 @@ mx.apply <- function(x, ...) { x$apply(list(...)) } +#' Get a symbol that contains all the internals +#' @param x The input symbol +#' +#' @export +internals <- function(x) { + if (!is.MXSymbol(x)) stop("only for MXSymbol type") + x$get.internals() +} + +#' Gets a new grouped symbol whose output contains inputs to output nodes of the original symbol. +#' @param x The input symbol +#' +#' @export +children <- function(x) { + if (!is.MXSymbol(x)) stop("only for MXSymbol type") + x$get.children() +} + #' Get the outputs of a symbol. #' @param x The input symbol #' @@ -140,22 +187,61 @@ init.symbol.methods <- function() { setMethod("+", signature(e1 = "Rcpp_MXSymbol", e2 = "numeric"), function(e1, e2) { mx.varg.symbol.internal.PlusScalar(list(e1, scalar = e2)) }) + setMethod("+", signature(e1 = "numeric", e2 = "Rcpp_MXSymbol"), function(e1, e2) { + mx.varg.symbol.internal.PlusScalar(list(e2, scalar = e1)) + }) setMethod("-", signature(e1 = "Rcpp_MXSymbol", e2 = "Rcpp_MXSymbol"), function(e1, e2) { mx.varg.symbol.internal.Minus(list(e1, e2)) }) setMethod("-", signature(e1 = "Rcpp_MXSymbol", e2 = "numeric"), function(e1, e2) { mx.varg.symbol.internal.MinusScalar(list(e1, scalar = e2)) }) + setMethod("-", signature(e1 = "numeric", e2 = "Rcpp_MXSymbol"), function(e1, e2) { + mx.varg.symbol.internal.rminus_scalar(list(e2, scalar = e1)) + }) setMethod("*", signature(e1 = "Rcpp_MXSymbol", e2 = "Rcpp_MXSymbol"), function(e1, e2) { mx.varg.symbol.internal.Mul(list(e1, e2)) }) setMethod("*", signature(e1 = "Rcpp_MXSymbol", e2 = "numeric"), function(e1, e2) { mx.varg.symbol.internal.MulScalar(list(e1, scalar = e2)) }) + setMethod("*", signature(e1 = "numeric", e2 = "Rcpp_MXSymbol"), function(e1, e2) { + mx.varg.symbol.internal.MulScalar(list(e2, scalar = e1)) + }) setMethod("/", signature(e1 = "Rcpp_MXSymbol", e2 = "Rcpp_MXSymbol"), function(e1, e2) { mx.varg.symbol.internal.Div(list(e1, e2)) }) setMethod("/", signature(e1 = "Rcpp_MXSymbol", e2 = "numeric"), function(e1, e2) { mx.varg.symbol.internal.DivScalar(list(e1, scalar = e2)) }) + setMethod("/", signature(e1 = "numeric", e2 = "Rcpp_MXSymbol"), function(e1, e2) { + mx.varg.symbol.internal.rdiv_scalar(list(e2, scalar = e1)) + }) + setMethod("%%", signature(e1 = "Rcpp_MXSymbol", e2 = "Rcpp_MXSymbol"), function(e1, e2) { + mx.varg.symbol.internal.Mod(list(e1, e2)) + }) + setMethod("%%", signature(e1 = "Rcpp_MXSymbol", e2 = "numeric"), function(e1, e2) { + mx.varg.symbol.internal.ModScalar(list(e1, scalar = e2)) + }) + setMethod("%%", signature(e1 = "numeric", e2 = "Rcpp_MXSymbol"), function(e1, e2) { + mx.varg.symbol.internal.RModScalar(list(e2, scalar = e1)) + }) + setMethod("%/%", signature(e1 = "Rcpp_MXSymbol", e2 = "Rcpp_MXSymbol"), function(e1, e2) { + mx.varg.symbol.internal.Mod(list(e1, e2)) + }) + setMethod("%/%", signature(e1 = "Rcpp_MXSymbol", e2 = "numeric"), function(e1, e2) { + mx.varg.symbol.internal.ModScalar(list(e1, scalar = e2)) + }) + setMethod("%/%", signature(e1 = "numeric", e2 = "Rcpp_MXSymbol"), function(e1, e2) { + mx.varg.symbol.internal.RModScalar(list(e2, scalar = e1)) + }) + setMethod("^", signature(e1 = "Rcpp_MXSymbol", e2 = "Rcpp_MXSymbol"), function(e1, e2) { + mx.varg.symbol.internal.power(list(e1, e2)) + }) + setMethod("^", signature(e1 = "Rcpp_MXSymbol", e2 = "numeric"), function(e1, e2) { + mx.varg.symbol.internal.power_scalar(list(e1, scalar = e2)) + }) + setMethod("^", signature(e1 = "numeric", e2 = "Rcpp_MXSymbol"), function(e1, e2) { + mx.varg.symbol.internal.rpower_scalar(list(e2, scalar = e1)) + }) } diff --git a/R-package/R/util.R b/R-package/R/util.R index 9eaddf260a83..acc9510ccfd4 100644 --- a/R-package/R/util.R +++ b/R-package/R/util.R @@ -1,20 +1,3 @@ -# Internal function to check if name end with suffix -mx.util.str.endswith <- function(name, suffix) { - slen <- nchar(suffix) - nlen <- nchar(name) - if (slen > nlen) return (FALSE) - nsuf <- substr(name, nlen - slen + 1, nlen) - return (nsuf == suffix) -} - -mx.util.str.startswith <- function(name, prefix) { - slen <- nchar(prefix) - nlen <- nchar(name) - if (slen > nlen) return (FALSE) - npre <- substr(name, 1, slen) - return (npre == prefix) -} - # filter out null, keep the names mx.util.filter.null <- function(lst) { lst[!sapply(lst, is.null)] @@ -26,5 +9,52 @@ mx.util.filter.null <- function(lst) { #' #' @export mxnet.export <- function(path) { - mxnet.internal.export(path.expand(path)) + mx.internal.export(path.expand(path)) +} + +#' Convert images into image recordio format +#' @param image_lst +#' The image lst file +#' @param root +#' The root folder for image files +#' @param output_rec +#' The output rec file +#' @param label_width +#' The label width in the list file. Default is 1. +#' @param pack_label +#' Whether to also pack multi dimenional label in the record file. Default is 0. +#' @param new_size +#' The shorter edge of image will be resized to the newsize. +#' Original images will be packed by default. +#' @param nsplit +#' It is used for part generation, logically split the image.lst to NSPLIT parts by position. +#' Default is 1. +#' @param partid +#' It is used for part generation, pack the images from the specific part in image.lst. +#' Default is 0. +#' @param center_crop +#' Whether to crop the center image to make it square. Default is 0. +#' @param quality +#' JPEG quality for encoding (1-100, default: 95) or PNG compression for encoding (1-9, default: 3). +#' @param color_mode +#' Force color (1), gray image (0) or keep source unchanged (-1). Default is 1. +#' @param unchanged +#' Keep the original image encoding, size and color. If set to 1, it will ignore the others parameters. +#' @param inter_method +#' NN(0), BILINEAR(1), CUBIC(2), AREA(3), LANCZOS4(4), AUTO(9), RAND(10). Default is 1. +#' @param encoding +#' The encoding type for images. It can be '.jpg' or '.png'. Default is '.jpg'. +#' @export +im2rec <- function(image_lst, root, output_rec, label_width = 1L, + pack_label = 0L, new_size = -1L, nsplit = 1L, + partid = 0L, center_crop = 0L, quality = 95L, + color_mode = 1L, unchanged = 0L, inter_method = 1L, + encoding = ".jpg") { + image_lst <- path.expand(image_lst) + root <- path.expand(root) + output_rec <- path.expand(output_rec) + mx.internal.im2rec(image_lst, root, output_rec, label_width, + pack_label, new_size, nsplit, partid, + center_crop, quality, color_mode, unchanged, + inter_method, encoding) } diff --git a/R-package/README.md b/R-package/README.md index 75d4da5da450..6576700e11c6 100644 --- a/R-package/README.md +++ b/R-package/README.md @@ -1,7 +1,5 @@ Deep Learning for R ========================== -[![Build Status](https://travis-ci.org/dmlc/mxnet.svg?branch=master)](https://travis-ci.org/dmlc/mxnet) -[![Documentation Status](https://readthedocs.org/projects/mxnet/badge/?version=latest)](http://mxnet.readthedocs.io/en/latest/api/r/index.html) You have found MXNet R Package! The MXNet R packages brings flexible and efficient GPU computing and state-of-art deep learning to R. @@ -12,20 +10,17 @@ computing and state-of-art deep learning to R. Sounds exciting? This page contains links to all the related documentation of the R package. -Resources ---------- -* [MXNet R Package Document](http://mxnet.io/get_started/install.html) - - Check this out for detailed documents, examples and installation guides. Installation ------------ -For Windows/Mac users, we provide a pre-built binary package using CPU. -You can install a weekly updated package directly from the R console: +We provide pre-built binary packages for Windows/OSX users. +You can install the CPU package directly from the R console: ```r -install.packages("drat", repos="https://cran.rstudio.com") -drat:::addRepo("dmlc") +cran <- getOption("repos") +cran["dmlc"] <- "https://s3-us-west-2.amazonaws.com/apache-mxnet/R/CRAN/" +options(repos = cran) install.packages("mxnet") ``` diff --git a/R-package/src/Makevars b/R-package/src/Makevars index a9cdabfd9e00..c089c093389b 100644 --- a/R-package/src/Makevars +++ b/R-package/src/Makevars @@ -1,3 +1,3 @@ - +CXX_STD = CXX11 PKG_CPPFLAGS = -I../inst/include PKG_LIBS = $(LAPACK_LIBS) $(BLAS_LIBS) diff --git a/R-package/src/export.cc b/R-package/src/export.cc index ed8f4bc0c7fe..ef77d25fdf89 100644 --- a/R-package/src/export.cc +++ b/R-package/src/export.cc @@ -41,7 +41,7 @@ Exporter* Exporter::Get() { void Exporter::InitRcppModule() { using namespace Rcpp; // NOLINT(*) Exporter::Get()->scope_ = ::getCurrentScope(); - function("mxnet.internal.export", &Exporter::Export, + function("mx.internal.export", &Exporter::Export, Rcpp::List::create(_["path"]), "Internal function of mxnet, used to export generated functions file."); } @@ -93,7 +93,10 @@ void Exporter::Export(const std::string& path) { std::string fname = Rcpp::as(func_names[i]); // skip internal functions if (fname.find("internal.") != std::string::npos) continue; - if (fname == "mx.varg.symbol.Concat") continue; + if (fname == "mx.varg.symbol.Concat" + || fname == "mx.varg.symbol.concat" + || fname == "mx.varg.symbol.min_axis" + || fname == "mx.varg.symbol.min") continue; Rcpp::List func_info(scope->get_function(fname)); std::string docstr = Rcpp::as(func_info[2]); if (docstr.find("@export") == std::string::npos) continue; diff --git a/R-package/src/im2rec.cc b/R-package/src/im2rec.cc new file mode 100644 index 000000000000..0c6bea964a50 --- /dev/null +++ b/R-package/src/im2rec.cc @@ -0,0 +1,269 @@ +/*! + * Copyright (c) 2017 by Contributors + * \file export.h + * \brief Export module that takes charge of code generation and document + * Generation for functions exported from R-side + */ + +#include +#include +#include +#include +#include +#include +#include +#include "dmlc/base.h" +#include "dmlc/io.h" +#include "dmlc/timer.h" +#include "dmlc/logging.h" +#include "dmlc/recordio.h" +#include +#include "image_recordio.h" +#include "base.h" +#include "im2rec.h" + +namespace mxnet { +namespace R { + +int GetInterMethod(int inter_method, int old_width, int old_height, + int new_width, int new_height, std::mt19937& prnd) { // NOLINT(*) + if (inter_method == 9) { + if (new_width > old_width && new_height > old_height) { + return 2; // CV_INTER_CUBIC for enlarge + } else if (new_width rand_uniform_int(0, 4); + return rand_uniform_int(prnd); + } else { + return inter_method; + } +} + +IM2REC* IM2REC::Get() { + static IM2REC inst; + return &inst; +} + +void IM2REC::InitRcppModule() { + using namespace Rcpp; // NOLINT(*) + IM2REC::Get()->scope_ = ::getCurrentScope(); + function("mx.internal.im2rec", &IM2REC::im2rec, + Rcpp::List::create(_["image_lst"], + _["root"], + _["output_rec"], + _["label_width"], + _["pack_label"], + _["new_size"], + _["nsplit"], + _["partid"], + _["center_crop"], + _["quality"], + _["color_mode"], + _["unchanged"], + _["inter_method"], + _["encoding"]), + ""); +} + +void IM2REC::im2rec(const std::string & image_lst, const std::string & root, + const std::string & output_rec, + int label_width, int pack_label, int new_size, int nsplit, + int partid, int center_crop, int quality, + int color_mode, int unchanged, + int inter_method, std::string encoding) { + // Check parameters ranges + if (color_mode != -1 && color_mode != 0 && color_mode != 1) { + Rcpp::stop("Color mode must be -1, 0 or 1."); + } + if (encoding != std::string(".jpg") && encoding != std::string(".png")) { + Rcpp::stop("Encoding mode must be .jpg or .png."); + } + if (label_width <= 1 && pack_label) { + Rcpp::stop("pack_label can only be used when label_width > 1"); + } + if (new_size > 0) { + LOG(INFO) << "New Image Size: Short Edge " << new_size; + } else { + LOG(INFO) << "Keep origin image size"; + } + if (center_crop) { + LOG(INFO) << "Center cropping to square"; + } + if (color_mode == 0) { + LOG(INFO) << "Use gray images"; + } + if (color_mode == -1) { + LOG(INFO) << "Keep original color mode"; + } + LOG(INFO) << "Encoding is " << encoding; + + if (encoding == std::string(".png") && quality > 9) { + quality = 3; + } + if (inter_method != 1) { + switch (inter_method) { + case 0: + LOG(INFO) << "Use inter_method CV_INTER_NN"; + break; + case 2: + LOG(INFO) << "Use inter_method CV_INTER_CUBIC"; + break; + case 3: + LOG(INFO) << "Use inter_method CV_INTER_AREA"; + break; + case 4: + LOG(INFO) << "Use inter_method CV_INTER_LANCZOS4"; + break; + case 9: + LOG(INFO) << "Use inter_method mod auto(cubic for enlarge, area for shrink)"; + break; + case 10: + LOG(INFO) << "Use inter_method mod rand(nn/bilinear/cubic/area/lanczos4)"; + break; + } + } + std::random_device rd; + std::mt19937 prnd(rd()); + using namespace dmlc; + static const size_t kBufferSize = 1 << 20UL; + mxnet::io::ImageRecordIO rec; + size_t imcnt = 0; + double tstart = dmlc::GetTime(); + dmlc::InputSplit *flist = + dmlc::InputSplit::Create(image_lst.c_str(), partid, nsplit, "text"); + std::ostringstream os; + if (nsplit == 1) { + os << output_rec; + } else { + os << output_rec << ".part" << std::setw(3) << std::setfill('0') << partid; + } + LOG(INFO) << "Write to output: " << os.str(); + dmlc::Stream *fo = dmlc::Stream::Create(os.str().c_str(), "w"); + LOG(INFO) << "Output: " << os.str(); + dmlc::RecordIOWriter writer(fo); + std::string fname, path, blob; + std::vector decode_buf; + std::vector encode_buf; + std::vector encode_params; + if (encoding == std::string(".png")) { + encode_params.push_back(CV_IMWRITE_PNG_COMPRESSION); + encode_params.push_back(quality); + LOG(INFO) << "PNG encoding compression: " << quality; + } else { + encode_params.push_back(CV_IMWRITE_JPEG_QUALITY); + encode_params.push_back(quality); + LOG(INFO) << "JPEG encoding quality: " << quality; + } + dmlc::InputSplit::Blob line; + std::vector label_buf(label_width, 0.f); + + while (flist->NextRecord(&line)) { + std::string sline(static_cast(line.dptr), line.size); + std::istringstream is(sline); + if (!(is >> rec.header.image_id[0] >> rec.header.label)) continue; + label_buf[0] = rec.header.label; + for (int k = 1; k < label_width; ++k) { + RCHECK(is >> label_buf[k]) + << "Invalid ImageList, did you provide the correct label_width?"; + } + if (pack_label) rec.header.flag = label_width; + rec.SaveHeader(&blob); + if (pack_label) { + size_t bsize = blob.size(); + blob.resize(bsize + label_buf.size()*sizeof(float)); + memcpy(BeginPtr(blob) + bsize, + BeginPtr(label_buf), label_buf.size()*sizeof(float)); + } + RCHECK(std::getline(is, fname)); + // eliminate invalid chars in the end + while (fname.length() != 0 && + (isspace(*fname.rbegin()) || !isprint(*fname.rbegin()))) { + fname.resize(fname.length() - 1); + } + // eliminate invalid chars in beginning. + const char *p = fname.c_str(); + while (isspace(*p)) ++p; + path = root + p; + // use "r" is equal to rb in dmlc::Stream + dmlc::Stream *fi = dmlc::Stream::Create(path.c_str(), "r"); + decode_buf.clear(); + size_t imsize = 0; + while (true) { + decode_buf.resize(imsize + kBufferSize); + size_t nread = fi->Read(BeginPtr(decode_buf) + imsize, kBufferSize); + imsize += nread; + decode_buf.resize(imsize); + if (nread != kBufferSize) break; + } + delete fi; + + + if (unchanged != 1) { + cv::Mat img = cv::imdecode(decode_buf, color_mode); + RCHECK(img.data != NULL) << "OpenCV decode fail:" << path; + cv::Mat res = img; + if (new_size > 0) { + if (center_crop) { + if (img.rows > img.cols) { + int margin = (img.rows - img.cols)/2; + img = img(cv::Range(margin, margin+img.cols), cv::Range(0, img.cols)); + } else { + int margin = (img.cols - img.rows)/2; + img = img(cv::Range(0, img.rows), cv::Range(margin, margin + img.rows)); + } + } + int interpolation_method = 1; + if (img.rows > img.cols) { + if (img.cols != new_size) { + interpolation_method = GetInterMethod(inter_method, img.cols, img.rows, + new_size, + img.rows * new_size / img.cols, prnd); + cv::resize(img, res, cv::Size(new_size, + img.rows * new_size / img.cols), + 0, 0, interpolation_method); + } else { + res = img.clone(); + } + } else { + if (img.rows != new_size) { + interpolation_method = GetInterMethod(inter_method, img.cols, + img.rows, new_size * img.cols / img.rows, + new_size, prnd); + cv::resize(img, res, cv::Size(new_size * img.cols / img.rows, + new_size), 0, 0, interpolation_method); + } else { + res = img.clone(); + } + } + } + encode_buf.clear(); + RCHECK(cv::imencode(encoding, res, encode_buf, encode_params)); + + // write buffer + size_t bsize = blob.size(); + blob.resize(bsize + encode_buf.size()); + memcpy(BeginPtr(blob) + bsize, + BeginPtr(encode_buf), encode_buf.size()); + } else { + size_t bsize = blob.size(); + blob.resize(bsize + decode_buf.size()); + memcpy(BeginPtr(blob) + bsize, + BeginPtr(decode_buf), decode_buf.size()); + } + writer.WriteRecord(BeginPtr(blob), blob.size()); + // write header + ++imcnt; + if (imcnt % 1000 == 0) { + LOG(INFO) << imcnt << " images processed, " << GetTime() - tstart << " sec elapsed"; + } + } + LOG(INFO) << "Total: " << imcnt << " images processed, " << GetTime() - tstart << " sec elapsed"; + delete fo; + delete flist; +} +} // namespace R +} // namespace mxnet diff --git a/R-package/src/im2rec.h b/R-package/src/im2rec.h new file mode 100644 index 000000000000..a98a73327b97 --- /dev/null +++ b/R-package/src/im2rec.h @@ -0,0 +1,42 @@ +/*! + * Copyright (c) 2017 by Contributors + * \file export.h + * \brief Export module that takes charge of code generation and document + * Generation for functions exported from R-side + */ + +#ifndef MXNET_RCPP_IM2REC_H_ +#define MXNET_RCPP_IM2REC_H_ + +#include +#include + +namespace mxnet { +namespace R { + +class IM2REC { + public: + /*! + * \brief Export the generated file into path. + * \param path The path to be exported. + */ + static void im2rec(const std::string & image_lst, const std::string & root, + const std::string & output_rec, + int label_width = 1, int pack_label = 0, int new_size = -1, int nsplit = 1, + int partid = 0, int center_crop = 0, int quality = 95, + int color_mode = 1, int unchanged = 0, + int inter_method = 1, std::string encoding = ".jpg"); + // intialize the Rcpp module + static void InitRcppModule(); + + public: + // get the singleton of exporter + static IM2REC* Get(); + /*! \brief The scope of current module to export */ + Rcpp::Module* scope_; +}; + +} // namespace R +} // namespace mxnet + +#endif // MXNET_RCPP_IM2REC_H_ diff --git a/R-package/src/kvstore.cc b/R-package/src/kvstore.cc index 9896a8762b94..b15106b1dda6 100644 --- a/R-package/src/kvstore.cc +++ b/R-package/src/kvstore.cc @@ -112,33 +112,51 @@ void KVStore::SetOptimizer(const Rcpp::List& optimizer) { this)); } -NDArray KVStore::CreateState(int index, const NDArray& weight) const { +Rcpp::List KVStore::CreateState(int index, const NDArray& weight) const { RCHECK(optimizer_set_) << "Need to call set.optimizer for KVStore " << type(); - // TODO(KK) review this // Use R Internal API here Rcpp::Shield call(Rf_lang3(fcreate_state_, Rcpp::wrap(index), weight.RObject())); - return NDArray(Rcpp_eval(call)); + SEXP ret = Rcpp_eval(call); + if (Rf_isNull(ret)) { + return Rcpp::List::create(); + } else if (TYPEOF(ret) == EXTPTRSXP) { + return Rcpp::List::create(Rcpp::Named("state") = ret); + } else { + return ret; + } } void KVStore::Update(int index, const NDArray& grad, NDArray *weight) { RCHECK(optimizer_set_) << "Need to call set.optimizer for KVStore " << type(); - std::map::iterator it = states_.find(index); + std::map::iterator it = states_.find(index); + Rcpp::List state_lst = this->CreateState(index, *weight); if (it == states_.end()) { - NDArray nd = this->CreateState(index, *weight); - states_.insert(std::make_pair(index, nd)); - it = states_.find(index); + if (state_lst.size() != 0) { + states_.insert(std::make_pair(index, state_lst)); + it = states_.find(index); + } + } + + Rcpp::List rlist; + if (state_lst.size() == 0) { + Rcpp::Shield call(Rf_lang5(fupdate_, Rcpp::wrap(index), + weight->RObject(), grad.RObject(), + R_NilValue)); + rlist = Rcpp_eval(call); + } else if (state_lst.size() == 1) { + Rcpp::Shield call(Rf_lang5(fupdate_, Rcpp::wrap(index), + weight->RObject(), grad.RObject(), + it->second[0])); + rlist = Rcpp_eval(call); + } else { + // Use R Internal API here + Rcpp::Shield call(Rf_lang5(fupdate_, Rcpp::wrap(index), + weight->RObject(), grad.RObject(), + it->second)); + rlist = Rcpp_eval(call); } - NDArray& state = it->second; - // TODO(KK) review this - // Use R Internal API here - Rcpp::Shield call(Rf_lang5(fupdate_, Rcpp::wrap(index), - weight->RObject(), grad.RObject(), - state.RObject())); - Rcpp::List rlist(Rcpp_eval(call)); - // update the state, and eight - state = rlist["state"]; NDArray::CopyFromTo(NDArray::FromRObject(rlist["weight"]), weight); } diff --git a/R-package/src/kvstore.h b/R-package/src/kvstore.h index d4a92dfb7dad..f93613042825 100644 --- a/R-package/src/kvstore.h +++ b/R-package/src/kvstore.h @@ -76,14 +76,14 @@ class KVStore { private: explicit KVStore(KVStoreHandle handle) : handle_(handle), optimizer_set_(false) {} - // the internal callback to kvstore. - NDArray CreateState(int index, const NDArray& weight) const; + // the internal callback to kvstore. This might return NULL + Rcpp::List CreateState(int index, const NDArray& weight) const; /*! \brief internal KVStore handle */ KVStoreHandle handle_; /*! \brief Whether optimizer is setted*/ bool optimizer_set_; /*! \brief The internal state */ - std::map states_; + std::map states_; /*! \brief Function to create state */ Rcpp::RObject fcreate_state_; /*! \brief Function to perform update */ diff --git a/R-package/src/mxnet.cc b/R-package/src/mxnet.cc index 9d16190b3bd3..9f8239b94aa8 100644 --- a/R-package/src/mxnet.cc +++ b/R-package/src/mxnet.cc @@ -12,6 +12,7 @@ #include "./io.h" #include "./kvstore.h" #include "./export.h" +#include "./im2rec.h" namespace mxnet { namespace R { @@ -56,4 +57,6 @@ RCPP_MODULE(mxnet) { DataIterCreateFunction::InitRcppModule(); KVStore::InitRcppModule(); Exporter::InitRcppModule(); + IM2REC::InitRcppModule(); } + diff --git a/R-package/src/ndarray.cc b/R-package/src/ndarray.cc index 335e5d47b486..b289809cca9e 100644 --- a/R-package/src/ndarray.cc +++ b/R-package/src/ndarray.cc @@ -398,10 +398,16 @@ SEXP NDArrayFunction::operator() (SEXP* args) { std::vector param_vals; std::vector out_args; - for (mx_uint i = 0; i < arg_names_.size() - 1; ++i) { if (arg_nd_array_[i]) { - nd_args.push_back(NDArray(args[i])->handle); + if (TYPEOF(args[i]) == 22) { + nd_args.push_back(NDArray(args[i])->handle); + } else if (TYPEOF(args[i]) == 19) { + Rcpp::List data_lst = Rcpp::as(args[i]); + for (size_t k = 0; k < data_lst.size(); k++) { + nd_args.push_back(NDArray((SEXP)data_lst[k])->handle); + } + } } else { if (args[i] != R_NilValue) { param_keys.push_back(arg_names_[i].c_str()); @@ -541,6 +547,21 @@ NDArray::RObjectType DispatchOps(SEXP op, SEXP lhs, SEXP rhs) { static OpHandle div = NDArrayFunction::FindHandle("_div"); static OpHandle div_scalar = NDArrayFunction::FindHandle("_div_scalar"); static OpHandle rdiv_scalar = NDArrayFunction::FindHandle("_rdiv_scalar"); + static OpHandle mod = NDArrayFunction::FindHandle("_mod"); + static OpHandle mod_scalar = NDArrayFunction::FindHandle("_mod_scalar"); + static OpHandle rmod_scalar = NDArrayFunction::FindHandle("_rmod_scalar"); + static OpHandle equal = NDArrayFunction::FindHandle("_equal"); + static OpHandle equal_scalar = NDArrayFunction::FindHandle("_equal_scalar"); + static OpHandle not_equal = NDArrayFunction::FindHandle("_not_equal"); + static OpHandle not_equal_scalar = NDArrayFunction::FindHandle("_not_equal_scalar"); + static OpHandle greater = NDArrayFunction::FindHandle("_greater"); + static OpHandle greater_scalar = NDArrayFunction::FindHandle("_greater_scalar"); + static OpHandle greater_equal = NDArrayFunction::FindHandle("_greater_equal"); + static OpHandle greater_equal_scalar = NDArrayFunction::FindHandle("_greater_equal_scalar"); + static OpHandle lesser = NDArrayFunction::FindHandle("_lesser"); + static OpHandle lesser_scalar = NDArrayFunction::FindHandle("_lesser_scalar"); + static OpHandle lesser_equal = NDArrayFunction::FindHandle("_lesser_equal"); + static OpHandle lesser_equal_scalar = NDArrayFunction::FindHandle("_lesser_equal_scalar"); // parse the arguments std::string values[2]; NDArrayHandle handles[2]; @@ -591,8 +612,78 @@ NDArray::RObjectType DispatchOps(SEXP op, SEXP lhs, SEXP rhs) { } break; } + case '%': { + if (lhs_nd && rhs_nd) { + out = BinaryOp(mod, handles); + } else if (lhs_nd && !rhs_nd) { + out = BinaryScalarOp(mod_scalar, handles[0], values[1]); + } else { + out = BinaryScalarOp(rmod_scalar, handles[1], values[0]); + } + break; + } + case '=': { + if (lhs_nd && rhs_nd) { + out = BinaryOp(equal, handles); + } else if (lhs_nd && !rhs_nd) { + out = BinaryScalarOp(equal_scalar, handles[0], values[1]); + } else { + out = BinaryScalarOp(equal_scalar, handles[1], values[0]); + } + break; + } + case '!': { + if (lhs_nd && rhs_nd) { + out = BinaryOp(not_equal, handles); + } else if (lhs_nd && !rhs_nd) { + out = BinaryScalarOp(not_equal_scalar, handles[0], values[1]); + } else { + out = BinaryScalarOp(not_equal_scalar, handles[1], values[0]); + } + break; + } + case '>': { + if (sop == ">=") { + if (lhs_nd && rhs_nd) { + out = BinaryOp(greater_equal, handles); + } else if (lhs_nd && !rhs_nd) { + out = BinaryScalarOp(greater_equal_scalar, handles[0], values[1]); + } else { + out = BinaryScalarOp(lesser_equal_scalar, handles[1], values[0]); + } + } else { + if (lhs_nd && rhs_nd) { + out = BinaryOp(greater, handles); + } else if (lhs_nd && !rhs_nd) { + out = BinaryScalarOp(greater_scalar, handles[0], values[1]); + } else { + out = BinaryScalarOp(lesser_scalar, handles[1], values[0]); + } + } + break; + } + case '<': { + if (sop == "<=") { + if (lhs_nd && rhs_nd) { + out = BinaryOp(lesser_equal, handles); + } else if (lhs_nd && !rhs_nd) { + out = BinaryScalarOp(lesser_equal_scalar, handles[0], values[1]); + } else { + out = BinaryScalarOp(greater_equal_scalar, handles[1], values[0]); + } + } else { + if (lhs_nd && rhs_nd) { + out = BinaryOp(lesser, handles); + } else if (lhs_nd && !rhs_nd) { + out = BinaryScalarOp(lesser_scalar, handles[0], values[1]); + } else { + out = BinaryScalarOp(greater_scalar, handles[1], values[0]); + } + } + break; + } default: { - RLOG_FATAL << "Operator " << sop << "not supported for MXNDArray"; + RLOG_FATAL << "Operator " << sop << " not supported for MXNDArray"; } } return NDArray::RObject(out, true); diff --git a/R-package/src/symbol.cc b/R-package/src/symbol.cc index aa901ff64d9a..b5d6eca5fbdd 100644 --- a/R-package/src/symbol.cc +++ b/R-package/src/symbol.cc @@ -134,6 +134,12 @@ Symbol::RObjectType Symbol::GetInternals() const { return Symbol::RObject(out); } +Symbol::RObjectType Symbol::GetChildren() const { + SymbolHandle out; + MX_CALL(MXSymbolGetChildren(handle_, &out)); + return Symbol::RObject(out); +} + Symbol::RObjectType Symbol::GetOutput(mx_uint index) const { SymbolHandle out; MX_CALL(MXSymbolGetOutput(handle_, index - 1, &out)); @@ -344,6 +350,8 @@ void Symbol::InitRcppModule() { "List the auxiliary state names of the symbol") .method("get.internals", &Symbol::GetInternals, "Get a symbol that contains all the internals") + .method("get.children", &Symbol::GetChildren, + "Get a symbol that contains all the children") .method("get.output", &Symbol::GetOutput, "Get index-th output symbol of current one") .method("[[", &Symbol::GetOutput, diff --git a/R-package/src/symbol.h b/R-package/src/symbol.h index a361bdf2426d..4bfdebf2b959 100644 --- a/R-package/src/symbol.h +++ b/R-package/src/symbol.h @@ -69,6 +69,12 @@ class Symbol { * \return The internal of the symbol. */ RObjectType GetInternals() const; + /*! + * \brief Gets a new grouped symbol whose output contains + * inputs to output nodes of the original symbol. + * \return The children of the symbol. + */ + RObjectType GetChildren() const; /*! * \brief Get index-th outputs of the symbol. * \param symbol The symbol diff --git a/R-package/tests/testthat.R b/R-package/tests/testthat.R deleted file mode 100644 index f002e4c52c53..000000000000 --- a/R-package/tests/testthat.R +++ /dev/null @@ -1,4 +0,0 @@ -library(testthat) -library(mxnet) - -test_check("mxnet") diff --git a/R-package/tests/testthat/get_data.R b/R-package/tests/testthat/get_data.R new file mode 100644 index 000000000000..6d8de8516ae1 --- /dev/null +++ b/R-package/tests/testthat/get_data.R @@ -0,0 +1,107 @@ + +GetMNIST_ubyte <- function() { + if (!dir.exists("data")) { + dir.create("data/") + } + if (!file.exists('data/train-images-idx3-ubyte') | + !file.exists('data/train-labels-idx1-ubyte') | + !file.exists('data/t10k-images-idx3-ubyte') | + !file.exists('data/t10k-labels-idx1-ubyte')) { + download.file('http://data.mxnet.io/mxnet/data/mnist.zip', destfile = 'data/mnist.zip') + unzip('data/mnist.zip', exdir = 'data/') + file.remove('data/mnist.zip') + } +} + +GetMNIST_csv <- function() { + if (!dir.exists("data")) { + dir.create("data/") + } + if (!file.exists('data/train.csv') | + !file.exists('data/test.csv')) { + download.file('https://s3-us-west-2.amazonaws.com/apache-mxnet/R/data/mnist_csv.zip', + destfile = 'data/mnist_csv.zip') + unzip('data/mnist_csv.zip', exdir = 'data/') + file.remove('data/mnist_csv.zip') + } +} + +GetCifar10 <- function() { + if (!dir.exists("data")) { + dir.create("data/") + } + if (!file.exists('data/cifar/train.rec') | + !file.exists('data/cifar/test.rec') | + !file.exists('data/cifar/train.lst') | + !file.exists('data/cifar/test.lst')) { + download.file('http://data.mxnet.io/mxnet/data/cifar10.zip', + destfile = 'data/cifar10.zip') + unzip('data/cifar10.zip', exdir = 'data/') + file.remove('data/cifar10.zip') + } +} + +GetInception <- function() { + if (!dir.exists("model")) { + dir.create("model/") + } + if (!file.exists('model/Inception-BN-0126.params')) { + download.file('http://data.dmlc.ml/models/imagenet/inception-bn/Inception-BN-0126.params', + destfile = 'model/Inception-BN-0126.params') + } + if (!file.exists('model/Inception-BN-symbol.json')) { + download.file('http://data.dmlc.ml/models/imagenet/inception-bn/Inception-BN-symbol.json', + destfile = 'model/Inception-BN-symbol.json') + } +} + +GetCatDog <- function() { + if (!dir.exists("data")) { + dir.create("data/") + } + if (!file.exists('data/cats_dogs/cats_dogs_train.rec') | + !file.exists('data/cats_dogs/cats_dogs_val.rec')) { + download.file('https://s3-us-west-2.amazonaws.com/apache-mxnet/R/data/cats_dogs.zip', + destfile = 'data/cats_dogs.zip') + unzip('data/cats_dogs.zip', exdir = 'data/') + file.remove('data/cats_dogs.zip') + } +} + +GetMovieLens <- function() { + if (!dir.exists("data")) { + dir.create("data/") + } + if (!file.exists('data/ml-100k/u.data')) { + download.file('http://files.grouplens.org/datasets/movielens/ml-100k.zip', + destfile = 'data/ml-100k.zip') + unzip('data/ml-100k.zip', exdir = 'data/') + file.remove('data/ml-100k.zip') + } +} + +GetISBI_data <- function() { + if (!dir.exists("data")) { + dir.create("data/") + } + if (!file.exists('data/ISBI/train-volume.tif') | + !file.exists('data/ISBI/train-labels.tif')) { + download.file('https://s3-us-west-2.amazonaws.com/apache-mxnet/R/data/ISBI.zip', + destfile = 'data/ISBI.zip') + unzip('data/ISBI.zip', exdir = 'data/') + file.remove('data/ISBI.zip') + } +} + +GetCaptcha_data <- function() { + if (!dir.exists("data")) { + dir.create("data/") + } + if (!file.exists('data/captcha_example/captcha_train.rec') | + !file.exists('data/captcha_example/captcha_test.rec')) { + download.file('https://s3-us-west-2.amazonaws.com/apache-mxnet/R/data/captcha_example.zip', + destfile = 'data/captcha_example.zip') + unzip('data/captcha_example.zip', exdir = 'data/') + file.remove('data/captcha_example.zip') + } +} diff --git a/R-package/tests/testthat/test_img_seg.R b/R-package/tests/testthat/test_img_seg.R new file mode 100644 index 000000000000..fbca92e2a8a2 --- /dev/null +++ b/R-package/tests/testthat/test_img_seg.R @@ -0,0 +1,135 @@ +require(mxnet) + +source("get_data.R") + +if (Sys.getenv("R_GPU_ENABLE") != "" & as.integer(Sys.getenv("R_GPU_ENABLE")) == 1) { + mx.ctx.default(new = mx.gpu()) + message("Using GPU for testing.") +} + +print_inferred_shape <- function(net) { + slist <- mx.symbol.infer.shape(symbol = net, data = c(168, 168, 1, 2)) + print(slist$out.shapes) +} + +convolution_module <- function(net, kernel_size, pad_size, filter_count, + stride = c(1, 1), work_space = 2048, batch_norm = TRUE, + down_pool = FALSE, up_pool = FALSE, act_type = "relu", + convolution = TRUE) { + if (up_pool) { + net = mx.symbol.Deconvolution(net, kernel = c(2, 2), pad = c(0, 0), + stride = c(2, 2), num_filter = filter_count, + workspace = work_space) + net = mx.symbol.BatchNorm(net) + if (act_type != "") { + net = mx.symbol.Activation(net, act_type = act_type) + } + } + if (convolution) { + conv = mx.symbol.Convolution(data = net, kernel = kernel_size, stride = stride, + pad = pad_size, num_filter = filter_count, + workspace = work_space) + net = conv + } + if (batch_norm) { + net = mx.symbol.BatchNorm(net) + } + + if (act_type != "") { + net = mx.symbol.Activation(net, act_type = act_type) + } + + if (down_pool) { + pool = mx.symbol.Pooling(net, pool_type = "max", kernel = c(2, 2), stride = c(2, 2)) + net = pool + } + print_inferred_shape(net) + return(net) +} + +get_unet <- function() { + data = mx.symbol.Variable('data') + kernel_size = c(3, 3) + pad_size = c(1, 1) + filter_count = 32 + pool1 = convolution_module(data, kernel_size, pad_size, filter_count = filter_count, down_pool = TRUE) + net = pool1 + pool2 = convolution_module(net, kernel_size, pad_size, filter_count = filter_count * 2, down_pool = TRUE) + net = pool2 + pool3 = convolution_module(net, kernel_size, pad_size, filter_count = filter_count * 4, down_pool = TRUE) + net = pool3 + pool4 = convolution_module(net, kernel_size, pad_size, filter_count = filter_count * 4, down_pool = TRUE) + net = pool4 + net = mx.symbol.Dropout(net) + pool5 = convolution_module(net, kernel_size, pad_size, filter_count = filter_count * 8, down_pool = TRUE) + net = pool5 + net = convolution_module(net, kernel_size, pad_size, filter_count = filter_count * 4, up_pool = TRUE) + net = convolution_module(net, kernel_size, pad_size = c(2, 2), filter_count = filter_count * 4, up_pool = TRUE) + net = mx.symbol.Crop(net, pool3, num.args = 2) + net = mx.symbol.concat(c(pool3, net), num.args = 2) + net = mx.symbol.Dropout(net) + net = convolution_module(net, kernel_size, pad_size, filter_count = filter_count * 4) + net = convolution_module(net, kernel_size, pad_size, filter_count = filter_count * 4, up_pool = TRUE) + + net = mx.symbol.Concat(c(pool2, net), num.args = 2) + net = mx.symbol.Dropout(net) + net = convolution_module(net, kernel_size, pad_size, filter_count = filter_count * 4) + net = convolution_module(net, kernel_size, pad_size, filter_count = filter_count * 4, up_pool = TRUE) + convolution_module(net, kernel_size, pad_size, filter_count = filter_count * 4) + net = mx.symbol.Concat(c(pool1, net), num.args = 2) + net = mx.symbol.Dropout(net) + net = convolution_module(net, kernel_size, pad_size, filter_count = filter_count * 2) + net = convolution_module(net, kernel_size, pad_size, filter_count = filter_count * 2, up_pool = TRUE) + net = convolution_module(net, kernel_size, pad_size, filter_count = 1, batch_norm = FALSE, act_type = "") + net = mx.symbol.SoftmaxOutput(data = net, name = 'sm') + return(net) +} + +context("Image segmentation") + +test_that("UNET", { + list.of.packages <- c("imager") + new.packages <- list.of.packages[!(list.of.packages %in% installed.packages()[,"Package"])] + if(length(new.packages)) install.packages(new.packages) + GetISBI_data() + library(imager) + IMG_SIZE <- 168 + files <- list.files(path = "data/ISBI/train-volume/") + a = 'data/ISBI/train-volume/' + filess = paste(a, files, sep = '') + list_of_images = lapply(filess, function(x) { + x <- load.image(x) + y <- resize(x, size_x = IMG_SIZE, size_y = IMG_SIZE) + }) + + train.x = do.call('cbind', lapply(list_of_images, as.vector)) + train.array <- train.x + dim(train.array) <- c(IMG_SIZE, IMG_SIZE, 1, 30) + + files <- list.files(path = "data/ISBI/train-labels") + b = 'data/ISBI/train-labels/' + filess = paste(b, files, sep = '') + list_of_images = lapply(filess, function(x) { + x <- load.image(x) + y <- resize(x, size_x = IMG_SIZE, size_y = IMG_SIZE) + }) + + train.y = do.call('cbind', lapply(list_of_images, as.vector)) + + train.y[which(train.y < 0.5)] = 0 + train.y[which(train.y > 0.5)] = 1 + train.y.array = train.y + dim(train.y.array) = c(IMG_SIZE, IMG_SIZE, 1, 30) + + devices <- mx.ctx.default() + mx.set.seed(0) + + net <- get_unet() + + model <- mx.model.FeedForward.create(net, X = train.array, y = train.y.array, + ctx = devices, num.round = 2, + initializer = mx.init.normal(sqrt(2 / 576)), + learning.rate = 0.05, + momentum = 0.99, + array.batch.size = 2) +}) \ No newline at end of file diff --git a/R-package/tests/testthat/test_io.R b/R-package/tests/testthat/test_io.R new file mode 100644 index 000000000000..d619856cbb99 --- /dev/null +++ b/R-package/tests/testthat/test_io.R @@ -0,0 +1,86 @@ +require(mxnet) + +context("io") + +source("get_data.R") + +test_that("MNISTIter", { + GetMNIST_ubyte() + batch.size <- 100 + train_dataiter <- mx.io.MNISTIter( + image = "data/train-images-idx3-ubyte", + label = "data/train-labels-idx1-ubyte", + data.shape = c(784), + batch.size = batch.size, + shuffle = TRUE, + flat = TRUE, + silent = 0, + seed = 10 + ) + train_dataiter$reset() + batch_count = 0 + while (train_dataiter$iter.next()) { + batch_count = batch_count + 1 + } + nbatch = 60000 / batch.size + expect_equal(batch_count, nbatch) + train_dataiter$reset() + train_dataiter$iter.next() + label_0 <- as.array(train_dataiter$value()$label) + train_dataiter$iter.next() + train_dataiter$iter.next() + train_dataiter$iter.next() + train_dataiter$iter.next() + train_dataiter$reset() + train_dataiter$iter.next() + label_1 <- as.array(train_dataiter$value()$label) + expect_equal(label_0, label_1) +}) + +test_that("Cifar10Rec", { + GetCifar10() + dataiter <- mx.io.ImageRecordIter( + path.imgrec = "./data/cifar/train.rec", + path.imglist = "./data/cifar/train.lst", + mean.img = "./data/cifar/cifar10_mean.bin", + batch.size = 100, + data.shape = c(28, 28, 3), + rand.crop = TRUE, + rand.mirror = TRUE + ) + labelcount = rep(0, 10) + dataiter$reset() + while (dataiter$iter.next()) { + label = as.array(dataiter$value()$label) + for (i in label) { + labelcount[i + 1] = labelcount[i + 1] + 1 + } + } + + expect_equal(labelcount, rep(5000, 10)) +}) + +test_that("mx.io.arrayiter", { + X <- matrix(c(1:10000), 100, 100) + y <- c(1:100) + dataiter <- mx.io.arrayiter(X, y, batch.size = 20, shuffle = FALSE) + dataiter$reset() + batch_count = 0 + while (dataiter$iter.next()) { + batch_count = batch_count + 1 + } + expect_equal(batch_count, 100 / 20) + + y <- round(y / 10) + dataiter <- mx.io.arrayiter(X, y, batch.size = 30, shuffle = FALSE) + labelcount <- rep(0, 11) + dataiter$reset() + while (dataiter$iter.next()) { + label <- as.array(dataiter$value()$label) + for (i in label) { + labelcount[i + 1] = labelcount[i + 1] + 1 + } + } + + expect_equal(labelcount, c(5, 9, 11, 9, 11, 9, 11, 13, 22, 14, 6)) +}) diff --git a/R-package/tests/testthat/test_lstm.R b/R-package/tests/testthat/test_lstm.R index 24b1a59636dc..4a5cdbeb436f 100644 --- a/R-package/tests/testthat/test_lstm.R +++ b/R-package/tests/testthat/test_lstm.R @@ -1,5 +1,10 @@ require(mxnet) +if (Sys.getenv("R_GPU_ENABLE") != "" & as.integer(Sys.getenv("R_GPU_ENABLE")) == 1) { + mx.ctx.default(new = mx.gpu()) + message("Using GPU for testing.") +} + context("lstm models") get.nll <- function(s) { @@ -26,7 +31,7 @@ test_that("training error decreasing", { X.train <- list(data=array(1:16, dim=c(2,8)), label=array(2:17, dim=c(2,8))) s <- capture.output(model <- mx.lstm( X.train, - ctx=mx.cpu(), + ctx=mx.ctx.default(), num.round=num.round, update.period=update.period, num.lstm.layer=num.lstm.layer, diff --git a/R-package/tests/testthat/test_model.R b/R-package/tests/testthat/test_model.R index 93784a622bbb..8cdd396c2525 100644 --- a/R-package/tests/testthat/test_model.R +++ b/R-package/tests/testthat/test_model.R @@ -1,62 +1,322 @@ require(mxnet) +source("get_data.R") + context("models") -# test_that("basic symbol operation", { +if (Sys.getenv("R_GPU_ENABLE") != "" & as.integer(Sys.getenv("R_GPU_ENABLE")) == 1) { + mx.ctx.default(new = mx.gpu()) + message("Using GPU for testing.") +} + +test_that("MNIST", { # # Network configuration -# batch.size <- 100 -# data <- mx.symbol.Variable("data") -# fc1 <- mx.symbol.FullyConnected(data, name="fc1", num_hidden=128) -# act1 <- mx.symbol.Activation(fc1, name="relu1", act_type="relu") -# fc2 <- mx.symbol.FullyConnected(act1, name = "fc2", num_hidden = 64) -# act2 <- mx.symbol.Activation(fc2, name="relu2", act_type="relu") -# fc3 <- mx.symbol.FullyConnected(act2, name="fc3", num_hidden=10) -# softmax <- mx.symbol.Softmax(fc3, name = "sm") -# -# dtrain = mx.io.MNISTIter( -# image="data/train-images-idx3-ubyte", -# label="data/train-labels-idx1-ubyte", -# data.shape=c(784), -# batch.size=batch.size, -# shuffle=TRUE, -# flat=TRUE, -# silent=0, -# seed=10) -# -# dtest = mx.io.MNISTIter( -# image="data/t10k-images-idx3-ubyte", -# label="data/t10k-labels-idx1-ubyte", -# data.shape=c(784), -# batch.size=batch.size, -# shuffle=FALSE, -# flat=TRUE, -# silent=0) -# -# mx.set.seed(0) -# devices = lapply(1:2, function(i) { -# mx.cpu(i) -# }) -# -# # create the model -# model <- mx.model.FeedForward.create(softmax, X=dtrain, eval.data=dtest, -# ctx=devices, num.round=1, -# learning.rate=0.1, momentum=0.9, -# initializer=mx.init.uniform(0.07), -# epoch.end.callback=mx.callback.save.checkpoint("chkpt"), -# batch.end.callback=mx.callback.log.train.metric(100)) -# -# # do prediction -# pred <- predict(model, dtest) -# label <- mx.io.extract(dtest, "label") -# dataX <- mx.io.extract(dtest, "data") -# # Predict with R's array -# pred2 <- predict(model, X=dataX) -# -# accuracy <- function(label, pred) { -# ypred = max.col(t(as.array(pred))) -# return(sum((as.array(label) + 1) == ypred) / length(label)) -# } -# -# print(paste0("Finish prediction... accuracy=", accuracy(label, pred))) -# print(paste0("Finish prediction... accuracy2=", accuracy(label, pred2))) -# }) + GetMNIST_ubyte() + batch.size <- 100 + data <- mx.symbol.Variable("data") + fc1 <- mx.symbol.FullyConnected(data, name="fc1", num_hidden=128) + act1 <- mx.symbol.Activation(fc1, name="relu1", act_type="relu") + fc2 <- mx.symbol.FullyConnected(act1, name = "fc2", num_hidden = 64) + act2 <- mx.symbol.Activation(fc2, name="relu2", act_type="relu") + fc3 <- mx.symbol.FullyConnected(act2, name="fc3", num_hidden=10) + softmax <- mx.symbol.Softmax(fc3, name = "sm") + + dtrain = mx.io.MNISTIter( + image="data/train-images-idx3-ubyte", + label="data/train-labels-idx1-ubyte", + data.shape=c(784), + batch.size=batch.size, + shuffle=TRUE, + flat=TRUE, + silent=0, + seed=10) + + dtest = mx.io.MNISTIter( + image="data/t10k-images-idx3-ubyte", + label="data/t10k-labels-idx1-ubyte", + data.shape=c(784), + batch.size=batch.size, + shuffle=FALSE, + flat=TRUE, + silent=0) + + mx.set.seed(0) + + # create the model + model <- mx.model.FeedForward.create(softmax, X=dtrain, eval.data=dtest, + ctx = mx.ctx.default(), num.round=1, + learning.rate=0.1, momentum=0.9, + initializer=mx.init.uniform(0.07), + epoch.end.callback=mx.callback.save.checkpoint("chkpt"), + batch.end.callback=mx.callback.log.train.metric(100)) + + # do prediction + pred <- predict(model, dtest) + label <- mx.io.extract(dtest, "label") + dataX <- mx.io.extract(dtest, "data") + # Predict with R's array + pred2 <- predict(model, X=dataX) + + accuracy <- function(label, pred) { + ypred = max.col(t(as.array(pred))) + return(sum((as.array(label) + 1) == ypred) / length(label)) + } + + expect_equal(accuracy(label, pred), accuracy(label, pred2)) + + file.remove("chkpt-0001.params") + file.remove("chkpt-symbol.json") +}) + +test_that("Regression", { + data(BostonHousing, package = "mlbench") + train.ind <- seq(1, 506, 3) + train.x <- data.matrix(BostonHousing[train.ind,-14]) + train.y <- BostonHousing[train.ind, 14] + test.x <- data.matrix(BostonHousing[-train.ind,-14]) + test.y <- BostonHousing[-train.ind, 14] + data <- mx.symbol.Variable("data") + fc1 <- mx.symbol.FullyConnected(data, num_hidden = 1) + lro <- mx.symbol.LinearRegressionOutput(fc1) + + demo.metric.mae <- mx.metric.custom("mae", function(label, pred) { + res <- mean(abs(label - pred)) + return(res) + }) + mx.set.seed(0) + model <- mx.model.FeedForward.create(lro, X = train.x, y = train.y, + ctx = mx.ctx.default(), num.round = 5, + array.batch.size = 20, + learning.rate = 2e-6, + momentum = 0.9, + eval.metric = demo.metric.mae) + + train.x <- data.matrix(BostonHousing[train.ind, -(13:14)]) + train.y <- BostonHousing[train.ind, c(13:14)] + test.x <- data.matrix(BostonHousing[-train.ind, -(13:14)]) + test.y <- BostonHousing[-train.ind, c(13:14)] + + data <- mx.symbol.Variable("data") + fc2 <- mx.symbol.FullyConnected(data, num_hidden=2) + lro2 <- mx.symbol.LinearRegressionOutput(fc2) + + mx.set.seed(0) + train_iter = mx.io.arrayiter(data = t(train.x), label = t(train.y)) + + model <- mx.model.FeedForward.create(lro2, X = train_iter, + ctx = mx.ctx.default(), + num.round = 50, + array.batch.size = 20, + learning.rate = 2e-6, + momentum = 0.9) +}) + + +test_that("Classification", { + data(Sonar, package = "mlbench") + Sonar[, 61] <- as.numeric(Sonar[, 61]) - 1 + train.ind <- c(1:50, 100:150) + train.x <- data.matrix(Sonar[train.ind, 1:60]) + train.y <- Sonar[train.ind, 61] + test.x <- data.matrix(Sonar[-train.ind, 1:60]) + test.y <- Sonar[-train.ind, 61] + mx.set.seed(0) + model <- mx.mlp(train.x, train.y, hidden_node = 10, + out_node = 2, out_activation = "softmax", + num.round = 5, array.batch.size = 15, + learning.rate = 0.07, + momentum = 0.9, + eval.metric = mx.metric.accuracy) +}) + +test_that("Fine-tune", { + GetInception() + GetCatDog() + train_iter <- mx.io.ImageRecordIter(path.imgrec = "./data/cats_dogs/cats_dogs_train.rec", + batch.size = 8, data.shape = c(224, 224, 3), + rand.crop = TRUE, rand.mirror = TRUE) + val_iter <- mx.io.ImageRecordIter(path.imgrec = "./data/cats_dogs/cats_dogs_val.rec", + batch.size = 8, data.shape = c(224, 224, 3), + rand.crop = FALSE, rand.mirror = FALSE) + inception_bn <- mx.model.load("./model/Inception-BN", iteration = 126) + symbol <- inception_bn$symbol + internals <- symbol$get.internals() + outputs <- internals$outputs + + flatten <- internals$get.output(which(outputs == "flatten_output")) + + new_fc <- mx.symbol.FullyConnected(data = flatten, num_hidden = 2, name = "fc1") + new_soft <- mx.symbol.SoftmaxOutput(data = new_fc, name = "softmax") + arg_params_new <- mx.model.init.params(symbol = new_soft, + input.shape = list("data" = c(224, 224, 3, 8)), + output.shape = NULL, + initializer = mx.init.uniform(0.1), + ctx = mx.cpu())$arg.params + fc1_weights_new <- arg_params_new[["fc1_weight"]] + fc1_bias_new <- arg_params_new[["fc1_bias"]] + + arg_params_new <- inception_bn$arg.params + + arg_params_new[["fc1_weight"]] <- fc1_weights_new + arg_params_new[["fc1_bias"]] <- fc1_bias_new + + #model <- mx.model.FeedForward.create(symbol = new_soft, X = train_iter, eval.data = val_iter, + # ctx = mx.ctx.default(), eval.metric = mx.metric.accuracy, + # num.round = 2, learning.rate = 0.05, momentum = 0.9, + # wd = 0.00001, kvstore = "local", + # batch.end.callback = mx.callback.log.train.metric(50), + # initializer = mx.init.Xavier(factor_type = "in", magnitude = 2.34), + # optimizer = "sgd", + # arg.params = arg_params_new, + # aux.params = inception_bn$aux.params) +}) + +test_that("Matrix Factorization", { + GetMovieLens() + DF <- read.table("./data/ml-100k/u.data", header = F, sep = "\t") + names(DF) <- c("user", "item", "score", "time") + max_user <- max(DF$user) + max_item <- max(DF$item) + DF_mat_x <- data.matrix(t(DF[, 1:2])) + DF_y <- DF[, 3] + k <- 64 + user <- mx.symbol.Variable("user") + item <- mx.symbol.Variable("item") + score <- mx.symbol.Variable("score") + user1 <- mx.symbol.Embedding(data = mx.symbol.BlockGrad(user), input_dim = max_user, + output_dim = k, name = "user1") + item1 <- mx.symbol.Embedding(data = mx.symbol.BlockGrad(item), input_dim = max_item, + output_dim = k, name = "item1") + pred <- user1 * item1 + pred1 <- mx.symbol.sum_axis(pred, axis = 1, name = "pred1") + pred2 <- mx.symbol.Flatten(pred1, name = "pred2") + pred3 <- mx.symbol.LinearRegressionOutput(data = pred2, label = score, name = "pred3") + + mx.set.seed(123) + + CustomIter <- setRefClass( "CustomIter", fields = c("iter1", "iter2"), + contains = "Rcpp_MXArrayDataIter", + methods = list( + initialize = function(iter1, iter2) { + .self$iter1 <- iter1 + .self$iter2 <- iter2 + .self + }, + value = function() { + user <- .self$iter1$value()$data + item <- .self$iter2$value()$data + score <- .self$iter1$value()$label + list(user = user, + item = item, + score = score) + }, + iter.next = function() { + .self$iter1$iter.next() + .self$iter2$iter.next() + }, + reset = function() { + .self$iter1$reset() + .self$iter2$reset() + }, + num.pad = function() { + .self$iter1$num.pad() + }, + finalize = function() { + .self$iter1$finalize() + .self$iter2$finalize() + } + ) + ) + + user_iter = mx.io.arrayiter(data = DF[, 1], label = DF[, 3], batch.size = k) + + item_iter = mx.io.arrayiter(data = DF[, 2], label = DF[, 3], batch.size = k) + + train_iter <- CustomIter$new(user_iter, item_iter) + + model <- mx.model.FeedForward.create(pred3, X = train_iter, ctx = mx.ctx.default(), + num.round = 5, initializer = mx.init.uniform(0.07), + learning.rate = 0.07, + eval.metric = mx.metric.rmse, + momentum = 0.9, + epoch.end.callback = mx.callback.log.train.metric(1), + input.names = c("user", "item"), + output.names = "score") +}) + +test_that("Captcha", { + GetCaptcha_data() + data.shape <- c(80, 30, 3) + batch_size <- 40 + train <- mx.io.ImageRecordIter( + path.imgrec = "./data/captcha_example/captcha_train.rec", + path.imglist = "./data/captcha_example/captcha_train.lst", + batch.size = batch_size, + label.width = 4, + data.shape = data.shape, + mean.img = "mean.bin") + + val <- mx.io.ImageRecordIter( + path.imgrec = "./data/captcha_example/captcha_test.rec", + path.imglist = "./data/captcha_example/captcha_test.lst", + batch.size = batch_size, + label.width = 4, + data.shape = data.shape, + mean.img = "mean.bin") + + data <- mx.symbol.Variable("data") + label <- mx.symbol.Variable("label") + conv1 <- mx.symbol.Convolution(data = data, kernel = c(5, 5), num_filter = 32) + pool1 <- mx.symbol.Pooling(data = conv1, pool_type = "max", kernel = c(2, 2), stride = c(1, 1)) + relu1 <- mx.symbol.Activation(data = pool1, act_type = "relu") + + conv2 <- mx.symbol.Convolution(data = relu1, kernel = c(5, 5), num_filter = 32) + pool2 <- mx.symbol.Pooling(data = conv2, pool_type = "avg", kernel = c(2, 2), stride = c(1, 1)) + relu2 <- mx.symbol.Activation(data = pool2, act_type = "relu") + + flatten <- mx.symbol.Flatten(data = relu2) + fc1 <- mx.symbol.FullyConnected(data = flatten, num_hidden = 120) + fc21 <- mx.symbol.FullyConnected(data = fc1, num_hidden = 10) + fc22 <- mx.symbol.FullyConnected(data = fc1, num_hidden = 10) + fc23 <- mx.symbol.FullyConnected(data = fc1, num_hidden = 10) + fc24 <- mx.symbol.FullyConnected(data = fc1, num_hidden = 10) + fc2 <- mx.symbol.Concat(c(fc21, fc22, fc23, fc24), dim = 0, num.args = 4) + label <- mx.symbol.transpose(data = label) + label <- mx.symbol.Reshape(data = label, target_shape = c(0)) + captcha_net <- mx.symbol.SoftmaxOutput(data = fc2, label = label, name = "softmax") + + mx.metric.acc2 <- mx.metric.custom("accuracy", function(label, pred) { + ypred <- max.col(t(pred)) - 1 + ypred <- matrix(ypred, nrow = nrow(label), ncol = ncol(label), byrow = TRUE) + return(sum(colSums(label == ypred) == 4)/ncol(label)) + }) + + mx.set.seed(42) + + train$reset() + train$iter.next() + + input.names <- "data" + input.shape <- sapply(input.names, function(n){dim(train$value()[[n]])}, simplify = FALSE) + arg_names <- arguments(captcha_net) + output.names <- "label" + output.shape <- sapply(output.names, function(n){dim(train$value()[[n]])}, simplify = FALSE) + params <- mx.model.init.params(captcha_net, input.shape, output.shape, + mx.init.Xavier(factor_type = "in", magnitude = 2.34), + mx.cpu()) + + #model <- mx.model.FeedForward.create( + # X = train, + # eval.data = val, + # ctx = mx.ctx.default(), + # symbol = captcha_net, + # eval.metric = mx.metric.acc2, + # num.round = 1, + # learning.rate = 1e-04, + # momentum = 0.9, + # wd = 1e-05, + # batch.end.callback = mx.callback.log.train.metric(50), + # initializer = mx.init.Xavier(factor_type = "in", magnitude = 2.34), + # optimizer = "sgd", + # clip_gradient = 10) +}) diff --git a/R-package/tests/testthat/test_ndarray.R b/R-package/tests/testthat/test_ndarray.R index 0be603eb5c2a..326ea6ca7f30 100644 --- a/R-package/tests/testthat/test_ndarray.R +++ b/R-package/tests/testthat/test_ndarray.R @@ -2,38 +2,48 @@ require(mxnet) context("ndarray") +if (Sys.getenv("R_GPU_ENABLE") != "" & as.integer(Sys.getenv("R_GPU_ENABLE")) == 1) { + mx.ctx.default(new = mx.gpu()) + message("Using GPU for testing.") +} + test_that("element-wise calculation for vector", { x = 1:10 - mat = mx.nd.array(as.array(x), mx.cpu(0)) + mat = mx.nd.array(as.array(x), mx.ctx.default()) expect_equal(x, as.array(mat)) - expect_equal(x+1, as.array(mat+1)) - expect_equal(x-10, as.array(mat-10)) - expect_equal(x*20, as.array(mat*20)) - expect_equal(x/3, as.array(mat/3), tolerance = 1e-5) - expect_equal(-1-x, as.array(-1-mat)) - expect_equal(-5/x, as.array(-5/mat), tolerance = 1e-5) - expect_equal(x+x, as.array(mat+mat)) - expect_equal(x/x, as.array(mat/mat)) - expect_equal(x*x, as.array(mat*mat)) - expect_equal(x-x, as.array(mat-mat)) - expect_equal(as.array(1-mat), as.array(1-mat)) -}) - -test_that("element-wise calculation for matrix", { + expect_equal(x + 1, as.array(mat + 1)) + expect_equal(x - 10, as.array(mat - 10)) + expect_equal(x * 20, as.array(mat * 20)) + expect_equal(x / 3, as.array(mat / 3), tolerance = 1e-5) + expect_equal(-1 - x, as.array(-1 - mat)) + expect_equal(-5 / x, as.array(-5 / mat), tolerance = 1e-5) + expect_equal(x + x, as.array(mat + mat)) + expect_equal(x / x, as.array(mat / mat)) + expect_equal(x * x, as.array(mat * mat)) + expect_equal(x - x, as.array(mat - mat)) + expect_equal(as.array(1 - mat), as.array(1 - mat)) + + x <- runif(10,-10, 10) + nd = mx.nd.array(as.array(x)) + expect_equal(sqrt(abs(x)), as.array(mx.nd.sqrt(mx.nd.abs(nd))), tolerance = 1e-6) + expect_equal(x ^ 2, as.array(mx.nd.square(nd)), tolerance = 1e-6) +}) + +test_that("element-wise calculation for matrix", { x = matrix(1:4, 2, 2) - mat = mx.nd.array(as.array(x), mx.cpu(0)) + mat = mx.nd.array(as.array(x), mx.ctx.default()) expect_equal(x, as.array(mat)) - expect_equal(x+1, as.array(mat+1)) - expect_equal(x-10, as.array(mat-10)) - expect_equal(x*20, as.array(mat*20)) - expect_equal(x/3, as.array(mat/3), tolerance = 1e-5) - expect_equal(-1-x, as.array(-1-mat)) - expect_equal(-5/x, as.array(-5/mat), tolerance = 1e-5) - expect_equal(x+x, as.array(mat+mat)) - expect_equal(x/x, as.array(mat/mat)) - expect_equal(x*x, as.array(mat*mat)) - expect_equal(x-x, as.array(mat-mat)) - expect_equal(as.array(1-mat), as.array(1-mat)) + expect_equal(x + 1, as.array(mat + 1)) + expect_equal(x - 10, as.array(mat - 10)) + expect_equal(x * 20, as.array(mat * 20)) + expect_equal(x / 3, as.array(mat / 3), tolerance = 1e-5) + expect_equal(-1 - x, as.array(-1 - mat)) + expect_equal(-5 / x, as.array(-5 / mat), tolerance = 1e-5) + expect_equal(x + x, as.array(mat + mat)) + expect_equal(x / x, as.array(mat / mat)) + expect_equal(x * x, as.array(mat * mat)) + expect_equal(x - x, as.array(mat - mat)) + expect_equal(as.array(1 - mat), as.array(1 - mat)) }) test_that("ndarray ones, zeros, save and load", { @@ -46,4 +56,139 @@ test_that("ndarray ones, zeros, save and load", { mat2 = mx.nd.load('temp.mat') expect_true(is.mx.ndarray(mat2[[1]])) expect_equal(as.array(mat), as.array(mat2[[1]])) + file.remove('temp.mat') +}) + +test_that("ndarray concatenate", { + shapes <- matrix(c(2, 3, 4, 2, 2, 2, 4, 2, 2, 1, 4, 2), nrow = 3, byrow = TRUE) + array_r <- apply(shapes, 2, function(s) { runif(s, -10, 10) }) + array_nd <- apply(array_r, 1, function(s) { mx.nd.array(matrix(s, nrow = 1)) }) + array_nd_concat <- mx.nd.concat(data = array_nd, num_args = 3, dim = 1) + expect_equal(array_r, as.matrix(array_nd_concat), tolerance = 1e-6) + + x1 <- mx.nd.array(c(1:24)) + x2 <- mx.nd.array(c(25:48)) + x3 <- mx.nd.concat(data = c(x1, x2), num_args = 2, dim = 0) + expect_equal(c(1:48), as.array(x3)) + expect_equal(dim(x3), 48) + + x1 <- array(1:24, dim = c(4, 3, 2)) + x2 <- array(25:48, dim = c(4, 3, 2)) + x3 <- c(1:4, 25:28, 5:8, 29:32, 9:12, 33:36, 13:16, 37:40, 17:20, 41:44, 21:24, 45:48) + y1 <- mx.nd.array(x1) + y2 <- mx.nd.array(x2) + y3 <- mx.nd.concat(data = c(y1, y2), num_args = 2, dim = 2) + expect_equal(dim(y3), c(8, 3, 2)) + expect_equal(as.array(y3), array(x3, dim = c(8, 3, 2))) +}) + +test_that("ndarray clip", { + nd <- mx.nd.array(runif(10,-10, 10)) + nd2 <- mx.nd.clip(nd,-2, 3) + arr <- as.array(nd2) + expect_equal(arr >= -2 | arr <= 3, rep(TRUE, length(arr))) +}) + +test_that("ndarray dot", { + a <- matrix(runif(12), nrow = 3) + b <- matrix(runif(20), nrow = 4) + c <- a %*% b + + A <- mx.nd.array(t(a)) + B <- mx.nd.array(t(b)) + C <- mx.nd.dot(A, B) + + expect_equal(c, t(as.matrix(C)), tolerance = 1e-6) +}) + +test_that("ndarray crop", { + x <- mx.nd.ones(c(2, 3, 4)) + y <- mx.nd.crop(x, begin = c(0, 0, 0), end = c(2, 1, 3)) + expect_equal(array(1, dim = c(2, 1, 3)), as.array(y)) + + z <- mx.nd.zeros(c(2, 1, 3)) + x <- mxnet:::mx.nd.internal.crop.assign(x, z, begin = c(0, 0, 0), end = c(2, 1, 3)) + arr_x <- array(1, dim = dim(x)) + arr_x[c(1:2), 1 , c(1:3)] <- 0 + + expect_equal(as.array(x), arr_x) +}) + +test_that("ndarray negate", { + arr <- array(runif(24, -10, 10), dim = c(2, 3, 4)) + nd <- mx.nd.array(arr) + + expect_equal(arr, as.array(nd), tolerance = 1e-6) + expect_equal(-arr, as.array(-nd), tolerance = 1e-6) + expect_equal(arr, as.array(nd), tolerance = 1e-6) +}) + +test_that("ndarray equal", { + x <- mx.nd.zeros(c(2, 3)) + y <- mx.nd.ones(c(2, 3)) + z = x == y + expect_equal(as.array(z), array(0, c(2,3))) + + z = 0 == x + expect_equal(as.array(z), array(1, c(2,3))) }) + +test_that("ndarray not equal", { + x <- mx.nd.zeros(c(2, 3)) + y <- mx.nd.ones(c(2, 3)) + z = x != y + expect_equal(as.array(z), array(1, c(2,3))) + + z = 0 != x + expect_equal(as.array(z), array(0, c(2,3))) +}) + +test_that("ndarray greater", { + x <- mx.nd.zeros(c(2, 3)) + y <- mx.nd.ones(c(2, 3)) + z = x > y + expect_equal(as.array(z), array(0, c(2,3))) + + z = y > 0 + expect_equal(as.array(z), array(1, c(2,3))) + + z = 0 > y + expect_equal(as.array(z), array(0, c(2,3))) + + z = x >= y + expect_equal(as.array(z), array(0, c(2,3))) + + z = y >= 0 + expect_equal(as.array(z), array(1, c(2,3))) + + z = 0 >= y + expect_equal(as.array(z), array(0, c(2,3))) + + z = y >= 1 + expect_equal(as.array(z), array(1, c(2,3))) +}) + +test_that("ndarray lesser", { + x <- mx.nd.zeros(c(2, 3)) + y <- mx.nd.ones(c(2, 3)) + z = x < y + expect_equal(as.array(z), array(1, c(2,3))) + + z = y < 0 + expect_equal(as.array(z), array(0, c(2,3))) + + z = 0 < y + expect_equal(as.array(z), array(1, c(2,3))) + + z = x <= y + expect_equal(as.array(z), array(1, c(2,3))) + + z = y <= 0 + expect_equal(as.array(z), array(0, c(2,3))) + + z = 0 <= y + expect_equal(as.array(z), array(1, c(2,3))) + + z = y <= 1 + expect_equal(as.array(z), array(1, c(2,3))) +}) \ No newline at end of file diff --git a/R-package/tests/testthat/test_symbol.R b/R-package/tests/testthat/test_symbol.R index 7e733e8690c4..656d146cd87c 100644 --- a/R-package/tests/testthat/test_symbol.R +++ b/R-package/tests/testthat/test_symbol.R @@ -4,18 +4,83 @@ context("symbol") test_that("basic symbol operation", { data = mx.symbol.Variable('data') - net1 = mx.symbol.FullyConnected(data=data, name='fc1', num_hidden=10) - net1 = mx.symbol.FullyConnected(data=net1, name='fc2', num_hidden=100) + net1 = mx.symbol.FullyConnected(data = data, name = 'fc1', num_hidden = 10) + net1 = mx.symbol.FullyConnected(data = net1, name = 'fc2', num_hidden = 100) expect_equal(arguments(net1), c('data', 'fc1_weight', 'fc1_bias', 'fc2_weight', 'fc2_bias')) + expect_equal(outputs(net1), 'fc2_output') - net2 = mx.symbol.FullyConnected(name='fc3', num_hidden=10) - net2 = mx.symbol.Activation(data=net2, act_type='relu') - net2 = mx.symbol.FullyConnected(data=net2, name='fc4', num_hidden=20) + net2 = mx.symbol.FullyConnected(name = 'fc3', num_hidden = 10) + net2 = mx.symbol.Activation(data = net2, act_type = 'relu') + net2 = mx.symbol.FullyConnected(data = net2, name = 'fc4', num_hidden = 20) - composed = mx.apply(net2, fc3_data=net1, name='composed') + composed = mx.apply(net2, fc3_data = net1, name = 'composed') expect_equal(arguments(composed), c('data', 'fc1_weight', 'fc1_bias', 'fc2_weight', 'fc2_bias', 'fc3_weight', 'fc3_bias', 'fc4_weight', 'fc4_bias')) + expect_equal(outputs(composed), 'composed_output') + + multi_out = mx.symbol.Group(c(composed, net1)) + expect_equal(outputs(multi_out), c('composed_output', 'fc2_output')) +}) + +test_that("symbol internal", { + data = mx.symbol.Variable('data') + oldfc = mx.symbol.FullyConnected(data = data, name = 'fc1', num_hidden = 10) + net1 = mx.symbol.FullyConnected(data = oldfc, name = 'fc2', num_hidden = 100) + + expect_equal(arguments(net1), c("data", "fc1_weight", "fc1_bias", "fc2_weight", "fc2_bias")) + + internal = internals(net1) + fc1 = internal[[match("fc1_output", internal$outputs)]] + + expect_equal(arguments(fc1), arguments(oldfc)) +}) + +test_that("symbol children", { + data = mx.symbol.Variable('data') + oldfc = mx.symbol.FullyConnected(data = data, + name = 'fc1', + num_hidden = 10) + net1 = mx.symbol.FullyConnected(data = oldfc, name = 'fc2', num_hidden = 100) + + expect_equal(outputs(children(net1)), c('fc1_output', 'fc2_weight', 'fc2_bias')) + expect_equal(outputs(children(children(net1))), c('data', 'fc1_weight', 'fc1_bias')) + + net2 = net1$get.children() + expect_equal(net2[[match('fc2_weight', net2$outputs)]]$arguments, 'fc2_weight') + + data = mx.symbol.Variable('data') + sliced = mx.symbol.SliceChannel(data, num_outputs = 3, name = 'slice') + expect_equal(outputs(children(sliced)), 'data') +}) + +test_that("symbol infer type", { + num_hidden = 128 + num_dim = 64 + num_sample = 10 + + data = mx.symbol.Variable('data') + prev = mx.symbol.Variable('prevstate') + x2h = mx.symbol.FullyConnected(data = data, name = 'x2h', num_hidden = num_hidden) + h2h = mx.symbol.FullyConnected(data = prev, name = 'h2h', num_hidden = num_hidden) + + out = mx.symbol.Activation(data = mx.symbol.elemwise_add(x2h, h2h), name = 'out', act_type = 'relu') + + # shape inference will fail because information is not available for h2h + ret = mx.symbol.infer.shape(out, data = c(num_dim, num_sample)) + + expect_equal(ret, NULL) +}) + +test_that("symbol save/load", { + data <- mx.symbol.Variable("data") + fc1 <- mx.symbol.FullyConnected(data, num_hidden = 1) + lro <- mx.symbol.LinearRegressionOutput(fc1) + mx.symbol.save(lro, "tmp_r_sym.json") + data2 = mx.symbol.load("tmp_r_sym.json") + + expect_equal(data2$as.json(), lro$as.json()) + file.remove("tmp_r_sym.json") }) test_that("symbol attributes access", { @@ -31,4 +96,16 @@ test_that("symbol attributes access", { expect_equal(y$attributes$`__shape__`, str) }) - +test_that("symbol concat", { + s1 <- mx.symbol.Variable("data1") + s2 <- mx.symbol.Variable("data2") + s3 <- mx.symbol.concat(data = c(s1, s2), num.args = 2, name = "concat") + expect_equal(outputs(s3), "concat_output") + expect_equal(outputs(children(s3)), c("data1", "data2")) + expect_equal(arguments(s3), c("data1", "data2")) + + s4 <- mx.symbol.Concat(data = c(s1, s2), num.args = 2, name = "concat") + expect_equal(outputs(s3), outputs(s4)) + expect_equal(outputs(children(s3)), outputs(children(s4))) + expect_equal(arguments(s3), arguments(s4)) +}) diff --git a/R-package/vignettes/CallbackFunctionTutorial.Rmd b/R-package/vignettes/CallbackFunction.Rmd similarity index 79% rename from R-package/vignettes/CallbackFunctionTutorial.Rmd rename to R-package/vignettes/CallbackFunction.Rmd index 97b6ce3161a0..12b7e28247e9 100644 --- a/R-package/vignettes/CallbackFunctionTutorial.Rmd +++ b/R-package/vignettes/CallbackFunction.Rmd @@ -1,17 +1,9 @@ -MXNet R Tutorial on Callback Function -====================================== +# Customized callback function This vignette gives users a guideline for using and writing callback functions, -which can very useful in model training. +which can be very useful in model training. -This tutorial is written in Rmarkdown. - -- You can directly view the hosted version of the tutorial from [MXNet R Document](http://mxnet.readthedocs.io/en/latest/packages/r/CallbackFunctionTutorial.html) - -- You can find the Rmarkdown source from [here](https://github.com/dmlc/mxnet/blob/master/R-package/vignettes/CallbackFunctionTutorial.Rmd) - -Model training example ----------- +## Model training example Let's begin from a small example. We can build and train a model using the following code: @@ -36,8 +28,8 @@ model <- mx.model.FeedForward.create( Besides, we provide two optional parameters, `batch.end.callback` and `epoch.end.callback`, which can provide great flexibility in model training. -How to use callback functions ---------- +## How to use callback functions + Two callback functions are provided in this package: @@ -50,11 +42,12 @@ model <- mx.model.FeedForward.create( ctx=mx.cpu(), num.round=10, array.batch.size=20, learning.rate=2e-6, momentum=0.9, eval.metric=mx.metric.rmse, epoch.end.callback = mx.callback.save.checkpoint("boston")) +list.files(pattern = "^boston") ``` -- `mx.callback.log.train.metric` is used to log training metric each period. You can use it either as a `batch.end.callback` or a -`epoch.end.callback`. +- `mx.callback.log.train.metric` is used to log training metric each period. +You can use it either as a `batch.end.callback` or a `epoch.end.callback`. ```{r} model <- mx.model.FeedForward.create( @@ -79,8 +72,8 @@ head(logger$train) head(logger$eval) ``` -How to write your own callback functions ----------- +## How to write your own callback functions + You can find the source code for two callback functions from [here](https://github.com/dmlc/mxnet/blob/master/R-package/R/callback.R) and they can be used as your template: @@ -97,26 +90,25 @@ The `mx.callback.save.checkpoint` function below is stateless. It just get the m ```{r, eval=FALSE} mx.callback.save.checkpoint <- function(prefix, period=1) { - function(iteration, nbatch, env, verbose) { + function(iteration, nbatch, env, verbose=TRUE) { if (iteration %% period == 0) { mx.model.save(env$model, prefix, iteration) - if(verbose) cat(sprintf("Model checkpoint saved to %s-%04d.params\n", prefix, iteration)) + if(verbose) message(sprintf("Model checkpoint saved to %s-%04d.params\n", prefix, iteration)) } return(TRUE) } } ``` -The `mx.callback.log.train.metric` is a little more complex. It will hold a reference class and update it during the training -process. +The `mx.callback.log.train.metric` is a little more complex. It holds a reference class and update it during the training process. ```{r, eval=FALSE} mx.callback.log.train.metric <- function(period, logger=NULL) { - function(iteration, nbatch, env, verbose) { + function(iteration, nbatch, env, verbose=TRUE) { if (nbatch %% period == 0 && !is.null(env$metric)) { result <- env$metric$get(env$train.metric) - if (nbatch != 0) - if(verbose) cat(paste0("Batch [", nbatch, "] Train-", result$name, "=", result$value, "\n")) + if (nbatch != 0 & verbose) + message(paste0("Batch [", nbatch, "] Train-", result$name, "=", result$value)) if (!is.null(logger)) { if (class(logger) != "mx.metric.logger") { stop("Invalid mx.metric.logger.") @@ -124,8 +116,8 @@ mx.callback.log.train.metric <- function(period, logger=NULL) { logger$train <- c(logger$train, result$value) if (!is.null(env$eval.metric)) { result <- env$metric$get(env$eval.metric) - if (nbatch != 0) - cat(paste0("Batch [", nbatch, "] Validation-", result$name, "=", result$value, "\n")) + if (nbatch != 0 & verbose) + message(paste0("Batch [", nbatch, "] Validation-", result$name, "=", result$value)) logger$eval <- c(logger$eval, result$value) } } @@ -163,3 +155,6 @@ model <- mx.model.FeedForward.create( ``` You can see once the validation metric goes below the threshold we set, the training process will stop early. + + + diff --git a/R-package/vignettes/CatsDogsFinetune.Rmd b/R-package/vignettes/CatsDogsFinetune.Rmd new file mode 100644 index 000000000000..680b5a302498 --- /dev/null +++ b/R-package/vignettes/CatsDogsFinetune.Rmd @@ -0,0 +1,272 @@ +# Dogs vs. Cats classification with mxnet and R + +## Packages and prerequisites + +In this tutorial, we mainly use the following three packages: + +* `mxnet`: model training +* `imager`: image processing +* `abind`: manipulations with arrays. + +It is an end-to-end R solution for the dogs vs cats Kaggle competition (https://www.kaggle.com/c/dogs-vs-cats-redux-kernels-edition/) +and it can be used as an example for fine-tuning. +All the code has been test on Ubuntu 16.04. + +```{r, echo=FALSE} +knitr::opts_chunk$set(eval = FALSE) +``` + + +```{r} +library(imager) +library(mxnet) +library(abind) +``` + + +## Image processing + +### Renaming train files + +```{r} +files <- list.files("./train/") +old_names <- sapply(files, strsplit, split = ".", fixed = TRUE) +max_length <- max(sapply(old_names, function(x) nchar(x[[2]]))) +zeros <- max_length - sapply(old_names, function(x) nchar(x[[2]])) +zeros <- sapply(zeros, function(x) paste(rep(0, x), collapse = "")) +new_names <- Map(function(x, y) {paste0("./train/", x[1], "/", y, x[2], ".jpg")}, + x = old_names, y = zeros) + +# Full names +files <- paste0("./train/", files) + +dir.create("./train/cat") +dir.create("./train/dog") + +# New names will be in 00001.jpg format +Map(function(x, y) file.rename(from = x, to = y), files, new_names) +``` + +### Training images: 224x224, padded with empty space + +```{r} +files <- list.files("./train/", recursive = TRUE) +new_names <- paste0("./train_pad_224x224/", files) +files <- paste0("./train/", files) +dir.create("./train_pad_224x224/") +dir.create("./train_pad_224x224/cat") +dir.create("./train_pad_224x224/dog") + +padImage <- function(x) { + long_side <- max(dim(x)[1:2]) + short_side <- min(dim(x)[1:2]) + pad_img <- pad(x, + nPix = long_side - short_side, + axes = ifelse(dim(x)[1] < dim(x)[2], "x", "y")) + return(pad_img) +} + +Map(function(x, y) { + pad_img <- padImage(load.image(x)) + res_img <- resize(pad_img, size_x = 224, size_y = 224) + imager::save.image(res_img, y) + }, x = files, y = new_names) +``` + +### Renaming test files + +```{r} +files <- list.files("./test/") +max_length <- max(sapply(files, nchar)) +zeros <- max_length - sapply(files, nchar) +zeros <- sapply(zeros, function(x) paste(rep(0, x), collapse = "")) +newnames <- paste0("./test/", zeros, files) + +files <- paste0("./test/", files) + +Map(function(x, y) file.rename(from = x, to = y), files, newnames) +``` + + +### Test images: 224x224, padded with empty space + +```{r} +files <- list.files("./test/") +new_names <- paste0("./test_pad_224x224/", files) +files <- paste0("./test/", files) +dir.create("./test_pad_224x224/") + +Map(function(x, y) { + pad_img <- padImage(load.image(x)) + res_img <- resize(pad_img, size_x = 224, size_y = 224) + imager::save.image(res_img, y) +}, x = files, y = new_names) +``` + +### Creating .rec files + +```{r} +cat_files <- list.files("train_pad_224x224/cat/", recursive=TRUE) +cat_files <- paste0("cat/", cat_files) + +dog_files <- list.files("train_pad_224x224/dog/", recursive=TRUE) +dog_files <- paste0("dog/", dog_files) + +train_ind <- sample(length(cat_files), length(cat_files) * 0.8) +train_data <- c(1:(length(train_ind) * 2)) +train_data <- cbind(train_data, c(rep(0, length(train_ind)), rep(1, length(train_ind)))) +train_data <- cbind(train_data, c(cat_files[train_ind], dog_files[train_ind])) +train_data <- train_data[sample(nrow(train_data)),] +write.table(train_data, "cats_dogs_train.lst", quote = FALSE, sep = "\t", row.names = FALSE, col.names = FALSE) +im2rec("cats_dogs_train.lst", "train_pad_224x224/", "cats_dogs_train.rec") + +val_ind <- c(1:length(cat_files))[!c(1:length(cat_files)) %in% train_ind] +val_data <- c(1:(length(val_ind) * 2)) +val_data <- cbind(val_data, c(rep(0, length(val_ind)), rep(1, length(val_ind)))) +val_data <- cbind(val_data, c(cat_files[val_ind], dog_files[val_ind])) +val_data <- val_data[sample(nrow(val_data)),] +write.table(val_data, "cats_dogs_val.lst", quote = FALSE, sep = "\t", row.names = FALSE, col.names = FALSE) +im2rec("cats_dogs_val.lst", "train_pad_224x224/", "cats_dogs_val.rec") +``` + +## The data iterator + +```{r} +get_iterator <- function(data_shape, train_data, val_data, batch_size = 128) { + train <- mx.io.ImageRecordIter(path.imgrec = train_data, + batch.size = batch_size, + data.shape = data_shape, + rand.crop = TRUE, + rand.mirror = TRUE) + + val <- mx.io.ImageRecordIter(path.imgrec = val_data, + batch.size = batch_size, + data.shape = data_shape, + rand.crop = FALSE, + rand.mirror = FALSE) + + return(list(train = train, val = val)) +} +``` + + +```{r} +data <- get_iterator(data_shape = c(224, 224, 3), + train_data = "cats_dogs_train.rec", + val_data = "cats_dogs_val.rec", + batch_size = 8) +train <- data$train +val <- data$val +``` + + +## Load pretrained model + +Here we use the pretrained model from http://data.dmlc.ml/models/imagenet/. +There are 1000 classes in imagenet, +and we need to replace the last fully connected layer with a new layer for 2 classes. + + +```{r} +download.file('http://data.dmlc.ml/data/Inception.zip', destfile = 'Inception.zip') +unzip("Inception.zip") +inception_bn <- mx.model.load("./Inception-BN", iteration = 126) + +symbol <- inception_bn$symbol +# check symbol$arguments for layer names +internals <- symbol$get.internals() +outputs <- internals$outputs + +flatten <- internals$get.output(which(outputs == "flatten_output")) + +new_fc <- mx.symbol.FullyConnected(data = flatten, + num_hidden = 2, + name = "fc1") +# set name to original name in symbol$arguments +new_soft <- mx.symbol.SoftmaxOutput(data = new_fc, + name = "softmax") +# set name to original name in symbol$arguments + +arg_params_new <- mx.model.init.params(symbol = new_soft, + input.shape = list("data" = c(224, 224, 3, 8)), + output.shape = NULL, + initializer = mx.init.uniform(0.1), + ctx = mx.cpu())$arg.params +fc1_weights_new <- arg_params_new[["fc1_weight"]] +fc1_bias_new <- arg_params_new[["fc1_bias"]] + +arg_params_new <- inception_bn$arg.params + +arg_params_new[["fc1_weight"]] <- fc1_weights_new +arg_params_new[["fc1_bias"]] <- fc1_bias_new +``` + + +## Fine-tuning + +```{r} +model <- mx.model.FeedForward.create( + symbol = new_soft, + X = train, + eval.data = val, + ctx = mx.gpu(0), + eval.metric = mx.metric.accuracy, + num.round = 2, + learning.rate = 0.05, + momentum = 0.9, + wd = 0.00001, + kvstore = "local", + array.batch.size = 128, + epoch.end.callback = mx.callback.save.checkpoint("inception_bn"), + batch.end.callback = mx.callback.log.train.metric(150), + initializer = mx.init.Xavier(factor_type = "in", magnitude = 2.34), + optimizer = "sgd", + arg.params = arg_params_new, + aux.params = inception_bn$aux.params +) +``` +## Making predictions + +```{r} +preprocImage<- function(src, # URL or file location + height = 224, + width = 224, + num_channels = 3, # 3 for RGB, 1 for grayscale + mult_by = 1, # set to 255 for normalized image + crop = FALSE) { # no crop by default + im <- load.image(src) + + if (crop) { + shape <- dim(im) + short_edge <- min(shape[1:2]) + xx <- floor((shape[1] - short_edge) / 2) + yy <- floor((shape[2] - short_edge) / 2) + im <- crop.borders(im, xx, yy) + } + + resized <- resize(im, size_x = width, size_y = height) + arr <- as.array(resized) * mult_by + dim(arr) <- c(width, height, num_channels, 1) + return(arr) +} +``` + +```{r} +files <- list.files("./test_pad_224x224/") +files <- paste0("./test_pad_224x224/", files) + +files <- split(files, rep(1:1250, each = 10)) +probs <- lapply(files, function(x) { + images <- lapply(x, preprocImage, mult_by = 255) + images <- do.call(abind, images) + probs <- predict(model, X = images, ctx = mx.gpu(0)) +}) +saveRDS(probs, "probs.rds") +probs <- t(do.call(cbind, probs)) + +preds <- data.frame(id = 1:12500, label = probs[, 2]) +write.csv(preds, "subm.csv", row.names = FALSE, quote = FALSE) +``` + + + diff --git a/R-package/vignettes/CharRnnModel.Rmd b/R-package/vignettes/CharRnnModel.Rmd index 2cb4b00ec1ac..9d3fd5c14786 100644 --- a/R-package/vignettes/CharRnnModel.Rmd +++ b/R-package/vignettes/CharRnnModel.Rmd @@ -1,23 +1,16 @@ -Char RNN Example -============================================= +# Char RNN Example -This example aims to show how to use lstm model to build a char level language model, and generate text from it. We use a tiny shakespeare text for demo purpose. -Data can be found at [here](https://github.com/dmlc/web-data/tree/master/mxnet/tinyshakespeare) +## Load Data -Preface -------- -This tutorial is written in Rmarkdown. -- You can directly view the hosted version of the tutorial from [MXNet R Document](http://mxnet.readthedocs.io/en/latest/packages/r/CharRnnModel.html) -- You can find the download the Rmarkdown source from [here](https://github.com/dmlc/mxnet/blob/master/R-package/vignettes/CharRnnModel.Rmd) - -Load Data ---------- First of all, load in the data and preprocess it. + ```{r} require(mxnet) ``` + Set basic network parameters. + ```{r} batch.size = 32 seq.len = 32 @@ -30,7 +23,9 @@ wd=0.00001 clip_gradient=1 update.period = 1 ``` -download the data. + +Download the data. + ```{r} download.data <- function(data_dir) { dir.create(data_dir, showWarnings = FALSE) @@ -40,7 +35,9 @@ download.data <- function(data_dir) { } } ``` + Make dictionary from text. + ```{r} make.dict <- function(text, max.vocab=10000) { text <- strsplit(text, '') @@ -58,7 +55,9 @@ make.dict <- function(text, max.vocab=10000) { return (dic) } ``` + Transfer text into data feature. + ```{r} make.data <- function(file.path, seq.len=32, max.vocab=10000, dic=NULL) { fi <- file(file.path, "r") @@ -91,7 +90,9 @@ make.data <- function(file.path, seq.len=32, max.vocab=10000, dic=NULL) { return (list(data=data, dic=dic, lookup.table=lookup.table)) } ``` + Move tail text. + ```{r} drop.tail <- function(X, batch.size) { shape <- dim(X) @@ -99,7 +100,9 @@ drop.tail <- function(X, batch.size) { return (X[, 1:(nstep * batch.size)]) } ``` -get the label of X + +Get the label of X + ```{r} get.label <- function(X) { label <- array(0, dim=dim(X)) @@ -113,7 +116,9 @@ get.label <- function(X) { return (label) } ``` -get training data and eval data + +Get training data and eval data + ```{r} download.data("./data/") ret <- make.data("./data/input.txt", seq.len=seq.len) @@ -139,8 +144,9 @@ X.train <- list(data=X.train.data, label=X.train.label) X.val <- list(data=X.val.data, label=X.val.label) ``` -Training Model --------------- +## Training Model + + In `mxnet`, we have a function called `mx.lstm` so that users can build a general lstm model. ```{r} @@ -162,9 +168,11 @@ model <- mx.lstm(X.train, X.val, ``` -Inference from model --------------------- -helper function for random sample. +## Inference from model + + +Some helper functions for random sample. + ```{r} cdf <- function(weights) { total <- sum(weights) @@ -190,6 +198,7 @@ search.val <- function(cdf, x) { } return (l) } + choice <- function(weights) { cdf.vals <- cdf(as.array(weights)) x <- runif(1) @@ -197,7 +206,9 @@ choice <- function(weights) { return (idx) } ``` -we can use random output or fixed output by choosing largest probability. + +We can use random output or fixed output by choosing largest probability. + ```{r} make.output <- function(prob, sample=FALSE) { if (!sample) { @@ -212,7 +223,7 @@ make.output <- function(prob, sample=FALSE) { ``` In `mxnet`, we have a function called `mx.lstm.inference` so that users can build a inference from lstm model and then use function `mx.lstm.forward` to get forward output from the inference. -Build inference from model. + ```{r} infer.model <- mx.lstm.inference(num.lstm.layer=num.lstm.layer, input.size=vocab, @@ -222,7 +233,9 @@ infer.model <- mx.lstm.inference(num.lstm.layer=num.lstm.layer, arg.params=model$arg.params, ctx=mx.cpu()) ``` -generate a sequence of 75 chars using function `mx.lstm.forward`. + +Generate a sequence of 75 chars using function `mx.lstm.forward`. + ```{r} start <- 'a' seq.len <- 75 @@ -238,16 +251,22 @@ for (i in (1:(seq.len-1))) { last.id <- make.output(prob, random.sample) out <- paste0(out, lookup.table[[last.id]]) } -cat (paste0(out, "\n")) +message(out) ``` The result: + ``` ah not a drobl greens Settled asing lately sistering sounted to their hight ``` -Other RNN models ----------------- +## Other RNN models + In `mxnet`, other RNN models like custom RNN and gru is also provided. - For **custom RNN model**, you can replace `mx.lstm` with `mx.rnn` to train rnn model. Also, you can replace `mx.lstm.inference` and `mx.lstm.forward` with `mx.rnn.inference` and `mx.rnn.forward` to inference from rnn model and get forward result from the inference model. -- For **GRU model**, you can replace `mx.lstm` with `mx.gru` to train gru model. Also, you can replace `mx.lstm.inference` and `mx.lstm.forward` with `mx.gru.inference` and `mx.gru.forward` to inference from gru model and get forward result from the inference model. \ No newline at end of file + +- For **GRU model**, you can replace `mx.lstm` with `mx.gru` to train gru model. Also, you can replace `mx.lstm.inference` and `mx.lstm.forward` with `mx.gru.inference` and `mx.gru.forward` to inference from gru model and get forward result from the inference model. + + + + \ No newline at end of file diff --git a/R-package/vignettes/CustomIterator.Rmd b/R-package/vignettes/CustomIterator.Rmd new file mode 100644 index 000000000000..22ac90fe0400 --- /dev/null +++ b/R-package/vignettes/CustomIterator.Rmd @@ -0,0 +1,207 @@ +# Customized iterator + + +This tutorial provides a guideline on how to use and write custom iterators, which can very useful when having a dataset that does not fit into memory. + +## Getting the data + +The data we are going to use is the [MNIST dataset](http://yann.lecun.com/exdb/mnist/) in CSV format, which can be found from [here](https://www.kaggle.com/c/digit-recognizer/data). + +To download the data: + +```{r} +download.file('https://s3-us-west-2.amazonaws.com/apache-mxnet/R/data/mnist_csv.zip', + destfile = 'mnist_csv.zip') +unzip('mnist_csv.zip', exdir = '.') +``` + +You'll get two files, `mnist_train.csv` that contains 60.000 examples of hand written numbers and `mxnist_test.csv` that contains 10.000 examples. The first element of each line in the CSV is the label, which is a number between 0 and 9. The rest of the line are 784 numbers between 0 and 255, corresponding to the levels of grey of a matrix of 28x28. Therefore, each line contains an image of 28x28 pixels of a hand written number and its true label. + +## Custom CSV Iterator + +Next we are going to create a custom CSV Iterator based on the [C++ CSVIterator class](https://github.com/dmlc/mxnet/blob/master/src/io/iter_csv.cc). + +For that we are going to use the R function `mx.io.CSVIter` as a base class. This class has as parameters `data.csv, data.shape, batch.size` and two main functions, `iter.next()` that calls the iterator in the next batch of data and `value()` that returns the train data and the label. + +The R Custom Iterator needs to inherit from the C++ data iterator class, for that we used the class `Rcpp_MXArrayDataIter` extracted with RCPP. Also, it needs to have the same parameters: `data.csv, data.shape, batch.size`. Apart from that, we can also add the field `iter`, which is the CSV Iterator that we are going to expand. + +```{r, eval=FALSE} +CustomCSVIter <- setRefClass("CustomCSVIter", + fields=c("iter", "data.csv", "data.shape", "batch.size"), + contains = "Rcpp_MXArrayDataIter", + #... + ) +``` + +The next step is to initialize the class. For that we call the base `mx.io.CSVIter` and fill the rest of the fields. + +```{r, eval=FALSE} +CustomCSVIter <- setRefClass("CustomCSVIter", + fields=c("iter", "data.csv", "data.shape", "batch.size"), + contains = "Rcpp_MXArrayDataIter", + methods=list( + initialize=function(iter, data.csv, data.shape, batch.size){ + feature_len <- data.shape*data.shape + 1 + csv_iter <- mx.io.CSVIter(data.csv=data.csv, data.shape=c(feature_len), batch.size=batch.size) + .self$iter <- csv_iter + .self$data.csv <- data.csv + .self$data.shape <- data.shape + .self$batch.size <- batch.size + .self + }, + #... + ) + ) +``` + +So far there is no difference between the original class and the custom class. Let's implement the function `value()`. In this case what we are going to do is transform the data that comes from the original class as an array of 785 numbers into a matrix of 28x28 and a label. We will also normalize the training data to be between 0 and 1. + +```{r, eval=FALSE} +CustomCSVIter <- setRefClass("CustomCSVIter", + fields=c("iter", "data.csv", "data.shape", "batch.size"), + contains = "Rcpp_MXArrayDataIter", + methods=list( + initialize=function(iter, data.csv, data.shape, batch.size){ + feature_len <- data.shape*data.shape + 1 + csv_iter <- mx.io.CSVIter(data.csv=data.csv, data.shape=c(feature_len), batch.size=batch.size) + .self$iter <- csv_iter + .self$data.csv <- data.csv + .self$data.shape <- data.shape + .self$batch.size <- batch.size + .self + }, + value=function(){ + val <- as.array(.self$iter$value()$data) + val.x <- val[-1,] + val.y <- val[1,] + val.x <- val.x/255 + dim(val.x) <- c(data.shape, data.shape, 1, ncol(val.x)) + val.x <- mx.nd.array(val.x) + val.y <- mx.nd.array(val.y) + list(data=val.x, label=val.y) + }, + #... + ) + ) +``` +Finally we are going to add the rest of the functions needed for the training to work correctly. The final `CustomCSVIter` looks like this: + +```{r} +CustomCSVIter <- setRefClass("CustomCSVIter", + fields=c("iter", "data.csv", "data.shape", "batch.size"), + contains = "Rcpp_MXArrayDataIter", + methods=list( + initialize=function(iter, data.csv, data.shape, batch.size){ + feature_len <- data.shape*data.shape + 1 + csv_iter <- mx.io.CSVIter(data.csv=data.csv, data.shape=c(feature_len), batch.size=batch.size) + .self$iter <- csv_iter + .self$data.csv <- data.csv + .self$data.shape <- data.shape + .self$batch.size <- batch.size + .self + }, + value=function(){ + val <- as.array(.self$iter$value()$data) + val.x <- val[-1,] + val.y <- val[1,] + val.x <- val.x/255 + dim(val.x) <- c(data.shape, data.shape, 1, ncol(val.x)) + val.x <- mx.nd.array(val.x) + val.y <- mx.nd.array(val.y) + list(data=val.x, label=val.y) + }, + iter.next=function(){ + .self$iter$iter.next() + }, + reset=function(){ + .self$iter$reset() + }, + num.pad=function(){ + .self$iter$num.pad() + }, + finalize=function(){ + .self$iter$finalize() + } + ) + ) +``` + +To call the class we can just do: + +```{r} +batch.size <- 100 +train.iter <- CustomCSVIter$new(iter = NULL, data.csv = "mnist_train.csv", data.shape = 28, batch.size = batch.size) +``` + +## CNN Model + + +For this tutorial we are going to use the known LeNet architecture: + +```{r} +library(mxnet) +lenet.model <- function(){ + data <- mx.symbol.Variable('data') + conv1 <- mx.symbol.Convolution(data=data, kernel=c(5,5), num_filter=20) #first conv + tanh1 <- mx.symbol.Activation(data=conv1, act_type="tanh") + pool1 <- mx.symbol.Pooling(data=tanh1, pool_type="max", kernel=c(2,2), stride=c(2,2)) + conv2 <- mx.symbol.Convolution(data=pool1, kernel=c(5,5), num_filter=50)# second conv + tanh2 <- mx.symbol.Activation(data=conv2, act_type="tanh") + pool2 <- mx.symbol.Pooling(data=tanh2, pool_type="max", kernel=c(2,2), stride=c(2,2)) + flatten <- mx.symbol.Flatten(data=pool2) + fc1 <- mx.symbol.FullyConnected(data=flatten, num_hidden=100) # first fullc + tanh3 <- mx.symbol.Activation(data=fc1, act_type="tanh") + fc2 <- mx.symbol.FullyConnected(data=tanh3, num_hidden=10) # second fullc + network <- mx.symbol.SoftmaxOutput(data=fc2) # loss + network +} +network <- lenet.model() +``` + +## Training with the Custom Iterator + +Finally, we can directly add the custom iterator as the training data source. + +```{r, eval=FALSE} +model <- mx.model.FeedForward.create(symbol=network, + X=train.iter, + ctx=mx.gpu(0), + num.round=10, + array.batch.size=batch.size, + learning.rate=0.1, + momentum=0.9, + eval.metric=mx.metric.accuracy, + wd=0.00001, + batch.end.callback=mx.callback.log.speedometer(batch.size, frequency = 100) + ) +``` + +The last 2 iterations with a K80 GPU looks like this: + +``` +## [8] Train-accuracy=0.998866666666667 +## Batch [100] Speed: 15413.0104454713 samples/sec Train-accuracy=0.999 +## Batch [200] Speed: 16629.3412459049 samples/sec Train-accuracy=0.99935 +## Batch [300] Speed: 18412.6900509319 samples/sec Train-accuracy=0.9995 +## Batch [400] Speed: 16757.2882328335 samples/sec Train-accuracy=0.999425 +## Batch [500] Speed: 17116.6529207406 samples/sec Train-accuracy=0.99946 +## Batch [600] Speed: 19627.589505195 samples/sec Train-accuracy=0.99945 +## [9] Train-accuracy=0.9991 +## Batch [100] Speed: 18971.5745536982 samples/sec Train-accuracy=0.9992 +## Batch [200] Speed: 15554.8822435383 samples/sec Train-accuracy=0.99955 +## Batch [300] Speed: 18327.6950115053 samples/sec Train-accuracy=0.9997 +## Batch [400] Speed: 17103.0705411788 samples/sec Train-accuracy=0.9997 +## Batch [500] Speed: 15104.8656902394 samples/sec Train-accuracy=0.99974 +## Batch [600] Speed: 13818.7899518255 samples/sec Train-accuracy=0.99975 +## [10] Train-accuracy=0.99975 +``` + +## Conclusion + + +We have shown how to create a custom CSV Iterator by extending the class `mx.io.CSVIter`. In our class, we iteratively read from a CSV file a batch of data that will be transformed and then processed in the stochastic gradient descent optimization. That way, we are able to manage CSV files that are bigger than the memory of the machine we are using. + +Based of this custom iterator, we can also create data loaders that internally transform or expand the data, allowing to manage files of any size. + + + diff --git a/R-package/vignettes/CustomLossFunction.Rmd b/R-package/vignettes/CustomLossFunction.Rmd new file mode 100644 index 000000000000..85e882567f8e --- /dev/null +++ b/R-package/vignettes/CustomLossFunction.Rmd @@ -0,0 +1,151 @@ +# Customized loss function + +This tutorial provides guidelines for using customized loss function in network construction. + +## Model Training Example + +Let's begin with a small regression example. We can build and train a regression model with the following code: + +```{r} +data(BostonHousing, package = "mlbench") +BostonHousing[, sapply(BostonHousing, is.factor)] <- + as.numeric(as.character(BostonHousing[, sapply(BostonHousing, is.factor)])) +BostonHousing <- data.frame(scale(BostonHousing)) + +test.ind = seq(1, 506, 5) # 1 pt in 5 used for testing +train.x = data.matrix(BostonHousing[-test.ind,-14]) +train.y = BostonHousing[-test.ind, 14] +test.x = data.matrix(BostonHousing[--test.ind,-14]) +test.y = BostonHousing[--test.ind, 14] + +require(mxnet) + +data <- mx.symbol.Variable("data") +label <- mx.symbol.Variable("label") +fc1 <- mx.symbol.FullyConnected(data, num_hidden = 14, name = "fc1") +tanh1 <- mx.symbol.Activation(fc1, act_type = "tanh", name = "tanh1") +fc2 <- mx.symbol.FullyConnected(tanh1, num_hidden = 1, name = "fc2") +lro <- mx.symbol.LinearRegressionOutput(fc2, name = "lro") + +mx.set.seed(0) +model <- mx.model.FeedForward.create(lro, X = train.x, y = train.y, + ctx = mx.cpu(), + num.round = 5, + array.batch.size = 60, + optimizer = "rmsprop", + verbose = TRUE, + array.layout = "rowmajor", + batch.end.callback = NULL, + epoch.end.callback = NULL) + +pred <- predict(model, test.x) +sum((test.y - pred[1,])^2) / length(test.y) +``` + +Besides the `LinearRegressionOutput`, we also provide `LogisticRegressionOutput` and `MAERegressionOutput`. +However, this might not be enough for real-world models. You can provide your own loss function +by using `mx.symbol.MakeLoss` when constructing the network. + +## How to Use Your Own Loss Function + +We still use our previous example, but this time we use `mx.symbol.MakeLoss` to minimize the `(pred-label)^2` + +```{r} +data <- mx.symbol.Variable("data") +label <- mx.symbol.Variable("label") +fc1 <- mx.symbol.FullyConnected(data, num_hidden = 14, name = "fc1") +tanh1 <- mx.symbol.Activation(fc1, act_type = "tanh", name = "tanh1") +fc2 <- mx.symbol.FullyConnected(tanh1, num_hidden = 1, name = "fc2") +lro2 <- mx.symbol.MakeLoss(mx.symbol.square(mx.symbol.Reshape(fc2, shape = 0) - label), name="lro2") +``` + +Then we can train the network just as usual. + +```{r} +mx.set.seed(0) +model2 <- mx.model.FeedForward.create(lro2, X = train.x, y = train.y, + ctx = mx.cpu(), + num.round = 5, + array.batch.size = 60, + optimizer = "rmsprop", + verbose = TRUE, + array.layout = "rowmajor", + batch.end.callback = NULL, + epoch.end.callback = NULL) +``` + +We should get very similar results because we are actually minimizing the same loss function. +However, the result is quite different. + +```{r} +pred2 <- predict(model2, test.x) +sum((test.y - pred2)^2) / length(test.y) +``` + +This is because output of `mx.symbol.MakeLoss` is the gradient of loss with respect to the input data. +We can get the real prediction as below. + +```{r} +internals = internals(model2$symbol) +fc_symbol = internals[[match("fc2_output", outputs(internals))]] + +model3 <- list(symbol = fc_symbol, + arg.params = model2$arg.params, + aux.params = model2$aux.params) + +class(model3) <- "MXFeedForwardModel" + +pred3 <- predict(model3, test.x) +sum((test.y - pred3[1,])^2) / length(test.y) +``` + +We have provided many operations on the symbols. An example of `|pred-label|` can be found below. + +```{r} +lro_abs <- mx.symbol.MakeLoss(mx.symbol.abs(mx.symbol.Reshape(fc2, shape = 0) - label)) +mx.set.seed(0) +model4 <- mx.model.FeedForward.create(lro_abs, X = train.x, y = train.y, + ctx = mx.cpu(), + num.round = 20, + array.batch.size = 60, + optimizer = "sgd", + learning.rate = 0.001, + verbose = TRUE, + array.layout = "rowmajor", + batch.end.callback = NULL, + epoch.end.callback = NULL) + +internals = internals(model4$symbol) +fc_symbol = internals[[match("fc2_output", outputs(internals))]] + +model5 <- list(symbol = fc_symbol, + arg.params = model4$arg.params, + aux.params = model4$aux.params) + +class(model5) <- "MXFeedForwardModel" + +pred5 <- predict(model5, test.x) +sum(abs(test.y - pred5[1,])) / length(test.y) +``` + + +```{r} +lro_mae <- mx.symbol.MAERegressionOutput(fc2, name = "lro") +mx.set.seed(0) +model6 <- mx.model.FeedForward.create(lro_mae, X = train.x, y = train.y, + ctx = mx.cpu(), + num.round = 20, + array.batch.size = 60, + optimizer = "sgd", + learning.rate = 0.001, + verbose = TRUE, + array.layout = "rowmajor", + batch.end.callback = NULL, + epoch.end.callback = NULL) +pred6 <- predict(model6, test.x) +sum(abs(test.y - pred6[1,])) / length(test.y) +``` + +We got the same result as expected. + + diff --git a/R-package/vignettes/classifyRealImageWithPretrainedModel.Rmd b/R-package/vignettes/classifyRealImageWithPretrainedModel.Rmd index e7e53d4d2d9f..ff631e0f5ce9 100644 --- a/R-package/vignettes/classifyRealImageWithPretrainedModel.Rmd +++ b/R-package/vignettes/classifyRealImageWithPretrainedModel.Rmd @@ -1,24 +1,19 @@ -Classify Real-World Images with Pre-trained Model -================================================= +# Classify Real-world Images with Pre-trained Model + MXNet is a flexible and efficient deep learning framework. One of the cool things that a deep learning algorithm can do is to classify real world images. -In this example we will show how to use a pretrained Inception-BatchNorm Network to predict the class of +In this example we will show how to use a pretrained Inception-BatchNorm network to predict the content of real world image. The network architecture is described in [1]. -The pre-trained Inception-BatchNorm network is able to be downloaded from [this link](http://data.mxnet.io/mxnet/data/Inception.zip) -This model gives the recent state-of-art prediction accuracy on image net dataset. +The pre-trained Inception-BatchNorm network can be downloaded from [this link](http://data.mxnet.io/mxnet/data/Inception.zip). +This model gives the recent state-of-art prediction accuracy on the image net dataset. + +## Package Loading -Preface -------- -This tutorial is written in Rmarkdown. -- You can directly view the hosted version of the tutorial from [MXNet R Document](http://mxnet.readthedocs.io/en/latest/packages/r/classifyRealImageWithPretrainedModel.html) -- You can find the download the Rmarkdown source from [here](https://github.com/dmlc/mxnet/blob/master/R-package/vignettes/classifyRealImageWithPretrainedModel.Rmd) +To get started, we load the `mxnet` package first. -Package Loading ---------------- -To get started, we load the mxnet package by require mxnet. ```{r} require(mxnet) ``` @@ -29,12 +24,15 @@ In this example, we also need the imager package to load and preprocess the imag require(imager) ``` -Load the Pretrained Model -------------------------- +## Load the Pretrained Model + + Make sure you unzip the pre-trained model in current folder. And we can use the model loading function to load the model into R. ```{r} +download.file('http://data.dmlc.ml/data/Inception.zip', destfile = 'Inception.zip') +unzip("Inception.zip") model <- mx.model.load("Inception/Inception_BN", iteration = 39) ``` @@ -44,8 +42,8 @@ We also need to load in the mean image, which is used for preprocessing using `` mean.img <- as.array(mx.nd.load("Inception/mean_224.nd")[["mean_img"]]) ``` -Load and Preprocess the Image ------------------------------ +## Load and Preprocess the Image + Now we are ready to classify a real image. In this example, we simply take the parrots image from imager package. But you can always change it to other images. @@ -90,8 +88,8 @@ We use the defined preprocessing function to get the normalized image. normed <- preproc.image(im, mean.img) ``` -Classify the Image ------------------- +## Classify the Image + Now we are ready to classify the image! We can use the predict function to get the probability over classes. @@ -104,6 +102,7 @@ As you can see ```prob``` is a 1 times 1000 array, which gives the probability over the 1000 image classes of the input. We can use the ```max.col``` on the transpose of prob. get the class index. + ```{r} max.idx <- max.col(t(prob)) max.idx @@ -125,6 +124,41 @@ print(paste0("Predicted Top-class: ", synsets[[max.idx]])) Actually I do not know what does the word mean when I saw it. So I searched on the web to check it out.. and hmm it does get the right answer :) -Reference ---------- +## Extract features + + +Besides the final classification results, we can also extract the internal features. +We need to get feature layer symbol out of internals first. Here we use `global_pool_output` +as an example. + +```{r} +internals = model$symbol$get.internals() +fea_symbol = internals[[match("global_pool_output", internals$outputs)]] +``` + +Next, we rebuild a new model using the feature symbol + +```{r} +model2 <- list(symbol = fea_symbol, + arg.params = model$arg.params, + aux.params = model$aux.params) + +class(model2) <- "MXFeedForwardModel" +``` + +Then we can do the `predict` using the new model to get the internal results. +You need to set `allow.extra.params = TRUE` since some parameters are not used this time. + +```{r} +global_pooling_feature <- predict(model2, X = normed, allow.extra.params = TRUE) +dim(global_pooling_feature) +``` + + +## Reference + + [1] Ioffe, Sergey, and Christian Szegedy. "Batch normalization: Accelerating deep network training by reducing internal covariate shift." arXiv preprint arXiv:1502.03167 (2015). + + + diff --git a/R-package/vignettes/fiveMinutesNeuralNetwork.Rmd b/R-package/vignettes/fiveMinutesNeuralNetwork.Rmd index c1b707fadc51..fb023bb5435f 100644 --- a/R-package/vignettes/fiveMinutesNeuralNetwork.Rmd +++ b/R-package/vignettes/fiveMinutesNeuralNetwork.Rmd @@ -1,16 +1,9 @@ -Neural Network with MXNet in Five Minutes -============================================= +# Neural Network with MXNet in Five Minutes This is the first tutorial for new users of the R package `mxnet`. You will learn to construct a neural network to do regression in 5 minutes. We will show you how to do classification and regression tasks respectively. The data we use comes from the package `mlbench`. -Preface -------- -This tutorial is written in Rmarkdown. -- You can directly view the hosted version of the tutorial from [MXNet R Document](http://mxnet.readthedocs.io/en/latest/packages/r/fiveMinutesNeuralNetwork.html) -- You can find the download the Rmarkdown source from [here](https://github.com/dmlc/mxnet/blob/master/R-package/vignettes/fiveMinutesNeuralNetwork.Rmd) - ## Classification First of all, let us load in the data and preprocess it: @@ -29,7 +22,14 @@ test.x <- data.matrix(Sonar[-train.ind, 1:60]) test.y <- Sonar[-train.ind, 61] ``` -Next we are going to use a multi-layer perceptron as our classifier. In `mxnet`, we have a function called `mx.mlp` so that users can build a general multi-layer neural network to do classification or regression. +Next we are going to use a multi-layer perceptron (MLP) as our classifier. +In `mxnet`, we have a function called `mx.mlp` so that users can build a general multi-layer neural network to do classification (`out_activation="softmax"`) or regression (`out_activation="rmse"`). +Note for the `softmax` activation, the output is zero-indexed not one-indexed. In the data we use: + +```{r} +table(train.y) +table(test.y) +``` There are several parameters we have to feed to `mx.mlp`: @@ -38,7 +38,7 @@ There are several parameters we have to feed to `mx.mlp`: - Number of nodes in the output layer. - Type of the activation. - Type of the output loss. -- The device to train (GPU or CPU). +- The device to train `mx.gpu()` for GPU or `mx.cpu()` for CPU. - Other parameters for `mx.model.FeedForward.create`. The following code piece is showing a possible usage of `mx.mlp`: @@ -130,6 +130,44 @@ model <- mx.model.FeedForward.create(lro, X=train.x, y=train.y, learning.rate=2e-6, momentum=0.9, eval.metric=demo.metric.mae) ``` +In the previous example, our target is to predict the last column ("medv") in the dataset. +It is also possible to build a regression model with multiple outputs. +This time we use the last two columns as the targets: + +```{r} +train.x <- data.matrix(BostonHousing[train.ind, -(13:14)]) +train.y <- BostonHousing[train.ind, c(13:14)] +test.x <- data.matrix(BostonHousing[-train.ind, -(13:14)]) +test.y <- BostonHousing[-train.ind, c(13:14)] +``` + +and build a similar network symbol: + +```{r} +data <- mx.symbol.Variable("data") +fc2 <- mx.symbol.FullyConnected(data, num_hidden=2) +lro2 <- mx.symbol.LinearRegressionOutput(fc2) +``` + +We use `mx.io.arrayiter` to build an iter for our training set and train the model using `mx.model.FeedForward.create`: + +```{r} +mx.set.seed(0) +train_iter = mx.io.arrayiter(data = t(train.x), label = t(train.y)) + +model <- mx.model.FeedForward.create(lro2, X=train_iter, + ctx=mx.cpu(), num.round=50, array.batch.size=20, + learning.rate=2e-6, momentum=0.9) +``` + +After training, we can see that the dimension of the prediction is the same with our target. + +```{r} +preds <- t(predict(model, test.x)) +dim(preds) +dim(test.y) +``` Congratulations! Now you have learnt the basic for using `mxnet`. Please check the other tutorials for advanced features. + diff --git a/R-package/vignettes/mnistCompetition.Rmd b/R-package/vignettes/mnistCompetition.Rmd index 74145415ec2a..988fd18e8b4d 100644 --- a/R-package/vignettes/mnistCompetition.Rmd +++ b/R-package/vignettes/mnistCompetition.Rmd @@ -1,19 +1,21 @@ -Handwritten Digits Classification Competition -============================================= +# Handwritten Digits Classification Competition [MNIST](http://yann.lecun.com/exdb/mnist/) is a handwritten digits image data set created by Yann LeCun. Every digit is represented by a 28x28 image. It has become a standard data set to test classifiers on simple image input. Neural network is no doubt a strong model for image classification tasks. There's a [long-term hosted competition](https://www.kaggle.com/c/digit-recognizer) on Kaggle using this data set. We will present the basic usage of [mxnet](https://github.com/dmlc/mxnet/tree/master/R-package) to compete in this challenge. -This tutorial is written in Rmarkdown. You can download the source [here](https://github.com/dmlc/mxnet/blob/master/R-package/vignettes/mnistCompetition.Rmd) and view a -hosted version of tutorial [here](http://mxnet.readthedocs.io/en/latest/packages/r/mnistCompetition.html). - ## Data Loading First, let us download the data from [here](https://www.kaggle.com/c/digit-recognizer/data), and put them under the `data/` folder in your working directory. Then we can read them in R and convert to matrices. -```{r, eval=FALSE} +```{r, echo=FALSE} +download.file('https://s3-us-west-2.amazonaws.com/apache-mxnet/R/data/mnist_csv.zip', destfile = 'mnist_csv.zip') +unzip('mnist_csv.zip', exdir = '.') +``` + + +```{r} require(mxnet) train <- read.csv("train.csv", header=TRUE) test <- read.csv("test.csv", header=TRUE) @@ -57,7 +59,7 @@ test.y <- load_label_file('mnist/t10k-labels-idx1-ubyte') Here every image is represented as a single row in train/test. The greyscale of each image falls in the range [0, 255], we can linearly transform it into [0,1] by -```{r, eval=FALSE} +```{r} train.x <- t(train.x/255) test <- t(test/255) ``` @@ -65,7 +67,7 @@ We also transpose the input matrix to npixel x nexamples, which is the column ma In the label part, we see the number of each digit is fairly even: -```{r, eval=FALSE} +```{r} table(train.y) ``` @@ -73,7 +75,7 @@ table(train.y) Now we have the data. The next step is to configure the structure of our network. -```{r, eval=FALSE} +```{r} data <- mx.symbol.Variable("data") fc1 <- mx.symbol.FullyConnected(data, name="fc1", num_hidden=128) act1 <- mx.symbol.Activation(fc1, name="relu1", act_type="relu") @@ -108,40 +110,42 @@ softmax <- mx.symbol.Variable("data") %>% We are almost ready for the training process. Before we start the computation, let's decide what device should we use. -```{r, eval=FALSE} +```{r} devices <- mx.cpu() ``` Here we assign CPU to `mxnet`. After all these preparation, you can run the following command to train the neural network! Note that `mx.set.seed` is the correct function to control the random process in `mxnet`. -```{r, eval=FALSE} +```{r} mx.set.seed(0) -model <- mx.model.FeedForward.create(softmax, X=train.x, y=train.y, - ctx=devices, num.round=10, array.batch.size=100, - learning.rate=0.07, momentum=0.9, eval.metric=mx.metric.accuracy, - initializer=mx.init.uniform(0.07), - batch.end.callback=mx.callback.log.train.metric(100)) +model <- mx.model.FeedForward.create(softmax, X = train.x, y = train.y, + ctx = devices, num.round = 5, + array.batch.size = 100, + learning.rate = 0.07, momentum = 0.9, + eval.metric = mx.metric.accuracy, + initializer = mx.init.uniform(0.07), + batch.end.callback = mx.callback.log.train.metric(100)) ``` ## Prediction and Submission To make prediction, we can simply write -```{r, eval=FALSE} +```{r} preds <- predict(model, test) dim(preds) ``` It is a matrix with 28000 rows and 10 cols, containing the desired classification probabilities from the output layer. To extract the maximum label for each row, we can use the `max.col` in R: -```{r, eval=FALSE} +```{r} pred.label <- max.col(t(preds)) - 1 table(pred.label) ``` With a little extra effort in the csv format, we can have our submission to the competition! -```{r, eval=FALSE} +```{r, eval = FALSE} submission <- data.frame(ImageId=1:ncol(test), Label=pred.label) write.csv(submission, file='submission.csv', row.names=FALSE, quote=FALSE) ``` @@ -179,16 +183,16 @@ lenet <- mx.symbol.SoftmaxOutput(data=fc2) Then let us reshape the matrices into arrays: -```{r, eval=FALSE} +```{r} train.array <- train.x dim(train.array) <- c(28, 28, 1, ncol(train.x)) -test.array <- test.x -dim(test.array) <- c(28, 28, 1, ncol(test.x)) +test.array <- test +dim(test.array) <- c(28, 28, 1, ncol(test)) ``` Next we are going to compare the training speed on different devices, so the definition of the devices goes first: -```{r, eval=FALSE} +```{r} n.gpu <- 1 device.cpu <- mx.cpu() device.gpu <- lapply(0:(n.gpu-1), function(i) { @@ -201,38 +205,42 @@ but since internal computation of cpu is already multi-threaded, there is less g We start by training on CPU first. Because it takes a bit time to do so, we will only run it for one iteration. -```{r, eval=FALSE} +```{r} mx.set.seed(0) tic <- proc.time() -model <- mx.model.FeedForward.create(lenet, X=train.array, y=train.y, - ctx=device.cpu, num.round=1, array.batch.size=100, - learning.rate=0.05, momentum=0.9, wd=0.00001, - eval.metric=mx.metric.accuracy, - batch.end.callback=mx.callback.log.train.metric(100)) +model <- mx.model.FeedForward.create(lenet, X = train.array, y = train.y, + ctx = device.cpu, num.round = 1, + array.batch.size = 100, + learning.rate = 0.05, momentum = 0.9, wd = 0.00001, + eval.metric = mx.metric.accuracy, + batch.end.callback = mx.callback.log.train.metric(100)) print(proc.time() - tic) ``` Training on GPU: -```{r, eval=FALSE} +```{r} mx.set.seed(0) tic <- proc.time() -model <- mx.model.FeedForward.create(lenet, X=train.array, y=train.y, - ctx=device.gpu, num.round=5, array.batch.size=100, - learning.rate=0.05, momentum=0.9, wd=0.00001, - eval.metric=mx.metric.accuracy, - batch.end.callback=mx.callback.log.train.metric(100)) +model <- mx.model.FeedForward.create(lenet, X = train.array, y = train.y, + ctx = device.gpu, num.round = 5, + array.batch.size = 100, + learning.rate = 0.05, momentum = 0.9, wd = 0.00001, + eval.metric = mx.metric.accuracy, + batch.end.callback = mx.callback.log.train.metric(100)) print(proc.time() - tic) ``` As you can see by using GPU, we can get a much faster speedup in training! Finally we can submit the result to Kaggle again to see the improvement of our ranking! -```{r, eval=FALSE} +```{r, eval = FALSE} preds <- predict(model, test.array) pred.label <- max.col(t(preds)) - 1 submission <- data.frame(ImageId=1:ncol(test), Label=pred.label) write.csv(submission, file='submission.csv', row.names=FALSE, quote=FALSE) ``` -![](../web-data/mxnet/knitr/mnistCompetition-kaggle-submission.png) +![](https://raw.githubusercontent.com/dmlc/web-data/master/mxnet/knitr/mnistCompetition-kaggle-submission.png) + + diff --git a/R-package/vignettes/ndarray.Rmd b/R-package/vignettes/ndarray.Rmd new file mode 100644 index 000000000000..08786b25fa86 --- /dev/null +++ b/R-package/vignettes/ndarray.Rmd @@ -0,0 +1,148 @@ +# NDArray: Vectorized Tensor Computations on CPUs and GPUs + +`NDArray` is the basic vectorized operation unit in MXNet for matrix and tensor computations. +Users can perform usual calculations as on an R"s array, but with two additional features: + +- Multiple devices: All operations can be run on various devices including +CPUs and GPUs. + +- Automatic parallelization: All operations are automatically executed in + parallel with each other. + +## Create and Initialize + +Let"s create `NDArray` on either a GPU or a CPU: + +```{r} +require(mxnet) +a <- mx.nd.zeros(c(2, 3)) # create a 2-by-3 matrix on cpu +b <- mx.nd.zeros(c(2, 3), mx.cpu()) # create a 2-by-3 matrix on cpu +c <- mx.nd.zeros(c(2, 3), mx.gpu(0)) # create a 2-by-3 matrix on gpu 0, if you have CUDA enabled. +``` + +Typically for CUDA-enabled devices, the device id of a GPU starts from 0. +That's why we passed in 0 to the GPU id. + +We can initialize an `NDArray` object in various ways: + + +```{r} +a <- mx.nd.ones(c(4, 4)) +b <- mx.rnorm(c(4, 5)) +c <- mx.nd.array(1:5) +``` + +To check the numbers in an `NDArray`, we can simply run: + + +```{r} +a <- mx.nd.ones(c(2, 3)) +b <- as.array(a) +class(b) +``` + +```{r} +b +``` + +## Performing Basic Operations + +### Elemental-wise Operations + +You can perform elemental-wise operations on `NDArray` objects, as follows: + + +```{r} +a <- mx.nd.ones(c(2, 4)) * 2 +b <- mx.nd.ones(c(2, 4)) / 8 +as.array(a) +``` + +```{r} +as.array(b) +``` + +```{r} +c <- a + b +as.array(c) +``` + +```{r} +d <- c / a - 5 +as.array(d) +``` + +If two `NDArray`s are located on different devices, we need to explicitly move them to the same one. For instance: + + +```{r} +a <- mx.nd.ones(c(2, 3)) * 2 +b <- mx.nd.ones(c(2, 3), mx.gpu()) / 8 +c <- mx.nd.copyto(a, mx.gpu()) * b +as.array(c) +``` + +### Loading and Saving + +You can save a list of `NDArray` object to your disk with `mx.nd.save`: + + +```{r} +a <- mx.nd.ones(c(2, 3)) +mx.nd.save(list(a), "temp.ndarray") +``` + +You can load it back easily: + + +```{r} +a <- mx.nd.load("temp.ndarray") +as.array(a[[1]]) +``` + +We can directly save data to and load it from a distributed file system, such as Amazon S3 and HDFS: + + +```{r, eval=FALSE} +mx.nd.save(list(a), "s3://mybucket/mydata.bin") +mx.nd.save(list(a), "hdfs///users/myname/mydata.bin") +``` + +## Automatic Parallelization + +`NDArray` can automatically execute operations in parallel. Automatic parallelization is useful when +using multiple resources, such as CPU cards, GPU cards, and CPU-to-GPU memory bandwidth. + +For example, if we write `a <- a + 1` followed by `b <- b + 1`, and `a` is on a CPU and +`b` is on a GPU, executing them in parallel improves +efficiency. Furthermore, because copying data between CPUs and GPUs are also expensive, running in parallel with other computations further increases efficiency. + +It's hard to find the code that can be executed in parallel by eye. In the +following example, `a <- a + 1` and `c <- c * 3` can be executed in parallel, but `a <- a + 1` and +`b <- b * 3` should be in sequential. + + +```{r} +a <- mx.nd.ones(c(2,3)) +b <- a +c <- mx.nd.copyto(a, mx.cpu()) +a <- a + 1 +b <- b * 3 +c <- c * 3 +``` + +Luckily, MXNet can automatically resolve the dependencies and +execute operations in parallel accurately. This allows us to write our program assuming there is only a single thread. MXNet will +automatically dispatch the program to multiple devices. + +MXNet achieves this with lazy evaluation. Each operation is issued to an +internal engine, and then returned. For example, if we run `a <- a + 1`, it +returns immediately after pushing the plus operator to the engine. This +asynchronous processing allows us to push more operators to the engine. It determines +the read and write dependencies and the best way to execute them in +parallel. + +The actual computations are finished, allowing us to copy the results someplace else, such as `as.array(a)` or `mx.nd.save(a, "temp.dat")`. To write highly parallelized codes, we only need to postpone when we need +the results. + + diff --git a/R-package/vignettes/ndarrayAndSymbolTutorial.Rmd b/R-package/vignettes/ndarrayAndSymbolTutorial.Rmd deleted file mode 100644 index a47147c9437a..000000000000 --- a/R-package/vignettes/ndarrayAndSymbolTutorial.Rmd +++ /dev/null @@ -1,242 +0,0 @@ -MXNet R Tutorial on NDArray and Symbol -====================================== - -This vignette gives a general overview of MXNet"s R package. MXNet contains a -mixed flavor of elements to bake flexible and efficient -applications. There are two major concepts introduced in this tutorial. - -* [NDArray](#ndarray-numpy-style-tensor-computations-on-cpus-and-gpus) - offers matrix and tensor computations on both CPU and GPU, with automatic - parallelization -* [Symbol](#symbol-and-automatic-differentiation) makes defining a neural - network extremely easy, and provides automatic differentiation. - -## NDArray: Vectorized tensor computations on CPUs and GPUs - -`NDArray` is the basic vectorized operation unit in MXNet for matrix and tensor computations. -Users can perform usual calculations as on R"s array, but with two additional features: - -1. **multiple devices**: all operations can be run on various devices including -CPU and GPU -2. **automatic parallelization**: all operations are automatically executed in - parallel with each other - -### Create and Initialization - -Let"s create `NDArray` on either GPU or CPU - -```{r} -require(mxnet) -a <- mx.nd.zeros(c(2, 3)) # create a 2-by-3 matrix on cpu -b <- mx.nd.zeros(c(2, 3), mx.cpu()) # create a 2-by-3 matrix on cpu -# c <- mx.nd.zeros(c(2, 3), mx.gpu(0)) # create a 2-by-3 matrix on gpu 0, if you have CUA enabled. -``` - -As a side note, normally for CUDA enabled devices, the device id of GPU starts from 0. -So that is why we passed in 0 to GPU id. We can also initialize an `NDArray` object in various ways: - -```{r} -a <- mx.nd.ones(c(4, 4)) -b <- mx.rnorm(c(4, 5)) -c <- mx.nd.array(1:5) -``` - -To check the numbers in an `NDArray`, we can simply run - -```{r} -a <- mx.nd.ones(c(2, 3)) -b <- as.array(a) -class(b) -b -``` - -### Basic Operations - -#### Elemental-wise operations - -You can perform elemental-wise operations on `NDArray` objects: - -```{r} -a <- mx.nd.ones(c(2, 4)) * 2 -b <- mx.nd.ones(c(2, 4)) / 8 -as.array(a) -as.array(b) -c <- a + b -as.array(c) -d <- c / a - 5 -as.array(d) -``` - -If two `NDArray`s sit on different devices, we need to explicitly move them -into the same one. For instance: - -```{r, eval=FALSE} -a <- mx.nd.ones(c(2, 3)) * 2 -b <- mx.nd.ones(c(2, 3), mx.gpu()) / 8 -c <- mx.nd.copyto(a, mx.gpu()) * b -as.array(c) -``` - -#### Load and Save - -You can save a list of `NDArray` object to your disk with `mx.nd.save`: - -```{r} -a <- mx.nd.ones(c(2, 3)) -mx.nd.save(a, "temp.ndarray") -``` - -You can also load it back easily: - -```{r} -a <- mx.nd.load("temp.ndarray") -as.array(a[[1]]) -``` - -In case you want to save data to the distributed file system such as S3 and HDFS, -we can directly save to and load from them. For example: - -```{r,eval=FALSE} -mx.nd.save(list(a), "s3://mybucket/mydata.bin") -mx.nd.save(list(a), "hdfs///users/myname/mydata.bin") -``` - -### Automatic Parallelization - -`NDArray` can automatically execute operations in parallel. It is desirable when we -use multiple resources such as CPU, GPU cards, and CPU-to-GPU memory bandwidth. - -For example, if we write `a <- a + 1` followed by `b <- b + 1`, and `a` is on CPU while -`b` is on GPU, then want to execute them in parallel to improve the -efficiency. Furthermore, data copy between CPU and GPU are also expensive, we -hope to run it parallel with other computations as well. - -However, finding the codes can be executed in parallel by eye is hard. In the -following example, `a <- a + 1` and `c <- c * 3` can be executed in parallel, but `a <- a + 1` and -`b <- b * 3` should be in sequential. - -```{r} -a <- mx.nd.ones(c(2,3)) -b <- a -c <- mx.nd.copyto(a, mx.cpu()) -a <- a + 1 -b <- b * 3 -c <- c * 3 -``` - -Luckily, MXNet can automatically resolve the dependencies and -execute operations in parallel with correctness guaranteed. In other words, we -can write program as by assuming there is only a single thread, while MXNet will -automatically dispatch it into multi-devices, such as multi GPU cards or multi -machines. - -It is achieved by lazy evaluation. Any operation we write down is issued into a -internal engine, and then returned. For example, if we run `a <- a + 1`, it -returns immediately after pushing the plus operator to the engine. This -asynchronous allows us to push more operators to the engine, so it can determine -the read and write dependency and find a best way to execute them in -parallel. - -The actual computations are finished if we want to copy the results into some -other place, such as `as.array(a)` or `mx.nd.save(a, "temp.dat")`. Therefore, if we -want to write highly parallelized codes, we only need to postpone when we need -the results. - -## Symbol and Automatic Differentiation - -WIth the computational unit `NDArray`, we need a way to construct neural networks. MXNet provides a symbolic interface named Symbol to do so. The symbol combines both flexibility and efficiency. - -### Basic Composition of Symbols - -The following codes create a two layer perceptrons network: - -```{r} -require(mxnet) -net <- mx.symbol.Variable("data") -net <- mx.symbol.FullyConnected(data=net, name="fc1", num_hidden=128) -net <- mx.symbol.Activation(data=net, name="relu1", act_type="relu") -net <- mx.symbol.FullyConnected(data=net, name="fc2", num_hidden=64) -net <- mx.symbol.SoftmaxOutput(data=net, name="out") -class(net) -``` - -Each symbol takes a (unique) string name. *Variable* often defines the inputs, -or free variables. Other symbols take a symbol as the input (*data*), -and may accept other hyper-parameters such as the number of hidden neurons (*num_hidden*) -or the activation type (*act_type*). - -The symbol can be simply viewed as a function taking several arguments, whose -names are automatically generated and can be get by - -```{r} -arguments(net) -``` - -As can be seen, these arguments are the parameters need by each symbol: - -- *data* : input data needed by the variable *data* -- *fc1_weight* and *fc1_bias* : the weight and bias for the first fully connected layer *fc1* -- *fc2_weight* and *fc2_bias* : the weight and bias for the second fully connected layer *fc2* -- *out_label* : the label needed by the loss - -We can also specify the automatic generated names explicitly: - -```{r} -data <- mx.symbol.Variable("data") -w <- mx.symbol.Variable("myweight") -net <- mx.symbol.FullyConnected(data=data, weight=w, name="fc1", num_hidden=128) -arguments(net) -``` - -### More Complicated Composition - -MXNet provides well-optimized symbols for -commonly used layers in deep learning. We can also easily define new operators -in python. The following example first performs an elementwise add between two -symbols, then feed them to the fully connected operator. - -```{r} -lhs <- mx.symbol.Variable("data1") -rhs <- mx.symbol.Variable("data2") -net <- mx.symbol.FullyConnected(data=lhs + rhs, name="fc1", num_hidden=128) -arguments(net) -``` - -We can also construct symbol in a more flexible way rather than the single -forward composition we addressed before. - -```{r} -net <- mx.symbol.Variable("data") -net <- mx.symbol.FullyConnected(data=net, name="fc1", num_hidden=128) -net2 <- mx.symbol.Variable("data2") -net2 <- mx.symbol.FullyConnected(data=net2, name="net2", num_hidden=128) -composed.net <- mx.apply(net, data=net2, name="compose") -arguments(composed.net) -``` - -In the above example, *net* is used a function to apply to an existing symbol -*net*, the resulting *composed.net* will replace the original argument *data* by -*net2* instead. - -### Training a Neural Net. - -The [model API](../../R-package/R/model.R) is a thin wrapper around the symbolic executors to support neural net training. - -You are also highly encouraged to read [Symbolic Configuration and Execution in Pictures for python package](../python/symbol_in_pictures.md), -which provides a detailed explanation of concepts in pictures. - -### How Efficient is Symbolic API - -In short, they are designed to be very efficient in both memory and runtime. - -The major reason for us to introduce Symbolic API, is to bring the efficient C++ -operations in powerful toolkits such as cxxnet and caffe together with the -flexible dynamic NArray operations. All the memory and computation resources are -allocated statically during Bind, to maximize the runtime performance and memory -utilization. - -The coarse grained operators are equivalent to cxxnet layers, which are -extremely efficient. We also provide fine grained operators for more flexible -composition. Because we are also doing more inplace memory allocation, mxnet can -be ***more memory efficient*** than cxxnet, and gets to same runtime, with -greater flexiblity. diff --git a/R-package/vignettes/symbol.Rmd b/R-package/vignettes/symbol.Rmd new file mode 100644 index 000000000000..228c6b26606c --- /dev/null +++ b/R-package/vignettes/symbol.Rmd @@ -0,0 +1,103 @@ +# Symbol and Automatic Differentiation + +The computational unit `NDArray` requires a way to construct neural networks. MXNet provides a symbolic interface, named Symbol, to do this. Symbol combines both flexibility and efficiency. + +## Basic Composition of Symbols + +The following code creates a two-layer perceptron network: + + +```{r} +require(mxnet) +net <- mx.symbol.Variable("data") +net <- mx.symbol.FullyConnected(data=net, name="fc1", num_hidden=128) +net <- mx.symbol.Activation(data=net, name="relu1", act_type="relu") +net <- mx.symbol.FullyConnected(data=net, name="fc2", num_hidden=64) +net <- mx.symbol.Softmax(data=net, name="out") +class(net) +``` + + +Each symbol takes a (unique) string name. *Variable* often defines the inputs, +or free variables. Other symbols take a symbol as the input (*data*), +and may accept other hyper parameters, such as the number of hidden neurons (*num_hidden*) +or the activation type (*act_type*). + +A symbol can be viewed as a function that takes several arguments, whose +names are automatically generated and can be retrieved with the following command: + + +```{r} +arguments(net) +``` + +The arguments are the parameters need by each symbol: + +- *data*: Input data needed by the variable *data* +- *fc1_weight* and *fc1_bias*: The weight and bias for the first fully connected layer, *fc1* +- *fc2_weight* and *fc2_bias*: The weight and bias for the second fully connected layer, *fc2* +- *out_label*: The label needed by the loss + +We can also specify the automatically generated names explicitly: + + +```{r} +data <- mx.symbol.Variable("data") +w <- mx.symbol.Variable("myweight") +net <- mx.symbol.FullyConnected(data=data, weight=w, name="fc1", num_hidden=128) +arguments(net) +``` + +## More Complicated Composition of Symbols + +MXNet provides well-optimized symbols for +commonly used layers in deep learning. You can also define new operators +in Python. The following example first performs an element-wise add between two +symbols, then feeds them to the fully connected operator: + + +```{r} +lhs <- mx.symbol.Variable("data1") +rhs <- mx.symbol.Variable("data2") +net <- mx.symbol.FullyConnected(data=lhs + rhs, name="fc1", num_hidden=128) +arguments(net) +``` + +We can construct a symbol more flexibly than by using the single +forward composition, for example: + +```{r} +net <- mx.symbol.Variable("data") +net <- mx.symbol.FullyConnected(data=net, name="fc1", num_hidden=128) +net2 <- mx.symbol.Variable("data2") +net2 <- mx.symbol.FullyConnected(data=net2, name="net2", num_hidden=128) +composed.net <- mx.apply(net, data=net2, name="compose") +arguments(composed.net) +``` + +In the example, *net* is used as a function to apply to an existing symbol +*net*. The resulting *composed.net* will replace the original argument *data* with +*net2* instead. + +## Training a Neural Net + +The [model API](../../../R-package/R/model.R) is a thin wrapper around the symbolic executors to support neural net training. + +We encourage you to read [Symbolic Configuration and Execution in Pictures for python package](../python/symbol_in_pictures.md)for a detailed explanation of concepts in pictures. + +## How Efficient Is the Symbolic API? + +The Symbolic API brings the efficient C++ +operations in powerful toolkits, such as CXXNet and Caffe, together with the +flexible dynamic NDArray operations. All of the memory and computation resources are +allocated statically during bind operations, to maximize runtime performance and memory +utilization. + +The coarse-grained operators are equivalent to CXXNet layers, which are +extremely efficient. We also provide fine-grained operators for more flexible +composition. Because MXNet does more in-place memory allocation, it can +be more memory efficient than CXXNet and gets to the same runtime with +greater flexibility. + + + \ No newline at end of file diff --git a/README.md b/README.md index 172d61d93529..a11780aa019b 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ - *for Deep Learning* +Apache MXNet (incubating) for Deep Learning ===== [![Build Status](https://travis-ci.org/dmlc/mxnet.svg?branch=master)](https://travis-ci.org/dmlc/mxnet) @@ -7,7 +7,7 @@ ![banner](https://raw.githubusercontent.com/dmlc/web-data/master/mxnet/image/banner.png) -MXNet is a deep learning framework designed for both *efficiency* and *flexibility*. +Apache MXNet (incubating) is a deep learning framework designed for both *efficiency* and *flexibility*. It allows you to ***mix*** [symbolic and imperative programming](http://mxnet.io/architecture/index.html#deep-learning-system-design-concepts) to ***maximize*** efficiency and productivity. At its core, MXNet contains a dynamic dependency scheduler that automatically parallelizes both symbolic and imperative operations on the fly. @@ -22,6 +22,9 @@ deep learning systems, and interesting insights of DL systems for hackers. What's New ---------- +* [Version 0.11.0-rc2 Release](https://github.com/apache/incubator-mxnet/releases/tag/0.11.0.rc2) - MXNet 0.11.0-rc2 Release. +* [Apache Incubator](http://incubator.apache.org/projects/mxnet.html) - We are now an Apache Incubator project. +* [Version 0.10.0 Release](https://github.com/dmlc/mxnet/releases/tag/v0.10.0) - MXNet 0.10.0 Release. * [Version 0.9.3 Release](./docs/architecture/release_note_0_9.md) - First 0.9 official release. * [Version 0.9.1 Release (NNVM refactor)](./docs/architecture/release_note_0_9.md) - NNVM branch is merged into master now. An official release will be made soon. * [Version 0.8.0 Release](https://github.com/dmlc/mxnet/releases/tag/v0.8.0) @@ -43,7 +46,7 @@ What's New Contents -------- -* [Documentation and Tutorials](http://mxnet.io/) +* [Documentation](http://mxnet.io/) and [Tutorials](http://mxnet.io/tutorials/) * [Design Notes](http://mxnet.io/architecture/index.html) * [Code Examples](https://github.com/dmlc/mxnet/tree/master/example) * [Installation](http://mxnet.io/get_started/install.html) diff --git a/amalgamation/Makefile b/amalgamation/Makefile index 2446667c1e9e..23a9c318fe31 100644 --- a/amalgamation/Makefile +++ b/amalgamation/Makefile @@ -61,8 +61,9 @@ dmlc.d: dmlc-minimum0.cc mxnet_predict0.d: mxnet_predict0.cc nnvm.d dmlc.d ${CXX} ${CFLAGS} -M -MT mxnet_predict0.o \ - -I ${MXNET_ROOT}/ -I ${MXNET_ROOT}/mshadow/ -I ${MXNET_ROOT}/dmlc-core/include \ + -I ${MXNET_ROOT}/ -I ${MXNET_ROOT}/mshadow/ -I ${MXNET_ROOT}/dmlc-core/include -I ${MXNET_ROOT}/dmlc-core/src \ -I ${MXNET_ROOT}/nnvm/include \ + -I ${MXNET_ROOT}/dlpack/include \ -I ${MXNET_ROOT}/include \ -D__MIN__=$(MIN) mxnet_predict0.cc > mxnet_predict0.d cat dmlc.d >> mxnet_predict0.d diff --git a/amalgamation/amalgamation.py b/amalgamation/amalgamation.py index da3b60ac8399..22b421d79fba 100644 --- a/amalgamation/amalgamation.py +++ b/amalgamation/amalgamation.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import sys import os.path, re, StringIO @@ -8,7 +25,8 @@ 'kvstore_dist.h', 'mach/clock.h', 'mach/mach.h', 'malloc.h', 'mkl.h', 'mkl_cblas.h', 'mkl_vsl.h', 'mkl_vsl_functions.h', 'nvml.h', 'opencv2/opencv.hpp', 'sys/stat.h', 'sys/types.h', 'cuda.h', 'cuda_fp16.h', - 'omp.h', 'execinfo.h', 'packet/sse-inl.h', 'emmintrin.h', 'thrust/device_vector.h' + 'omp.h', 'execinfo.h', 'packet/sse-inl.h', 'emmintrin.h', 'thrust/device_vector.h', + 'cusolverDn.h' ] minimum = int(sys.argv[6]) if len(sys.argv) > 5 else 0 diff --git a/amalgamation/dmlc-minimum0.cc b/amalgamation/dmlc-minimum0.cc index bce61129ed2e..3f7a97bb0139 100644 --- a/amalgamation/dmlc-minimum0.cc +++ b/amalgamation/dmlc-minimum0.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright 2015 by Contributors. * \brief Mininum DMLC library Amalgamation, used for easy plugin of dmlc lib. * Normally this is not needed. */ diff --git a/amalgamation/jni/org/dmlc/mxnet/MxnetException.java b/amalgamation/jni/org/dmlc/mxnet/MxnetException.java index c342cfaeee1d..08d80d683a4a 100644 --- a/amalgamation/jni/org/dmlc/mxnet/MxnetException.java +++ b/amalgamation/jni/org/dmlc/mxnet/MxnetException.java @@ -1,3 +1,22 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + package org.dmlc.mxnet; public class MxnetException extends Exception { diff --git a/amalgamation/jni/org/dmlc/mxnet/Predictor.java b/amalgamation/jni/org/dmlc/mxnet/Predictor.java index a91312a4121d..53152dcf7436 100644 --- a/amalgamation/jni/org/dmlc/mxnet/Predictor.java +++ b/amalgamation/jni/org/dmlc/mxnet/Predictor.java @@ -1,3 +1,22 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + package org.dmlc.mxnet; import android.graphics.Bitmap; @@ -37,7 +56,7 @@ int ctype() { private long handle = 0; public Predictor(byte[] symbol, byte[] params, Device dev, InputNode[] input) { - String[] keys = new String[input.length]; + String[] keys = new String[input.length]; int[][] shapes = new int[input.length][]; for (int i=0; i /* Header for class org_dmlc_mxnet_Predictor */ diff --git a/amalgamation/jni/predictor.cc b/amalgamation/jni/predictor.cc index 2687d1d9d93e..1936daf99f3d 100644 --- a/amalgamation/jni/predictor.cc +++ b/amalgamation/jni/predictor.cc @@ -1,3 +1,22 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 "org_dmlc_mxnet_Predictor.h" @@ -6,105 +25,105 @@ JNIEXPORT jlong JNICALL Java_org_dmlc_mxnet_Predictor_createPredictor (JNIEnv *env, jclass, jbyteArray jsymbol, jbyteArray jparams, jint devType, jint devId, jobjectArray jkeys, jobjectArray jshapes) { - jbyte* symbol = env->GetByteArrayElements(jsymbol, 0); - jbyte* params = env->GetByteArrayElements(jparams, 0); - jsize params_len = env->GetArrayLength(jparams); + jbyte* symbol = env->GetByteArrayElements(jsymbol, 0); + jbyte* params = env->GetByteArrayElements(jparams, 0); + jsize params_len = env->GetArrayLength(jparams); - std::vector> track; - std::vector keys; + std::vector> track; + std::vector keys; for (int i=0; iGetArrayLength(jkeys); i++) { jstring js = (jstring) env->GetObjectArrayElement(jkeys, i); const char *s = env->GetStringUTFChars(js, 0); - keys.emplace_back(s); - track.emplace_back(js, s); + keys.emplace_back(s); + track.emplace_back(js, s); } - std::vector index; - std::vector shapes; + std::vector index; + std::vector shapes; mx_uint prev = 0; index.emplace_back(prev); for (int i=0; iGetArrayLength(jshapes); i++) { jintArray jshape = (jintArray) env->GetObjectArrayElement(jshapes, i); - jsize shape_len = env->GetArrayLength(jshape); - jint *shape = env->GetIntArrayElements(jshape, 0); + jsize shape_len = env->GetArrayLength(jshape); + jint *shape = env->GetIntArrayElements(jshape, 0); prev += shape_len; - index.emplace_back(prev); - for (int j=0; jReleaseIntArrayElements(jshape, shape, 0); + index.emplace_back(prev); + for (int j=0; jReleaseIntArrayElements(jshape, shape, 0); } - PredictorHandle handle = 0; - if (MXPredCreate((const char *)symbol, (const char *)params, params_len, devType, devId, (mx_uint)keys.size(), &(keys[0]), &(index[0]), &(shapes[0]), &handle) < 0) { - jclass MxnetException = env->FindClass("org/dmlc/mxnet/MxnetException"); - env->ThrowNew(MxnetException, MXGetLastError()); - } + PredictorHandle handle = 0; + if (MXPredCreate((const char *)symbol, (const char *)params, params_len, devType, devId, (mx_uint)keys.size(), &(keys[0]), &(index[0]), &(shapes[0]), &handle) < 0) { + jclass MxnetException = env->FindClass("org/dmlc/mxnet/MxnetException"); + env->ThrowNew(MxnetException, MXGetLastError()); + } - env->ReleaseByteArrayElements(jsymbol, symbol, 0); - env->ReleaseByteArrayElements(jparams, params, 0); - for (auto& t: track) { - env->ReleaseStringUTFChars(t.first, t.second); - } + env->ReleaseByteArrayElements(jsymbol, symbol, 0); + env->ReleaseByteArrayElements(jparams, params, 0); + for (auto& t: track) { + env->ReleaseStringUTFChars(t.first, t.second); + } - return (jlong)handle; + return (jlong)handle; } JNIEXPORT void JNICALL Java_org_dmlc_mxnet_Predictor_nativeFree (JNIEnv *, jclass, jlong h) { - PredictorHandle handle = (PredictorHandle)h; - MXPredFree(handle); + PredictorHandle handle = (PredictorHandle)h; + MXPredFree(handle); } JNIEXPORT jfloatArray JNICALL Java_org_dmlc_mxnet_Predictor_nativeGetOutput (JNIEnv *env, jclass, jlong h, jint index) { - PredictorHandle handle = (PredictorHandle)h; - - mx_uint *shape = 0; - mx_uint shape_len; - if (MXPredGetOutputShape(handle, index, &shape, &shape_len) < 0) { - jclass MxnetException = env->FindClass("org/dmlc/mxnet/MxnetException"); - env->ThrowNew(MxnetException, MXGetLastError()); - } - - size_t size = 1; - for (mx_uint i=0; i data(size); - if (MXPredGetOutput(handle, index, &(data[0]), size) < 0) { - jclass MxnetException = env->FindClass("org/dmlc/mxnet/MxnetException"); - env->ThrowNew(MxnetException, MXGetLastError()); - } - - jfloatArray joutput = env->NewFloatArray(size); + PredictorHandle handle = (PredictorHandle)h; + + mx_uint *shape = 0; + mx_uint shape_len; + if (MXPredGetOutputShape(handle, index, &shape, &shape_len) < 0) { + jclass MxnetException = env->FindClass("org/dmlc/mxnet/MxnetException"); + env->ThrowNew(MxnetException, MXGetLastError()); + } + + size_t size = 1; + for (mx_uint i=0; i data(size); + if (MXPredGetOutput(handle, index, &(data[0]), size) < 0) { + jclass MxnetException = env->FindClass("org/dmlc/mxnet/MxnetException"); + env->ThrowNew(MxnetException, MXGetLastError()); + } + + jfloatArray joutput = env->NewFloatArray(size); jfloat *out = env->GetFloatArrayElements(joutput, NULL); for (int i=0; iReleaseFloatArrayElements(joutput, out, 0); - return joutput; + return joutput; } JNIEXPORT void JNICALL Java_org_dmlc_mxnet_Predictor_nativeForward (JNIEnv *env, jclass, jlong h, jstring jkey, jfloatArray jinput) { - PredictorHandle handle = (PredictorHandle)h; - const char *key = env->GetStringUTFChars(jkey, 0); - jfloat* input = env->GetFloatArrayElements(jinput, 0); - jsize input_len = env->GetArrayLength(jinput); - - if (MXPredSetInput(handle, key, input, input_len) < 0) { - jclass MxnetException = env->FindClass("org/dmlc/mxnet/MxnetException"); - env->ThrowNew(MxnetException, MXGetLastError()); - } - - env->ReleaseStringUTFChars(jkey, key); - env->ReleaseFloatArrayElements(jinput, input, 0); - if (MXPredForward(handle) < 0) { - jclass MxnetException = env->FindClass("org/dmlc/mxnet/MxnetException"); - env->ThrowNew(MxnetException, MXGetLastError()); - } + PredictorHandle handle = (PredictorHandle)h; + const char *key = env->GetStringUTFChars(jkey, 0); + jfloat* input = env->GetFloatArrayElements(jinput, 0); + jsize input_len = env->GetArrayLength(jinput); + + if (MXPredSetInput(handle, key, input, input_len) < 0) { + jclass MxnetException = env->FindClass("org/dmlc/mxnet/MxnetException"); + env->ThrowNew(MxnetException, MXGetLastError()); + } + + env->ReleaseStringUTFChars(jkey, key); + env->ReleaseFloatArrayElements(jinput, input, 0); + if (MXPredForward(handle) < 0) { + jclass MxnetException = env->FindClass("org/dmlc/mxnet/MxnetException"); + env->ThrowNew(MxnetException, MXGetLastError()); + } } diff --git a/amalgamation/mxnet_predict0.cc b/amalgamation/mxnet_predict0.cc index d51deb285c99..badf23771dbc 100644 --- a/amalgamation/mxnet_predict0.cc +++ b/amalgamation/mxnet_predict0.cc @@ -1,3 +1,22 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + // mxnet.cc #define MSHADOW_FORCE_STREAM @@ -26,6 +45,7 @@ #include "src/ndarray/ndarray_function.cc" +#include "src/ndarray/autograd.cc" #include "src/ndarray/ndarray.cc" #include "src/engine/engine.cc" diff --git a/amalgamation/python/mxnet_predict.py b/amalgamation/python/mxnet_predict.py index 684f23119d62..3dd6b387936f 100644 --- a/amalgamation/python/mxnet_predict.py +++ b/amalgamation/python/mxnet_predict.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # coding: utf-8 # pylint: disable=invalid-name, too-many-arguments """Lightweight API for mxnet prediction. diff --git a/appveyor.yml b/appveyor.yml index 54434bbf7b02..a5432b1483f5 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -52,7 +52,7 @@ before_build: set OpenCV_DIR=%APPVEYOR_BUILD_FOLDER%/%MXNET_OPENCV_DIR%/build - cmake .. -DOPENCV_DIR=%OpenCV_DIR% -DUSE_PROFILER=1 -DUSE_CUDA=0 -DUSE_CUDNN=0 -DUSE_NVRTC=0 -DUSE_OPENCV=1 -DUSE_OPENMP=1 -DUSE_BLAS=open -DUSE_DIST_KVSTORE=0 -G "Visual Studio 12 2013 Win64" + cmake .. -DOPENCV_DIR=%OpenCV_DIR% -DUSE_PROFILER=1 -DUSE_CUDA=0 -DUSE_CUDNN=0 -DUSE_NVRTC=0 -DUSE_OPENCV=1 -DUSE_OPENMP=1 -DUSE_BLAS=open -DUSE_LAPACK=1 -DUSE_DIST_KVSTORE=0 -G "Visual Studio 12 2013 Win64" build_script: - cmd: >- diff --git a/cmake/Modules/FindAccelerate.cmake b/cmake/Modules/FindAccelerate.cmake new file mode 100644 index 000000000000..695538ac924c --- /dev/null +++ b/cmake/Modules/FindAccelerate.cmake @@ -0,0 +1,45 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +# Find the Apple Accelerate framework +# +# The following are set after configuration is done: +# Accelerate_FOUND +# Accelerate_INCLUDE_DIRS +# Accelerate_LIBRARIES + +set(Accelerate_INCLUDE_SEARCH_PATHS + /System/Library/Frameworks/Accelerate.framework/Versions/Current/Frameworks/vecLib.framework/Versions/Current/Headers/ +) + +find_path(Accelerate_CBLAS_INCLUDE_DIR NAMES cblas.h PATHS ${Accelerate_INCLUDE_SEARCH_PATHS}) + +set(LOOKED_FOR + Accelerate_CBLAS_INCLUDE_DIR +) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(Accelerate DEFAULT_MSG ${LOOKED_FOR}) + +if(Accelerate_FOUND) + set(Accelerate_INCLUDE_DIR ${Accelerate_CBLAS_INCLUDE_DIR}) + set(Accelerate_LIBRARIES "-framework Accelerate") + mark_as_advanced(${LOOKED_FOR}) + + message(STATUS "Found Accelerate (include: ${Accelerate_CBLAS_INCLUDE_DIR}, library: ${Accelerate_BLAS_LIBRARY})") +endif(Accelerate_FOUND) + diff --git a/cmake/Modules/FindAtlas.cmake b/cmake/Modules/FindAtlas.cmake index 350bbe9df5dc..27aaa0e856ab 100644 --- a/cmake/Modules/FindAtlas.cmake +++ b/cmake/Modules/FindAtlas.cmake @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # Find the Atlas (and Lapack) libraries # # The following variables are optionally searched for defaults diff --git a/cmake/Modules/FindJeMalloc.cmake b/cmake/Modules/FindJeMalloc.cmake index 8b965cf6c3bb..57f47448f0a0 100644 --- a/cmake/Modules/FindJeMalloc.cmake +++ b/cmake/Modules/FindJeMalloc.cmake @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # Copyright (c) 2014 Thomas Heller # Copyright (c) 2007-2012 Hartmut Kaiser diff --git a/cmake/Modules/FindMKL.cmake b/cmake/Modules/FindMKL.cmake index 9679f3d72e60..743a871ee7cd 100644 --- a/cmake/Modules/FindMKL.cmake +++ b/cmake/Modules/FindMKL.cmake @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # Find the MKL libraries # # Options: diff --git a/cmake/Modules/FindOpenBLAS.cmake b/cmake/Modules/FindOpenBLAS.cmake index b63817a29d3e..7c5272b7f779 100644 --- a/cmake/Modules/FindOpenBLAS.cmake +++ b/cmake/Modules/FindOpenBLAS.cmake @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + if(MKL_FOUND) message(ERROR " OpenBLAS is not required since MKL is enabled") endif() diff --git a/cmake/Utils.cmake b/cmake/Utils.cmake index c367edb75a74..ac6ce3926c37 100644 --- a/cmake/Utils.cmake +++ b/cmake/Utils.cmake @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # For cmake_parse_arguments include(CMakeParseArguments) diff --git a/cpp-package/example/CMakeLists.txt b/cpp-package/example/CMakeLists.txt index 66e3dd8964b5..7083dfd014e9 100644 --- a/cpp-package/example/CMakeLists.txt +++ b/cpp-package/example/CMakeLists.txt @@ -3,7 +3,8 @@ if(NOT MSVC) endif() set(CPP_EXAMPLE_LIBS - ${BEGIN_WHOLE_ARCHIVE} mxnet ${END_WHOLE_ARCHIVE} + ${BEGIN_WHOLE_ARCHIVE} mxnet_static ${END_WHOLE_ARCHIVE} + ${BEGIN_WHOLE_ARCHIVE} dmlc ${END_WHOLE_ARCHIVE} ${mxnet_LINKER_LIBS} ) diff --git a/cpp-package/example/alexnet.cpp b/cpp-package/example/alexnet.cpp index c0d8273d559b..4194b5bae905 100644 --- a/cpp-package/example/alexnet.cpp +++ b/cpp-package/example/alexnet.cpp @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2016 by Contributors */ #include #include @@ -199,6 +217,7 @@ int main(int argc, char const *argv[]) { /*with data and label, executor can be generated automatically*/ auto *exec = Net.SimpleBind(ctx, args_map); + auto arg_names = Net.ListArguments(); aux_map = exec->aux_dict(); args_map = exec->arg_dict(); @@ -240,7 +259,9 @@ int main(int argc, char const *argv[]) { Optimizer* opt = OptimizerRegistry::Find("ccsgd"); opt->SetParam("momentum", 0.9) ->SetParam("rescale_grad", 1.0 / batch_size) - ->SetParam("clip_gradient", 10); + ->SetParam("clip_gradient", 10) + ->SetParam("lr", learning_rate) + ->SetParam("wd", weight_decay); Accuracy acu_train, acu_val; LogLoss logloss_val; @@ -258,7 +279,11 @@ int main(int argc, char const *argv[]) { batch.label.CopyTo(&args_map["label"]); exec->Forward(true); exec->Backward(); - exec->UpdateAll(opt, learning_rate, weight_decay); + for (size_t i = 0; i < arg_names.size(); ++i) { + if (arg_names[i] == "data" || arg_names[i] == "label") continue; + opt->Update(i, exec->arg_arrays[i], exec->grad_arrays[i]); + } + NDArray::WaitAll(); acu_train.Update(batch.label, exec->outputs[0]); } diff --git a/cpp-package/example/charRNN.cpp b/cpp-package/example/charRNN.cpp index 87393cc544ed..f5fff853cbad 100644 --- a/cpp-package/example/charRNN.cpp +++ b/cpp-package/example/charRNN.cpp @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2016 by Contributors * Hua Zhang mz24cn@hotmail.com * The code implements C++ version charRNN for mxnet\example\rnn\char-rnn.ipynb with MXNet.cpp API. * The generated params file is compatiable with python version. @@ -115,7 +133,7 @@ Symbol LSTMUnroll(int num_lstm_layer, int sequence_length, int input_dim, auto label = Symbol::Variable("softmax_label"); label = transpose(label); - label = Reshape(label, Shape(), false, Shape(-1), false); // -1: infer from graph + label = Reshape(label, Shape(), false, Shape(0), false); // -1: infer from graph auto sm = SoftmaxOutput("softmax", pred, label); if (isTrain) return sm; @@ -141,7 +159,7 @@ Symbol LSTMWithBuiltInRNNOp(int num_lstm_layer, int sequence_length, int input_d auto label = Symbol::Variable("softmax_label"); label = transpose(label); label = Reshape(label, Shape(), false, - Shape(-1), false); // FullyConnected requires one dimension + Shape(0), false); // FullyConnected requires one dimension if (!TIME_MAJOR && isTrain) embed = SwapAxis(embed, 0, 1); // Change to time-major as cuDNN requires @@ -151,7 +169,7 @@ Symbol LSTMWithBuiltInRNNOp(int num_lstm_layer, int sequence_length, int input_d auto rnn_params = Symbol::Variable("LSTM_parameters"); // See explanations near RNNXavier class auto rnn = RNN(embed, rnn_params, rnn_h_init, rnn_c_init, num_hidden, num_lstm_layer, RNNMode::kLstm, false, dropout, !isTrain); - auto hidden = Reshape(rnn[0], Shape(), false, Shape(-1, num_hidden), false); + auto hidden = Reshape(rnn[0], Shape(), false, Shape(0, num_hidden), false); auto cls_weight = Symbol::Variable("cls_weight"); auto cls_bias = Symbol::Variable("cls_bias"); @@ -451,6 +469,8 @@ void train(const string file, int batch_size, int max_epoch, int start_epoch) { mx_float learning_rate = 0.0002; mx_float weight_decay = 0.000002; Optimizer* opt = OptimizerRegistry::Find("ccsgd"); + opt->SetParam("lr", learning_rate) + ->SetParam("wd", weight_decay); // opt->SetParam("momentum", 0.9)->SetParam("rescale_grad", 1.0 / batch_size) // ->SetParam("clip_gradient", 10); @@ -470,7 +490,10 @@ void train(const string file, int batch_size, int max_epoch, int start_epoch) { exe->Forward(true); exe->Backward(); - exe->UpdateAll(opt, learning_rate, weight_decay); + for (size_t i = 0; i < exe->arg_arrays.size(); ++i) { + opt->Update(i, exe->arg_arrays[i], exe->grad_arrays[i]); + } + NDArray::WaitAll(); } auto toc = chrono::system_clock::now(); @@ -547,7 +570,9 @@ void trainWithBuiltInRNNOp(const string file, int batch_size, int max_epoch, int exe->Forward(true); exe->Backward(); - exe->UpdateAll(opt, learning_rate, weight_decay); + for (size_t i = 0; i < exe->arg_arrays.size(); ++i) { + opt->Update(i, exe->arg_arrays[i], exe->grad_arrays[i]); + } NDArray::WaitAll(); } auto toc = chrono::system_clock::now(); diff --git a/cpp-package/example/feature_extract/feature_extract.cpp b/cpp-package/example/feature_extract/feature_extract.cpp index 21853a3912e7..1886c576400d 100644 --- a/cpp-package/example/feature_extract/feature_extract.cpp +++ b/cpp-package/example/feature_extract/feature_extract.cpp @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors */ #include #include diff --git a/cpp-package/example/feature_extract/prepare_data_with_opencv.cpp b/cpp-package/example/feature_extract/prepare_data_with_opencv.cpp index 20cbe140fc09..a7b4cba0a64a 100644 --- a/cpp-package/example/feature_extract/prepare_data_with_opencv.cpp +++ b/cpp-package/example/feature_extract/prepare_data_with_opencv.cpp @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors */ #include #include diff --git a/cpp-package/example/feature_extract/run.sh b/cpp-package/example/feature_extract/run.sh index afac492b0a9d..dc6665604b1e 100755 --- a/cpp-package/example/feature_extract/run.sh +++ b/cpp-package/example/feature_extract/run.sh @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + ### To run the this example, ### ### 1. diff --git a/cpp-package/example/get_mnist.sh b/cpp-package/example/get_mnist.sh index 2bbe7a801872..40379621025d 100755 --- a/cpp-package/example/get_mnist.sh +++ b/cpp-package/example/get_mnist.sh @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + if [ ! -d "./mnist_data" ]; then mkdir mnist_data (cd mnist_data; wget http://yann.lecun.com/exdb/mnist/train-images-idx3-ubyte.gz) diff --git a/cpp-package/example/googlenet.cpp b/cpp-package/example/googlenet.cpp index a4dcbbd4a6cf..ac0585e81a70 100644 --- a/cpp-package/example/googlenet.cpp +++ b/cpp-package/example/googlenet.cpp @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2016 by Contributors */ #include #include @@ -128,7 +146,13 @@ int main(int argc, char const *argv[]) { Optimizer* opt = OptimizerRegistry::Find("ccsgd"); opt->SetParam("momentum", 0.9) ->SetParam("rescale_grad", 1.0 / batch_size) - ->SetParam("clip_gradient", 10); + ->SetParam("clip_gradient", 10) + ->SetParam("lr", learning_rate) + ->SetParam("wd", weight_decay); + + + auto *exec = googlenet.SimpleBind(Context::gpu(), args_map); + auto arg_names = googlenet.ListArguments(); for (int iter = 0; iter < max_epoch; ++iter) { LG << "Epoch: " << iter; @@ -138,11 +162,12 @@ int main(int argc, char const *argv[]) { args_map["data"] = data_batch.data.Copy(Context::gpu()); args_map["data_label"] = data_batch.label.Copy(Context::gpu()); NDArray::WaitAll(); - auto *exec = googlenet.SimpleBind(Context::gpu(), args_map); exec->Forward(true); exec->Backward(); - exec->UpdateAll(opt, learning_rate, weight_decay); - delete exec; + for (size_t i = 0; i < arg_names.size(); ++i) { + if (arg_names[i] == "data" || arg_names[i] == "data_label") continue; + opt->Update(i, exec->arg_arrays[i], exec->grad_arrays[i]); + } } Accuracy acu; @@ -152,14 +177,14 @@ int main(int argc, char const *argv[]) { args_map["data"] = data_batch.data.Copy(Context::gpu()); args_map["data_label"] = data_batch.label.Copy(Context::gpu()); NDArray::WaitAll(); - auto *exec = googlenet.SimpleBind(Context::gpu(), args_map); exec->Forward(false); NDArray::WaitAll(); acu.Update(data_batch.label, exec->outputs[0]); - delete exec; } LG << "Accuracy: " << acu.Get(); } + + delete exec; MXNotifyShutdown(); return 0; } diff --git a/cpp-package/example/inception_bn.cpp b/cpp-package/example/inception_bn.cpp index 6c0754e60d00..de21aadea9b5 100644 --- a/cpp-package/example/inception_bn.cpp +++ b/cpp-package/example/inception_bn.cpp @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2016 by Contributors */ #include #include @@ -11,9 +29,6 @@ using namespace mxnet::cpp; -static const Symbol BN_BETA; -static const Symbol BN_GAMMA; - Symbol ConvFactoryBN(Symbol data, int num_filter, Shape kernel, Shape stride, Shape pad, const std::string & name, @@ -23,7 +38,12 @@ Symbol ConvFactoryBN(Symbol data, int num_filter, Symbol conv = Convolution("conv_" + name + suffix, data, conv_w, conv_b, kernel, num_filter, stride, Shape(1, 1), pad); - Symbol bn = BatchNorm("bn_" + name + suffix, conv, Symbol(), Symbol(), Symbol(), Symbol()); + std::string name_suffix = name + suffix; + Symbol gamma(name_suffix + "_gamma"); + Symbol beta(name_suffix + "_beta"); + Symbol mmean(name_suffix + "_mmean"); + Symbol mvar(name_suffix + "_mvar"); + Symbol bn = BatchNorm("bn_" + name + suffix, conv, gamma, beta, mmean, mvar); return Activation("relu_" + name + suffix, bn, "relu"); } @@ -154,9 +174,12 @@ int main(int argc, char const *argv[]) { Optimizer* opt = OptimizerRegistry::Find("ccsgd"); opt->SetParam("momentum", 0.9) ->SetParam("rescale_grad", 1.0 / batch_size) - ->SetParam("clip_gradient", 10); + ->SetParam("clip_gradient", 10) + ->SetParam("lr", learning_rate) + ->SetParam("wd", weight_decay); auto *exec = inception_bn_net.SimpleBind(Context::gpu(), args_map); + auto arg_names = inception_bn_net.ListArguments(); for (int iter = 0; iter < max_epoch; ++iter) { LG << "Epoch: " << iter; @@ -169,7 +192,12 @@ int main(int argc, char const *argv[]) { exec->Forward(true); exec->Backward(); - exec->UpdateAll(opt, learning_rate, weight_decay); + // Update parameters + for (size_t i = 0; i < arg_names.size(); ++i) { + if (arg_names[i] == "data" || arg_names[i] == "data_label") continue; + opt->Update(i, exec->arg_arrays[i], exec->grad_arrays[i]); + } + NDArray::WaitAll(); } diff --git a/cpp-package/example/lenet.cpp b/cpp-package/example/lenet.cpp index 91b83a090fa3..05cc4517fe1e 100644 --- a/cpp-package/example/lenet.cpp +++ b/cpp-package/example/lenet.cpp @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors */ #include #include @@ -118,7 +136,12 @@ class Lenet { Optimizer* opt = OptimizerRegistry::Find("ccsgd"); opt->SetParam("momentum", 0.9) ->SetParam("rescale_grad", 1.0) - ->SetParam("clip_gradient", 10); + ->SetParam("clip_gradient", 10) + ->SetParam("lr", learning_rate) + ->SetParam("wd", weight_decay); + + Executor *exe = lenet.SimpleBind(ctx_dev, args_map); + auto arg_names = lenet.ListArguments(); for (int ITER = 0; ITER < max_epoch; ++ITER) { size_t start_index = 0; @@ -135,17 +158,19 @@ class Lenet { start_index += batch_size; NDArray::WaitAll(); - Executor *exe = lenet.SimpleBind(ctx_dev, args_map); exe->Forward(true); exe->Backward(); - exe->UpdateAll(opt, learning_rate, weight_decay); - - delete exe; + // Update parameters + for (size_t i = 0; i < arg_names.size(); ++i) { + if (arg_names[i] == "data" || arg_names[i] == "data_label") continue; + opt->Update(i, exe->arg_arrays[i], exe->grad_arrays[i]); + } } LG << "Iter " << ITER << ", accuracy: " << ValAccuracy(batch_size * 10, lenet); } + delete exe; } private: diff --git a/cpp-package/example/lenet_with_mxdataiter.cpp b/cpp-package/example/lenet_with_mxdataiter.cpp index 85a4b2012eb6..077f55622561 100644 --- a/cpp-package/example/lenet_with_mxdataiter.cpp +++ b/cpp-package/example/lenet_with_mxdataiter.cpp @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2016 by Contributors */ #include #include @@ -85,7 +103,13 @@ int main(int argc, char const *argv[]) { Optimizer* opt = OptimizerRegistry::Find("ccsgd"); opt->SetParam("momentum", 0.9) ->SetParam("rescale_grad", 1.0) - ->SetParam("clip_gradient", 10); + ->SetParam("clip_gradient", 10) + ->SetParam("lr", learning_rate) + ->SetParam("wd", weight_decay); + + + auto *exec = lenet.SimpleBind(Context::gpu(), args_map); + auto arg_names = lenet.ListArguments(); for (int iter = 0; iter < max_epoch; ++iter) { LG << "Epoch: " << iter; @@ -95,11 +119,13 @@ int main(int argc, char const *argv[]) { args_map["data"] = data_batch.data.Copy(Context::gpu()); args_map["data_label"] = data_batch.label.Copy(Context::gpu()); NDArray::WaitAll(); - auto *exec = lenet.SimpleBind(Context::gpu(), args_map); exec->Forward(true); exec->Backward(); - exec->UpdateAll(opt, learning_rate, weight_decay); - delete exec; + // Update parameters + for (size_t i = 0; i < arg_names.size(); ++i) { + if (arg_names[i] == "data" || arg_names[i] == "data_label") continue; + opt->Update(i, exec->arg_arrays[i], exec->grad_arrays[i]); + } } Accuracy acu; @@ -109,14 +135,14 @@ int main(int argc, char const *argv[]) { args_map["data"] = data_batch.data.Copy(Context::gpu()); args_map["data_label"] = data_batch.label.Copy(Context::gpu()); NDArray::WaitAll(); - auto *exec = lenet.SimpleBind(Context::gpu(), args_map); exec->Forward(false); NDArray::WaitAll(); acu.Update(data_batch.label, exec->outputs[0]); - delete exec; } LG << "Accuracy: " << acu.Get(); } + + delete exec; MXNotifyShutdown(); return 0; } diff --git a/cpp-package/example/mlp.cpp b/cpp-package/example/mlp.cpp index 6152eddc726a..c9c4ff245180 100644 --- a/cpp-package/example/mlp.cpp +++ b/cpp-package/example/mlp.cpp @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors */ #include diff --git a/cpp-package/example/mlp_cpu.cpp b/cpp-package/example/mlp_cpu.cpp index 6d8ed79f2e33..748c32e8c274 100644 --- a/cpp-package/example/mlp_cpu.cpp +++ b/cpp-package/example/mlp_cpu.cpp @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2017 by Contributors * Xin Li yakumolx@gmail.com */ #include @@ -70,7 +88,13 @@ int main(int argc, char** argv) { // Create sgd optimizer Optimizer* opt = OptimizerRegistry::Find("sgd"); - opt->SetParam("rescale_grad", 1.0/batch_size); + opt->SetParam("rescale_grad", 1.0/batch_size) + ->SetParam("lr", learning_rate) + ->SetParam("wd", weight_decay); + + // Create executor by binding parameters to the model + auto *exec = net.SimpleBind(ctx, args); + auto arg_names = net.ListArguments(); // Start training for (int iter = 0; iter < max_epoch; ++iter) { @@ -85,15 +109,14 @@ int main(int argc, char** argv) { args["X"] = data_batch.data; args["label"] = data_batch.label; - // Create executor by binding parmeters to the model - auto *exec = net.SimpleBind(ctx, args); // Compute gradients exec->Forward(true); exec->Backward(); // Update parameters - exec->UpdateAll(opt, learning_rate, weight_decay); - // Remember to free the memory - delete exec; + for (size_t i = 0; i < arg_names.size(); ++i) { + if (arg_names[i] == "X" || arg_names[i] == "label") continue; + opt->Update(i, exec->arg_arrays[i], exec->grad_arrays[i]); + } } auto toc = chrono::system_clock::now(); @@ -103,16 +126,15 @@ int main(int argc, char** argv) { auto data_batch = val_iter.GetDataBatch(); args["X"] = data_batch.data; args["label"] = data_batch.label; - auto *exec = net.SimpleBind(ctx, args); // Forward pass is enough as no gradient is needed when evaluating exec->Forward(false); acc.Update(data_batch.label, exec->outputs[0]); - delete exec; } float duration = chrono::duration_cast(toc - tic).count() / 1000.0; LG << "Epoch: " << iter << " " << samples/duration << " samples/sec Accuracy: " << acc.Get(); } + delete exec; MXNotifyShutdown(); return 0; } diff --git a/cpp-package/example/mlp_gpu.cpp b/cpp-package/example/mlp_gpu.cpp index 29d6ece4f553..531afbb29db6 100644 --- a/cpp-package/example/mlp_gpu.cpp +++ b/cpp-package/example/mlp_gpu.cpp @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2016 by Contributors * Xin Li yakumolx@gmail.com */ #include @@ -24,7 +42,7 @@ Symbol mlp(const vector &layers) { weights[i], biases[i], layers[i]); - outputs[i] = i == layers.size()-1? fc : Activation(fc, ActivationActType::kRelu); + outputs[i] = i == layers.size()-1 ? fc : Activation(fc, ActivationActType::kRelu); } return SoftmaxOutput(outputs.back(), label); @@ -70,12 +88,24 @@ int main(int argc, char** argv) { // Create sgd optimizer Optimizer* opt = OptimizerRegistry::Find("sgd"); - opt->SetParam("rescale_grad", 1.0/batch_size); + opt->SetParam("rescale_grad", 1.0/batch_size) + ->SetParam("lr", learning_rate) + ->SetParam("wd", weight_decay); + std::unique_ptr lr_sch(new FactorScheduler(5000, 0.1)); + opt->SetLRScheduler(std::move(lr_sch)); + + // Create executor by binding parameters to the model + auto *exec = net.SimpleBind(ctx, args); + auto arg_names = net.ListArguments(); + + // Create metrics + Accuracy train_acc, val_acc; // Start training for (int iter = 0; iter < max_epoch; ++iter) { int samples = 0; train_iter.Reset(); + train_acc.Reset(); auto tic = chrono::system_clock::now(); while (train_iter.Next()) { @@ -87,35 +117,40 @@ int main(int argc, char** argv) { // CopyTo is imperative, need to wait for it to complete. NDArray::WaitAll(); - // Create executor by binding parmeters to the model - auto *exec = net.SimpleBind(ctx, args); // Compute gradients exec->Forward(true); exec->Backward(); + // Update parameters - exec->UpdateAll(opt, learning_rate, weight_decay); - // Remember to free the memory - delete exec; + for (size_t i = 0; i < arg_names.size(); ++i) { + if (arg_names[i] == "X" || arg_names[i] == "label") continue; + opt->Update(i, exec->arg_arrays[i], exec->grad_arrays[i]); + } + // Update metric + train_acc.Update(data_batch.label, exec->outputs[0]); } + // one epoch of training is finished auto toc = chrono::system_clock::now(); + float duration = chrono::duration_cast(toc - tic).count() / 1000.0; + LG << "Epoch[" << iter << "] " << samples/duration \ + << " samples/sec " << "Train-Accuracy=" << train_acc.Get();; - Accuracy acc; val_iter.Reset(); + val_acc.Reset(); while (val_iter.Next()) { auto data_batch = val_iter.GetDataBatch(); data_batch.data.CopyTo(&args["X"]); data_batch.label.CopyTo(&args["label"]); NDArray::WaitAll(); - auto *exec = net.SimpleBind(ctx, args); + // Only forward pass is enough as no gradient is needed when evaluating exec->Forward(false); - acc.Update(data_batch.label, exec->outputs[0]); - delete exec; + val_acc.Update(data_batch.label, exec->outputs[0]); } - float duration = chrono::duration_cast(toc - tic).count() / 1000.0; - LG << "Epoch: " << iter << " " << samples/duration << " samples/sec Accuracy: " << acc.Get(); + LG << "Epoch[" << iter << "] Val-Accuracy=" << val_acc.Get(); } + delete exec; MXNotifyShutdown(); return 0; } diff --git a/cpp-package/example/resnet.cpp b/cpp-package/example/resnet.cpp index ace3459d4bd7..ca5643de9d81 100644 --- a/cpp-package/example/resnet.cpp +++ b/cpp-package/example/resnet.cpp @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2016 by Contributors */ #include #include @@ -35,9 +53,6 @@ Symbol ConvolutionNoBias(const std::string& symbol_name, .CreateSymbol(symbol_name); } -static const Symbol BN_BETA; -static const Symbol BN_GAMMA; - Symbol getConv(const std::string & name, Symbol data, int num_filter, Shape kernel, Shape stride, Shape pad, @@ -48,8 +63,13 @@ Symbol getConv(const std::string & name, Symbol data, kernel, num_filter, stride, Shape(1, 1), pad, 1, 512); - Symbol bn = BatchNorm(name + "_bn", conv, Symbol(), Symbol(), Symbol(), - Symbol(), 2e-5, bn_momentum, false); + Symbol gamma(name + "_gamma"); + Symbol beta(name + "_beta"); + Symbol mmean(name + "_mmean"); + Symbol mvar(name + "_mvar"); + + Symbol bn = BatchNorm(name + "_bn", conv, gamma, + beta, mmean, mvar, 2e-5, bn_momentum, false); if (with_relu) { return Activation(name + "_relu", bn, "relu"); @@ -109,8 +129,13 @@ Symbol ResNetSymbol(int num_class, int num_level = 3, int num_block = 9, Symbol data = Symbol::Variable("data"); Symbol data_label = Symbol::Variable("data_label"); - Symbol zscore = BatchNorm("zscore", data, Symbol(), Symbol(), Symbol(), - Symbol(), 0.001, bn_momentum); + Symbol gamma("gamma"); + Symbol beta("beta"); + Symbol mmean("mmean"); + Symbol mvar("mvar"); + + Symbol zscore = BatchNorm("zscore", data, gamma, + beta, mmean, mvar, 0.001, bn_momentum); Symbol conv = getConv("conv0", zscore, num_filter, Shape(3, 3), Shape(1, 1), Shape(1, 1), @@ -158,11 +183,14 @@ int main(int argc, char const *argv[]) { .CreateDataIter(); Optimizer* opt = OptimizerRegistry::Find("ccsgd"); - opt->SetParam("momentum", 0.9) + opt->SetParam("lr", learning_rate) + ->SetParam("wd", weight_decay) + ->SetParam("momentum", 0.9) ->SetParam("rescale_grad", 1.0 / batch_size) ->SetParam("clip_gradient", 10); auto *exec = resnet.SimpleBind(Context::gpu(), args_map); + auto arg_names = resnet.ListArguments(); for (int iter = 0; iter < max_epoch; ++iter) { LG << "Epoch: " << iter; @@ -175,7 +203,11 @@ int main(int argc, char const *argv[]) { exec->Forward(true); exec->Backward(); - exec->UpdateAll(opt, learning_rate, weight_decay); + + for (size_t i = 0; i < arg_names.size(); ++i) { + if (arg_names[i] == "data" || arg_names[i] == "data_label") continue; + opt->Update(i, exec->arg_arrays[i], exec->grad_arrays[i]); + } NDArray::WaitAll(); } diff --git a/cpp-package/example/run_lenet_with_mxdataiter.sh b/cpp-package/example/run_lenet_with_mxdataiter.sh index fffc355865bc..cafad3201635 100755 --- a/cpp-package/example/run_lenet_with_mxdataiter.sh +++ b/cpp-package/example/run_lenet_with_mxdataiter.sh @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + if [ ! -f "./mnist.zip" ]; then wget http://webdocs.cs.ualberta.ca/~bx3/data/mnist.zip unzip -u mnist.zip diff --git a/cpp-package/example/test_score.cpp b/cpp-package/example/test_score.cpp index 0606dbd1a191..254a6d242fd6 100644 --- a/cpp-package/example/test_score.cpp +++ b/cpp-package/example/test_score.cpp @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2016 by Contributors * Xin Li yakumolx@gmail.com */ #include @@ -72,7 +90,15 @@ int main(int argc, char** argv) { // Create sgd optimizer Optimizer* opt = OptimizerRegistry::Find("sgd"); - opt->SetParam("rescale_grad", 1.0/batch_size); + opt->SetParam("rescale_grad", 1.0/batch_size) + ->SetParam("lr", learning_rate) + ->SetParam("wd", weight_decay); + std::unique_ptr lr_sch(new FactorScheduler(5000, 0.1)); + opt->SetLRScheduler(std::move(lr_sch)); + + // Create executor by binding parameters to the model + auto *exec = net.SimpleBind(ctx, args); + auto arg_names = net.ListArguments(); float score = 0; // Start training @@ -90,15 +116,14 @@ int main(int argc, char** argv) { // CopyTo is imperative, need to wait for it to complete. NDArray::WaitAll(); - // Create executor by binding parmeters to the model - auto *exec = net.SimpleBind(ctx, args); // Compute gradients exec->Forward(true); exec->Backward(); // Update parameters - exec->UpdateAll(opt, learning_rate, weight_decay); - // Remember to free the memory - delete exec; + for (size_t i = 0; i < arg_names.size(); ++i) { + if (arg_names[i] == "X" || arg_names[i] == "label") continue; + opt->Update(i, exec->arg_arrays[i], exec->grad_arrays[i]); + } } auto toc = chrono::system_clock::now(); @@ -109,17 +134,16 @@ int main(int argc, char** argv) { data_batch.data.CopyTo(&args["X"]); data_batch.label.CopyTo(&args["label"]); NDArray::WaitAll(); - auto *exec = net.SimpleBind(ctx, args); // Only forward pass is enough as no gradient is needed when evaluating exec->Forward(false); acc.Update(data_batch.label, exec->outputs[0]); - delete exec; } float duration = chrono::duration_cast(toc - tic).count() / 1000.0; LG << "Epoch: " << iter << " " << samples/duration << " samples/sec Accuracy: " << acc.Get(); score = acc.Get(); } + delete exec; MXNotifyShutdown(); return score >= MIN_SCORE ? 0 : 1; } diff --git a/cpp-package/include/mxnet-cpp/MxNetCpp.h b/cpp-package/include/mxnet-cpp/MxNetCpp.h index 8ed90e3c751a..882bbead51e5 100644 --- a/cpp-package/include/mxnet-cpp/MxNetCpp.h +++ b/cpp-package/include/mxnet-cpp/MxNetCpp.h @@ -1,12 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2016 by Contributors * \file MxNetCpp.h * \brief meta include file for mxnet.cpp * \author Chuntao Hong, Zhang Chen */ -#ifndef CPP_PACKAGE_INCLUDE_MXNET_CPP_MXNETCPP_H_ -#define CPP_PACKAGE_INCLUDE_MXNET_CPP_MXNETCPP_H_ +#ifndef MXNET_CPP_MXNETCPP_H_ +#define MXNET_CPP_MXNETCPP_H_ #include "mxnet-cpp/executor.hpp" #include "mxnet-cpp/symbol.hpp" @@ -21,4 +39,4 @@ #include "mxnet-cpp/metric.h" #include "mxnet-cpp/initializer.h" -#endif // CPP_PACKAGE_INCLUDE_MXNET_CPP_MXNETCPP_H_ +#endif // MXNET_CPP_MXNETCPP_H_ diff --git a/cpp-package/include/mxnet-cpp/base.h b/cpp-package/include/mxnet-cpp/base.h index 18f268a8a85a..19375c0f81e8 100644 --- a/cpp-package/include/mxnet-cpp/base.h +++ b/cpp-package/include/mxnet-cpp/base.h @@ -1,12 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! -* Copyright (c) 2016 by Contributors * \file base.h * \brief base definitions for mxnetcpp * \author Chuntao Hong, Zhang Chen */ -#ifndef CPP_PACKAGE_INCLUDE_MXNET_CPP_BASE_H_ -#define CPP_PACKAGE_INCLUDE_MXNET_CPP_BASE_H_ +#ifndef MXNET_CPP_BASE_H_ +#define MXNET_CPP_BASE_H_ #include #include "mxnet/c_api.h" @@ -35,4 +53,4 @@ enum OpReqType { } // namespace cpp } // namespace mxnet -#endif // CPP_PACKAGE_INCLUDE_MXNET_CPP_BASE_H_ +#endif // MXNET_CPP_BASE_H_ diff --git a/cpp-package/include/mxnet-cpp/executor.h b/cpp-package/include/mxnet-cpp/executor.h index e4343a19a50d..7e45ef56ab95 100644 --- a/cpp-package/include/mxnet-cpp/executor.h +++ b/cpp-package/include/mxnet-cpp/executor.h @@ -1,12 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! -* Copyright (c) 2016 by Contributors * \file executor.h * \brief executor definition * \author Chuntao Hong, Zhang Chen */ -#ifndef CPP_PACKAGE_INCLUDE_MXNET_CPP_EXECUTOR_H_ -#define CPP_PACKAGE_INCLUDE_MXNET_CPP_EXECUTOR_H_ +#ifndef MXNET_CPP_EXECUTOR_H_ +#define MXNET_CPP_EXECUTOR_H_ #include #include @@ -79,18 +97,6 @@ class Executor { */ std::string DebugStr(); /*! - * \brief update the arguments with given learning rate and optimizer - * \param opt the pointer to the optimizer - * \param lr learning rate - * \param wd weight decay - * \param arg_update_begin begin index of the arguments to be updated, it - * starts after the input data by default - * \param arg_update_end end index of the arguments to be updated, it ends - * before the label data by default - */ - void UpdateAll(Optimizer *opt, float lr, float wd, int arg_update_begin = 1, - int arg_update_end = -1); - /*! * \brief destructor, free the handle */ ~Executor() { MXExecutorFree(handle_); } @@ -135,4 +141,4 @@ class Executor { }; } // namespace cpp } // namespace mxnet -#endif // CPP_PACKAGE_INCLUDE_MXNET_CPP_EXECUTOR_H_ +#endif // MXNET_CPP_EXECUTOR_H_ diff --git a/cpp-package/include/mxnet-cpp/executor.hpp b/cpp-package/include/mxnet-cpp/executor.hpp index 4cae684f8881..0aa698174005 100644 --- a/cpp-package/include/mxnet-cpp/executor.hpp +++ b/cpp-package/include/mxnet-cpp/executor.hpp @@ -1,12 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2016 by Contributors * \file executor.hpp * \brief implementation of the executor * \author Zhang Chen, Chuntao Hong */ -#ifndef CPP_PACKAGE_INCLUDE_MXNET_CPP_EXECUTOR_HPP_ -#define CPP_PACKAGE_INCLUDE_MXNET_CPP_EXECUTOR_HPP_ +#ifndef MXNET_CPP_EXECUTOR_HPP_ +#define MXNET_CPP_EXECUTOR_HPP_ #include #include @@ -79,14 +97,7 @@ inline std::string Executor::DebugStr() { return std::string(output); } -inline void Executor::UpdateAll(Optimizer *opt, float lr, float wd, - int arg_update_begin, int arg_update_end) { - arg_update_end = arg_update_end < 0 ? arg_arrays.size() - 1 : arg_update_end; - for (int i = arg_update_begin; i < arg_update_end; ++i) { - opt->Update(i, arg_arrays[i], grad_arrays[i], lr, wd); - } -} } // namespace cpp } // namespace mxnet -#endif // CPP_PACKAGE_INCLUDE_MXNET_CPP_EXECUTOR_HPP_ +#endif // MXNET_CPP_EXECUTOR_HPP_ diff --git a/cpp-package/include/mxnet-cpp/initializer.h b/cpp-package/include/mxnet-cpp/initializer.h index 843965256df1..e5bfa4da8eed 100644 --- a/cpp-package/include/mxnet-cpp/initializer.h +++ b/cpp-package/include/mxnet-cpp/initializer.h @@ -1,12 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2016 by Contributors * \file initializer.h * \brief random initializer * \author Zhang Chen */ -#ifndef CPP_PACKAGE_INCLUDE_MXNET_CPP_INITIALIZER_H_ -#define CPP_PACKAGE_INCLUDE_MXNET_CPP_INITIALIZER_H_ +#ifndef MXNET_CPP_INITIALIZER_H_ +#define MXNET_CPP_INITIALIZER_H_ #include #include @@ -179,4 +197,4 @@ class Xavier : public Initializer { } // namespace cpp } // namespace mxnet -#endif // CPP_PACKAGE_INCLUDE_MXNET_CPP_INITIALIZER_H_ +#endif // MXNET_CPP_INITIALIZER_H_ diff --git a/cpp-package/include/mxnet-cpp/io.h b/cpp-package/include/mxnet-cpp/io.h index 171803831109..7281416ae36a 100644 --- a/cpp-package/include/mxnet-cpp/io.h +++ b/cpp-package/include/mxnet-cpp/io.h @@ -1,11 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! -* Copyright (c) 2016 by Contributors * \file operator.h * \brief definition of io, such as DataIter * \author Zhang Chen */ -#ifndef CPP_PACKAGE_INCLUDE_MXNET_CPP_IO_H_ -#define CPP_PACKAGE_INCLUDE_MXNET_CPP_IO_H_ +#ifndef MXNET_CPP_IO_H_ +#define MXNET_CPP_IO_H_ #include #include @@ -124,5 +142,5 @@ class MXDataIter : public DataIter { } // namespace cpp } // namespace mxnet -#endif // CPP_PACKAGE_INCLUDE_MXNET_CPP_IO_H_ +#endif // MXNET_CPP_IO_H_ diff --git a/cpp-package/include/mxnet-cpp/io.hpp b/cpp-package/include/mxnet-cpp/io.hpp index 61e575e949a9..677c0f6ee1f0 100644 --- a/cpp-package/include/mxnet-cpp/io.hpp +++ b/cpp-package/include/mxnet-cpp/io.hpp @@ -1,11 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! -* Copyright (c) 2016 by Contributors * \file operator.hpp * \brief implementation of data iter * \author Zhang Chen */ -#ifndef CPP_PACKAGE_INCLUDE_MXNET_CPP_IO_HPP_ -#define CPP_PACKAGE_INCLUDE_MXNET_CPP_IO_HPP_ +#ifndef MXNET_CPP_IO_HPP_ +#define MXNET_CPP_IO_HPP_ #include #include @@ -86,5 +104,5 @@ inline MXDataIter MXDataIter::CreateDataIter() { } // namespace cpp } // namespace mxnet -#endif // CPP_PACKAGE_INCLUDE_MXNET_CPP_IO_HPP_ +#endif // MXNET_CPP_IO_HPP_ diff --git a/cpp-package/include/mxnet-cpp/kvstore.h b/cpp-package/include/mxnet-cpp/kvstore.h index 6d3987ecf030..9c3c81f37ff7 100644 --- a/cpp-package/include/mxnet-cpp/kvstore.h +++ b/cpp-package/include/mxnet-cpp/kvstore.h @@ -1,12 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! -* Copyright (c) 2016 by Contributors * \file kvstore.h * \brief definition of kvstore * \author Chuntao Hong */ -#ifndef CPP_PACKAGE_INCLUDE_MXNET_CPP_KVSTORE_H_ -#define CPP_PACKAGE_INCLUDE_MXNET_CPP_KVSTORE_H_ +#ifndef MXNET_CPP_KVSTORE_H_ +#define MXNET_CPP_KVSTORE_H_ #include #include @@ -46,4 +64,4 @@ class KVStore { } // namespace cpp } // namespace mxnet -#endif // CPP_PACKAGE_INCLUDE_MXNET_CPP_KVSTORE_H_ +#endif // MXNET_CPP_KVSTORE_H_ diff --git a/cpp-package/include/mxnet-cpp/kvstore.hpp b/cpp-package/include/mxnet-cpp/kvstore.hpp index d9effcf82f3c..f2b5e74990ce 100644 --- a/cpp-package/include/mxnet-cpp/kvstore.hpp +++ b/cpp-package/include/mxnet-cpp/kvstore.hpp @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2016 by Contributors * \file kvstore.hpp * \brief implementation of kvstore * \author Xin Li @@ -14,8 +32,8 @@ #include "mxnet-cpp/kvstore.h" #include "mxnet-cpp/optimizer.h" -#ifndef CPP_PACKAGE_INCLUDE_MXNET_CPP_KVSTORE_HPP_ -#define CPP_PACKAGE_INCLUDE_MXNET_CPP_KVSTORE_HPP_ +#ifndef MXNET_CPP_KVSTORE_HPP_ +#define MXNET_CPP_KVSTORE_HPP_ namespace mxnet { namespace cpp { @@ -175,4 +193,4 @@ inline std::string KVStore::GetRole() { } // namespace cpp } // namespace mxnet -#endif // CPP_PACKAGE_INCLUDE_MXNET_CPP_KVSTORE_HPP_ +#endif // MXNET_CPP_KVSTORE_HPP_ diff --git a/cpp-package/include/mxnet-cpp/lr_scheduler.h b/cpp-package/include/mxnet-cpp/lr_scheduler.h new file mode 100644 index 000000000000..b9381a830a88 --- /dev/null +++ b/cpp-package/include/mxnet-cpp/lr_scheduler.h @@ -0,0 +1,96 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +/*! +* \file lr_scheduler.h +* \brief Scheduling learning rate +*/ + +#ifndef MXNET_CPP_LR_SCHEDULER_H_ +#define MXNET_CPP_LR_SCHEDULER_H_ + +#include "dmlc/logging.h" + +namespace mxnet { +namespace cpp { + +/*! +* \brief lr scheduler interface +*/ +class LRScheduler { + public: + /*! + * \brief constructor + * \param base_lr the initial learning rate. + */ + explicit LRScheduler(float base_lr = 0.01) + : base_lr_(base_lr) {} + /*! + * \brief set base lr + * \param lr learning rate from optimizer + */ + void SetLR(const float lr) { base_lr_ = lr; } + /*! + * \brief get a new learning rate + */ + virtual float GetLR(unsigned num_update) = 0; + /*! + * \brief destructor + */ + virtual ~LRScheduler() {} + + protected: + float base_lr_; +}; + +class FactorScheduler : public LRScheduler { + public: + explicit FactorScheduler(int step, float factor = 1, float stop_factor_lr = 1e-8) + : LRScheduler() { + step_ = step; + factor_ = factor; + stop_factor_lr_ = stop_factor_lr; + } + + float GetLR(unsigned num_update) override { + while (num_update > unsigned(count_ + step_)) { + count_ += step_; + base_lr_ *= factor_; + if (base_lr_ < stop_factor_lr_) { + base_lr_ = stop_factor_lr_; + LG << "Update[" << num_update << "]: now learning rate arrived at " \ + << base_lr_ << ", will not change in the future"; + } else { + LG << "Update[" << num_update << "]: Change learning rate to " << base_lr_; + } + } + return base_lr_; + } + + private: + int count_ = 0; + int step_; + float factor_; + float stop_factor_lr_; +}; + +} // namespace cpp +} // namespace mxnet + +#endif // MXNET_CPP_LR_SCHEDULER_H_ diff --git a/cpp-package/include/mxnet-cpp/metric.h b/cpp-package/include/mxnet-cpp/metric.h index 24b3d73bae00..6dbb197dae49 100644 --- a/cpp-package/include/mxnet-cpp/metric.h +++ b/cpp-package/include/mxnet-cpp/metric.h @@ -1,12 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! -* Copyright (c) 2016 by Contributors * \file base.h * \brief metrics defined * \author Zhang Chen */ -#ifndef CPP_PACKAGE_INCLUDE_MXNET_CPP_METRIC_H_ -#define CPP_PACKAGE_INCLUDE_MXNET_CPP_METRIC_H_ +#ifndef MXNET_CPP_METRIC_H_ +#define MXNET_CPP_METRIC_H_ #include #include @@ -187,5 +205,5 @@ class PSNR : public EvalMetric { } // namespace cpp } // namespace mxnet -#endif // CPP_PACKAGE_INCLUDE_MXNET_CPP_METRIC_H_ +#endif // MXNET_CPP_METRIC_H_ diff --git a/cpp-package/include/mxnet-cpp/model.h b/cpp-package/include/mxnet-cpp/model.h index 7bfe1980f095..c8af6a476a52 100644 --- a/cpp-package/include/mxnet-cpp/model.h +++ b/cpp-package/include/mxnet-cpp/model.h @@ -1,12 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! -* Copyright (c) 2016 by Contributors * \file model.h * \brief MXNET.cpp model module * \author Zhang Chen */ -#ifndef CPP_PACKAGE_INCLUDE_MXNET_CPP_MODEL_H_ -#define CPP_PACKAGE_INCLUDE_MXNET_CPP_MODEL_H_ +#ifndef MXNET_CPP_MODEL_H_ +#define MXNET_CPP_MODEL_H_ #include #include @@ -54,5 +72,5 @@ class FeedForward { } // namespace cpp } // namespace mxnet -#endif // CPP_PACKAGE_INCLUDE_MXNET_CPP_MODEL_H_ +#endif // MXNET_CPP_MODEL_H_ diff --git a/cpp-package/include/mxnet-cpp/monitor.h b/cpp-package/include/mxnet-cpp/monitor.h index 2ce4e9590794..33ef4855c1a9 100644 --- a/cpp-package/include/mxnet-cpp/monitor.h +++ b/cpp-package/include/mxnet-cpp/monitor.h @@ -1,12 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! -* Copyright (c) 2017 by Contributors * \file monitor.h * \brief monitor definition * \author Xin Li */ -#ifndef CPP_PACKAGE_INCLUDE_MXNET_CPP_MONITOR_H_ -#define CPP_PACKAGE_INCLUDE_MXNET_CPP_MONITOR_H_ +#ifndef MXNET_CPP_MONITOR_H_ +#define MXNET_CPP_MONITOR_H_ #include #include @@ -85,4 +103,4 @@ class Monitor { } // namespace cpp } // namespace mxnet -#endif // CPP_PACKAGE_INCLUDE_MXNET_CPP_MONITOR_H_ +#endif // MXNET_CPP_MONITOR_H_ diff --git a/cpp-package/include/mxnet-cpp/monitor.hpp b/cpp-package/include/mxnet-cpp/monitor.hpp index d37652dd2c05..f3584e2e8092 100644 --- a/cpp-package/include/mxnet-cpp/monitor.hpp +++ b/cpp-package/include/mxnet-cpp/monitor.hpp @@ -1,12 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! -* Copyright (c) 2017 by Contributors * \file monitor.hpp * \brief monitor implementation * \author Xin Li */ -#ifndef CPP_PACKAGE_INCLUDE_MXNET_CPP_MONITOR_HPP_ -#define CPP_PACKAGE_INCLUDE_MXNET_CPP_MONITOR_HPP_ +#ifndef MXNET_CPP_MONITOR_HPP_ +#define MXNET_CPP_MONITOR_HPP_ #include #include @@ -103,4 +121,4 @@ inline void Monitor::executor_callback(const char *name, NDArrayHandle handle, } // namespace cpp } // namespace mxnet -#endif // CPP_PACKAGE_INCLUDE_MXNET_CPP_MONITOR_HPP_ +#endif // MXNET_CPP_MONITOR_HPP_ diff --git a/cpp-package/include/mxnet-cpp/ndarray.h b/cpp-package/include/mxnet-cpp/ndarray.h index 4e6863091e63..9e196d0730a8 100644 --- a/cpp-package/include/mxnet-cpp/ndarray.h +++ b/cpp-package/include/mxnet-cpp/ndarray.h @@ -1,12 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! -* Copyright (c) 2016 by Contributors * \file ndarray.h * \brief definition of ndarray * \author Chuntao Hong, Zhang Chen */ -#ifndef CPP_PACKAGE_INCLUDE_MXNET_CPP_NDARRAY_H_ -#define CPP_PACKAGE_INCLUDE_MXNET_CPP_NDARRAY_H_ +#ifndef MXNET_CPP_NDARRAY_H_ +#define MXNET_CPP_NDARRAY_H_ #include #include @@ -145,10 +163,12 @@ class NDArray { NDArray operator-(mx_float scalar); NDArray operator*(mx_float scalar); NDArray operator/(mx_float scalar); + NDArray operator%(mx_float scalar); NDArray operator+(const NDArray &); NDArray operator-(const NDArray &); NDArray operator*(const NDArray &); NDArray operator/(const NDArray &); + NDArray operator%(const NDArray &); /*! * \brief set all the elements in ndarray to be scalar * \param scalar the scalar to set @@ -165,25 +185,32 @@ class NDArray { /*! * \brief elementwise subtract from current ndarray * this mutate the current NDArray - * \param scalar the data to substract + * \param scalar the data to subtract * \return reference of self */ NDArray &operator-=(mx_float scalar); /*! * \brief elementwise multiplication to current ndarray * this mutate the current NDArray - * \param scalar the data to substract + * \param scalar the data to subtract * \return reference of self */ NDArray &operator*=(mx_float scalar); /*! * \brief elementwise division from current ndarray * this mutate the current NDArray - * \param scalar the data to substract + * \param scalar the data to subtract * \return reference of self */ NDArray &operator/=(mx_float scalar); /*! + * \brief elementwise modulo from current ndarray + * this mutate the current NDArray + * \param scalar the data to subtract + * \return reference of self + */ + NDArray &operator%=(mx_float scalar); + /*! * \brief elementwise add to current space * this mutate the current NDArray * \param src the data to add @@ -193,24 +220,31 @@ class NDArray { /*! * \brief elementwise subtract from current ndarray * this mutate the current NDArray - * \param src the data to substract + * \param src the data to subtract * \return reference of self */ NDArray &operator-=(const NDArray &src); /*! * \brief elementwise multiplication to current ndarray * this mutate the current NDArray - * \param src the data to substract + * \param src the data to subtract * \return reference of self */ NDArray &operator*=(const NDArray &src); /*! * \brief elementwise division from current ndarray * this mutate the current NDArray - * \param src the data to substract + * \param src the data to subtract * \return reference of self */ NDArray &operator/=(const NDArray &src); + /*! + * \brief elementwise modulo from current ndarray + * this mutate the current NDArray + * \param src the data to subtract + * \return reference of self + */ + NDArray &operator%=(const NDArray &src); NDArray ArgmaxChannel(); /*! * \brief Do a synchronize copy from a continugous CPU memory region. @@ -412,4 +446,4 @@ std::ostream& operator<<(std::ostream& out, const NDArray &ndarray); } // namespace cpp } // namespace mxnet -#endif // CPP_PACKAGE_INCLUDE_MXNET_CPP_NDARRAY_H_ +#endif // MXNET_CPP_NDARRAY_H_ diff --git a/cpp-package/include/mxnet-cpp/ndarray.hpp b/cpp-package/include/mxnet-cpp/ndarray.hpp index 69d1082bf8fa..5ed04a547b85 100644 --- a/cpp-package/include/mxnet-cpp/ndarray.hpp +++ b/cpp-package/include/mxnet-cpp/ndarray.hpp @@ -1,12 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2016 by Contributors * \file ndarray.hpp * \brief implementation of the ndarray * \author Zhang Chen, Chuntao Hong */ -#ifndef CPP_PACKAGE_INCLUDE_MXNET_CPP_NDARRAY_HPP_ -#define CPP_PACKAGE_INCLUDE_MXNET_CPP_NDARRAY_HPP_ +#ifndef MXNET_CPP_NDARRAY_HPP_ +#define MXNET_CPP_NDARRAY_HPP_ #include #include @@ -93,6 +111,11 @@ inline NDArray NDArray::operator/(mx_float scalar) { Operator("_div_scalar")(*this, scalar).Invoke(ret); return ret; } +inline NDArray NDArray::operator%(mx_float scalar) { + NDArray ret; + Operator("_mod_scalar")(*this, scalar).Invoke(ret); + return ret; +} inline NDArray NDArray::operator+(const NDArray &rhs) { NDArray ret; Operator("_plus")(*this, rhs).Invoke(ret); @@ -113,6 +136,11 @@ inline NDArray NDArray::operator/(const NDArray &rhs) { Operator("_div")(*this, rhs).Invoke(ret); return ret; } +inline NDArray NDArray::operator%(const NDArray &rhs) { + NDArray ret; + Operator("_mod")(*this, rhs).Invoke(ret); + return ret; +} inline NDArray &NDArray::operator=(mx_float scalar) { Operator("_set_value")(scalar).Invoke(*this); return *this; @@ -133,6 +161,10 @@ inline NDArray &NDArray::operator/=(mx_float scalar) { Operator("_div_scalar")(*this, scalar).Invoke(*this); return *this; } +inline NDArray &NDArray::operator%=(mx_float scalar) { + Operator("_mod_scalar")(*this, scalar).Invoke(*this); + return *this; +} inline NDArray &NDArray::operator+=(const NDArray &rhs) { Operator("_plus")(*this, rhs).Invoke(*this); return *this; @@ -149,6 +181,10 @@ inline NDArray &NDArray::operator/=(const NDArray &rhs) { Operator("_div")(*this, rhs).Invoke(*this); return *this; } +inline NDArray &NDArray::operator%=(const NDArray &rhs) { + Operator("_mod")(*this, rhs).Invoke(*this); + return *this; +} inline NDArray NDArray::ArgmaxChannel() { NDArray ret; @@ -360,4 +396,4 @@ inline std::ostream & operator<<(std::ostream &out, const NDArray &ndarray) { } // namespace cpp } // namespace mxnet -#endif // CPP_PACKAGE_INCLUDE_MXNET_CPP_NDARRAY_HPP_ +#endif // MXNET_CPP_NDARRAY_HPP_ diff --git a/cpp-package/include/mxnet-cpp/op_map.h b/cpp-package/include/mxnet-cpp/op_map.h index 2a2ae50a4e84..b54cc0ae2c01 100644 --- a/cpp-package/include/mxnet-cpp/op_map.h +++ b/cpp-package/include/mxnet-cpp/op_map.h @@ -1,12 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! -* Copyright (c) 2016 by Contributors * \file op_map.h * \brief definition of OpMap * \author Chuntao Hong */ -#ifndef CPP_PACKAGE_INCLUDE_MXNET_CPP_OP_MAP_H_ -#define CPP_PACKAGE_INCLUDE_MXNET_CPP_OP_MAP_H_ +#ifndef MXNET_CPP_OP_MAP_H_ +#define MXNET_CPP_OP_MAP_H_ #include #include @@ -89,4 +107,4 @@ class OpMap { } // namespace cpp } // namespace mxnet -#endif // CPP_PACKAGE_INCLUDE_MXNET_CPP_OP_MAP_H_ +#endif // MXNET_CPP_OP_MAP_H_ diff --git a/cpp-package/include/mxnet-cpp/op_suppl.h b/cpp-package/include/mxnet-cpp/op_suppl.h index 9381a1ecade9..52cdae772a68 100644 --- a/cpp-package/include/mxnet-cpp/op_suppl.h +++ b/cpp-package/include/mxnet-cpp/op_suppl.h @@ -1,12 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! -* Copyright (c) 2016 by Contributors * \file op_suppl.h * \brief A supplement and amendment of the operators from op.h * \author Zhang Chen, zhubuntu, Xin Li */ -#ifndef CPP_PACKAGE_INCLUDE_MXNET_CPP_OP_SUPPL_H_ -#define CPP_PACKAGE_INCLUDE_MXNET_CPP_OP_SUPPL_H_ +#ifndef MXNET_CPP_OP_SUPPL_H_ +#define MXNET_CPP_OP_SUPPL_H_ #include #include @@ -35,6 +53,10 @@ inline Symbol _Div(Symbol lhs, Symbol rhs) { return Operator("_Div")(lhs, rhs) .CreateSymbol(); } +inline Symbol _Mod(Symbol lhs, Symbol rhs) { + return Operator("_Mod")(lhs, rhs) + .CreateSymbol(); +} inline Symbol _Power(Symbol lhs, Symbol rhs) { return Operator("_Power")(lhs, rhs) .CreateSymbol(); @@ -77,6 +99,16 @@ inline Symbol _RDivScalar(mx_float scalar, Symbol rhs) { .SetParam("scalar", scalar) .CreateSymbol(); } +inline Symbol _ModScalar(Symbol lhs, mx_float scalar) { + return Operator("_ModScalar")(lhs) + .SetParam("scalar", scalar) + .CreateSymbol(); +} +inline Symbol _RModScalar(mx_float scalar, Symbol rhs) { + return Operator("_RModScalar")(rhs) + .SetParam("scalar", scalar) + .CreateSymbol(); +} inline Symbol _PowerScalar(Symbol lhs, mx_float scalar) { return Operator("_PowerScalar")(lhs) .SetParam("scalar", scalar) @@ -143,5 +175,5 @@ inline Symbol Activation(const std::string& symbol_name, } // namespace cpp } // namespace mxnet -#endif // CPP_PACKAGE_INCLUDE_MXNET_CPP_OP_SUPPL_H_ +#endif // MXNET_CPP_OP_SUPPL_H_ diff --git a/cpp-package/include/mxnet-cpp/op_util.h b/cpp-package/include/mxnet-cpp/op_util.h index bf67eab4c1ae..20e06a851814 100644 --- a/cpp-package/include/mxnet-cpp/op_util.h +++ b/cpp-package/include/mxnet-cpp/op_util.h @@ -1,12 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! -* Copyright (c) 2017 by Contributors * \file op_util.h * \brief operator helper functions * \author Chris Olivier */ -#ifndef CPP_PACKAGE_INCLUDE_MXNET_CPP_OP_UTIL_H_ -#define CPP_PACKAGE_INCLUDE_MXNET_CPP_OP_UTIL_H_ +#ifndef MXNET_CPP_OP_UTIL_H_ +#define MXNET_CPP_OP_UTIL_H_ #include @@ -43,4 +61,4 @@ inline StreamType& operator << (StreamType& os, const ::caffe::LayerParameter& o } // namespace cpp } // namespace mxnet -#endif // CPP_PACKAGE_INCLUDE_MXNET_CPP_OP_UTIL_H_ +#endif // MXNET_CPP_OP_UTIL_H_ diff --git a/cpp-package/include/mxnet-cpp/operator.h b/cpp-package/include/mxnet-cpp/operator.h index 9851abe55e24..02bd21ebe8c9 100644 --- a/cpp-package/include/mxnet-cpp/operator.h +++ b/cpp-package/include/mxnet-cpp/operator.h @@ -1,12 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! -* Copyright (c) 2016 by Contributors * \file operator.h * \brief definition of operator * \author Chuntao Hong, Zhang Chen */ -#ifndef CPP_PACKAGE_INCLUDE_MXNET_CPP_OPERATOR_H_ -#define CPP_PACKAGE_INCLUDE_MXNET_CPP_OPERATOR_H_ +#ifndef MXNET_CPP_OPERATOR_H_ +#define MXNET_CPP_OPERATOR_H_ #include #include @@ -74,7 +92,7 @@ class Operator { */ template void PushInput(const Symbol &symbol) { - input_symbols.push_back(symbol.GetHandle()); + input_symbols_.push_back(symbol.GetHandle()); } /*! * \brief add input symbols @@ -87,7 +105,7 @@ class Operator { * \return reference of self */ Operator &operator()(const Symbol &symbol) { - input_symbols.push_back(symbol.GetHandle()); + input_symbols_.push_back(symbol.GetHandle()); return *this; } /*! @@ -97,7 +115,7 @@ class Operator { */ Operator &operator()(const std::vector &symbols) { for (auto &s : symbols) { - input_symbols.push_back(s.GetHandle()); + input_symbols_.push_back(s.GetHandle()); } return *this; } @@ -121,7 +139,7 @@ class Operator { */ template Operator &PushInput(const NDArray &ndarray) { - input_ndarrays.push_back(ndarray.GetHandle()); + input_ndarrays_.push_back(ndarray.GetHandle()); return *this; } /*! @@ -147,7 +165,7 @@ class Operator { * \return reference of self */ Operator &operator()(const NDArray &ndarray) { - input_ndarrays.push_back(ndarray.GetHandle()); + input_ndarrays_.push_back(ndarray.GetHandle()); return *this; } /*! @@ -157,7 +175,7 @@ class Operator { */ Operator &operator()(const std::vector &ndarrays) { for (auto &s : ndarrays) { - input_ndarrays.push_back(s.GetHandle()); + input_ndarrays_.push_back(s.GetHandle()); } return *this; } @@ -178,9 +196,9 @@ class Operator { std::map params_desc_; bool variable_params_ = false; std::map params_; - std::vector input_symbols; - std::vector input_ndarrays; - std::vector input_keys; + std::vector input_symbols_; + std::vector input_ndarrays_; + std::vector input_keys_; std::vector arg_names_; AtomicSymbolCreator handle_; static OpMap*& op_map(); @@ -188,4 +206,4 @@ class Operator { } // namespace cpp } // namespace mxnet -#endif // CPP_PACKAGE_INCLUDE_MXNET_CPP_OPERATOR_H_ +#endif // MXNET_CPP_OPERATOR_H_ diff --git a/cpp-package/include/mxnet-cpp/operator.hpp b/cpp-package/include/mxnet-cpp/operator.hpp index b979b7c56d73..a0100cd601be 100644 --- a/cpp-package/include/mxnet-cpp/operator.hpp +++ b/cpp-package/include/mxnet-cpp/operator.hpp @@ -1,12 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! -* Copyright (c) 2016 by Contributors * \file operator.hpp * \brief implementation of operator * \author Chuntao Hong, Zhang Chen */ -#ifndef CPP_PACKAGE_INCLUDE_MXNET_CPP_OPERATOR_HPP_ -#define CPP_PACKAGE_INCLUDE_MXNET_CPP_OPERATOR_HPP_ +#ifndef MXNET_CPP_OPERATOR_HPP_ +#define MXNET_CPP_OPERATOR_HPP_ #include #include @@ -25,12 +43,12 @@ namespace cpp { */ template <> inline Operator& Operator::SetParam(int pos, const NDArray &value) { - input_ndarrays.push_back(value.GetHandle()); + input_ndarrays_.push_back(value.GetHandle()); return *this; } template <> inline Operator& Operator::SetParam(int pos, const Symbol &value) { - input_symbols.push_back(value.GetHandle()); + input_symbols_.push_back(value.GetHandle()); return *this; } @@ -62,8 +80,8 @@ inline Operator::Operator(const std::string &operator_name) { } inline Symbol Operator::CreateSymbol(const std::string &name) { - if (input_keys.size() > 0) { - CHECK_EQ(input_keys.size(), input_symbols.size()); + if (input_keys_.size() > 0) { + CHECK_EQ(input_keys_.size(), input_symbols_.size()); } const char *pname = name == "" ? nullptr : name.c_str(); @@ -76,7 +94,7 @@ inline Symbol Operator::CreateSymbol(const std::string &name) { param_keys.push_back(data.first.c_str()); param_values.push_back(data.second.c_str()); } - for (auto &data : this->input_keys) { + for (auto &data : this->input_keys_) { input_keys.push_back(data.c_str()); } const char **input_keys_p = @@ -84,14 +102,14 @@ inline Symbol Operator::CreateSymbol(const std::string &name) { MXSymbolCreateAtomicSymbol(handle_, param_keys.size(), param_keys.data(), param_values.data(), &symbol_handle); - MXSymbolCompose(symbol_handle, pname, input_symbols.size(), input_keys_p, - input_symbols.data()); + MXSymbolCompose(symbol_handle, pname, input_symbols_.size(), input_keys_p, + input_symbols_.data()); return Symbol(symbol_handle); } inline void Operator::Invoke(std::vector &outputs) { - if (input_keys.size() > 0) { - CHECK_EQ(input_keys.size(), input_ndarrays.size()); + if (input_keys_.size() > 0) { + CHECK_EQ(input_keys_.size(), input_ndarrays_.size()); } std::vector input_keys; @@ -103,7 +121,7 @@ inline void Operator::Invoke(std::vector &outputs) { param_values.push_back(data.second.c_str()); } - int num_inputs = input_ndarrays.size(); + int num_inputs = input_ndarrays_.size(); int num_outputs = outputs.size(); std::vector output_handles; std::transform(outputs.begin(), outputs.end(), @@ -116,7 +134,7 @@ inline void Operator::Invoke(std::vector &outputs) { outputs_receiver = output_handles.data(); } - MXImperativeInvoke(handle_, num_inputs, input_ndarrays.data(), + MXImperativeInvoke(handle_, num_inputs, input_ndarrays_.data(), &num_outputs, &outputs_receiver, param_keys.size(), param_keys.data(), param_values.data()); @@ -141,18 +159,18 @@ inline void Operator::Invoke(NDArray &output) { } inline Operator &Operator::SetInput(const std::string &name, Symbol symbol) { - input_keys.push_back(name.c_str()); - input_symbols.push_back(symbol.GetHandle()); + input_keys_.push_back(name.c_str()); + input_symbols_.push_back(symbol.GetHandle()); return *this; } inline Operator &Operator::SetInput(const std::string &name, NDArray ndarray) { - input_keys.push_back(name.c_str()); - input_ndarrays.push_back(ndarray.GetHandle()); + input_keys_.push_back(name.c_str()); + input_ndarrays_.push_back(ndarray.GetHandle()); return *this; } } // namespace cpp } // namespace mxnet -#endif // CPP_PACKAGE_INCLUDE_MXNET_CPP_OPERATOR_HPP_ +#endif // MXNET_CPP_OPERATOR_HPP_ diff --git a/cpp-package/include/mxnet-cpp/optimizer.h b/cpp-package/include/mxnet-cpp/optimizer.h index 8dbbbf7f39ea..e57da5d95ceb 100644 --- a/cpp-package/include/mxnet-cpp/optimizer.h +++ b/cpp-package/include/mxnet-cpp/optimizer.h @@ -1,12 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! -* Copyright (c) 2016 by Contributors * \file optimizer.h * \brief definition of optimizer * \author Chuntao Hong, Zhang Chen */ -#ifndef CPP_PACKAGE_INCLUDE_MXNET_CPP_OPTIMIZER_H_ -#define CPP_PACKAGE_INCLUDE_MXNET_CPP_OPTIMIZER_H_ +#ifndef MXNET_CPP_OPTIMIZER_H_ +#define MXNET_CPP_OPTIMIZER_H_ #include #include @@ -17,6 +35,7 @@ #include "dmlc/logging.h" #include "mxnet-cpp/ndarray.h" #include "mxnet-cpp/op_map.h" +#include "mxnet-cpp/lr_scheduler.h" namespace mxnet { namespace cpp { @@ -57,15 +76,16 @@ class Optimizer { return this; } /*! - * \brief Update a weight with gradient. - * \param index the unique index for the weight. - * \param weight the weight to update. - * \param grad gradient for the weight. - * \param lr learning rate. - * \param wd weight decay. + * \bried set the lr scheduler + * \param lrScheduler lr scheduler used for this optimizer + * \return reference if self */ - void Update(int index, NDArray weight, NDArray grad, mx_float lr, - mx_float wd); + Optimizer *SetLRScheduler(std::unique_ptr lrScheduler) { + CHECK(lrScheduler); + lrScheduler_ = std::move(lrScheduler); + lrScheduler_->SetLR(std::stof(params_["lr"])); + return this; + } /*! * \brief Update a weight with gradient. * \param index the unique index for the weight. @@ -92,7 +112,10 @@ class Optimizer { std::map count_; unsigned begin_num_update_, num_update_; unsigned UpdateCount_(int index); + float GetLR_(int index); + float GetWD_(int index); virtual void CreateState_(int index, NDArray weight); + std::unique_ptr lrScheduler_ = nullptr; }; typedef std::function OptimizerCreator; @@ -172,8 +195,7 @@ class AdaDeltaOptimizer : public Optimizer { std::map acc_g_, acc_delta_; }; - } // namespace cpp } // namespace mxnet -#endif // CPP_PACKAGE_INCLUDE_MXNET_CPP_OPTIMIZER_H_ +#endif // MXNET_CPP_OPTIMIZER_H_ diff --git a/cpp-package/include/mxnet-cpp/optimizer.hpp b/cpp-package/include/mxnet-cpp/optimizer.hpp index c86476f65417..f9c885fc1fdd 100644 --- a/cpp-package/include/mxnet-cpp/optimizer.hpp +++ b/cpp-package/include/mxnet-cpp/optimizer.hpp @@ -1,12 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! -* Copyright (c) 2016 by Contributors * \file optimizer.hpp * \brief implementation of optimizer * \author Chuntao Hong, Zhang Chen */ -#ifndef CPP_PACKAGE_INCLUDE_MXNET_CPP_OPTIMIZER_HPP_ -#define CPP_PACKAGE_INCLUDE_MXNET_CPP_OPTIMIZER_HPP_ +#ifndef MXNET_CPP_OPTIMIZER_HPP_ +#define MXNET_CPP_OPTIMIZER_HPP_ #include #include @@ -42,6 +60,8 @@ namespace cpp { inline Optimizer::Optimizer(unsigned begin_num_update) : begin_num_update_(begin_num_update), num_update_(begin_num_update_) { + params_["lr"] = "0.01f"; + params_["wd"] = "0.f"; } inline std::map& OptimizerRegistry::cmap() { @@ -56,14 +76,6 @@ inline OpMap*& Optimizer::op_map() { inline Optimizer::~Optimizer() {} -inline void Optimizer::Update(int index, NDArray weight, NDArray grad, mx_float lr, - mx_float wd) { - params_["lr"] = std::to_string(lr); - params_["wd"] = std::to_string(wd); - UpdateCount_(index); - Update(index, weight, grad); -} - inline void Optimizer::CreateState_(int index, NDArray weight) { } @@ -100,6 +112,18 @@ inline unsigned Optimizer::UpdateCount_(int index) { return new_count; } +inline float Optimizer::GetLR_(int index) { + if (nullptr != lrScheduler_) { + return lrScheduler_->GetLR(num_update_); + } + return std::stof(params_["lr"]); +} + +inline float Optimizer::GetWD_(int index) { + float wd = std::stof(params_["wd"]); + return wd; +} + inline Optimizer* OptimizerRegistry::Find(const std::string& name) { MXNETCPP_REGISTER_OPTIMIZER(sgd, SGDOptimizer); MXNETCPP_REGISTER_OPTIMIZER(ccsgd, SGDOptimizer); // For backward compatibility @@ -140,6 +164,9 @@ inline void SGDOptimizer::Update(int index, NDArray weight, NDArray grad) { CreateState_(index, weight); } + params_["lr"] = std::to_string(GetLR_(index)); + params_["wd"] = std::to_string(GetWD_(index)); + UpdateCount_(index); auto keys = GetParamKeys_(); auto values = GetParamValues_(); CHECK_EQ(keys.size(), values.size()); @@ -203,6 +230,9 @@ inline void RMSPropOptimizer::Update(int index, NDArray weight, NDArray grad) { CreateState_(index, weight); } + params_["lr"] = std::to_string(GetLR_(index)); + params_["wd"] = std::to_string(GetWD_(index)); + UpdateCount_(index); auto keys = GetParamKeys_(); auto values = GetParamValues_(); CHECK_EQ(keys.size(), values.size()); @@ -257,6 +287,10 @@ inline void AdamOptimizer::Update(int index, NDArray weight, NDArray grad) { if (mean_.count(index) == 0) { CreateState_(index, weight); } + + params_["lr"] = std::to_string(GetLR_(index)); + params_["wd"] = std::to_string(GetWD_(index)); + UpdateCount_(index); auto keys = GetParamKeys_(); auto values = GetParamValues_(); CHECK_EQ(keys.size(), values.size()); @@ -306,9 +340,11 @@ inline void AdaGradOptimizer::Update(int index, NDArray weight, NDArray grad) { if (history_.count(index) == 0) { CreateState_(index, weight); } - float lr = std::stof(params_["lr"]); - float wd = std::stof(params_["wd"]); + float eps = std::stof(params_["eps"]); + float lr = GetLR_(index); + float wd = GetWD_(index); + UpdateCount_(index); if (params_.count("rescale_grad") > 0) { grad *= std::stof(params_["rescale_grad"]); } @@ -345,9 +381,11 @@ inline void AdaDeltaOptimizer::Update(int index, NDArray weight, NDArray grad) { if (acc_g_.count(index) == 0) { CreateState_(index, weight); } - float wd = std::stof(params_["wd"]); + float rho = std::stof(params_["rho"]); float epsilon = std::stof(params_["epsilon"]); + float wd = GetWD_(index); + UpdateCount_(index); if (params_.count("rescale_grad") > 0) { grad *= std::stof(params_["rescale_grad"]); @@ -387,4 +425,4 @@ inline void AdaDeltaOptimizer::CreateState_(int index, NDArray weight) { } // namespace cpp } // namespace mxnet -#endif // CPP_PACKAGE_INCLUDE_MXNET_CPP_OPTIMIZER_HPP_ +#endif // MXNET_CPP_OPTIMIZER_HPP_ diff --git a/cpp-package/include/mxnet-cpp/shape.h b/cpp-package/include/mxnet-cpp/shape.h index d8e3f2c95282..2793e436c072 100644 --- a/cpp-package/include/mxnet-cpp/shape.h +++ b/cpp-package/include/mxnet-cpp/shape.h @@ -1,12 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! -* Copyright (c) 2016 by Contributors * \file shape.h * \brief definition of shape * \author Chuntao Hong, Zhang Chen */ -#ifndef CPP_PACKAGE_INCLUDE_MXNET_CPP_SHAPE_H_ -#define CPP_PACKAGE_INCLUDE_MXNET_CPP_SHAPE_H_ +#ifndef MXNET_CPP_SHAPE_H_ +#define MXNET_CPP_SHAPE_H_ #include #include @@ -386,4 +404,4 @@ inline std::istream &operator>>(std::istream &is, Shape &shape) { } // namespace cpp } // namespace mxnet -#endif // CPP_PACKAGE_INCLUDE_MXNET_CPP_SHAPE_H_ +#endif // MXNET_CPP_SHAPE_H_ diff --git a/cpp-package/include/mxnet-cpp/symbol.h b/cpp-package/include/mxnet-cpp/symbol.h index 03a8409f8087..888aebd6f3ad 100644 --- a/cpp-package/include/mxnet-cpp/symbol.h +++ b/cpp-package/include/mxnet-cpp/symbol.h @@ -1,12 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! -* Copyright (c) 2016 by Contributors * \file symbol.h * \brief definition of symbol * \author Chuntao Hong, Zhang Chen */ -#ifndef CPP_PACKAGE_INCLUDE_MXNET_CPP_SYMBOL_H_ -#define CPP_PACKAGE_INCLUDE_MXNET_CPP_SYMBOL_H_ +#ifndef MXNET_CPP_SYMBOL_H_ +#define MXNET_CPP_SYMBOL_H_ #include #include @@ -72,11 +90,13 @@ class Symbol { Symbol operator-(const Symbol &rhs) const; Symbol operator*(const Symbol &rhs) const; Symbol operator/(const Symbol &rhs) const; + Symbol operator%(const Symbol &rhs) const; Symbol operator+(mx_float scalar) const; Symbol operator-(mx_float scalar) const; Symbol operator*(mx_float scalar) const; Symbol operator/(mx_float scalar) const; + Symbol operator%(mx_float scalar) const; Symbol Copy() const; /*! * \brief construct a variable Symbol @@ -252,6 +272,7 @@ Symbol operator+(mx_float lhs, const Symbol &rhs); Symbol operator-(mx_float lhs, const Symbol &rhs); Symbol operator*(mx_float lhs, const Symbol &rhs); Symbol operator/(mx_float lhs, const Symbol &rhs); +Symbol operator%(mx_float lhs, const Symbol &rhs); } // namespace cpp } // namespace mxnet -#endif // CPP_PACKAGE_INCLUDE_MXNET_CPP_SYMBOL_H_ +#endif // MXNET_CPP_SYMBOL_H_ diff --git a/cpp-package/include/mxnet-cpp/symbol.hpp b/cpp-package/include/mxnet-cpp/symbol.hpp index 40108325d594..ee1a11e26a40 100644 --- a/cpp-package/include/mxnet-cpp/symbol.hpp +++ b/cpp-package/include/mxnet-cpp/symbol.hpp @@ -1,12 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2016 by Contributors * \file symbol.hpp * \brief implementation of the symbol * \author Zhang Chen, Chuntao Hong */ -#ifndef CPP_PACKAGE_INCLUDE_MXNET_CPP_SYMBOL_HPP_ -#define CPP_PACKAGE_INCLUDE_MXNET_CPP_SYMBOL_HPP_ +#ifndef MXNET_CPP_SYMBOL_HPP_ +#define MXNET_CPP_SYMBOL_HPP_ #include #include @@ -38,6 +56,7 @@ inline Symbol Symbol::operator+(const Symbol &rhs) const { return _Plus(*this, r inline Symbol Symbol::operator-(const Symbol &rhs) const { return _Minus(*this, rhs); } inline Symbol Symbol::operator*(const Symbol &rhs) const { return _Mul(*this, rhs); } inline Symbol Symbol::operator/(const Symbol &rhs) const { return _Div(*this, rhs); } +inline Symbol Symbol::operator%(const Symbol &rhs) const { return _Mod(*this, rhs); } inline Symbol Symbol::operator+(mx_float scalar) const { return _PlusScalar(*this, scalar); } @@ -50,6 +69,9 @@ inline Symbol Symbol::operator*(mx_float scalar) const { inline Symbol Symbol::operator/(mx_float scalar) const { return _DivScalar(*this, scalar); } +inline Symbol Symbol::operator%(mx_float scalar) const { + return _ModScalar(*this, scalar); +} inline Symbol Symbol::operator[](int index) { SymbolHandle out; MXSymbolGetOutput(GetHandle(), index, &out); @@ -337,7 +359,10 @@ inline Symbol operator*(mx_float lhs, const Symbol &rhs) { return rhs * lhs; } inline Symbol operator/(mx_float lhs, const Symbol &rhs) { return mxnet::cpp::_RDivScalar(lhs, rhs); } +inline Symbol operator%(mx_float lhs, const Symbol &rhs) { + return mxnet::cpp::_RModScalar(lhs, rhs); +} } // namespace cpp } // namespace mxnet -#endif // CPP_PACKAGE_INCLUDE_MXNET_CPP_SYMBOL_HPP_ +#endif // MXNET_CPP_SYMBOL_HPP_ diff --git a/cpp-package/scripts/OpWrapperGenerator.py b/cpp-package/scripts/OpWrapperGenerator.py index 392e07f9caa4..83495febcc63 100644 --- a/cpp-package/scripts/OpWrapperGenerator.py +++ b/cpp-package/scripts/OpWrapperGenerator.py @@ -1,4 +1,21 @@ -# -*- coding: utf-8 -*- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +# -*- coding: utf-8 -*- # This is a python script that generates operator wrappers such as FullyConnected, # based on current libmxnet.dll. This script is written so that we don't need to # write new operator wrappers when new ones are added to the library. @@ -372,8 +389,8 @@ def ParseAllOps(): "* \\author Chuntao Hong, Xin Li\n" "*/\n" "\n" - "#ifndef CPP_PACKAGE_INCLUDE_MXNET_CPP_OP_H_\n" - "#define CPP_PACKAGE_INCLUDE_MXNET_CPP_OP_H_\n" + "#ifndef MXNET_CPP_OP_H_\n" + "#define MXNET_CPP_OP_H_\n" "\n" "#include \n" "#include \n" @@ -389,7 +406,7 @@ def ParseAllOps(): "%s" "} //namespace cpp\n" "} //namespace mxnet\n" - "#endif // CPP_PACKAGE_INCLUDE_MXNET_CPP_OP_H_\n") + "#endif // MXNET_CPP_OP_H_\n") # Generate a temporary file name tf = tempfile.NamedTemporaryFile() diff --git a/cpp-package/scripts/lint.py b/cpp-package/scripts/lint.py index 89492eda4d82..f9f284ffc005 100644 --- a/cpp-package/scripts/lint.py +++ b/cpp-package/scripts/lint.py @@ -1,4 +1,22 @@ #!/usr/bin/env python + +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # pylint: disable=protected-access, unused-variable, locally-disabled, redefined-variable-type """Lint helper to generate lint summary of source. Copyright by Contributors diff --git a/cpp-package/tests/ci_test.sh b/cpp-package/tests/ci_test.sh index 29d0a9df340a..3b2af35bf1be 100755 --- a/cpp-package/tests/ci_test.sh +++ b/cpp-package/tests/ci_test.sh @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + set -e # exit on the first error cd $(dirname $(readlink -f $0))/../example echo $PWD diff --git a/cpp-package/tests/travis/run_test.sh b/cpp-package/tests/travis/run_test.sh index 27506584f40c..4925b3526bf3 100755 --- a/cpp-package/tests/travis/run_test.sh +++ b/cpp-package/tests/travis/run_test.sh @@ -1,5 +1,23 @@ #!/bin/bash +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + + if [ ${TASK} == "lint" ]; then make lint || exit -1 echo "Check documentations of c++ code..." diff --git a/cpp-package/tests/travis/setup.sh b/cpp-package/tests/travis/setup.sh index 4238c7654fe4..5a3813ee34eb 100755 --- a/cpp-package/tests/travis/setup.sh +++ b/cpp-package/tests/travis/setup.sh @@ -1,5 +1,23 @@ #!/bin/bash +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + + if [ ${TASK} == "lint" ]; then pip install cpplint 'pylint==1.4.4' 'astroid==1.3.6' --user fi diff --git a/cub b/cub index 7ba78ae3ad4d..05eb57faa0a4 160000 --- a/cub +++ b/cub @@ -1 +1 @@ -Subproject commit 7ba78ae3ad4d454fc59a7dd9f548ca94e2bcaf6a +Subproject commit 05eb57faa0a4cac37c2a86fdf4b4dc865a95a1a3 diff --git a/dlpack b/dlpack new file mode 160000 index 000000000000..a6e09b58dc00 --- /dev/null +++ b/dlpack @@ -0,0 +1 @@ +Subproject commit a6e09b58dc00ee0065f5b7879800e646fbb01d1e diff --git a/dmlc-core b/dmlc-core index a6c5701219e6..71bfbd3a9460 160000 --- a/dmlc-core +++ b/dmlc-core @@ -1 +1 @@ -Subproject commit a6c5701219e635fea808d264aefc5b03c3aec314 +Subproject commit 71bfbd3a946075cea66ca9e19bad86dd33c19b46 diff --git a/docker/Dockerfiles/Dockerfile.in.scala b/docker/Dockerfiles/Dockerfile.in.scala index 6898126c7cb2..1fe93652920b 100644 --- a/docker/Dockerfiles/Dockerfile.in.scala +++ b/docker/Dockerfiles/Dockerfile.in.scala @@ -1,3 +1,22 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + # -*- mode: dockerfile -*- # part of the dockerfile to install the scala binding diff --git a/docker/install/cpp.sh b/docker/install/cpp.sh index 91b8b8db0607..1aa55acfc977 100755 --- a/docker/install/cpp.sh +++ b/docker/install/cpp.sh @@ -1,8 +1,26 @@ #!/usr/bin/env bash + +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # libraries for building mxnet c++ core on ubuntu apt-get update && apt-get install -y \ - build-essential git libatlas-base-dev libopencv-dev \ + build-essential git libatlas-base-dev libopencv-dev python-opencv \ libcurl4-openssl-dev libgtest-dev cmake wget unzip cd /usr/src/gtest && cmake CMakeLists.txt && make && cp *.a /usr/lib diff --git a/docker/install/julia.sh b/docker/install/julia.sh index 604a1bc2c234..e6fe49bd5acc 100755 --- a/docker/install/julia.sh +++ b/docker/install/julia.sh @@ -1,4 +1,22 @@ #!/usr/bin/env bash + +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # install libraries for mxnet's julia package on ubuntu # the julia version shipped with ubuntu (version 0.4) is too low. so download a diff --git a/docker/install/perl.sh b/docker/install/perl.sh index da4df67a464a..a981746bc18d 100755 --- a/docker/install/perl.sh +++ b/docker/install/perl.sh @@ -1,4 +1,22 @@ #!/usr/bin/env bash + +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # install libraries for mxnet's perl package on ubuntu apt-get update && apt-get install -y libmouse-perl pdl cpanminus swig libgraphviz-perl cpanm -q Function::Parameters diff --git a/docker/install/python.sh b/docker/install/python.sh index 0459bb9198c4..763f27b8282f 100755 --- a/docker/install/python.sh +++ b/docker/install/python.sh @@ -1,4 +1,22 @@ #!/usr/bin/env bash + +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # install libraries for mxnet's python package on ubuntu apt-get update && apt-get install -y python-dev python3-dev diff --git a/docker/install/r.sh b/docker/install/r.sh index 9351763ddcee..a0fa27359ba5 100755 --- a/docker/install/r.sh +++ b/docker/install/r.sh @@ -1,4 +1,22 @@ #!/usr/bin/env bash + +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # install libraries for mxnet's r package on ubuntu echo "deb http://cran.rstudio.com/bin/linux/ubuntu trusty/" >> /etc/apt/sources.list diff --git a/docker/install/scala.sh b/docker/install/scala.sh index 8cbe91199463..bb0bb9c900d4 100755 --- a/docker/install/scala.sh +++ b/docker/install/scala.sh @@ -1,4 +1,22 @@ #!/usr/bin/env bash + +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # install libraries for mxnet's scala package on ubuntu apt-get install -y maven default-jdk diff --git a/docker/run.sh b/docker/run.sh index b13e13caa5fc..f570f706d9ec 100644 --- a/docker/run.sh +++ b/docker/run.sh @@ -1,4 +1,22 @@ #!/usr/bin/env bash + +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # Build and push all docker containers DEVICES=('cpu' 'gpu') diff --git a/docker/tool.sh b/docker/tool.sh index 222d428fb68b..d8ab9dba0f26 100755 --- a/docker/tool.sh +++ b/docker/tool.sh @@ -1,4 +1,22 @@ #!/usr/bin/env bash + +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # # Script to build, test and push a docker container # diff --git a/docs/Dockerfile b/docs/Dockerfile index bea556ed398a..99bb3d5be492 100644 --- a/docs/Dockerfile +++ b/docs/Dockerfile @@ -5,7 +5,7 @@ MAINTAINER Mu Li # First, build MXNet binaries (ref mxnet/docker/cpu/Dockerfile) # -RUN apt-get update && apt-get install -y build-essential git libopenblas-dev libopencv-dev +RUN apt-get update && apt-get install -y build-essential git libopenblas-dev liblapack-dev libopencv-dev RUN git clone --recursive https://github.com/dmlc/mxnet/ && cd mxnet && \ cp make/config.mk . && \ echo "USE_BLAS=openblas" >>config.mk && \ diff --git a/docs/_static/js/copycode.js b/docs/_static/js/copycode.js index 9abdbeefb0fa..141e797fa2c2 100644 --- a/docs/_static/js/copycode.js +++ b/docs/_static/js/copycode.js @@ -1,8 +1,8 @@ /*Copy code to clipboard*/ -LANG_GP = {'default':'>>> ', 'python':'>>> ' , 'scala':'scala>', 'julia':'julia> ', 'r':'> ', 'perl':'pdl>' , 'cpp':'', 'bash':''}; +LANG_GP = {'default':'>>> ', 'python':'>>> ' , 'scala':'scala>', 'julia':'julia> ', 'r':'> ', 'perl':'pdl>' , 'cpp':'', 'bash':'$ '}; function addBtn() { - copyBtn = '' for (var lang in LANG_GP) { codeBlock = $('div .highlight-' + lang); @@ -24,10 +24,10 @@ function html2clipboard(content) { tmpEl.style.position = "absolute"; tmpEl.style.pointerEvents = "none"; tmpEl.style.zIndex = -1; - + tmpEl.innerHTML = content; document.body.appendChild(tmpEl); - + var range = document.createRange(); range.selectNode(tmpEl); window.getSelection().addRange(range); @@ -43,13 +43,13 @@ $(document).ready(function(){ $(this).attr('title', 'Copy to clipboard').tooltip('fixTitle'); } ); - + clipboard = new Clipboard('.copy-btn', { target: function(trigger) { return trigger.parentNode.querySelector('.highlight'); } }); - + clipboard.on('success', function(e) { //Deal with codes with leading gap var btnClass = e.trigger.classList; @@ -57,14 +57,14 @@ $(document).ready(function(){ var lines = e.text.split('\n'); var hasGap = false; var continueSign = '...'; - + e.clearSelection(); - + for(var i = 0; i < lines.length; ++i) { lines[i] = lines[i].replace(/^\s+|\s+$/g, ""); if(!hasGap && lines[i].startsWith(LANG_GP[lang])) hasGap = true; } - + if(hasGap) { var content = ''; for(var i = 0; i < lines.length; ++i) { @@ -81,10 +81,10 @@ $(document).ready(function(){ .tooltip('fixTitle') .tooltip('show'); }); - + clipboard.on('error', function(e) { $(e.trigger).attr('title', 'Copy failed. Try again.') .tooltip('fixTitle') .tooltip('show'); }); -}); \ No newline at end of file +}); diff --git a/docs/_static/js/navbar.js b/docs/_static/js/navbar.js index 44764efe5ef0..91e0356d9263 100644 --- a/docs/_static/js/navbar.js +++ b/docs/_static/js/navbar.js @@ -3,6 +3,7 @@ var TITLE = ['/get_started/', '/tutorials/', '/how_to/', '/api/', '/architecture var APIsubMenu; $("#burgerMenu").children().each(function () { if($(this).children().first().html() == 'API') APIsubMenu = $(this).clone() + if($(this).children().first().html().startsWith('Versions')) VersionsubMenu = $(this).clone() }); function navbar() { @@ -38,9 +39,12 @@ function navbar() { } $("#plusMenu").empty(); for (var i = 0; i < plusMenuList.length; ++i) { - if(plusMenuList[i].html().length > 20) { + if(plusMenuList[i].attr('id') == 'dropdown-menu-position-anchor') { $("#plusMenu").append(APIsubMenu); } + else if(plusMenuList[i].attr('id') == 'dropdown-menu-position-anchor-version') { + $("#plusMenu").append(VersionsubMenu); + } else { $("#plusMenu").append("
  • "); plusMenuList[i].removeClass("main-nav-link"); @@ -52,6 +56,7 @@ function navbar() { /*Show bottom border of current tab*/ function showTab() { var url = window.location.href; + if(url.indexOf('/get_started/why_mxnet') != -1) return; for(var i = 0; i < TITLE.length; ++i) { if(url.indexOf(TITLE[i]) != -1) { var tab = $($('#main-nav').children().eq(i)); diff --git a/docs/_static/js/sidebar.js b/docs/_static/js/sidebar.js index 2bacf5382c78..42607068e16e 100644 --- a/docs/_static/js/sidebar.js +++ b/docs/_static/js/sidebar.js @@ -19,7 +19,7 @@ function render_left_helper(toc, currentText) { $('.leftsidebar > .sphinxsidebarwrapper').children().remove(); $('.leftsidebar > .sphinxsidebarwrapper').append(lefttoc); - $('.leftsidebar > .sphinxsidebarwrapper').prepend('

    ' + currentText + ' Contents

    '); + $('.leftsidebar > .sphinxsidebarwrapper').prepend('

    Contents

    '); addToggle('.leftsidebar'); $('.leftsidebar li a').click(function () { @@ -42,7 +42,15 @@ function render_lefttoc() { for(var i = 0; i < TITLE_WITH_LANG.length; ++i) { var path = TITLE_WITH_LANG[i]; if (url.indexOf(path) != -1) { - var urlPath = 'http://' + window.location.host + path; + urlElem = url.split('/'); + version = ''; + for (var j = 0; j < urlElem.length; ++j) { + if(urlElem[j] == 'versions') { + version = '/versions/' + urlElem[j + 1]; + break; + } + } + var urlPath = 'https://' + window.location.host + version + path; $.get(urlPath + indexTrailing, null, function(data) { var currentText = $($.parseHTML(data)).find('.leftsidebar > .sphinxsidebarwrapper > ul.current > li.current > a').html(); if (isAPI) { @@ -210,20 +218,25 @@ function keepExpand() { $(document).ready(function () { var url = window.location.href, searchFlag = 'search.html'; - if(url.indexOf('/get_started/') != -1) { - $('body').css("visibility", "visible"); - } - if (url.indexOf(searchFlag) == -1) { - for(var i = 0; i < API_PAGE.length; ++i) { - if (url.indexOf('/api/' + API_PAGE[i]) != -1) { - isAPI = true; - break; + try { + if(url.indexOf('/get_started/') != -1) return; + if (url.indexOf(searchFlag) == -1) { + for(var i = 0; i < API_PAGE.length; ++i) { + if (url.indexOf('/api/' + API_PAGE[i]) != -1) { + isAPI = true; + break; + } } + render_righttoc(); + if ($('.leftsidebar').length) render_lefttoc(); } - render_righttoc(); - if ($('.leftsidebar').length) render_lefttoc(); + + if(url.indexOf('/api/') != -1) return; + $(window).scroll(function () { + scroll_righttoc(); + }); + } + catch(err) { + return; } - $(window).scroll(function () { - scroll_righttoc(); - }); }); \ No newline at end of file diff --git a/docs/_static/mxnet-theme/footer.html b/docs/_static/mxnet-theme/footer.html index f7eec1321724..45ba457a0722 100644 --- a/docs/_static/mxnet-theme/footer.html +++ b/docs/_static/mxnet-theme/footer.html @@ -1,5 +1,5 @@
    diff --git a/docs/_static/mxnet-theme/index.html b/docs/_static/mxnet-theme/index.html index 81277b4e0ceb..e381428758c0 100644 --- a/docs/_static/mxnet-theme/index.html +++ b/docs/_static/mxnet-theme/index.html @@ -9,16 +9,6 @@ - -
    - - - - -
    - @@ -27,35 +17,50 @@
    -
    -

    Flexible

    -

    Supports both imperative and symbolic programming

    -
    -
    -

    Portable

    -

    Runs on CPUs or GPUs, servers, desktops, or mobile phones

    -
    -
    -

    Multiple Languages

    -

    Supports C++, Python, R, Scala, Julia, Perl, Matlab and Javascript - All with the same amazing performance.

    -
    -
    -

    Auto-Differentiation

    -

    Calculates the gradients automatically for training a model

    -
    -
    -

    Distributed on Cloud

    -

    Supports distributed training on multiple CPU/GPU machines, including AWS, - GCE, Azure, and Yarn clusters

    -
    -
    -

    Performance

    -

    Optimized C++ backend engine parallelizes both I/O and computation

    +
    +

    Introducing - Gluon

    +

    We’re happy to introduce a new elegant, easy to use, imperative interface for MXNet. +

    + Learn More +
    +
    +

    MXNet 0.10.0 Released

    +

    We're excited to announce the release of MXNet 0.10.0! Check out the release notes for latest updates.

    + Learn More +
    +
    +

    MXNet Joining Apache

    +

    We’re excited to announce that MXNet has been accepted to the Apache Incubator. +

    + Learn More
    +
    +
    +
    +
    + +

    Examples

    +

    Explore projects from simple demos to state-of-the-art research

    + +
    +
    + +

    Model Zoo

    +

    Off the shelf pre-trained models

    +
    + Model Zoo +
    +
    +
    +
    +
    +
    @@ -122,3 +127,10 @@

    Performance

    + +
    + +

    + Apache MXNet is an effort undergoing incubation at The Apache Software Foundation (ASF), sponsored by the Apache Incubator. Incubation is required of all newly accepted projects until a further review indicates that the infrastructure, communications, and decision making process have stabilized in a manner consistent with other successful ASF projects. While incubation status is not necessarily a reflection of the completeness or stability of the code, it does indicate that the project has yet to be fully endorsed by the ASF. +

    +
    \ No newline at end of file diff --git a/docs/_static/mxnet-theme/navbar.html b/docs/_static/mxnet-theme/navbar.html index e99cb179b1d8..c88fb58bb5c2 100644 --- a/docs/_static/mxnet-theme/navbar.html +++ b/docs/_static/mxnet-theme/navbar.html @@ -74,7 +74,7 @@

    {{searchform('', False)}} diff --git a/docs/_static/mxnet.css b/docs/_static/mxnet.css index b0baf78f5192..db2c5a275c53 100644 --- a/docs/_static/mxnet.css +++ b/docs/_static/mxnet.css @@ -189,7 +189,9 @@ img { text-decoration: none; } -#dropdown-menu-position-anchor { +#dropdown-menu-position-anchor, +#dropdown-menu-position-anchor-version, +#dropdown-menu-position-anchor-version-mobile { position: relative; } @@ -358,7 +360,6 @@ div .burgerIcon a { li.dropdown-submenu ul.dropdown-menu { min-width: 75px; - width: 75px } li.dropdown-submenu ul.dropdown-menu li { @@ -445,7 +446,7 @@ li.dropdown-submenu ul.dropdown-menu a { } /*--------------------------banner---------------------------*/ #splash{ - padding:60px 0 0 0; + padding:60px 0 50px 0; background-color:#0079b2; /* background-image:url(../img/bg.jpg); */ background-size:cover; @@ -499,9 +500,8 @@ li.dropdown-submenu ul.dropdown-menu a { margin:2em 0 } -#why_mxnet_btn, #install_btn { +#why_mxnet_btn, #install_btn, #release_btn { border: 1.8px solid #FFFFFF; - border-radius: 2px; color: #FFFFFF; display: inline-block; font-size: 18px; @@ -513,6 +513,11 @@ li.dropdown-submenu ul.dropdown-menu a { border-radius: 30px; } +#release_btn { + width: auto; + border: none; +} + @media (min-width: 768px) { #why_mxnet_btn { margin-right: -45%; @@ -560,27 +565,126 @@ li.dropdown-submenu ul.dropdown-menu a { .section-tout { padding:3em 0 3em; border-bottom:1px solid rgba(0,0,0,.05); - background-color:#eaf1f1 + background-color:#fff +} + +.section-tout .container { + height: 200px; +} + +.section-tout .row { + height: 100%; } .section-tout .row div { - height: 140px; + height: 100%; + padding-left: 50px; } -.section-tout .fa{ - margin-right:.5em +.section-tout .row a { + position: absolute; + bottom: 20px; } .section-tout h3{ font-size:20px; - color: #0079b2; + color: #444; } .section-tout p { - margin-bottom:2em + margin-bottom:2em; } -.section-inst{ +@media (max-width: 1199px) { + .section-tout .container { + height: auto; + } + + .section-tout .row a { + position: inherit; + } + + .section-tout .row div { + margin-bottom: 20px; + padding-left: 20px; + } +} + +.section-util { + background-color: #eaf1f1; + padding:3em 0 3em; + border-bottom:1px solid rgba(0,0,0,.05); + text-align: center; +} + +.section-util p { + color: #999; + position: absolute; + width: 50%; + margin: auto; + left: 0; + right: 0; +} + +.section-util .util-btn { + position: absolute; + margin: auto; + left: 0; + right: 0; + padding-top: 10px; + margin-top: 60px; +} + +@media (max-width: 600px) { + .section-util .util-btn { + margin-top: 100px; + } +} + +.section-util .glyphicon { + font-size: 50px; + color: #999; +} + +.util-btn a { + display: inline-block; + border: 1.8px solid #0079b2; + border-radius: 30px; + width: 200px; + height: 50px; + -webkit-transition: .2s; + transition: .2s; + padding: 10px 30px; +} + +.util-btn a:hover { + background-color: #0079b2; + color: #FFFFFF; + opacity: 0.9; + text-decoration: none; +} + +.section-util .container { + height: 250px; +} + +@media (max-width: 1199px) { + .section-util .container { + height: auto + } + + .section-util .row div { + margin-bottom: 200px; + } +} + +@media (max-width: 767px) { + .section-util .row div { + margin-bottom: 250px; + } +} + +.section-inst { padding:3em 0 3em; border-bottom:1px solid rgba(0,0,0,.05); @@ -613,6 +717,14 @@ li.dropdown-submenu ul.dropdown-menu a { filter: grayscale(0%); } +.section-disclaimer { + padding: 3em 3em 3em; +} + +.section-disclaimer p { + padding-top: 2em; +} + .footer{ padding-top: 40px; } @@ -639,6 +751,7 @@ div.content { right: 200px; margin-right: 5%; padding: 40px 0 0 0; + overflow-x: hidden; z-index: -1; } @@ -817,28 +930,54 @@ div.informaltable { text-align: left; } /*----------------API class and function formatting---------------------*/ -dl > dt:before { - content: " "; - display: block; - height: 70px; /* fixed header height*/ - margin-top: -50px; /* negative fixed header height */ -} - p.rubric { margin-top: 10px; } +dl { + padding-top: 20px; +} + dt:target, .highlighted { - background-color: #fff; - background: transparent; + background-color: #e7f2fa; border-bottom: 3px solid #c7254e; margin-bottom: -3px; } +dt:target:before { + background-color: white; + content: ''; + display: block; + height: 60px; +} + +dt { + background: #e7f2fa; + border-bottom: solid #0079b2; +} + +dl.method dt { + background: #f0f0f0; + border-bottom: solid #ccc; +} + +dl.method dt code.descname { + color:#555; +} + +dl.attribute dt { + background: #f0f0f0; + border-bottom: solid #ccc; +} + +dl.attribute dt code.descname { + color:#555; +} + dt em { font-weight: normal; font-style: normal; - font-size: 90%; + font-size: 90%; } code { @@ -846,6 +985,19 @@ code { background-color: #f5f5f5; } +dt code { + color: #555; +} + +dl.last.docutils dt{ + background-color: transparent; + border-bottom: none; +} + +dl.docutils dt { + color: #555; +} + /*----------------Model zoo page style------------------*/ #mxnet-model-zoo table, #mxnet-model-zoo td, #mxnet-model-zoo th { border: 1px solid lightgray; @@ -1062,3 +1214,47 @@ table.docutils tr:nth-child(even) { transform: translate(-50%); } +button.download { + color: #0079b2; +} + +/*----------------------Download button------------------------*/ +div.download_btn { + + border: solid 1px lightgray; + border-radius: 3px; + font-size: 90%; + height: 40px; + display: table; + float: left; +} + +div.download_btn a { + padding: 0 10px; + display: table-cell; + vertical-align: middle; +} + +div.download_btn a:hover { + background-color: #0079b2; + color: white; + text-decoration: none; +} + +/*-------------output blocks----------------*/ + +.highlight-results .highlight pre { + background-color: #eeffcc; +} + +.cell-results-header { + color: #888; + padding-bottom: 3px; + font-style: italic; +} + +/*------------Download source-----------------*/ +#download-source-package { + display: none; + padding-top: 40px; +} diff --git a/docs/_static/searchtools_custom.js b/docs/_static/searchtools_custom.js index b2a17af4bcc2..c6fd37f72233 100644 --- a/docs/_static/searchtools_custom.js +++ b/docs/_static/searchtools_custom.js @@ -492,7 +492,7 @@ var Search = { displayNextItem(); }); } else if (DOCUMENTATION_OPTIONS.HAS_SOURCE) { - $.ajax({url: DOCUMENTATION_OPTIONS.URL_ROOT + '_sources/' + item[0] + '.md.txt', + $.ajax({url: DOCUMENTATION_OPTIONS.URL_ROOT + '_sources/' + item[0] + '.txt', dataType: "text", complete: function(jqxhr, textstatus) { var data = jqxhr.responseText; diff --git a/docs/api/python/autograd.md b/docs/api/python/autograd.md new file mode 100644 index 000000000000..de8188446b7c --- /dev/null +++ b/docs/api/python/autograd.md @@ -0,0 +1,101 @@ +# Autograd Package + + +```eval_rst +.. currentmodule:: mxnet.autograd +``` + +```eval_rst +.. warning:: This package is currently experimental and may change in the near future. +``` + +## Overview + +The `autograd` package enables automatic +differentiation of NDArray operations. +In machine learning applications, +`autograd` is often used to calculate the gradients +of loss functions with respect to parameters. + + +### Record vs Pause + +`autograd` records computation history on the fly to calculate gradients later. +This is only enabled inside a `with autograd.record():` block. +A `with auto_grad.pause()` block can be used inside a `record()` block +to temporarily disable recording. + +To compute gradient with respect to an `NDArray` `x`, first call `x.attach_grad()` +to allocate space for the gradient. Then, start a `with autograd.record()` block, +and do some computation. Finally, call `backward()` on the result: + +```python +>>> x = mx.nd.array([1,2,3,4]) +>>> x.attach_grad() +>>> with mx.autograd.record(): +... y = x * x + 1 +>>> y.backward() +>>> print(x.grad) +[ 2. 4. 6. 8.] + +``` + + +## Train mode and Predict Mode + +Some operators (Dropout, BatchNorm, etc) behave differently in +when training and when making predictions. +This can be controlled with `train_mode` and `predict_mode` scope. + +By default, MXNet is in `predict_mode`. +A `with autograd.record()` block by default turns on `train_mode` +(equivalent to ``with autograd.record(train_mode=True)``). +To compute a gradient in prediction mode (as when generating adversarial examples), +call record with `train_mode=False` and then call `backward(train_mode=False)` + +Although training usually coincides with recording, +this isn't always the case. +To control *training* vs *predict_mode* without changing +*recording* vs *not recording*, +Use a `with autograd.train_mode():` +or `with autograd.predict_mode():` block. + +Detailed tutorials are available in Part 1 of +[the MXNet gluon book](http://gluon.mxnet.io/). + + + + + + + + +## Autograd + +```eval_rst +.. autosummary:: + :nosignatures: + + record + pause + train_mode + predict_mode + backward + set_training + is_training + set_recording + is_recording + mark_variables + Function +``` + +## API Reference + + + +```eval_rst +.. automodule:: mxnet.autograd + :members: +``` + + diff --git a/docs/api/python/gluon.md b/docs/api/python/gluon.md new file mode 100644 index 000000000000..ac637749f856 --- /dev/null +++ b/docs/api/python/gluon.md @@ -0,0 +1,565 @@ +# Gluon Package + + +```eval_rst +.. currentmodule:: mxnet.gluon +``` + +```eval_rst +.. warning:: This package is currently experimental and may change in the near future. +``` + + + +## Overview + +Gluon package is a high-level interface for MXNet designed to be easy to use while +keeping most of the flexibility of low level API. Gluon supports both imperative +and symbolic programming, making it easy to train complex models imperatively +in Python and then deploy with symbolic graph in C++ and Scala. + +## Parameter + +```eval_rst +.. autosummary:: + :nosignatures: + + Parameter + ParameterDict +``` + + +## Containers + +```eval_rst +.. autosummary:: + :nosignatures: + + Block + HybridBlock + SymbolBlock +``` + +## Neural Network Layers + +```eval_rst +.. currentmodule:: mxnet.gluon.nn +``` + +### Containers + +```eval_rst +.. autosummary:: + :nosignatures: + + Sequential + HybridSequential +``` + + +### Basic Layers + + +```eval_rst +.. autosummary:: + :nosignatures: + + Dense + Activation + Dropout + BatchNorm + LeakyReLU + Embedding +``` + + +### Convolutional Layers + + +```eval_rst +.. autosummary:: + :nosignatures: + + Conv1D + Conv2D + Conv3D + Conv1DTranspose + Conv2DTranspose + Conv3DTranspose +``` + + + +### Pooling Layers + + +```eval_rst +.. autosummary:: + :nosignatures: + + MaxPool1D + MaxPool2D + MaxPool3D + AvgPool1D + AvgPool2D + AvgPool3D + GlobalMaxPool1D + GlobalMaxPool2D + GlobalMaxPool3D + GlobalAvgPool1D + GlobalAvgPool2D + GlobalAvgPool3D +``` + + + +## Recurrent Layers + +```eval_rst +.. currentmodule:: mxnet.gluon.rnn +``` + + +```eval_rst +.. autosummary:: + :nosignatures: + + RecurrentCell + RNN + LSTM + GRU + RNNCell + LSTMCell + GRUCell + SequentialRNNCell + BidirectionalCell + DropoutCell + ZoneoutCell + ResidualCell +``` + + +## Trainer + +```eval_rst +.. currentmodule:: mxnet.gluon + +.. autosummary:: + :nosignatures: + + Trainer +``` + + +## Loss functions + +```eval_rst +.. currentmodule:: mxnet.gluon.loss +``` + + +```eval_rst +.. autosummary:: + :nosignatures: + + L2Loss + L1Loss + SoftmaxCrossEntropyLoss + KLDivLoss +``` + +## Utilities + +```eval_rst +.. currentmodule:: mxnet.gluon.utils +``` + + +```eval_rst +.. autosummary:: + :nosignatures: + + split_data + split_and_load + clip_global_norm +``` + +## Data + +```eval_rst +.. currentmodule:: mxnet.gluon.data +``` + +```eval_rst +.. autosummary:: + :nosignatures: + + Dataset + ArrayDataset + RecordFileDataset + ImageRecordDataset +``` + +```eval_rst +.. autosummary:: + :nosignatures: + + Sampler + SequentialSampler + RandomSampler + BatchSampler +``` + +```eval_rst +.. autosummary:: + :nosignatures: + + DataLoader +``` + +### Vision + +```eval_rst +.. currentmodule:: mxnet.gluon.data.vision +``` + +```eval_rst +.. autosummary:: + :nosignatures: + + MNIST + CIFAR10 +``` + +## Model Zoo + +Model zoo provides pre-defined and pre-trained models to help bootstrap machine learning applications. + +### Vision + +```eval_rst +.. currentmodule:: mxnet.gluon.model_zoo.vision +.. automodule:: mxnet.gluon.model_zoo.vision +``` + +```eval_rst +.. autosummary:: + :nosignatures: + + get_model +``` + +#### ResNet + +```eval_rst +.. autosummary:: + :nosignatures: + + resnet18_v1 + resnet34_v1 + resnet50_v1 + resnet101_v1 + resnet152_v1 + resnet18_v2 + resnet34_v2 + resnet50_v2 + resnet101_v2 + resnet152_v2 +``` + +```eval_rst +.. autosummary:: + :nosignatures: + + ResNetV1 + ResNetV2 + BasicBlockV1 + BasicBlockV2 + BottleneckV1 + BottleneckV2 + get_resnet +``` + +#### VGG + +```eval_rst +.. autosummary:: + :nosignatures: + + vgg11 + vgg13 + vgg16 + vgg19 + vgg11_bn + vgg13_bn + vgg16_bn + vgg19_bn +``` + +```eval_rst +.. autosummary:: + :nosignatures: + + VGG + get_vgg +``` + +#### Alexnet + +```eval_rst +.. autosummary:: + :nosignatures: + + alexnet +``` + +```eval_rst +.. autosummary:: + :nosignatures: + + AlexNet +``` + +#### DenseNet + +```eval_rst +.. autosummary:: + :nosignatures: + + densenet121 + densenet161 + densenet169 + densenet201 +``` + +```eval_rst +.. autosummary:: + :nosignatures: + + DenseNet +``` + +#### SqueezeNet + +```eval_rst +.. autosummary:: + :nosignatures: + + squeezenet1_0 + squeezenet1_1 +``` + +```eval_rst +.. autosummary:: + :nosignatures: + + SqueezeNet +``` + +#### Inception + +```eval_rst +.. autosummary:: + :nosignatures: + + inception_v3 +``` + +```eval_rst +.. autosummary:: + :nosignatures: + + Inception3 +``` + +## API Reference + + + +```eval_rst +.. autoclass:: mxnet.gluon.Parameter + :members: +.. autoclass:: mxnet.gluon.ParameterDict + :members: + +.. autoclass:: mxnet.gluon.Block + :members: + + .. automethod:: __call__ +.. autoclass:: mxnet.gluon.HybridBlock + :members: +.. autoclass:: mxnet.gluon.SymbolBlock + :members: + +.. autoclass:: mxnet.gluon.nn.Sequential + :members: +.. autoclass:: mxnet.gluon.nn.HybridSequential + :members: +.. autoclass:: mxnet.gluon.nn.Dense + :members: +.. autoclass:: mxnet.gluon.nn.Activation + :members: +.. autoclass:: mxnet.gluon.nn.Dropout + :members: +.. autoclass:: mxnet.gluon.nn.BatchNorm + :members: +.. autoclass:: mxnet.gluon.nn.LeakyReLU + :members: +.. autoclass:: mxnet.gluon.nn.Embedding + :members: +.. autoclass:: mxnet.gluon.nn.Conv1D + :members: +.. autoclass:: mxnet.gluon.nn.Conv2D + :members: +.. autoclass:: mxnet.gluon.nn.Conv3D + :members: +.. autoclass:: mxnet.gluon.nn.Conv1DTranspose + :members: +.. autoclass:: mxnet.gluon.nn.Conv2DTranspose + :members: +.. autoclass:: mxnet.gluon.nn.Conv3DTranspose + :members: +.. autoclass:: mxnet.gluon.nn.MaxPool1D + :members: +.. autoclass:: mxnet.gluon.nn.MaxPool2D + :members: +.. autoclass:: mxnet.gluon.nn.MaxPool3D + :members: +.. autoclass:: mxnet.gluon.nn.AvgPool1D + :members: +.. autoclass:: mxnet.gluon.nn.AvgPool2D + :members: +.. autoclass:: mxnet.gluon.nn.AvgPool3D + :members: +.. autoclass:: mxnet.gluon.nn.GlobalMaxPool1D + :members: +.. autoclass:: mxnet.gluon.nn.GlobalMaxPool2D + :members: +.. autoclass:: mxnet.gluon.nn.GlobalMaxPool3D + :members: +.. autoclass:: mxnet.gluon.nn.GlobalAvgPool1D + :members: +.. autoclass:: mxnet.gluon.nn.GlobalAvgPool2D + :members: +.. autoclass:: mxnet.gluon.nn.GlobalAvgPool3D + :members: + +.. autoclass:: mxnet.gluon.rnn.RecurrentCell + :members: + + .. automethod:: __call__ +.. autoclass:: mxnet.gluon.rnn.RNN + :members: +.. autoclass:: mxnet.gluon.rnn.LSTM + :members: +.. autoclass:: mxnet.gluon.rnn.GRU + :members: +.. autoclass:: mxnet.gluon.rnn.RNNCell + :members: +.. autoclass:: mxnet.gluon.rnn.LSTMCell + :members: +.. autoclass:: mxnet.gluon.rnn.GRUCell + :members: +.. autoclass:: mxnet.gluon.rnn.SequentialRNNCell + :members: +.. autoclass:: mxnet.gluon.rnn.BidirectionalCell + :members: +.. autoclass:: mxnet.gluon.rnn.DropoutCell + :members: +.. autoclass:: mxnet.gluon.rnn.ZoneoutCell + :members: +.. autoclass:: mxnet.gluon.rnn.ResidualCell + :members: + +.. autoclass:: mxnet.gluon.Trainer + :members: + +.. autoclass:: mxnet.gluon.loss.L2Loss + :members: +.. autoclass:: mxnet.gluon.loss.L1Loss + :members: +.. autoclass:: mxnet.gluon.loss.SoftmaxCrossEntropyLoss + :members: +.. autoclass:: mxnet.gluon.loss.KLDivLoss + :members: +.. automethod:: mxnet.gluon.utils.split_data + +.. automethod:: mxnet.gluon.utils.split_and_load + +.. automethod:: mxnet.gluon.utils.clip_global_norm + +.. autoclass:: mxnet.gluon.data.Dataset + :members: +.. autoclass:: mxnet.gluon.data.ArrayDataset + :members: +.. autoclass:: mxnet.gluon.data.RecordFileDataset + :members: +.. autoclass:: mxnet.gluon.data.ImageRecordDataset + :members: +.. autoclass:: mxnet.gluon.data.Sampler + :members: +.. autoclass:: mxnet.gluon.data.SequentialSampler + :members: +.. autoclass:: mxnet.gluon.data.RandomSampler + :members: +.. autoclass:: mxnet.gluon.data.BatchSampler + :members: +.. autoclass:: mxnet.gluon.data.DataLoader + :members: +.. automodule:: mxnet.gluon.data.vision + :members: + +.. automethod:: mxnet.gluon.model_zoo.vision.get_model +.. automethod:: mxnet.gluon.model_zoo.vision.resnet18_v1 +.. automethod:: mxnet.gluon.model_zoo.vision.resnet34_v1 +.. automethod:: mxnet.gluon.model_zoo.vision.resnet50_v1 +.. automethod:: mxnet.gluon.model_zoo.vision.resnet101_v1 +.. automethod:: mxnet.gluon.model_zoo.vision.resnet152_v1 +.. automethod:: mxnet.gluon.model_zoo.vision.resnet18_v2 +.. automethod:: mxnet.gluon.model_zoo.vision.resnet34_v2 +.. automethod:: mxnet.gluon.model_zoo.vision.resnet50_v2 +.. automethod:: mxnet.gluon.model_zoo.vision.resnet101_v2 +.. automethod:: mxnet.gluon.model_zoo.vision.resnet152_v2 +.. automethod:: mxnet.gluon.model_zoo.vision.get_resnet +.. autoclass:: mxnet.gluon.model_zoo.vision.ResNetV1 + :members: +.. autoclass:: mxnet.gluon.model_zoo.vision.BasicBlockV1 + :members: +.. autoclass:: mxnet.gluon.model_zoo.vision.BottleneckV1 + :members: +.. autoclass:: mxnet.gluon.model_zoo.vision.ResNetV2 + :members: +.. autoclass:: mxnet.gluon.model_zoo.vision.BasicBlockV2 + :members: +.. autoclass:: mxnet.gluon.model_zoo.vision.BottleneckV2 + :members: +.. automethod:: mxnet.gluon.model_zoo.vision.vgg11 +.. automethod:: mxnet.gluon.model_zoo.vision.vgg13 +.. automethod:: mxnet.gluon.model_zoo.vision.vgg16 +.. automethod:: mxnet.gluon.model_zoo.vision.vgg19 +.. automethod:: mxnet.gluon.model_zoo.vision.vgg11_bn +.. automethod:: mxnet.gluon.model_zoo.vision.vgg13_bn +.. automethod:: mxnet.gluon.model_zoo.vision.vgg16_bn +.. automethod:: mxnet.gluon.model_zoo.vision.vgg19_bn +.. automethod:: mxnet.gluon.model_zoo.vision.get_vgg +.. autoclass:: mxnet.gluon.model_zoo.vision.VGG + :members: +.. automethod:: mxnet.gluon.model_zoo.vision.alexnet +.. autoclass:: mxnet.gluon.model_zoo.vision.AlexNet + :members: +.. automethod:: mxnet.gluon.model_zoo.vision.densenet121 +.. automethod:: mxnet.gluon.model_zoo.vision.densenet161 +.. automethod:: mxnet.gluon.model_zoo.vision.densenet169 +.. automethod:: mxnet.gluon.model_zoo.vision.densenet201 +.. autoclass:: mxnet.gluon.model_zoo.vision.DenseNet + :members: +.. automethod:: mxnet.gluon.model_zoo.vision.squeezenet1_0 +.. automethod:: mxnet.gluon.model_zoo.vision.squeezenet1_1 +.. autoclass:: mxnet.gluon.model_zoo.vision.SqueezeNet + :members: +.. automethod:: mxnet.gluon.model_zoo.vision.inception_v3 +.. autoclass:: mxnet.gluon.model_zoo.vision.Inception3 + :members: +``` + + diff --git a/docs/api/python/image.md b/docs/api/python/image.md new file mode 100644 index 000000000000..23b5ee3d1415 --- /dev/null +++ b/docs/api/python/image.md @@ -0,0 +1,206 @@ +# Image API + +## Overview +This document summarizes supporting functions and iterators to read and process +images provided in +```eval_rst +.. autosummary:: + :nosignatures: + + mxnet.image +``` + +## Image processing functions +```eval_rst +.. currentmodule:: mxnet +.. autosummary:: + :nosignatures: + + image.imdecode + image.scale_down + image.resize_short + image.fixed_crop + image.random_crop + image.center_crop + image.color_normalize + image.random_size_crop +``` + +## Image iterators +Iterators support loading image from binary `Record IO` and raw image files. +```eval_rst +.. autosummary:: + :nosignatures: + + image.ImageIter +``` +```python +>>> data_iter = mx.image.ImageIter(batch_size=4, data_shape=(3, 224, 224), label_width=1, + path_imglist='data/custom.lst') +>>> data_iter.reset() +>>> for data in data_iter: +... d = data.data[0] +... print(d.shape) +>>> # we can apply lots of augmentations as well +>>> data_iter = mx.image.ImageIter(4, (3, 224, 224), path_imglist='data/custom.lst', + rand_crop=resize=True, rand_mirror=True, mean=True, + brightness=0.1, contrast=0.1, saturation=0.1, hue=0.1, + pca_noise=0.1, rand_gray=0.05) +>>> data = data_iter.next() +>>> # specify augmenters manually is also supported +>>> data_iter = mx.image.ImageIter(32, (3, 224, 224), path_rec='data/caltech.rec', + path_imgidx='data/caltech.idx', shuffle=True, + aug_list=[mx.image.HorizontalFlipAug(0.5), + mx.image.ColorJitterAug(0.1, 0.1, 0.1)]) +``` + +We use helper function to initialize augmenters +```eval_rst + .. currentmodule:: mxnet +.. autosummary:: + :nosignatures: + + image.CreateAugmenter +``` + +A list of supporting augmenters +```eval_rst +.. autosummary:: + :nosignatures: + + image.Augmenter + image.ResizeAug + image.ForceResizeAug + image.RandomCropAug + image.RandomSizedCropAug + image.CenterCropAug + image.RandomOrderAug + image.BrightnessJitterAug + image.ContrastJitterAug + image.SaturationJitterAug + image.HueJitterAug + image.ColorJitterAug + image.LightingAug + image.ColorNormalizeAug + image.RandomGrayAug + image.HorizontalFlipAug + image.CastAug +``` + +Similar to `ImageIter`, `ImageDetIter` is designed for `Object Detection` tasks. +```eval_rst +.. autosummary:: + :nosignatures: + + image.ImageDetIter +``` + +```python +>>> data_iter = mx.image.ImageDetIter(batch_size=4, data_shape=(3, 224, 224), + path_imglist='data/train.lst') +>>> data_iter.reset() +>>> for data in data_iter: +... d = data.data[0] +... l = data.label[0] +... print(d.shape) +... print(l.shape) +``` + +Unlike object classification with fixed label_width, object count may vary from +image to image. Thus we have special format for object detection labels. +Usually the `lst` file generated by `tools/im2rec.py` is a list of +``` +index_0 label_0 image_path_0 +index_1 label_1 image_path_1 +``` +Where `label_N` is a number a of fixed-width vector. +The format of label used in object detection is a variable length vector +``` +A B [header] [(object0), (object1), ... (objectN)] +``` +Where A is the width of header, B is the width of each object. +Header is optional and used for inserting helper information such as (width, height). +Each object is usually 5 or 6 numbers describing the object properties, for example: +[id, xmin, ymin, xmax, ymax, difficulty] +Putting all together, we have a `lst` file for object detection: +``` +0 2 5 640 480 1 0.1 0.2 0.8 0.9 2 0.5 0.3 0.6 0.8 data/xxx.jpg +1 2 5 480 640 3 0.05 0.16 0.75 0.9 data/xxx.jpg +2 2 5 500 600 2 0.6 0.1 0.7 0.5 0 0.1 0.3 0.2 0.4 3 0.25 0.25 0.3 0.3 data/xxx.jpg +... +``` + +A helper function to initialize Augmenters for `Object detection` task +```eval_rst +.. autosummary:: + :nosignatures: + + image.CreateDetAugmenter + ``` + +Since `Detection` task is sensitive to object localization, any modification +to image that introduced localization shift will require correction to label, +and a list of augmenters specific for `Object detection` is provided +```eval_rst +.. autosummary:: + :nosignatures: + + image.DetBorrowAug + image.DetRandomSelectAug + image.DetHorizontalFlipAug + image.DetRandomCropAug + image.DetRandomPadAug +``` + +## API Reference + + + +```eval_rst +.. automodule:: mxnet.image +.. autoclass:: mxnet.image.ImageIter + :members: + +.. automethod:: mxnet.image.imdecode +.. automethod:: mxnet.image.scale_down +.. automethod:: mxnet.image.resize_short +.. automethod:: mxnet.image.fixed_crop +.. automethod:: mxnet.image.random_crop +.. automethod:: mxnet.image.center_crop +.. automethod:: mxnet.image.color_normalize +.. automethod:: mxnet.image.random_size_crop + +.. autoclass:: mxnet.image.Augmenter + :members: +.. autoclass:: mxnet.image.ResizeAug +.. autoclass:: mxnet.image.ForceResizeAug +.. autoclass:: mxnet.image.RandomCropAug +.. autoclass:: mxnet.image.RandomSizedCropAug +.. autoclass:: mxnet.image.CenterCropAug +.. autoclass:: mxnet.image.RandomOrderAug +.. autoclass:: mxnet.image.BrightnessJitterAug +.. autoclass:: mxnet.image.ContrastJitterAug +.. autoclass:: mxnet.image.SaturationJitterAug +.. autoclass:: mxnet.image.HueJitterAug +.. autoclass:: mxnet.image.ColorJitterAug +.. autoclass:: mxnet.image.LightingAug +.. autoclass:: mxnet.image.ColorNormalizeAug +.. autoclass:: mxnet.image.RandomGrayAug +.. autoclass:: mxnet.image.HorizontalFlipAug +.. autoclass:: mxnet.image.CastAug + +.. automethod:: mxnet.image.CreateAugmenter + +.. autoclass:: mxnet.image.ImageDetIter + :members: +.. autoclass:: mxnet.image.DetAugmenter + :members: +.. autoclass:: mxnet.image.DetBorrowAug +.. autoclass:: mxnet.image.DetRandomSelectAug +.. autoclass:: mxnet.image.DetHorizontalFlipAug +.. autoclass:: mxnet.image.DetRandomCropAug +.. autoclass:: mxnet.image.DetRandomPadAug + +.. automethod:: mxnet.image.CreateDetAugmenter +``` + diff --git a/docs/api/python/index.md b/docs/api/python/index.md index 2a0493b34caf..964ccde0145a 100644 --- a/docs/api/python/index.md +++ b/docs/api/python/index.md @@ -1,7 +1,7 @@ # MXNet - Python API MXNet provides a rich Python API to serve a broad community of Python developers. -In this section, we provide a in-depth discussion of the functionality provided by +In this section, we provide an in-depth discussion of the functionality provided by various MXNet Python packages. We have included code samples for most of the APIs for improved clarity. These code samples will run as-is as long as MXNet is first imported by running: @@ -28,8 +28,13 @@ imported by running: ndarray symbol module + autograd + gluon + rnn kvstore io + image optimization callback + metric ``` diff --git a/docs/api/python/io.md b/docs/api/python/io.md index e408ef8a6727..15f8aa3ce354 100644 --- a/docs/api/python/io.md +++ b/docs/api/python/io.md @@ -62,6 +62,7 @@ A detailed tutorial is available at recordio.MXRecordIO recordio.MXIndexedRecordIO image.ImageIter + image.ImageDetIter ``` ## Helper classes and functions @@ -81,33 +82,6 @@ Data structures and other iterators provided in the ``mxnet.io`` packages. io.MXDataIter ``` -A list of image modification functions provided by ``mxnet.image``. - -```eval_rst -.. autosummary:: - :nosignatures: - - image.imdecode - image.scale_down - image.resize_short - image.fixed_crop - image.random_crop - image.center_crop - image.color_normalize - image.random_size_crop - image.ResizeAug - image.RandomCropAug - image.RandomSizedCropAug - image.CenterCropAug - image.RandomOrderAug - image.ColorJitterAug - image.LightingAug - image.ColorNormalizeAug - image.HorizontalFlipAug - image.CastAug - image.CreateAugmenter -``` - Functions to read and write RecordIO files. ```eval_rst @@ -123,7 +97,7 @@ Functions to read and write RecordIO files. ## Develop a new iterator Writing a new data iterator in Python is straightforward. Most MXNet -training/inference programs accept an iteratable object with ``provide_data`` +training/inference programs accept an iterable object with ``provide_data`` and ``provide_label`` properties. This [tutorial](http://mxnet.io/tutorials/basic/data.html) explains how to write an iterator from scratch. @@ -158,6 +132,20 @@ Parsing and performing another pre-processing such as augmentation may be expens If performance is critical, we can implement a data iterator in C++. Refer to [src/io](https://github.com/dmlc/mxnet/tree/master/src/io) for examples. +### Change batch layout + +By default, the backend engine treats the first dimension of each data and label variable in data +iterators as the batch size (i.e. `NCHW` or `NT` layout). In order to override the axis for batch size, +the `provide_data` (and `provide_label` if there is label) properties should include the layouts. This +is especially useful in RNN since `TNC` layouts are often more efficient. For example: + +```python +@property +def provide_data(self): + return [DataDesc(name='seq_var', shape=(seq_length, batch_size), layout='TN')] +``` +The backend engine will recognize the index of `N` in the `layout` as the axis for batch size. + ## API Reference @@ -165,8 +153,6 @@ If performance is critical, we can implement a data iterator in C++. Refer to ```eval_rst .. automodule:: mxnet.io :members: -.. automodule:: mxnet.image - :members: .. automodule:: mxnet.recordio :members: ``` diff --git a/docs/api/python/metric.md b/docs/api/python/metric.md new file mode 100644 index 000000000000..50a4a9be4550 --- /dev/null +++ b/docs/api/python/metric.md @@ -0,0 +1,28 @@ +# Evaluation Metric API + +```eval_rst +.. currentmodule:: mxnet.metric +``` + +## Overview + +This document lists all the evaluation metrics available to evaluate +the performance of a learned model. + +```eval_rst +.. autosummary:: + :nosignatures: + + mxnet.metric +``` + +## API Reference + + + +```eval_rst +.. automodule:: mxnet.metric + :members: +``` + + diff --git a/docs/api/python/model.md b/docs/api/python/model.md index 270557aa9b10..964095883910 100644 --- a/docs/api/python/model.md +++ b/docs/api/python/model.md @@ -10,7 +10,7 @@ Topics: * [Save the Model](#save-the-model) * [Periodic Checkpoint](#periodic-checkpointing) * [Initializer API Reference](#initializer-api-reference) -* [Evaluation Metric API Reference](#initializer-api-reference) +* [Evaluation Metric API Reference](#evaluation-metric-api-reference) * [Optimizer API Reference](#optimizer-api-reference) * [Model API Reference](#model-api-reference) diff --git a/docs/api/python/ndarray.md b/docs/api/python/ndarray.md index 9f04c8b8a046..5e9f7e1a1184 100644 --- a/docs/api/python/ndarray.md +++ b/docs/api/python/ndarray.md @@ -6,7 +6,7 @@ ## Overview -This document lists the routines of the *n*-dimensional array package +This document lists the routines of the *n*-dimensional array package: ```eval_rst .. autosummary:: @@ -17,7 +17,7 @@ This document lists the routines of the *n*-dimensional array package The `NDArray` API, defined in the `ndarray` (or simply `nd`) package, provides imperative tensor operations on CPU/GPU. -A `NDArray` represents a multidimensional, fixed-size homogenous array. +An `NDArray` represents a multi-dimensional, fixed-size homogenous array. ```python >>> x = mx.nd.array([[1, 2, 3], [4, 5, 6]]) @@ -36,29 +36,31 @@ A `NDArray` represents a multidimensional, fixed-size homogenous array. A detailed tutorial is available at [NDArray - Imperative tensor operations on CPU/GPU](http://mxnet.io/tutorials/basic/ndarray.html). +

    ```eval_rst -.. note:: ``mxnet.ndarray`` is similar to ``numpy.ndarray`` in some aspects. But the difference is not negligible. For example +.. note:: ``mxnet.ndarray`` is similar to ``numpy.ndarray`` in some aspects. But the differences are not negligible. For instance: - - ``NDArray.T`` does real data transpose to return new a copied array, instead - of returning a view of the input array. - - ``ndarray.dot`` performs dot between the last axis of the first input array - and the first axis of the second input, while `numpy.dot` uses the second - last axis of the input array. + - ``mxnet.ndarray.NDArray.T`` does real data transpose to return new a copied + array, instead of returning a view of the input array. + - ``mxnet.ndarray.dot`` performs dot product between the last axis of the + first input array and the first axis of the second input, while `numpy.dot` + uses the second last axis of the input array. - In additional, ``NDArray`` supports GPU computation and various neural + In addition, ``mxnet.ndarray.NDArray`` supports GPU computation and various neural network layers. -.. note:: ``ndarray`` also provides almost same routines to ``symbol``. Most - routines between these two packages share the same C++ operator source - codes. But ``ndarray`` differs to ``symbol`` in several aspects: +.. note:: ``ndarray`` provides almost the same routines as ``symbol``. Most + routines between these two packages share the source code. But ``ndarray`` + differs from ``symbol`` in few aspects: - ``ndarray`` adopts imperative programming, namely sentences are executed - step-by-step so that the results can be obtained immediately. + step-by-step so that the results can be obtained immediately whereas + ``symbol`` adopts declarative programming. - - Most binary operators such as ``+`` and ``>`` are enabled broadcasting in - default. + - Most binary operators in ``ndarray`` such as ``+`` and ``>`` have + broadcasting enabled by default. ``` In the rest of this document, we first overview the methods provided by the @@ -118,6 +120,8 @@ In the rest of this document, we first overview the methods provided by the NDArray.__mul__ NDArray.__div__ NDArray.__rdiv__ + NDArray.__mod__ + NDArray.__rmod__ NDArray.__pow__ ``` @@ -131,6 +135,7 @@ In the rest of this document, we first overview the methods provided by the NDArray.__isub__ NDArray.__imul__ NDArray.__idiv__ + NDArray.__imod__ ``` ### Comparison operators @@ -257,6 +262,7 @@ In the rest of this document, we first overview the methods provided by the negative multiply divide + modulo dot batch_dot add_n @@ -319,6 +325,7 @@ In the rest of this document, we first overview the methods provided by the fix floor ceil + trunc ``` @@ -456,6 +463,37 @@ In the rest of this document, we first overview the methods provided by the Custom ``` +## Contrib + +```eval_rst +.. warning:: This package contains experimental APIs and may change in the near future. +``` + +The `contrib.ndarray` module contains many useful experimental APIs for new features. This is a place for the community to try out the new features, so that feature contributors can receive feedback. + +```eval_rst +.. currentmodule:: mxnet.contrib.ndarray + +.. autosummary:: + :nosignatures: + + CTCLoss + DeformableConvolution + DeformablePSROIPooling + MultiBoxDetection + MultiBoxPrior + MultiBoxTarget + MultiProposal + PSROIPooling + Proposal + count_sketch + ctc_loss + dequantize + fft + ifft + quantize +``` + ## API Reference @@ -467,6 +505,9 @@ In the rest of this document, we first overview the methods provided by the .. automodule:: mxnet.random :members: +.. automodule:: mxnet.contrib.ndarray + :members: + ``` diff --git a/docs/api/python/rnn.md b/docs/api/python/rnn.md new file mode 100644 index 000000000000..4021b26af154 --- /dev/null +++ b/docs/api/python/rnn.md @@ -0,0 +1,361 @@ +# RNN Cell API + +```eval_rst +.. currentmodule:: mxnet.rnn +``` + +```eval_rst +.. warning:: This package is currently experimental and may change in the near future. +``` + +## Overview + +The `rnn` module includes the recurrent neural network (RNN) cell APIs, a suite of tools for building an RNN's symbolic graph. +```eval_rst +.. note:: The `rnn` module offers higher-level interface while `symbol.RNN` is a lower-level interface. The cell APIs in `rnn` module are easier to use in most cases. +``` + +## The `rnn` module + +### Cell interfaces + +```eval_rst +.. autosummary:: + :nosignatures: + + BaseRNNCell.__call__ + BaseRNNCell.unroll + BaseRNNCell.reset + BaseRNNCell.begin_state + BaseRNNCell.unpack_weights + BaseRNNCell.pack_weights +``` + +When working with the cell API, the precise input and output symbols +depend on the type of RNN you are using. Take Long Short-Term Memory (LSTM) for example: + +```python +import mxnet as mx +# Shape of 'step_data' is (batch_size,). +step_input = mx.symbol.Variable('step_data') + +# First we embed our raw input data to be used as LSTM's input. +embedded_step = mx.symbol.Embedding(data=step_input, \ + input_dim=input_dim, \ + output_dim=embed_dim) + +# Then we create an LSTM cell. +lstm_cell = mx.rnn.LSTMCell(num_hidden=50) +# Initialize its hidden and memory states. +# 'begin_state' method takes an initialization function, and uses 'zeros' by default. +begin_state = lstm_cell.begin_state() +``` + +The LSTM cell and other non-fused RNN cells are callable. Calling the cell updates it's state once. This transformation depends on both the current input and the previous states. See this [blog post](http://colah.github.io/posts/2015-08-Understanding-LSTMs/) for a great introduction to LSTM and other RNN. +```python +# Call the cell to get the output of one time step for a batch. +output, states = lstm_cell(embedded_step, begin_state) + +# 'output' is lstm_t0_out_output of shape (batch_size, hidden_dim). + +# 'states' has the recurrent states that will be carried over to the next step, +# which includes both the "hidden state" and the "cell state": +# Both 'lstm_t0_out_output' and 'lstm_t0_state_output' have shape (batch_size, hidden_dim). +``` + +Most of the time our goal is to process a sequence of many steps. For this, we need to unroll the LSTM according to the sequence length. +```python +# Embed a sequence. 'seq_data' has the shape of (batch_size, sequence_length). +seq_input = mx.symbol.Variable('seq_data') +embedded_seq = mx.symbol.Embedding(data=seq_input, \ + input_dim=input_dim, \ + output_dim=embed_dim) +``` +```eval_rst +.. note:: Remember to reset the cell when unrolling/stepping for a new sequence by calling `lstm_cell.reset()`. +``` +```python +# Note that when unrolling, if 'merge_outputs' is set to True, the 'outputs' is merged into a single symbol +# In the layout, 'N' represents batch size, 'T' represents sequence length, and 'C' represents the +# number of dimensions in hidden states. +outputs, states = lstm_cell.unroll(length=sequence_length, \ + inputs=embedded_seq, \ + layout='NTC', \ + merge_outputs=True) +# 'outputs' is concat0_output of shape (batch_size, sequence_length, hidden_dim). +# The hidden state and cell state from the final time step is returned: +# Both 'lstm_t4_out_output' and 'lstm_t4_state_output' have shape (batch_size, hidden_dim). + +# If merge_outputs is set to False, a list of symbols for each of the time steps is returned. +outputs, states = lstm_cell.unroll(length=sequence_length, \ + inputs=embedded_seq, \ + layout='NTC', \ + merge_outputs=False) +# In this case, 'outputs' is a list of symbols. Each symbol is of shape (batch_size, hidden_dim). +``` + +```eval_rst +.. note:: Loading and saving models that are built with RNN cells API requires using + `mx.rnn.load_rnn_checkpoint`, `mx.rnn.save_rnn_checkpoint`, and `mx.rnn.do_rnn_checkpoint`. + The list of all the used cells should be provided as the first argument to those functions. +``` + +### Basic RNN cells + +`rnn` module supports the following RNN cell types. + +```eval_rst +.. autosummary:: + :nosignatures: + + LSTMCell + GRUCell + RNNCell +``` + +### Modifier cells + +```eval_rst +.. autosummary:: + :nosignatures: + + BidirectionalCell + DropoutCell + ZoneoutCell + ResidualCell +``` + +A modifier cell takes in one or more cells and transforms the output of those cells. +`BidirectionalCell` is one example. It takes two cells for forward unroll and backward unroll +respectively. After unrolling, the outputs of the forward and backward pass are concatenated. +```python +# Bidirectional cell takes two RNN cells, for forward and backward pass respectively. +# Having different types of cells for forward and backward unrolling is allowed. +bi_cell = mx.rnn.BidirectionalCell( + mx.rnn.LSTMCell(num_hidden=50), + mx.rnn.GRUCell(num_hidden=75)) +outputs, states = bi_cell.unroll(length=sequence_length, \ + inputs=embedded_seq, \ + merge_outputs=True) +# The output feature is the concatenation of the forward and backward pass. +# Thus, the number of output dimensions is the sum of the dimensions of the two cells. +# 'outputs' is the symbol 'bi_out_output' of shape (batch_size, sequence_length, 125L) + +# The states of the BidirectionalCell is a list of two lists, corresponding to the +# states of the forward and backward cells respectively. +``` +```eval_rst +.. note:: BidirectionalCell cannot be called or stepped, because the backward unroll requires the output of + future steps, and thus the whole sequence is required. +``` + +Dropout and zoneout are popular regularization techniques that can be applied to RNN. `rnn` +module provides `DropoutCell` and `ZoneoutCell` for regularization on the output and recurrent +states of RNN. `ZoneoutCell` takes one RNN cell in the constructor, and supports unrolling like +other cells. +```python +zoneout_cell = mx.rnn.ZoneoutCell(lstm_cell, zoneout_states=0.5) +outputs, states = zoneout_cell.unroll(length=sequence_length, \ + inputs=embedded_seq, \ + merge_outputs=True) +``` +`DropoutCell` performs dropout on the input sequence. It can be used in a stacked +multi-layer RNN setting, which we will cover next. + +Residual connection is a useful technique for training deep neural models because it helps the +propagation of gradients by shortening the paths. `ResidualCell` provides such functionality for +RNN models. +```python +residual_cell = mx.rnn.ResidualCell(lstm_cell) +outputs, states = residual_cell.unroll(length=sequence_length, \ + inputs=embedded_seq, \ + merge_outputs=True) +``` +The `outputs` are the element-wise sum of both the input and the output of the LSTM cell. + +### Multi-layer cells + +```eval_rst +.. autosummary:: + :nosignatures: + + SequentialRNNCell + SequentialRNNCell.add +``` + +The `SequentialRNNCell` allows stacking multiple layers of RNN cells to improve the expressiveness +and performance of the model. Cells can be added to a `SequentialRNNCell` in order, from bottom to +top. When unrolling, the output of a lower-level cell is automatically passed to the cell above. + +```python +stacked_rnn_cells = mx.rnn.SequentialRNNCell() +stacked_rnn_cells.add(mx.rnn.BidirectionalCell( + mx.rnn.LSTMCell(num_hidden=50), + mx.rnn.LSTMCell(num_hidden=50))) + +# Dropout the output of the bottom layer BidirectionalCell with a retention probability of 0.5. +stacked_rnn_cells.add(mx.rnn.DropoutCell(0.5)) + +stacked_rnn_cells.add(mx.rnn.LSTMCell(num_hidden=50)) +outputs, states = stacked_rnn_cells.unroll(length=sequence_length, \ + inputs=embedded_seq, \ + merge_outputs=True) + +# The output of SequentialRNNCell is the same as that of the last layer. +# In this case 'outputs' is the symbol 'concat6_output' of shape (batch_size, sequence_length, hidden_dim) +# The states of the SequentialRNNCell is a list of lists, with each list +# corresponding to the states of each of the added cells respectively. +``` + +### Fused RNN cell + +```eval_rst +.. autosummary:: + :nosignatures: + + FusedRNNCell + FusedRNNCell.unfuse +``` + +The computation of an RNN for an input sequence consists of many GEMM and point-wise operations with +temporal dependencies dependencies. This could make the computation memory-bound especially on GPU, +resulting in longer wall-time. By combining the computation of many small matrices into that of +larger ones and streaming the computation whenever possible, the ratio of computation to memory I/O +can be increased, which results in better performance on GPU. Such optimization technique is called +"fusing". +[This post](https://devblogs.nvidia.com/parallelforall/optimizing-recurrent-neural-networks-cudnn-5/) +talks in greater detail. + +The `rnn` module includes a `FusedRNNCell`, which provides the optimized fused implementation. +The FusedRNNCell supports bidirectional RNNs and dropout. + +```python +fused_lstm_cell = mx.rnn.FusedRNNCell(num_hidden=50, \ + num_layers=3, \ + mode='lstm', \ + bidirectional=True, \ + dropout=0.5) +outputs, _ = fused_lstm_cell.unroll(length=sequence_length, \ + inputs=embedded_seq, \ + merge_outputs=True) +# The 'outputs' is the symbol 'lstm_rnn_output' that has the shape +# (batch_size, sequence_length, forward_backward_concat_dim) +``` +```eval_rst +.. note:: `FusedRNNCell` supports GPU-only. It cannot be called or stepped. +.. note:: When `dropout` is set to non-zero in `FusedRNNCell`, the dropout is applied to the + output of all layers except the last layer. If there is only one layer in the `FusedRNNCell`, the + dropout rate is ignored. +.. note:: Similar to `BidirectionalCell`, when `bidirectional` flag is set to `True`, the output + of `FusedRNNCell` is twice the size specified by `num_hidden`. +``` + +When training a deep, complex model *on multiple GPUs* it's recommended to stack +fused RNN cells (one layer per cell) together instead of one with all layers. +The reason is that fused RNN cells don't set gradients to be ready until the +computation for the entire layer is completed. Breaking a multi-layer fused RNN +cell into several one-layer ones allows gradients to be processed ealier. This +reduces communication overhead, especially with multiple GPUs. + +The `unfuse()` method can be used to convert the `FusedRNNCell` into an equivalent +and CPU-compatible `SequentialRNNCell` that mirrors the settings of the `FusedRNNCell`. +```python +unfused_lstm_cell = fused_lstm_cell.unfuse() +unfused_outputs, _ = unfused_lstm_cell.unroll(length=sequence_length, \ + inputs=embedded_seq, \ + merge_outputs=True) +# The 'outputs' is the symbol 'lstm_bi_l2_out_output' that has the shape +# (batch_size, sequence_length, forward_backward_concat_dim) +``` + +### RNN checkpoint methods and parameters + +```eval_rst +.. autosummary:: + :nosignatures: + + save_rnn_checkpoint + load_rnn_checkpoint + do_rnn_checkpoint +``` +```eval_rst +.. autosummary:: + :nosignatures: + + RNNParams + RNNParams.get +``` + +The model parameters from the training with fused cell can be used for inference with unfused cell, +and vice versa. As the parameters of fused and unfused cells are organized differently, they need to +be converted first. `FusedRNNCell`'s parameters are merged and flattened. In the fused example above, +the mode has `lstm_parameters` of shape `(total_num_params,)`, whereas the +equivalent SequentialRNNCell's parameters are separate: +```python +'lstm_l0_i2h_weight': (out_dim, embed_dim) +'lstm_l0_i2h_bias': (out_dim,) +'lstm_l0_h2h_weight': (out_dim, hidden_dim) +'lstm_l0_h2h_bias': (out_dim,) +'lstm_r0_i2h_weight': (out_dim, embed_dim) +... +``` + +All cells in the `rnn` module support the method `unpack_weights()` for converting `FusedRNNCell` +parameters to the unfused format and `pack_weights()` for fusing the parameters. The RNN-specific +checkpointing methods (`load_rnn_checkpoint, save_rnn_checkpoint, do_rnn_checkpoint`) handle the +conversion transparently based on the provided cells. + +### I/O utilities + +```eval_rst +.. autosummary:: + :nosignatures: + + BucketSentenceIter + encode_sentences +``` + +## API Reference + + + +```eval_rst +.. autoclass:: mxnet.rnn.BaseRNNCell + :members: + + .. automethod:: __call__ +.. autoclass:: mxnet.rnn.LSTMCell + :members: +.. autoclass:: mxnet.rnn.GRUCell + :members: +.. autoclass:: mxnet.rnn.RNNCell + :members: +.. autoclass:: mxnet.rnn.FusedRNNCell + :members: +.. autoclass:: mxnet.rnn.SequentialRNNCell + :members: +.. autoclass:: mxnet.rnn.BidirectionalCell + :members: +.. autoclass:: mxnet.rnn.DropoutCell + :members: +.. autoclass:: mxnet.rnn.ZoneoutCell + :members: +.. autoclass:: mxnet.rnn.ResidualCell + :members: +.. autoclass:: mxnet.rnn.RNNParams + :members: + + +.. autoclass:: mxnet.rnn.BucketSentenceIter + :members: +.. automethod:: mxnet.rnn.encode_sentences + +.. automethod:: mxnet.rnn.save_rnn_checkpoint + +.. automethod:: mxnet.rnn.load_rnn_checkpoint + +.. automethod:: mxnet.rnn.do_rnn_checkpoint + +``` + + diff --git a/docs/api/python/symbol.md b/docs/api/python/symbol.md index 66094ace1576..dd455eee587a 100644 --- a/docs/api/python/symbol.md +++ b/docs/api/python/symbol.md @@ -40,19 +40,20 @@ array([ 4., 7.], dtype=float32) ``` A detailed tutorial is available at [Symbol - Neural network graphs and auto-differentiation](http://mxnet.io/tutorials/basic/symbol.html). +

    ```eval_rst -.. note:: most operators provided in ``symbol`` are similar to ``ndarray``. But - also note that ``symbol`` differs to ``ndarray`` in several aspects: +.. note:: most operators provided in ``symbol`` are similar to those in ``ndarray`` + although there are few differences: - - ``symbol`` adopts declare programming. In other words, we need to first - composite the computations, and then feed with data to execute. + - ``symbol`` adopts declarative programming. In other words, we need to first + compose the computations, and then feed it with data for execution whereas + ndarray adopts imperative programming. - - Most binary operators such as ``+`` and ``>`` are not enabled broadcasting. - We need to call the broadcasted version such as ``broadcast_plus`` + - Most binary operators in ``symbol`` such as ``+`` and ``>`` don't broadcast. + We need to call the broadcast version of the operator such as ``broadcast_plus`` explicitly. - ``` In the rest of this document, we first overview the methods provided by the @@ -85,6 +86,8 @@ Composite multiple symbols into a new one by an operator. Symbol.__mul__ Symbol.__div__ Symbol.__rdiv__ + Symbol.__mod__ + Symbol.__rmod__ Symbol.__pow__ ``` @@ -248,7 +251,9 @@ Composite multiple symbols into a new one by an operator. broadcast_sub broadcast_mul broadcast_div + broadcast_mod negative + reciprocal dot batch_dot add_n @@ -313,6 +318,7 @@ Composite multiple symbols into a new one by an operator. fix floor ceil + trunc ``` @@ -391,6 +397,21 @@ Composite multiple symbols into a new one by an operator. argmin ``` +### Linear Algebra + +```eval_rst +.. autosummary:: + :nosignatures: + + linalg_gemm + linalg_gemm2 + linalg_potrf + linalg_potri + linalg_trmm + linalg_trsm + linalg_sumlogdiag +``` + ### Miscellaneous ```eval_rst @@ -459,6 +480,37 @@ Composite multiple symbols into a new one by an operator. Custom ``` +## Contrib + +```eval_rst +.. warning:: This package contains experimental APIs and may change in the near future. +``` + +The `contrib.symbol` module contains many useful experimental APIs for new features. This is a place for the community to try out the new features, so that feature contributors can receive feedback. + +```eval_rst +.. currentmodule:: mxnet.contrib.symbol + +.. autosummary:: + :nosignatures: + + CTCLoss + DeformableConvolution + DeformablePSROIPooling + MultiBoxDetection + MultiBoxPrior + MultiBoxTarget + MultiProposal + PSROIPooling + Proposal + count_sketch + ctc_loss + dequantize + fft + ifft + quantize +``` + ## API Reference @@ -467,6 +519,9 @@ Composite multiple symbols into a new one by an operator. .. automodule:: mxnet.symbol :members: +.. automodule:: mxnet.contrib.symbol + :members: + ``` diff --git a/docs/architecture/note_engine.md b/docs/architecture/note_engine.md index 8fa8b52b3a8c..dc0b84aa53d1 100644 --- a/docs/architecture/note_engine.md +++ b/docs/architecture/note_engine.md @@ -136,8 +136,8 @@ training one batch on a two-layer neural network. # aggregate gradient and update fc1_wgrad[cpu] = fc1_wgrad[gpu0] + fc1_wgrad[gpu1] fc2_wgrad[cpu] = fc2_wgrad[gpu0] + fc2_wgrad[gpu1] - fc1_weight[cpu] -= lr * fc1_wgrad[gpu0] - fc2_weight[cpu] -= lr * fc2_wgrad[gpu0] + fc1_weight[cpu] -= lr * fc1_wgrad[cpu] + fc2_weight[cpu] -= lr * fc2_wgrad[cpu] fc1_weight[cpu].copyto(fc1_weight[gpu0] , fc1_weight[gpu1]) fc2_weight[cpu].copyto(fc2_weight[gpu0] , fc2_weight[gpu1]) ``` diff --git a/docs/architecture/overview.md b/docs/architecture/overview.md index 9c7d9a945ffb..a7632d4a61e8 100644 --- a/docs/architecture/overview.md +++ b/docs/architecture/overview.md @@ -48,7 +48,7 @@ The following API is the core interface for the execution engine: This API allows you to push a function (`exec_fun`), along with its context information and dependencies, to the engine. `exec_ctx` is the context information in which the `exec_fun` should be executed, -`const_vars` denotes the variables that the function reads from, +`const_vars` denotes the variables that the function reads from, and `mutate_vars` are the variables to be modified. The engine provides the following guarantee: @@ -184,7 +184,7 @@ In MXNet, an operator is a class that contains both actual computation logic and auxiliary information that can aid the system in performing optimizations, like in-place updates and auto-derivatives. To understand the remainder of the document, -we recommend that you familiarize youself with the `mshadow` library, +we recommend that you familiarize yourself with the `mshadow` library, because all operators compute on the tensor-like structure `mshadow::TBlob` provided by the system during runtime. diff --git a/docs/architecture/program_model.md b/docs/architecture/program_model.md index 380990e7019f..519a9a9024d8 100644 --- a/docs/architecture/program_model.md +++ b/docs/architecture/program_model.md @@ -92,7 +92,7 @@ are powerful DSLs that generate callable computation graphs for neural networks. Intuitively, you might say that imperative programs -are more *native* than symbolic programs. +are more *native* than symbolic programs. It's easier to use native language features. For example, it's straightforward to print out the values in the middle of computation or to use native control flow and loops @@ -269,7 +269,7 @@ Recall the *be prepared to encounter all possible demands* requirement of impera If you are creating an array library that supports automatic differentiation, you have to keep the grad closure along with the computation. This means that none of the history variables can be -garbage-collected because they are referenced by variable `d` by way of function closure. +garbage-collected because they are referenced by variable `d` by way of function closure. What if you want to compute only the value of `d`, and don't want the gradient value? @@ -305,7 +305,6 @@ For example, one solution to the preceding problem is to introduce a context variable. You can introduce a no-gradient context variable to turn gradient calculation off. - ```python with context.NoGradient(): @@ -315,6 +314,8 @@ to turn gradient calculation off. d = c + 1 ``` + + However, this example still must be prepared to encounter all possible demands, which means that you can't perform the in-place calculation to reuse memory in the forward pass (a trick commonly used to reduce GPU memory usage). @@ -380,7 +381,7 @@ It's usually easier to write parameter updates in an imperative style, especially when you need multiple updates that relate to each other. For symbolic programs, the update statement is also executed as you call it. So in that sense, most symbolic deep learning libraries -fall back on the imperative approach to perform updates, +fall back on the imperative approach to perform updates, while using the symbolic approach to perform gradient calculation. ### There Is No Strict Boundary @@ -388,7 +389,7 @@ while using the symbolic approach to perform gradient calculation. In comparing the two programming styles, some of our arguments might not be strictly true, i.e., it's possible to make an imperative program -more like a traditional symbolic program or vice versa. +more like a traditional symbolic program or vice versa. However, the two archetypes are useful abstractions, especially for understanding the differences between deep learning libraries. We might reasonably conclude that there is no clear boundary between programming styles. @@ -400,7 +401,7 @@ information held in symbolic programs. ## Big vs. Small Operations -When designing a deep learning library, another important programming model decision +When designing a deep learning library, another important programming model decision is precisely what operations to support. In general, there are two families of operations supported by most deep learning libraries: @@ -418,7 +419,7 @@ For example, the sigmoid unit can simply be composed of division, addition and a sigmoid(x) = 1.0 / (1.0 + exp(-x)) ``` Using smaller operations as building blocks, you can express nearly anything you want. -If you're more familiar with CXXNet- or Caffe-style layers, +If you're more familiar with CXXNet- or Caffe-style layers, note that these operations don't differ from a layer, except that they are smaller. ```python @@ -433,7 +434,7 @@ because you only need to compose the components. Directly composing sigmoid layers requires three layers of operation, instead of one. ```python - SigmoidLayer(x) = EWiseDivisionLayer(1.0, AddScalarLayer(ExpLayer(-x), 1.0)) + SigmoidLayer(x) = EWiseDivisionLayer(1.0, AddScalarLayer(ExpLayer(-x), 1.0)) ``` This code creates overhead for computation and memory (which could be optimized, with cost). @@ -467,7 +468,7 @@ these optimizations are crucial to performance. Because the operations are small, there are many sub-graph patterns that can be matched. Also, because the final, generated operations -might not enumerable, +might not be enumerable, an explicit recompilation of the kernels is required, as opposed to the fixed amount of precompiled kernels in the big operation libraries. @@ -476,7 +477,7 @@ that support small operations. Requiring compilation optimization also creates engineering overhead for the libraries that solely support smaller operations. -As in the case of symbolic vs imperative, +As in the case of symbolic vs. imperative, the bigger operation libraries "cheat" by asking you to provide restrictions (to the common layer), so that you actually perform the sub-graph matching. @@ -522,7 +523,7 @@ The more suitable programming style depends on the problem you are trying to sol For example, imperative programs are better for parameter updates, and symbolic programs for gradient calculation. -We advocate *mixing* the approaches. +We advocate *mixing* the approaches. Sometimes the part that we want to be flexible isn't crucial to performance. In these cases, it's okay to leave some efficiency on the table @@ -562,7 +563,7 @@ This is exactly like writing C++ programs and exposing them to Python, which we Because parameter memory resides on the GPU, you might not want to use NumPy as an imperative component. Supporting a GPU-compatible imperative library -that interacts with symbolic compiled functions +that interacts with symbolic compiled functions or provides a limited amount of updating syntax in the update statement in symbolic program execution might be a better choice. diff --git a/docs/build_version_doc/AddPackageLink.py b/docs/build_version_doc/AddPackageLink.py new file mode 100644 index 000000000000..8fe04b50b5ce --- /dev/null +++ b/docs/build_version_doc/AddPackageLink.py @@ -0,0 +1,58 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +import os +import argparse +from bs4 import BeautifulSoup as bs + +parser = argparse.ArgumentParser(description="Add download package link.", + formatter_class=argparse.ArgumentDefaultsHelpFormatter) +parser.add_argument('--file_path', type=str, default='docs/_build/html/get_started/install.html', + help='file to be modified') +parser.add_argument('--current_version', type=str, default='master', + help='Current version') + +if __name__ == '__main__': + args = parser.parse_args() + tag = args.current_version + + src_url = "http://www.apache.org/dyn/closer.cgi/incubator/" \ + "mxnet/%s-incubating/apache-mxnet-src-%s-incubating.tar.gz" % (tag, tag) + pgp_url = "http://www.apache.org/dyn/closer.cgi/incubator/" \ + "mxnet/%s-incubating/apache-mxnet-src-%s-incubating.tar.gz.asc" % (tag, tag) + sha_url = "http://www.apache.org/dyn/closer.cgi/incubator/" \ + "mxnet/%s-incubating/apache-mxnet-src-%s-incubating.tar.gz.sha" % (tag, tag) + md5_url = "http://www.apache.org/dyn/closer.cgi/incubator/" \ + "mxnet/%s-incubating/apache-mxnet-src-%s-incubating.tar.gz.md5" % (tag, tag) + + download_str = "
    " + download_str += "" % (src_url, tag) + download_str += "" % (pgp_url) + download_str += "" % (sha_url) + download_str += "" % (md5_url) + download_str += "
    " + + with open(args.file_path, 'r') as html_file: + content = bs(html_file, 'html.parser') + download_div = content.find(id="download-source-package") + download_div['style'] = "display:block" + download_div.append(download_str) + outstr = str(content).replace('<', '<').replace('>', '>') + with open(args.file_path, 'w') as outf: + outf.write(outstr) \ No newline at end of file diff --git a/docs/build_version_doc/AddVersion.py b/docs/build_version_doc/AddVersion.py new file mode 100755 index 000000000000..c48c630565b7 --- /dev/null +++ b/docs/build_version_doc/AddVersion.py @@ -0,0 +1,91 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +import os +import argparse +from bs4 import BeautifulSoup as bs + +parser = argparse.ArgumentParser(description="Manipulate index page", + formatter_class=argparse.ArgumentDefaultsHelpFormatter) +parser.add_argument('--file_path', type=str, default='mxnet/docs/_build/html/', + help='file to be modified') +parser.add_argument('--current_version', type=str, default='master', + help='Current version') +parser.add_argument('--root_url', type=str, default='https://mxnet.incubator.apache.org/', + help='Root URL') + +if __name__ == '__main__': + args = parser.parse_args() + + root_url = args.root_url + tag_list = list() + with open('tag_list.txt', 'r') as tag_file: + for line in tag_file: + tag_list.append(line.lstrip().rstrip()) + tag_list.append('master') + + version_str = '' \ + 'Versions(%s)' \ + '' + + for path, subdirs, files in os.walk(args.file_path): + for name in files: + if not name.endswith('.html'): + continue + with open(os.path.join(path, name), 'r') as html_file: + content = bs(html_file, 'html.parser') + if os.path.join(path, name) == args.file_path + 'index.html': + content.find(id='example-link')['href'] = \ + 'https://github.com/apache/incubator-mxnet/tree/%s/example' % (args.current_version) + navbar = content.find(id="main-nav") + navbar_mobile = content.find(id="burgerMenu") + outstr = str(content) + if navbar and navbar_mobile: + version_tag = content.find(id="dropdown-menu-position-anchor-version") + version_tag_mobile = content.find(id="dropdown-menu-position-anchor-version-mobile") + if version_tag: + version_tag.extract() + if version_tag_mobile: + version_tag_mobile.extract() + navbar.append(version_str) + navbar_mobile.append(version_str_mobile) + outstr = str(content).replace('<', '<').replace('>', '>') + # Fix link + if args.current_version == tag_list[0]: + print("Fixing" + os.path.join(path, name)) + outstr = outstr.replace('https://mxnet.io', 'https://mxnet.incubator.apache.org') + outstr = outstr.replace('http://mxnet.io', 'https://mxnet.incubator.apache.org') + else: + outstr = outstr.replace('https://mxnet.io', 'https://mxnet.incubator.apache.org/' + 'versions/%s' % (args.current_version)) + outstr = outstr.replace('http://mxnet.io', 'https://mxnet.incubator.apache.org/' + 'versions/%s' % (args.current_version)) + + with open(os.path.join(path, name), "w") as outf: + outf.write(outstr) + diff --git a/docs/build_version_doc/build_doc.sh b/docs/build_version_doc/build_doc.sh new file mode 100755 index 000000000000..f98e1e0683dc --- /dev/null +++ b/docs/build_version_doc/build_doc.sh @@ -0,0 +1,92 @@ +#!/bin/bash + +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + + +web_url="$1" +web_folder="VersionedWeb" +local_build="latest" +web_branch="$2" +git clone $web_url $web_folder +cd $web_folder +git checkout $web_branch +cd .. +mkdir "$local_build" + +# Fetch tag information +tag_list_file="tag_list.txt" +cp "$web_folder/tag.txt" "$tag_list_file" +tag_list=() +while read -r line +do + tag_list+=("$line") +done < "$tag_list_file" +latest_tag=${tag_list[0]} +echo "latest_tag is: $latest_tag" +commit_id=$(git rev-parse HEAD) +curr_tag=${TAG} +curr_tag=${curr_tag:5} +echo "Current tag is $curr_tag" +if [[ "$curr_tag" != 'master' ]] && [ $curr_tag != $latest_tag ] +then + latest_tag=$curr_tag +fi + +# Build new released tag +if [ $latest_tag != ${tag_list[0]} ] +then + echo "Building new tag" + git submodule update + make docs || exit 1 + echo -e "$latest_tag\n$(cat $tag_list_file)" > "$tag_list_file" + cat $tag_list_file + tests/ci_build/ci_build.sh doc python docs/build_version_doc/AddVersion.py --file_path "docs/_build/html/" --current_version "$latest_tag" + tests/ci_build/ci_build.sh doc python docs/build_version_doc/AddPackageLink.py \ + --file_path "docs/_build/html/get_started/install.html" --current_version "$latest_tag" + cp -a "docs/_build/html/." "$local_build" + cp $tag_list_file "$local_build/tag.txt" + rm -rf "$web_folder/.git" + cp -a "$web_folder/versions/." "$local_build/versions" + mkdir "$local_build/versions/${tag_list[0]}" + cp -a "$web_folder/." "$local_build/versions/${tag_list[0]}" || exit 1 + rm -rf "$local_build/versions/${tag_list[0]}/versions" + rm -rf "$web_folder/*" + cp -a "$local_build/." "$web_folder" +fi + +# Build latest master +git checkout master +git checkout -- . +git submodule update +echo "Building master" +make docs || exit 1 + +rm -rfv "$web_folder/versions/master/*" +cp -a "docs/_build/html/." "$web_folder/versions/master" +tests/ci_build/ci_build.sh doc python docs/build_version_doc/AddVersion.py --file_path "$web_folder/versions/master" + +# Update version list for all previous version website +if [ $latest_tag != ${tag_list[0]} ] +then + total=${#tag_list[*]} + for (( i=0; i<=$(( $total -1 )); i++ )) + do + tests/ci_build/ci_build.sh doc python docs/build_version_doc/AddVersion.py --file_path "$web_folder/versions/${tag_list[$i]}" \ + --current_version "${tag_list[$i]}" + done +fi diff --git a/docs/community/contribute.md b/docs/community/contribute.md index 6c8d5629fe14..3a39743af33d 100644 --- a/docs/community/contribute.md +++ b/docs/community/contribute.md @@ -8,7 +8,7 @@ After your patch has been merged, remember to add your name to [CONTRIBUTORS.md] ### Core Library -- Follow the Google C++ Style Guide for C++ code. +- Follow the [Google C++ Style Guide](https://google.github.io/styleguide/cppguide.html) for C++ code. - Use doxygen to document all of the interface code. - To reproduce the linter checks, type ```make lint```. diff --git a/docs/community/mxnet_channels.md b/docs/community/mxnet_channels.md index 3f7a24cd5653..c938bd03465c 100644 --- a/docs/community/mxnet_channels.md +++ b/docs/community/mxnet_channels.md @@ -3,4 +3,4 @@ Converse with the MXNet community via the following channels: - [MXNet Apache mailing list](https://lists.apache.org/list.html?dev@mxnet.apache.org) (dev@mxnet.apache.org): To subscribe, send an email to dev-subscribe@mxnet.apache.org. -- [MXNet Slack channel](https://apache-mxnet.slack.com): To request an invitation to the channel please email: dev@mxnet.apache.org. Note: if you have an email address with apache.org, you do not need an approval to join the MXNet Slack channel. +- [MXNet Slack channel](https://apache-mxnet.slack.com): To request an invitation to the channel please subscribe to the mailing list above and then email: dev@mxnet.apache.org. Note: if you have an email address with apache.org, you do not need an approval to join the MXNet Slack channel. diff --git a/docs/conf.py b/docs/conf.py index fd816668b139..ad51323f01e9 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # -*- coding: utf-8 -*- import sys, os, re, subprocess import mock @@ -9,7 +26,7 @@ sys.path.insert(0, curr_path) # -- mock out modules -MOCK_MODULES = ['numpy', 'numpy.testing', 'scipy', 'scipy.sparse', 'sklearn', 'matplotlib'] +MOCK_MODULES = ['scipy', 'scipy.sparse', 'sklearn'] for mod_name in MOCK_MODULES: sys.modules[mod_name] = mock.Mock() diff --git a/docs/get_started/amazonlinux_setup.md b/docs/get_started/amazonlinux_setup.md index 6829acaa0465..054e0304e107 100644 --- a/docs/get_started/amazonlinux_setup.md +++ b/docs/get_started/amazonlinux_setup.md @@ -1,228 +1,8 @@ -# Installing MXNet on Amazon Linux - -**NOTE:** For MXNet with Python installation, please refer to the [new install guide](http://mxnet.io/get_started/install.html). - -Installing MXNet is a two-step process: - -1. Build the shared library from the MXNet C++ source code. -2. Install the supported language-specific packages for MXNet. - -**Note:** To change the compilation options for your build, edit the ```make/config.mk``` file and submit a build request with the ```make``` command. - -## Build the Shared Library -On Amazon Linux, you need the following dependencies: - -- Git (to pull code from GitHub) - -- libatlas-base-dev (for linear algebraic operations) - -- libopencv-dev (for computer vision operations) - -Install these dependencies using the following commands: - -```bash - # CMake is required for installing dependencies. - sudo yum install -y cmake - - # Set appropriate library path env variables - echo 'export PATH=/usr/local/bin:$PATH' >> ~/.profile - echo 'export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH' >> ~/.profile - echo 'export PKG_CONFIG_PATH=/usr/local/lib/pkgconfig:$PKG_CONFIG_PATH' >> ~/.profile - echo '. ~/.profile' >> ~/.bashrc - source ~/.profile - - # Install gcc-4.8/make and other development tools on Amazon Linux - # Reference: http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/compile-software.html - # Install Python, Numpy, Scipy and set up tools. - sudo yum groupinstall -y "Development Tools" - sudo yum install -y python27 python27-setuptools python27-tools python-pip - sudo yum install -y python27-numpy python27-scipy python27-nose python27-matplotlib graphviz - - # Install OpenBLAS at /usr/local/openblas - git clone https://github.com/xianyi/OpenBLAS - cd OpenBLAS - make FC=gfortran -j $(($(nproc) + 1)) - sudo make PREFIX=/usr/local install - cd .. - - # Install OpenCV at /usr/local/opencv - git clone https://github.com/opencv/opencv - cd opencv - mkdir -p build - cd build - cmake -D BUILD_opencv_gpu=OFF -D WITH_EIGEN=ON -D WITH_TBB=ON -D WITH_CUDA=OFF -D WITH_1394=OFF -D CMAKE_BUILD_TYPE=RELEASE -D CMAKE_INSTALL_PREFIX=/usr/local .. - sudo make PREFIX=/usr/local install - - # Install Graphviz for visualization and Jupyter notebook for running examples and tutorials - sudo pip install graphviz - sudo pip install jupyter - - # Export env variables for pkg config - export PKG_CONFIG_PATH=/usr/local/lib/pkgconfig:$PKG_CONFIG_PATH -``` -After installing the dependencies, use the following command to pull the MXNet source code from GitHub - -```bash - # Get MXNet source code - git clone https://github.com/dmlc/mxnet.git ~/mxnet --recursive - # Move to source code parent directory - cd ~/mxnet - cp make/config.mk . - echo "USE_BLAS=openblas" >>config.mk - echo "ADD_CFLAGS += -I/usr/include/openblas" >>config.mk - echo "ADD_LDFLAGS += -lopencv_core -lopencv_imgproc -lopencv_imgcodecs" >>config.mk -``` - -If building with ```GPU``` support, run below commands to add GPU dependency configurations to config.mk file: - -```bash - echo "USE_CUDA=1" >>config.mk - echo "USE_CUDA_PATH=/usr/local/cuda" >>config.mk - echo "USE_CUDNN=1" >>config.mk -``` - -Then build mxnet: - -```bash - make -j$(nproc) -``` - -Executing these commands creates a library called ```libmxnet.so``` - - -  - -We have installed MXNet core library. Next, we will install MXNet interface package for the programming language of your choice: -- [R](#install-the-mxnet-package-for-r) -- [Julia](#install-the-mxnet-package-for-julia) -- [Scala](#install-the-mxnet-package-for-scala) -- [Perl](#install-the-mxnet-package-for-perl) - -## Install the MXNet Package for R -Run the following commands to install the MXNet dependencies and build the MXNet R package. - -```r - Rscript -e "install.packages('devtools', repo = 'https://cran.rstudio.com')" -``` -```bash - cd R-package - Rscript -e "library(devtools); library(methods); options(repos=c(CRAN='https://cran.rstudio.com')); install_deps(dependencies = TRUE)" - cd .. - make rpkg -``` - -**Note:** R-package is a folder in the MXNet source. - -These commands create the MXNet R package as a tar.gz file that you can install as an R package. To install the R package, run the following command, use your MXNet version number: - -```bash - R CMD INSTALL mxnet_current_r.tar.gz -``` - -## Install the MXNet Package for Julia -The MXNet package for Julia is hosted in a separate repository, MXNet.jl, which is available on [GitHub](https://github.com/dmlc/MXNet.jl). To use Julia binding it with an existing libmxnet installation, set the ```MXNET_HOME``` environment variable by running the following command: - -```bash - export MXNET_HOME=//libmxnet -``` - -The path to the existing libmxnet installation should be the root directory of libmxnet. In other words, you should be able to find the ```libmxnet.so``` file at ```$MXNET_HOME/lib```. For example, if the root directory of libmxnet is ```~```, you would run the following command: - -```bash - export MXNET_HOME=/~/libmxnet -``` - -You might want to add this command to your ```~/.bashrc``` file. If you do, you can install the Julia package in the Julia console using the following command: - -```julia - Pkg.add("MXNet") -``` - -For more details about installing and using MXNet with Julia, see the [MXNet Julia documentation](http://dmlc.ml/MXNet.jl/latest/user-guide/install/). - -## Install the MXNet Package for Scala - -There are two ways to install the MXNet package for Scala: - -* Use the prebuilt binary package - -* Build the library from source code - -### Use the Prebuilt Binary Package -For Linux users, MXNet provides prebuilt binary packages that support computers with either GPU or CPU processors. To download and build these packages using ```Maven```, change the ```artifactId``` in the following Maven dependency to match your architecture: - -```HTML - - ml.dmlc.mxnet - mxnet-full_ - 0.1.1 - -``` - -For example, to download and build the 64-bit CPU-only version for Linux, use: - -```HTML - - ml.dmlc.mxnet - mxnet-full_2.10-linux-x86_64-cpu - 0.1.1 - -``` - -If your native environment differs slightly from the assembly package, for example, if you use the openblas package instead of the atlas package, it's better to use the mxnet-core package and put the compiled Java native library in your load path: - -```HTML - - ml.dmlc.mxnet - mxnet-core_2.10 - 0.1.1 - -``` - -### Build the Library from Source Code -Before you build MXNet for Scala from source code, you must complete [building the shared library](#build-the-shared-library). After you build the shared library, run the following command from the MXNet source root directory to build the MXNet Scala package: - -```bash - make scalapkg -``` - -This command creates the JAR files for the assembly, core, and example modules. It also creates the native library in the ```native/{your-architecture}/target directory```, which you can use to cooperate with the core module. - -To install the MXNet Scala package into your local Maven repository, run the following command from the MXNet source root directory: - -```bash - make scalainstall -``` - -## Install the MXNet Package for Perl - -Before you build MXNet for Perl from source code, you must complete [building the shared library](#build-the-shared-library). After you build the shared library, run the following command from the MXNet source root directory to build the MXNet Perl package: - -```bash - ## install PDL, Graphviz, Mouse, App::cpanminus, swig via yum before running these commands - cpanm -q -L "${HOME}/perl5" Function::Parameters - - MXNET_HOME=${PWD} - export LD_LIBRARY_PATH=${MXNET_HOME}/lib - export PERL5LIB=${HOME}/perl5/lib/perl5 - - cd ${MXNET_HOME}/perl-package/AI-MXNetCAPI/ - perl Makefile.PL INSTALL_BASE=${HOME}/perl5 - make install - - cd ${MXNET_HOME}/perl-package/AI-NNVMCAPI/ - perl Makefile.PL INSTALL_BASE=${HOME}/perl5 - make install - - cd ${MXNET_HOME}/perl-package/AI-MXNet/ - perl Makefile.PL INSTALL_BASE=${HOME}/perl5 - make install -``` - -**Note -** You are more than welcome to contribute easy installation scripts for other operating systems and programming languages, see [community page](http://mxnet.io/community/index.html) for contributors guidelines. - -## Next Steps - -* [Tutorials](http://mxnet.io/tutorials/index.html) -* [How To](http://mxnet.io/how_to/index.html) -* [Architecture](http://mxnet.io/architecture/index.html) + + + +

    + + This content is moved to a new MXNet install page. Redirecting... +

    diff --git a/docs/get_started/centos_setup.md b/docs/get_started/centos_setup.md index 9cfa865b09d9..054e0304e107 100644 --- a/docs/get_started/centos_setup.md +++ b/docs/get_started/centos_setup.md @@ -1,160 +1,8 @@ -# Installing MXNet on CentOS - -**NOTE:** For MXNet with Python installation, please refer to the [new install guide](http://mxnet.io/get_started/install.html). - -MXNet currently supports Python, R, Julia, Scala, and Perl. For users on CentOS with Docker environment, MXNet provides [Docker installation guide](http://mxnet.io/get_started/docker_setup.html). If you do not have a Docker environment set up, follow below-provided step by step instructions. - - -## Minimum Requirements -Make sure you have the root permission, and `yum` is properly installed. Check it using the following command: - -```bash -sudo yum check-update -``` -If you don't get an error message, then `yum` is installed. - -**To install MXNet on CentOS, you must have the following:** - -1. gcc, g++ (4.8 or later) -2. python2, python-numpy, python-pip, clang -3. graphviz, jupyter (pip or yum install) -4. OpenBLAS -5. CUDA for GPU -6. cmake and opencv (do not use yum to install opencv, some shared libs may not be installed) - -## Install Dependencies -Make sure your machine is connected to Internet. A few installations need to download (`git clone` or `wget`) some packages from Internet. - -### Install Basic Environment -```bash - # Install gcc-4.8/make and other development tools - sudo yum install -y gcc - sudo yum install -y gcc-c++ - sudo yum install -y clang - - # Install Python, Numpy, pip and set up tools. - sudo yum groupinstall -y "Development Tools" - sudo yum install -y python27 python27-setuptools python27-tools python-pip - sudo yum install -y python27-numpy - - # install graphviz, jupyter - sudo pip install graphviz - sudo pip install jupyter -``` -### Install OpenBLAS -Note that OpenBLAS can be replaced by other BLAS libs, e.g, Intel MKL. - -```bash - # Install OpenBLAS at /usr/local/openblas - git clone https://github.com/xianyi/OpenBLAS - cd OpenBLAS - make -j $(($(nproc) + 1)) - sudo make PREFIX=/usr/local install - cd .. -``` -### Install CUDA for GPU -Note: Setting up CUDA is optional for MXNet. If you do not have a GPU machine (or if you want to train with CPU), you can skip this section and proceed with installation of OpenCV. - -If you plan to build with GPU, you need to set up the environment for CUDA and CUDNN. - -First, download and install [CUDA 8 toolkit](https://developer.nvidia.com/cuda-toolkit). - -Then download [cudnn 5](https://developer.nvidia.com/cudnn). - -Unzip the file and change to the cudnn root directory. Move the header and libraries to your local CUDA Toolkit folder: - -```bash - tar xvzf cudnn-8.0-linux-x64-v5.1-ga.tgz - sudo cp -P cuda/include/cudnn.h /usr/local/cuda/include - sudo cp -P cuda/lib64/libcudnn* /usr/local/cuda/lib64 - sudo chmod a+r /usr/local/cuda/include/cudnn.h /usr/local/cuda/lib64/libcudnn* - sudo ldconfig -``` -### Install opencv -Note: Setting up opencv is optional but strongly recommended for MXNet, unless you do not want to work on Computer Vision and Image Augmentation. If you are quite sure about that, skip this section and set `USE_OPENCV = 0` in `config.mk`. - -The Open Source Computer Vision (OpenCV) library contains programming functions for computer vision and image augmentation. For more information, see [OpenCV](https://en.wikipedia.org/wiki/OpenCV). - -```bash - # Install cmake for building opencv - sudo yum install -y cmake - # Install OpenCV at /usr/local/opencv - git clone https://github.com/opencv/opencv - cd opencv - mkdir -p build - cd build - cmake -D BUILD_opencv_gpu=OFF -D WITH_EIGEN=ON -D WITH_TBB=ON -D WITH_CUDA=OFF -D WITH_1394=OFF -D CMAKE_BUILD_TYPE=RELEASE -D CMAKE_INSTALL_PREFIX=/usr/local .. - sudo make PREFIX=/usr/local install -``` - -## Install MXNet - -### Build MXNet shared library -After installing the dependencies, use the following command to pull the MXNet source code from GitHub. - -```bash - # Download MXNet source code to ~/mxnet directory - git clone https://github.com/dmlc/mxnet.git ~/mxnet --recursive - # Move to source code parent directory - cd ~/mxnet - cp make/config.mk . - # Replace this line if you use other BLAS libs - echo "USE_BLAS=openblas" >>config.mk - echo "ADD_CFLAGS += -I/usr/include/openblas" >>config.mk - echo "ADD_LDFLAGS += -lopencv_core -lopencv_imgproc -lopencv_imgcodecs" >>config.mk -``` - -If building with ```GPU``` support, run below commands to add GPU dependency configurations to `config.mk` file: - -```bash - echo "USE_CUDA=1" >>config.mk - echo "USE_CUDA_PATH=/usr/local/cuda" >>config.mk - echo "USE_CUDNN=1" >>config.mk -``` - -Then build mxnet: - -```bash - make -j$(nproc) -``` - -Executing these commands creates a library called ```libmxnet.so``` in `~/mxnet/lib/`. - -### Install MXNet for R, Julia, Scala, and Perl. - -- [R](http://mxnet.io/get_started/amazonlinux_setup.html#install-the-mxnet-package-for-r) -- [Julia](http://mxnet.io/get_started/amazonlinux_setup.html#install-the-mxnet-package-for-julia) -- [Scala](http://mxnet.io/get_started/amazonlinux_setup.html#install-the-mxnet-package-for-scala) -- [Perl](http://mxnet.io/get_started/amazonlinux_setup.html#install-the-mxnet-package-for-perl) - -## Troubleshooting - -Here is some information to help you troubleshoot, in case you encounter error messages: - -**1. Cannot build opencv from source code** - -This may be caused by download failure during building, e.g., `ippicv`. - -Prepare some large packages by yourself, then copy them to the right place, e.g, `opencv/3rdparty/ippicv/downloads/linux-808XXXXXXXXX/`. - -**2. Link errors when building MXNet** - -```bash -/usr/bin/ld: /tmp/ccQ9qruP.o: undefined reference to symbol '_ZN2cv6String10deallocateEv' -/usr/local/lib/libopencv_core.so.3.2: error adding symbols: DSO missing from command line -``` -This error occurs when you already have old opencv (e.g, 2.4) installed using `yum` (in `/usr/lib64`). When g++ tries to link opencv libs, it will first find and link old opencv libs in `/usr/lib64`. - -Please modify `config.mk` in `mxnet` directory, and add `-L/usr/local/lib` to `ADD_CFLAGS`. - -```bash - ADD_CFLAGS += -I/usr/include/openblas -L/usr/local/lib -``` -This solution solves this link error, but there are still lots of warnings. - - -## Next Steps - -* [Tutorials](http://mxnet.io/tutorials/index.html) -* [How To](http://mxnet.io/how_to/index.html) -* [Architecture](http://mxnet.io/architecture/index.html) + + + +

    + + This content is moved to a new MXNet install page. Redirecting... +

    diff --git a/docs/get_started/install.md b/docs/get_started/install.md index 1da0bfb8d77d..0e88a0d2a2ee 100644 --- a/docs/get_started/install.md +++ b/docs/get_started/install.md @@ -73,6 +73,7 @@ The following installation instructions have been tested on Ubuntu 14.04 and 16. **Step 1** Install virtualenv for Ubuntu. ```bash +$ sudo apt-get update $ sudo apt-get install -y python-dev python-virtualenv ``` @@ -101,16 +102,22 @@ After activating the environment, you should see the prompt as below. Installing *MXNet* with pip requires a latest version of `pip`. Install the latest version of `pip` by issuing the following command. ```bash -(mxnet)$ pip install --upgrade pip +$ pip install --upgrade pip ``` Install *MXNet* with OpenBLAS acceleration. ```bash -(mxnet)$ pip install mxnet +$ pip install mxnet ``` -**Step 4** Validate the installation by running simple *MXNet* code described [here](#validate-mxnet-installation). +**Step 4** Install [Graphviz](http://www.graphviz.org/). (Optional, needed for graph visualization using `mxnet.viz` package). +```bash +sudo apt-get install graphviz +pip install graphviz +``` + +**Step 5** Validate the installation by running simple *MXNet* code described [here](#validate-mxnet-installation). **Note** You can read more about virtualenv [here](https://virtualenv.pypa.io/en/stable/userguide/). @@ -125,7 +132,7 @@ Installing *MXNet* with pip requires a latest version of `pip`. Install the late ```bash $ sudo apt-get update -$ sudo apt-get install -y wget python +$ sudo apt-get install -y wget python gcc $ wget https://bootstrap.pypa.io/get-pip.py && sudo python get-pip.py ``` @@ -135,7 +142,13 @@ $ wget https://bootstrap.pypa.io/get-pip.py && sudo python get-pip.py $ pip install mxnet ``` -**Step 3** Validate the installation by running simple MXNet code described [here](#validate-mxnet-installation). +**Step 3** Install [Graphviz](http://www.graphviz.org/). (Optional, needed for graph visualization using `mxnet.viz` package). +```bash +sudo apt-get install graphviz +pip install graphviz +``` + +**Step 4** Validate the installation by running simple MXNet code described [here](#validate-mxnet-installation).
    @@ -196,9 +209,9 @@ $ sudo apt-get install -y build-essential git **Step 2** Install OpenBLAS. -*MXNet* uses [BLAS](https://en.wikipedia.org/wiki/Basic_Linear_Algebra_Subprograms) library for accelerated numerical computations on CPU machine. There are several flavors of BLAS libraries - [OpenBLAS](http://www.openblas.net/), [ATLAS](http://math-atlas.sourceforge.net/) and [MKL](https://software.intel.com/en-us/intel-mkl). In this step we install OpenBLAS. You can choose to install ATLAS or MKL. +*MXNet* uses [BLAS](https://en.wikipedia.org/wiki/Basic_Linear_Algebra_Subprograms) and [LAPACK](https://en.wikipedia.org/wiki/LAPACK) libraries for accelerated numerical computations on CPU machine. There are several flavors of BLAS/LAPACK libraries - [OpenBLAS](http://www.openblas.net/), [ATLAS](http://math-atlas.sourceforge.net/) and [MKL](https://software.intel.com/en-us/intel-mkl). In this step we install OpenBLAS. You can choose to install ATLAS or MKL. ```bash -$ sudo apt-get install -y libopenblas-dev +$ sudo apt-get install -y libopenblas-dev liblapack-dev ``` **Step 3** Install OpenCV. @@ -222,20 +235,29 @@ $ make -j $(nproc) USE_OPENCV=1 USE_BLAS=openblas **Build the MXNet Python binding** -**Step 1** Install prerequisites - python setup tools and numpy. +**Step 1** Install prerequisites - python, setup-tools, python-pip and numpy. ```bash -$ sudo apt-get install -y python-dev python-setuptools python-numpy +$ sudo apt-get install -y python-dev python-setuptools python-numpy python-pip ``` -**Step 2** Build the MXNet Python binding. +**Step 2** Install the MXNet Python binding. ```bash $ cd python -$ sudo python setup.py install +$ pip install --upgrade pip +$ pip install -e . ``` -**Step 3** Validate the installation by running simple MXNet code described [here](#validate-mxnet-installation). +Note that the `-e` flag is optional. It is equivalent to `--editable` and means that if you edit the source files, these changes will be reflected in the package installed. + +**Step 3** Install [Graphviz](http://www.graphviz.org/). (Optional, needed for graph visualization using `mxnet.viz` package). +```bash +sudo apt-get install graphviz +pip install graphviz +``` + +**Step 4** Validate the installation by running simple MXNet code described [here](#validate-mxnet-installation).

    @@ -283,7 +305,13 @@ $ wget https://bootstrap.pypa.io/get-pip.py && sudo python get-pip.py $ pip install mxnet-cu80 ``` -**Step 3** Validate the installation by running simple MXNet code described [here](#validate-mxnet-installation). +**Step 3** Install [Graphviz](http://www.graphviz.org/). (Optional, needed for graph visualization using `mxnet.viz` package). +```bash +sudo apt-get install graphviz +pip install graphviz +``` + +**Step 4** Validate the installation by running simple MXNet code described [here](#validate-mxnet-installation). @@ -332,7 +360,13 @@ Install *MXNet* with GPU support using CUDA 8.0. (mxnet)$ pip install mxnet-cu80 ``` -**Step 4** Validate the installation by running simple *MXNet* code described [here](#validate-mxnet-installation). +**Step 4** Install [Graphviz](http://www.graphviz.org/). (Optional, needed for graph visualization using `mxnet.viz` package). +```bash +sudo apt-get install graphviz +pip install graphviz +``` + +**Step 5** Validate the installation by running simple *MXNet* code described [here](#validate-mxnet-installation). **Note** You can read more about virtualenv [here](https://virtualenv.pypa.io/en/stable/userguide/). @@ -398,9 +432,9 @@ $ sudo apt-get install -y build-essential git ``` **Step 2** Install OpenBLAS. -*MXNet* uses [BLAS](https://en.wikipedia.org/wiki/Basic_Linear_Algebra_Subprograms) library for accelerated numerical computations. There are several flavors of BLAS libraries - [OpenBLAS](http://www.openblas.net/), [ATLAS](http://math-atlas.sourceforge.net/) and [MKL](https://software.intel.com/en-us/intel-mkl). In this step we install OpenBLAS. You can choose to install ATLAS or MKL. +*MXNet* uses [BLAS](https://en.wikipedia.org/wiki/Basic_Linear_Algebra_Subprograms) and [LAPACK](https://en.wikipedia.org/wiki/LAPACK) libraries for accelerated numerical computations on CPU machine. There are several flavors of BLAS/LAPACK libraries - [OpenBLAS](http://www.openblas.net/), [ATLAS](http://math-atlas.sourceforge.net/) and [MKL](https://software.intel.com/en-us/intel-mkl). In this step we install OpenBLAS. You can choose to install ATLAS or MKL. ```bash -$ sudo apt-get install -y libopenblas-dev +$ sudo apt-get install -y libopenblas-dev liblapack-dev ``` **Step 3** Install OpenCV. @@ -422,22 +456,31 @@ $ make -j $(nproc) USE_OPENCV=1 USE_BLAS=openblas USE_CUDA=1 USE_CUDA_PATH=/usr/
    -**Build the MXNet Python binding** +**Install the MXNet Python binding** -**Step 1** Install prerequisites - python setup tools and numpy. +**Step 1** Install prerequisites - python, setup-tools, python-pip and numpy. ```bash -$ sudo apt-get install -y python-dev python-setuptools python-numpy +$ sudo apt-get install -y python-dev python-setuptools python-numpy python-pip ``` -**Step 2** Build the MXNet Python binding. +**Step 2** Install the MXNet Python binding. ```bash $ cd python -$ sudo python setup.py install +$ pip install --upgrade pip +$ pip install -e . ``` -**Step 3** Validate the installation by running simple MXNet code described [here](#validate-mxnet-installation). +Note that the `-e` flag is optional. It is equivalent to `--editable` and means that if you edit the source files, these changes will be reflected in the package installed. + +**Step 3** Install [Graphviz](http://www.graphviz.org/). (Optional, needed for graph visualization using `mxnet.viz` package). +```bash +sudo apt-get install graphviz +pip install graphviz +``` + +**Step 4** Validate the installation by running simple MXNet code described [here](#validate-mxnet-installation). @@ -455,7 +498,7 @@ The following installation instructions have been tested on OSX Sierra and El Ca **Prerequisites** -If not already installed, [download and install Xcode](https://developer.apple.com/xcode/) for macOS. [Xcode](https://en.wikipedia.org/wiki/Xcode) is an integrated development environment for macOS containing a suite of software development tools like C/C++ compilers, BLAS library and more. +If not already installed, [download and install Xcode](https://developer.apple.com/xcode/) (or [insall it from the App Store](https://itunes.apple.com/us/app/xcode/id497799835)) for macOS. [Xcode](https://en.wikipedia.org/wiki/Xcode) is an integrated development environment for macOS containing a suite of software development tools like C/C++ compilers, BLAS library and more.

    @@ -512,7 +555,13 @@ Install *MXNet* with OpenBLAS acceleration. (mxnet)$ pip install mxnet ``` -**Step 5** Validate the installation by running simple *MXNet* code described [here](#validate-mxnet-installation). +**Step 5** Install [Graphviz](http://www.graphviz.org/). (Optional, needed for graph visualization using `mxnet.viz` package). +```bash +$ brew install graphviz +(mxnet)$ pip install graphviz +``` + +**Step 6** Validate the installation by running simple *MXNet* code described [here](#validate-mxnet-installation). **Note** You can read more about virtualenv [here](https://virtualenv.pypa.io/en/stable/userguide/). @@ -545,7 +594,13 @@ $ pip install --upgrade setuptools $ pip install mxnet ``` -**Step 3** Validate the installation by running simple MXNet code described [here](#validate-mxnet-installation). +**Step 3** Install [Graphviz](http://www.graphviz.org/). (Optional, needed for graph visualization using `mxnet.viz` package). +```bash +$ brew install graphviz +$ pip install graphviz +``` + +**Step 4** Validate the installation by running simple MXNet code described [here](#validate-mxnet-installation).
    @@ -647,8 +702,213 @@ You could also run distributed deeplearning with *MXNet* on AWS using [Cloudform + + + +
    +
    +
    + +The CPU version of MXNet R package can be installed in R like other packages + +```r +cran <- getOption("repos") +cran["dmlc"] <- "https://s3-us-west-2.amazonaws.com/apache-mxnet/R/CRAN/" +options(repos = cran) +install.packages("mxnet") +``` + + +
    + + +
    + +Will be available soon. + +
    + +
    +
    + + + +
    +
    +
    +
    + +Building *MXNet* from source is a 2 step process. +1. Build the *MXNet* core shared library, `libmxnet.so`, from the C++ sources. +2. Build the language specific bindings. + +**Minimum Requirements** +1. [GCC 4.8](https://gcc.gnu.org/gcc-4.8/) or later to compile C++ 11. +2. [GNU Make](https://www.gnu.org/software/make/) + +
    + +**Build the MXNet core shared library** + +**Step 1** Install build tools and git. +```bash +$ sudo apt-get update +$ sudo apt-get install -y build-essential git +``` + +**Step 2** Install OpenBLAS. + +*MXNet* uses [BLAS](https://en.wikipedia.org/wiki/Basic_Linear_Algebra_Subprograms) and [LAPACK](https://en.wikipedia.org/wiki/LAPACK) libraries for accelerated numerical computations on CPU machine. There are several flavors of BLAS/LAPACK libraries - [OpenBLAS](http://www.openblas.net/), [ATLAS](http://math-atlas.sourceforge.net/) and [MKL](https://software.intel.com/en-us/intel-mkl). In this step we install OpenBLAS. You can choose to install ATLAS or MKL. +```bash +$ sudo apt-get install -y libopenblas-dev liblapack-dev +``` + +**Step 3** Install OpenCV. + +*MXNet* uses [OpenCV](http://opencv.org/) for efficient image loading and augmentation operations. +```bash +$ sudo apt-get install -y libopencv-dev +``` + +**Step 4** Download MXNet sources and build MXNet core shared library. + +```bash +$ git clone --recursive https://github.com/dmlc/mxnet +$ cd mxnet +$ make -j $(nproc) USE_OPENCV=1 USE_BLAS=openblas +``` + +*Note* - USE_OPENCV and USE_BLAS are make file flags to set compilation options to use OpenCV and BLAS library. You can explore and use more compilation options in `make/config.mk`. + +
    + +**Build and install the MXNet R binding** + + +```bash +$ make rpkg +$ R CMD INSTALL mxnet_current_r.tar.gz +``` + + +
    + +
    + +The following installation instructions have been tested on Ubuntu 14.04 and 16.04. + + +**Prerequisites** + +Install the following NVIDIA libraries to setup *MXNet* with GPU support: + +1. Install CUDA 8.0 following the NVIDIA's [installation guide](http://docs.nvidia.com/cuda/cuda-installation-guide-linux/). +2. Install cuDNN 5 for CUDA 8.0 following the NVIDIA's [installation guide](https://developer.nvidia.com/cudnn). You may need to register with NVIDIA for downloading the cuDNN library. + +**Note:** Make sure to add CUDA install path to `LD_LIBRARY_PATH`. + +Example - *export LD_LIBRARY_PATH=/usr/local/cuda/lib64/:$LD_LIBRARY_PATH* + +
    + +Building *MXNet* from source is a 2 step process. +1. Build the *MXNet* core shared library, `libmxnet.so`, from the C++ sources. +2. Build the language specific bindings. + +**Minimum Requirements** +1. [GCC 4.8](https://gcc.gnu.org/gcc-4.8/) or later to compile C++ 11. +2. [GNU Make](https://www.gnu.org/software/make/) + +
    + +**Build the MXNet core shared library** + +**Step 1** Install build tools and git. +```bash +$ sudo apt-get update +$ sudo apt-get install -y build-essential git +``` +**Step 2** Install OpenBLAS. + +*MXNet* uses [BLAS](https://en.wikipedia.org/wiki/Basic_Linear_Algebra_Subprograms) and [LAPACK](https://en.wikipedia.org/wiki/LAPACK) libraries for accelerated numerical computations on CPU machine. There are several flavors of BLAS/LAPACK libraries - [OpenBLAS](http://www.openblas.net/), [ATLAS](http://math-atlas.sourceforge.net/) and [MKL](https://software.intel.com/en-us/intel-mkl). In this step we install OpenBLAS. You can choose to install ATLAS or MKL. +```bash +$ sudo apt-get install -y libopenblas-dev liblapack-dev +``` + +**Step 3** Install OpenCV. + +*MXNet* uses [OpenCV](http://opencv.org/) for efficient image loading and augmentation operations. +```bash +$ sudo apt-get install -y libopencv-dev +``` + +**Step 4** Download MXNet sources and build MXNet core shared library. + +```bash +$ git clone --recursive https://github.com/dmlc/mxnet +$ cd mxnet +$ make -j $(nproc) USE_OPENCV=1 USE_BLAS=openblas USE_CUDA=1 USE_CUDA_PATH=/usr/local/cuda USE_CUDNN=1 +``` + +*Note* - USE_OPENCV, USE_BLAS, USE_CUDA, USE_CUDA_PATH AND USE_CUDNN are make file flags to set compilation options to use OpenCV, OpenBLAS, CUDA and cuDNN libraries. You can explore and use more compilation options in `make/config.mk`. Make sure to set USE_CUDA_PATH to right CUDA installation path. In most cases it is - */usr/local/cuda*. + +
    + +**Build and install the MXNet R binding** + +```bash +$ make rpkg +$ R CMD INSTALL mxnet_current_r.tar.gz +``` + +
    + +
    +
    + + + + +
    +
    +
    + +The CPU version of MXNet R package can be installed in R like other packages + + +```r +cran <- getOption("repos") +cran["dmlc"] <- "https://s3-us-west-2.amazonaws.com/apache-mxnet/R/CRAN/" +options(repos = cran) +install.packages("mxnet") +``` + +
    + + + +
    + +The GPU version of MXNet R package can be installed in R like other packages + + +```r +cran <- getOption("repos") +cran["dmlc"] <- "https://s3-us-west-2.amazonaws.com/apache-mxnet/R/CRAN/GPU" +options(repos = cran) +install.packages("mxnet") +``` + +Alternatively, You can also follow the installation instructions [in this guide](./windows_setup.md) to build MXNet from source. + +
    +
    +
    + + +
    -
    +
    Follow the installation instructions [in this guide](./ubuntu_setup.md) to set up MXNet. @@ -658,7 +918,7 @@ Follow the installation instructions [in this guide](./ubuntu_setup.md) to set u
    -
    +
    Follow the installation instructions [in this guide](./osx_setup.md) to set up MXNet. @@ -668,8 +928,8 @@ Follow the installation instructions [in this guide](./osx_setup.md) to set up M
    -
    -
    +
    +
    Follow the installation instructions [in this guide](./windows_setup.md) to set up MXNet. @@ -733,7 +993,7 @@ Otherwise, you can build the complete MXNet library with the following command: Executing either of these commands start the build process, which can take up to a couple hours, and creates a file called ```libmxnet.so``` in the mxnet/lib directory. -If you are getting build errors in which the compiler is being killed, it is likely that the compiler is running out of memory (espeically if you are on Raspberry Pi 1, 2 or Zero, which have less than 1GB of RAM), this can often be rectified by increasing the swapfile size on the Pi by editing the file /etc/dphys-swapfile and changing the line CONF_SWAPSIZE=100 to CONF_SWAPSIZE=1024, then running: +If you are getting build errors in which the compiler is being killed, it is likely that the compiler is running out of memory (especially if you are on Raspberry Pi 1, 2 or Zero, which have less than 1GB of RAM), this can often be rectified by increasing the swapfile size on the Pi by editing the file /etc/dphys-swapfile and changing the line CONF_SWAPSIZE=100 to CONF_SWAPSIZE=1024, then running: ```bash sudo /etc/init.d/dphys-swapfile stop sudo /etc/init.d/dphys-swapfile start @@ -746,9 +1006,12 @@ To install python bindings run the following commands in the MXNet directory: ```bash cd python - sudo python setup.py install + pip install --upgrade pip + pip install -e . ``` +Note that the `-e` flag is optional. It is equivalent to `--editable` and means that if you edit the source files, these changes will be reflected in the package installed. + You are now ready to run MXNet on your Raspberry Pi device. You can get started by following the tutorial on [Real-time Object Detection with MXNet On The Raspberry Pi](http://mxnet.io/tutorials/embedded/wine_detector.html). *Note - Because the complete MXNet library takes up a significant amount of the Raspberry Pi's limited RAM, when loading training data or large models into memory, you might have to turn off the GUI and terminate running processes to free RAM.* @@ -825,7 +1088,15 @@ To install python bindings run the following commands in the MXNet directory: ```bash cd python - sudo python setup.py install + pip install --upgrade pip + pip install -e . +``` + +Note that the `-e` flag is optional. It is equivalent to `--editable` and means that if you edit the source files, these changes will be reflected in the package installed. + +Add the mxnet folder to the path: + +```bash cd .. export MXNET_HOME=$(pwd) echo "export PYTHONPATH=$MXNET_HOME/python:$PYTHONPATH" >> ~/.bashrc @@ -1067,7 +1338,7 @@ Start the python terminal. ```bash $ python ``` - +
    @@ -1091,7 +1362,7 @@ $
    - +
    @@ -1110,8 +1381,47 @@ array([[ 3., 3., 3.],
    + + +
    +
    +
    + +Run a short *MXNet* R program to create a 2X3 matrix of ones, multiply each element in the matrix by 2 followed by adding 1. We expect the output to be a 2X3 matrix with all elements being 3. + +```r +library(mxnet) +a <- mx.nd.ones(c(2,3), ctx = mx.cpu()) +b <- a * 2 + 1 +b +``` + +
    +
    +
    + + + +
    +
    +
    + +Run a short *MXNet* R program to create a 2X3 matrix of ones *a* on a *GPU*, multiply each element in the matrix by 2 followed by adding 1. We expect the output to be a 2X3 matrix with all elements being 3. We use *mx.gpu()*, to set *MXNet* context to be GPUs. + +```r +library(mxnet) +a <- mx.nd.ones(c(2,3), ctx = mx.gpu()) +b <- a * 2 + 1 +b +``` + +
    +
    +
    + +
    -
    +
    Will be available soon. @@ -1121,7 +1431,7 @@ Will be available soon.
    -
    +
    Will be available soon. @@ -1131,7 +1441,7 @@ Will be available soon.
    -
    +
    Will be available soon. @@ -1152,3 +1462,5 @@ Will be available soon.
    + +# Download Source Package \ No newline at end of file diff --git a/docs/get_started/osx_setup.md b/docs/get_started/osx_setup.md index 1d58ddf36731..8e5439435a59 100644 --- a/docs/get_started/osx_setup.md +++ b/docs/get_started/osx_setup.md @@ -1,6 +1,6 @@ -# Installing MXNet on OS X (Mac) +# Installing MXNet froum source on OS X (Mac) -**NOTE:** For MXNet with Python installation, please refer to the [new install guide](http://mxnet.io/get_started/install.html). +**NOTE:** For prebuild MXNet with Python installation, please refer to the [new install guide](http://mxnet.io/get_started/install.html). Installing MXNet is a two-step process: @@ -117,9 +117,10 @@ You have 2 options: For OS X (Mac) users, MXNet provides a prebuilt binary package for CPUs. The prebuilt package is updated weekly. You can install the package directly in the R console using the following commands: ```r - install.packages("drat", repos="https://cran.rstudio.com") - drat:::addRepo("dmlc") - install.packages("mxnet") + cran <- getOption("repos") + cran["dmlc"] <- "https://s3-us-west-2.amazonaws.com/apache-mxnet/R/CRAN/" + options(repos = cran) + install.packages("mxnet") ``` ### Building MXNet from Source Code diff --git a/docs/get_started/ubuntu_setup.md b/docs/get_started/ubuntu_setup.md index 95c59d3f2109..b7130bef4967 100644 --- a/docs/get_started/ubuntu_setup.md +++ b/docs/get_started/ubuntu_setup.md @@ -76,45 +76,48 @@ Installing MXNet is a two-step process: On Ubuntu versions 13.10 or later, you need the following dependencies: -- Git (to pull code from GitHub) - -- libatlas-base-dev (for linear algebraic operations) +**Step 1** Install build tools and git. +```bash + sudo apt-get update + sudo apt-get install -y build-essential git +``` -- libopencv-dev (for computer vision operations) +**Step 2** Install OpenBLAS. -Install these dependencies using the following commands: +*MXNet* uses [BLAS](https://en.wikipedia.org/wiki/Basic_Linear_Algebra_Subprograms) library for accelerated numerical computations on CPU machine. There are several flavors of BLAS libraries - [OpenBLAS](http://www.openblas.net/), [ATLAS](http://math-atlas.sourceforge.net/) and [MKL](https://software.intel.com/en-us/intel-mkl). In this step we install OpenBLAS. You can choose to install ATLAS or MKL. ```bash - sudo apt-get update - sudo apt-get install -y build-essential git libatlas-base-dev libopencv-dev + sudo apt-get install -y libopenblas-dev ``` -After installing the dependencies, use the following command to pull the MXNet source code from GitHub +**Step 3** Install OpenCV. + +*MXNet* uses [OpenCV](http://opencv.org/) for efficient image loading and augmentation operations. ```bash - # Get MXNet source code - git clone https://github.com/dmlc/mxnet.git ~/mxnet --recursive - # Move to source code parent directory - cd ~/mxnet - cp make/config.mk . - echo "USE_BLAS=openblas" >>config.mk - echo "ADD_CFLAGS += -I/usr/include/openblas" >>config.mk - echo "ADD_LDFLAGS += -lopencv_core -lopencv_imgproc -lopencv_imgcodecs" >>config.mk + sudo apt-get install -y libopencv-dev ``` -If building with ```GPU``` support, run below commands to add GPU dependency configurations to config.mk file: + +**Step 4** Download MXNet sources and build MXNet core shared library. + +If building on CPU: ```bash - echo "USE_CUDA=1" >>config.mk - echo "USE_CUDA_PATH=/usr/local/cuda" >>config.mk - echo "USE_CUDNN=1" >>config.mk + git clone --recursive https://github.com/dmlc/mxnet + cd mxnet + make -j $(nproc) USE_OPENCV=1 USE_BLAS=openblas ``` -Then build mxnet: +If building on GPU: ```bash - make -j$(nproc) + git clone --recursive https://github.com/dmlc/mxnet + cd mxnet + make -j $(nproc) USE_OPENCV=1 USE_BLAS=openblas USE_CUDA=1 USE_CUDA_PATH=/usr/local/cuda USE_CUDNN=1 ``` +*Note* - USE_OPENCV and USE_BLAS are make file flags to set compilation options to use OpenCV and BLAS library. You can explore and use more compilation options in `make/config.mk`. + Executing these commands creates a library called ```libmxnet.so```. Next, we install ```graphviz``` library that we use for visualizing network graphs you build on MXNet. We will also install [Jupyter Notebook](http://jupyter.readthedocs.io/) used for running MXNet tutorials and examples. diff --git a/docs/get_started/windows_setup.md b/docs/get_started/windows_setup.md index 9025add50cd6..f9067732d11a 100755 --- a/docs/get_started/windows_setup.md +++ b/docs/get_started/windows_setup.md @@ -9,7 +9,6 @@ You can either use a prebuilt binary package or build from source to build the M MXNet provides a prebuilt package for Windows. The prebuilt package includes the MXNet library, all of the dependent third-party libraries, a sample C++ solution for Visual Studio, and the Python installation script. To install the prebuilt package: 1. Download the latest prebuilt package from the [Releases](https://github.com/dmlc/mxnet/releases) tab of MXNet. - There are two versions. One with GPU support (using CUDA and CUDNN v3), and one without GPU support. Choose the version that suits your hardware configuration. For more information on which version works on each hardware configuration, see [Requirements for GPU](http://mxnet.io/get_started/setup.html#requirements-for-using-gpus). 2. Unpack the package into a folder, with an appropriate name, such as ```D:\MXNet```. 3. Open the folder, and install the package by double-clicking ```setupenv.cmd```. This sets up all of the environment variables required by MXNet. 4. Test the installation by opening the provided sample C++ Visual Studio solution and building it. @@ -23,7 +22,7 @@ This produces a library called ```libmxnet.dll```. To build and install MXNet yourself, you need the following dependencies. Install the required dependencies: 1. If [Microsoft Visual Studio 2013](https://www.visualstudio.com/downloads/) is not already installed, download and install it. You can download and install the free community edition. -2. Install [Visual C++ Compiler Nov 2013 CTP](https://www.microsoft.com/en-us/download/details.aspx?id=41151). +2. Install [Visual C++ Compiler](http://landinghub.visualstudio.com/visual-cpp-build-tools). 3. Back up all of the files in the ```C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC``` folder to a different location. 4. Copy all of the files in the ```C:\Program Files (x86)\Microsoft Visual C++ Compiler Nov 2013 CTP``` folder (or the folder where you extracted the zip archive) to the ```C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC``` folder, and overwrite all existing files. 5. Download and install [OpenCV](http://sourceforge.net/projects/opencvlibrary/files/opencv-win/3.0.0/opencv-3.0.0.exe/download). @@ -53,7 +52,7 @@ Next, we install ```graphviz``` library that we use for visualizing network grap We have installed MXNet core library. Next, we will install MXNet interface package for programming language of your choice: - [Python](#install-the-mxnet-package-for-python) -- [R](#install-the-mxnet-package-for-r) +- [R](#install-mxnet-for-r) - [Julia](#install-the-mxnet-package-for-julia) - [Scala](#install-the-mxnet-package-for-scala) @@ -92,12 +91,25 @@ To install MXNet on a computer with a CPU processor, choose from two options: * Use the prebuilt binary package * Build the library from source code -#### Building MXNet with the Prebuilt Binary Package -For Windows users, MXNet provides a prebuilt binary package for CPUs. The prebuilt package is updated weekly. You can install the package directly in the R console using the following commands: +#### Installing MXNet with the Prebuilt Binary Package +For Windows users, MXNet provides prebuilt binary packages. +You can install the package directly in the R console. + +For CPU-only package: + +```r + cran <- getOption("repos") + cran["dmlc"] <- "https://s3-us-west-2.amazonaws.com/apache-mxnet/R/CRAN/" + options(repos = cran) + install.packages("mxnet") +``` + +For GPU-enabled package: ```r - install.packages("drat", repos="https://cran.rstudio.com") - drat:::addRepo("dmlc") + cran <- getOption("repos") + cran["dmlc"] <- "https://s3-us-west-2.amazonaws.com/apache-mxnet/R/CRAN/GPU" + options(repos = cran) install.packages("mxnet") ``` @@ -106,11 +118,12 @@ For Windows users, MXNet provides a prebuilt binary package for CPUs. The prebui Run the following commands to install the MXNet dependencies and build the MXNet R package. ```r - Rscript -e "install.packages('devtools', repo = 'https://cran.rstudio.com')" + Rscript -e "install.packages('devtools', repo = 'https://cloud.r-project.org/')" ``` + ```bash cd R-package - Rscript -e "library(devtools); library(methods); options(repos=c(CRAN='https://cran.rstudio.com')); install_deps(dependencies = TRUE)" + Rscript -e "library(devtools); library(methods); options(repos=c(CRAN='https://cloud.r-project.org/')); install_deps(dependencies = TRUE)" cd .. make rpkg ``` @@ -125,7 +138,7 @@ These commands create the MXNet R package as a tar.gz file that you can install ### Installing MXNet on a Computer with a GPU Processor -To install MXNet on a computer with a GPU processor, you need the following: +To install MXNet R package on a computer with a GPU processor, you need the following: * Microsoft Visual Studio 2013 @@ -137,20 +150,62 @@ To install MXNet on a computer with a GPU processor, you need the following: To install the required dependencies and install MXNet for R: -1. If [Microsoft Visual Studio 2013](https://www.visualstudio.com/downloads/) is not already installed, download and install it. You can download and install the free community edition. -2. Install the [CUDA Toolkit](https://developer.nvidia.com/cuda-toolkit). The CUDA Toolkit depends on Visual Studio. To check whether your GPU is compatible with the CUDA Toolkit and for information on installing it, see NVidia's [CUDA Installation Guide](http://docs.nvidia.com/cuda/cuda-installation-guide-microsoft-windows/). -3. Download the MXNet package as a .zip file from the [MXNet Github repository](https://github.com/dmlc/mxnet/) and unpack it. You will be editing the ```"/mxnet/R-package"``` folder. -4. Download the most recent GPU-enabled MXNet package from the [Releases](https://github.com/dmlc/mxnet/releases) tab. Unzip this file and navigate to the ```/nocudnn``` folder. -**Note:** You will copy some of these extracted files into MXNet's R-package folder. We are now working two folders, ```R-package/``` and ```nocudnn/```. -5. Download and install [CuDNN V3](https://developer.nvidia.com/cudnn). To get access to the download link, register as an NVIDIA community user. Unpack the .zip file. You will see three folders: ```/bin```, ```/include```, and ```/lib```. Copy these folders into ```nocudnn/3rdparty/cudnn/```, replacing the folders that are already there. You can also unpack the .zip file directly into the nocudnn/ folder. +1. Install the [CUDA Toolkit](https://developer.nvidia.com/cuda-toolkit). The CUDA Toolkit depends on Visual Studio. To check whether your GPU is compatible with the CUDA Toolkit and for information on installing it, see NVidia's [CUDA Installation Guide](http://docs.nvidia.com/cuda/cuda-installation-guide-microsoft-windows/). +3. Clone the MXNet github repo. + +```sh +git clone --recursive https://github.com/dmlc/mxnet +``` + +The `--recursive` is to clone all the submodules used by MXNet. You will be editing the ```"/mxnet/R-package"``` folder. +4. Download prebuilt GPU-enabled MXNet libraries for Windows from https://github.com/yajiedesign/mxnet/releases. You will need `mxnet_x64_vc14_gpu.7z` and `prebuildbase_win10_x64_vc14.7z`. +5. Download and install [CuDNN](https://developer.nvidia.com/cudnn). 6. Create a folder called ```R-package/inst/libs/x64```. MXNet supports only 64-bit operating systems, so you need the x64 folder. 7. Copy the following shared libraries (.dll files) into the ```R-package/inst/libs/x64``` folder: - * nocudnn/lib/libmxnet.dll. - * The *.dll files in all four subfolders of the nocudnn/3rdparty/ directory. The cudnn and openblas .dll files are in the /bin folders. -You should now have 11 .dll files in the R-package/inst/libs/x64 folder. -8. Copy the ```nocudnn/include/``` folder into ```R-package/inst/```. You should now have a folder called ```R-package/inst/include/``` with three subfolders. +``` +cublas64_80.dll +cudart64_80.dll +cudnn64_5.dll +curand64_80.dll +libgcc_s_seh-1.dll +libgfortran-3.dll +libmxnet.dll +libmxnet.lib +libopenblas.dll +libquadmath-0.dll +nvrtc64_80.dll +``` +These dlls can be found in `prebuildbase_win10_x64_vc14/3rdparty/cudart`, `prebuildbase_win10_x64_vc14/3rdparty/openblas/bin`, `mxnet_x64_vc14_gpu/build`, `mxnet_x64_vc14_gpu/lib` and the `cuDNN` downloaded from NVIDIA. +8. Copy the header files from `dmlc`, `mxnet` and `nnvm` into `./R-package/inst/include`. It should look like: + +``` +./R-package/inst +└── include + ├── dmlc + ├── mxnet + └── nnvm +``` 9. Make sure that R is added to your ```PATH``` in the environment variables. Running the ```where R``` command at the command prompt should return the location. -10. Run ```R CMD INSTALL --no-multiarch R-package```. +10. Now open the Windows CMD and change the directory to the `mxnet` folder. Then use the following commands +to build R package: + +```bat +echo import(Rcpp) > R-package\NAMESPACE +echo import(methods) >> R-package\NAMESPACE +Rscript -e "install.packages('devtools', repos = 'https://cloud.r-project.org')" +cd R-package +Rscript -e "library(devtools); library(methods); options(repos=c(CRAN='https://cloud.r-project.org')); install_deps(dependencies = TRUE)" +cd .. + +R CMD INSTALL --no-multiarch R-package + +Rscript -e "require(mxnet); mxnet:::mxnet.export('R-package')" +rm R-package/NAMESPACE +Rscript -e "require(devtools); install_version('roxygen2', version = '5.0.1', repos = 'https://cloud.r-project.org/', quiet = TRUE)" +Rscript -e "require(roxygen2); roxygen2::roxygenise('R-package')" + +R CMD INSTALL --build --no-multiarch R-package +``` **Note:** To maximize its portability, the MXNet library is built with the Rcpp end. Computers running Windows need [MSVC](https://en.wikipedia.org/wiki/Visual_C%2B%2B) (Microsoft Visual C++) to handle CUDA toolchain compatibilities. diff --git a/docs/how_to/cloud.md b/docs/how_to/cloud.md index 47ea40cf4595..67b28f8b4338 100644 --- a/docs/how_to/cloud.md +++ b/docs/how_to/cloud.md @@ -1,183 +1,183 @@ -# MXNet on the Cloud - -Deep learning can require extremely powerful hardware, often for unpredictable durations of time. -Moreover, _MXNet_ can benefit from both multiple GPUs and multiple machines. -Accordingly, cloud computing, as offered by AWS and others, -is especially well suited to training deep learning models. -Using AWS, we can rapidly fire up multiple machines with multiple GPUs each at will -and maintain the resources for precisely the amount of time needed. - -## Set Up an AWS GPU Cluster from Scratch - -In this document, we provide a step-by-step guide that will teach you -how to set up an AWS cluster with _MXNet_. We show how to: - -- [Use Amazon S3 to host data](#use-amazon-s3-to-host-data) -- [Set up an EC2 GPU instance with all dependencies installed](#set-up-an-ec2-gpu-instance) -- [Build and run MXNet on a single computer](#build-and-run-mxnet-on-a-gpu-instance) -- [Set up an EC2 GPU cluster for distributed training](#set-up-an-ec2-gpu-cluster-for-distributed-training) - -### Use Amazon S3 to Host Data - -Amazon S3 provides distributed data storage which proves especially convenient for hosting large datasets. -To use S3, you need [AWS credentials](http://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSGettingStartedGuide/AWSCredentials.html), -including an `ACCESS_KEY_ID` and a `SECRET_ACCESS_KEY`. - -To use _MXNet_ with S3, set the environment variables `AWS_ACCESS_KEY_ID` and -`AWS_SECRET_ACCESS_KEY` by adding the following two lines in -`~/.bashrc` (replacing the strings with the correct ones): - -```bash -export AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE -export AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY -``` - -There are several ways to upload data to S3. One simple way is to use -[s3cmd](http://s3tools.org/s3cmd). For example: - -```bash -wget http://data.mxnet.io/mxnet/data/mnist.zip -unzip mnist.zip && s3cmd put t*-ubyte s3://dmlc/mnist/ -``` - -### Use Pre-installed EC2 GPU Instance -The [Deep Learning AMI](https://aws.amazon.com/marketplace/pp/B01M0AXXQB?qid=1475211685369&sr=0-1&ref_=srh_res_product_title) is an Amazon Linux image -supported and maintained by Amazon Web Services for use on Amazon Elastic Compute Cloud (Amazon EC2). -It contains [MXNet-v0.9.3 tag](https://github.com/dmlc/mxnet) and the necessary components to get going with deep learning, -including Nvidia drivers, CUDA, cuDNN, Anaconda, Python2 and Python3. -The AMI IDs are the following: - -* us-east-1: ami-e7c96af1 -* us-west-2: ami-dfb13ebf -* eu-west-1: ami-6e5d6808 - -Now you can launch _MXNet_ directly on an EC2 GPU instance. -You can also use [Jupyter](http://jupyter.org) notebook on EC2 machine. -Here is a [good tutorial](https://github.com/dmlc/mxnet-notebooks) -on how to connect to a Jupyter notebook running on an EC2 instance. - -### Set Up an EC2 GPU Instance from Scratch - -_MXNet_ requires the following libraries: - -- C++ compiler with C++11 support, such as `gcc >= 4.8` -- `CUDA` (`CUDNN` in optional) for GPU linear algebra -- `BLAS` (cblas, open-blas, atblas, mkl, or others) for CPU linear algebra -- `opencv` for image augmentations -- `curl` and `openssl` for the ability to read/write to Amazon S3 - -Installing `CUDA` on EC2 instances requires some effort. Caffe has a good -[tutorial](https://github.com/BVLC/caffe/wiki/Install-Caffe-on-EC2-from-scratch-(Ubuntu,-CUDA-7,-cuDNN-3)) -on how to install CUDA 7.0 on Ubuntu 14.04. - -***Note:*** We tried CUDA 7.5 on Nov 7, 2015, but found it problematic. - -You can install the rest using the package manager. For example, on Ubuntu: - -``` -sudo apt-get update -sudo apt-get install -y build-essential git libcurl4-openssl-dev libatlas-base-dev libopencv-dev python-numpy -``` - -The Amazon Machine Image (AMI) [ami-12fd8178](https://console.aws.amazon.com/ec2/v2/home?region=us-east-1#LaunchInstanceWizard:ami=ami-12fd8178) has the packages listed above installed. - - -### Build and Run MXNet on a GPU Instance - -The following commands build _MXNet_ with CUDA/CUDNN, Amazon S3, and distributed -training. - -```bash -git clone --recursive https://github.com/dmlc/mxnet -cd mxnet; cp make/config.mk . -echo "USE_CUDA=1" >>config.mk -echo "USE_CUDA_PATH=/usr/local/cuda" >>config.mk -echo "USE_CUDNN=1" >>config.mk -echo "USE_BLAS=atlas" >> config.mk -echo "USE_DIST_KVSTORE = 1" >>config.mk -echo "USE_S3=1" >>config.mk -make -j$(nproc) -``` - -To test whether everything is installed properly, we can try training a convolutional neural network (CNN) on the MNIST dataset using a GPU: - -```bash -python tests/python/gpu/test_conv.py -``` - -If you've placed the MNIST data on `s3://dmlc/mnist`, you can read the data stored on Amazon S3 directly with the following command: - -```bash -sed -i.bak "s!data_dir = 'data'!data_dir = 's3://dmlc/mnist'!" tests/python/gpu/test_conv.py -``` - -***Note:*** You can use `sudo ln /dev/null /dev/raw1394` to fix the opencv error `libdc1394 error: Failed to initialize libdc1394`. - -### Set Up an EC2 GPU Cluster for Distributed Training - -A cluster consists of multiple computers. -You can use one computer with _MXNet_ installed as the root computer for submitting jobs,and then launch several -slave computers to run the jobs. For example, launch multiple instances using an -AMI, e.g., -[ami-12fd8178](https://console.aws.amazon.com/ec2/v2/home?region=us-east-1#LaunchInstanceWizard:ami=ami-12fd8178), -with dependencies installed. There are two options: - -- Make all slaves' ports accessible (same for the root) by setting type: All TCP, - Source: Anywhere in Configure Security Group. - -- Use the same `pem` as the root computer to access all slave computers, and - then copy the `pem` file into the root computer's `~/.ssh/id_rsa`. If you do this, all slave computers can be accessed with SSH from the root. - -Now, run the CNN on multiple computers. Assume that we are on a working -directory of the root computer, such as `~/train`, and MXNet is built as `~/mxnet`. - -1. Pack the _MXNet_ Python library into this working directory for easy - synchronization: - - ```bash - cp -r ~/mxnet/python/mxnet . - cp ~/mxnet/lib/libmxnet.so mxnet/ - ``` - - And then copy the training program: - - ```bash - cp ~/mxnet/example/image-classification/*.py . - cp -r ~/mxnet/example/image-classification/common . - ``` - -2. Prepare a host file with all slaves private IPs. For example, `cat hosts`: - - ```bash - 172.30.0.172 - 172.30.0.171 - ``` - -3. Assuming that there are two computers, train the CNN using two workers: - - ```bash - ../../tools/launch.py -n 2 -H hosts --sync-dir /tmp/mxnet python train_mnist.py --kv-store dist_sync - ``` - -***Note:*** Sometimes the jobs linger at the slave computers even though you've pressed `Ctrl-c` -at the root node. To terminate them, use the following command: - -```bash -cat hosts | xargs -I{} ssh -o StrictHostKeyChecking=no {} 'uname -a; pgrep python | xargs kill -9' -``` - -***Note:*** The preceding example is very simple to train and therefore isn't a good -benchmark for distributed training. Consider using other [examples](https://github.com/dmlc/mxnet/tree/master/example/image-classification). - -### More Options -#### Use Multiple Data Shards -It is common to pack a dataset into multiple files, especially when working in a distributed environment. -_MXNet_ supports direct loading from multiple data shards. -Put all of the record files into a folder, and point the data path to the folder. - -#### Use YARN and SGE -Although using SSH can be simple when you don't have a cluster scheduling framework, -_MXNet_ is designed to be portable to various platforms. -We provide scripts available in [tracker](https://github.com/dmlc/dmlc-core/tree/master/tracker) -to allow running on other cluster frameworks, including Hadoop (YARN) and SGE. -We welcome contributions from the community of examples of running _MXNet_ on your favorite distributed platform. +# MXNet on the Cloud + +Deep learning can require extremely powerful hardware, often for unpredictable durations of time. +Moreover, _MXNet_ can benefit from both multiple GPUs and multiple machines. +Accordingly, cloud computing, as offered by AWS and others, +is especially well suited to training deep learning models. +Using AWS, we can rapidly fire up multiple machines with multiple GPUs each at will +and maintain the resources for precisely the amount of time needed. + +## Set Up an AWS GPU Cluster from Scratch + +In this document, we provide a step-by-step guide that will teach you +how to set up an AWS cluster with _MXNet_. We show how to: + +- [Use Amazon S3 to host data](#use-amazon-s3-to-host-data) +- [Set up an EC2 GPU instance with all dependencies installed](#set-up-an-ec2-gpu-instance) +- [Build and run MXNet on a single computer](#build-and-run-mxnet-on-a-gpu-instance) +- [Set up an EC2 GPU cluster for distributed training](#set-up-an-ec2-gpu-cluster-for-distributed-training) + +### Use Amazon S3 to Host Data + +Amazon S3 provides distributed data storage which proves especially convenient for hosting large datasets. +To use S3, you need [AWS credentials](http://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSGettingStartedGuide/AWSCredentials.html), +including an `ACCESS_KEY_ID` and a `SECRET_ACCESS_KEY`. + +To use _MXNet_ with S3, set the environment variables `AWS_ACCESS_KEY_ID` and +`AWS_SECRET_ACCESS_KEY` by adding the following two lines in +`~/.bashrc` (replacing the strings with the correct ones): + +```bash +export AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE +export AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY +``` + +There are several ways to upload data to S3. One simple way is to use +[s3cmd](http://s3tools.org/s3cmd). For example: + +```bash +wget http://data.mxnet.io/mxnet/data/mnist.zip +unzip mnist.zip && s3cmd put t*-ubyte s3://dmlc/mnist/ +``` + +### Use Pre-installed EC2 GPU Instance +The [Deep Learning AMI](https://aws.amazon.com/marketplace/pp/B01M0AXXQB?qid=1475211685369&sr=0-1&ref_=srh_res_product_title) is an Amazon Linux image +supported and maintained by Amazon Web Services for use on Amazon Elastic Compute Cloud (Amazon EC2). +It contains [MXNet-v0.9.3 tag](https://github.com/dmlc/mxnet) and the necessary components to get going with deep learning, +including Nvidia drivers, CUDA, cuDNN, Anaconda, Python2 and Python3. +The AMI IDs are the following: + +* us-east-1: ami-e7c96af1 +* us-west-2: ami-dfb13ebf +* eu-west-1: ami-6e5d6808 + +Now you can launch _MXNet_ directly on an EC2 GPU instance. +You can also use [Jupyter](http://jupyter.org) notebook on EC2 machine. +Here is a [good tutorial](https://github.com/dmlc/mxnet-notebooks) +on how to connect to a Jupyter notebook running on an EC2 instance. + +### Set Up an EC2 GPU Instance from Scratch + +_MXNet_ requires the following libraries: + +- C++ compiler with C++11 support, such as `gcc >= 4.8` +- `CUDA` (`CUDNN` in optional) for GPU linear algebra +- `BLAS` (cblas, open-blas, atblas, mkl, or others) for CPU linear algebra +- `opencv` for image augmentations +- `curl` and `openssl` for the ability to read/write to Amazon S3 + +Installing `CUDA` on EC2 instances requires some effort. Caffe has a good +[tutorial](https://github.com/BVLC/caffe/wiki/Install-Caffe-on-EC2-from-scratch-(Ubuntu,-CUDA-7,-cuDNN-3)) +on how to install CUDA 7.0 on Ubuntu 14.04. + +***Note:*** We tried CUDA 7.5 on Nov 7, 2015, but found it problematic. + +You can install the rest using the package manager. For example, on Ubuntu: + +``` +sudo apt-get update +sudo apt-get install -y build-essential git libcurl4-openssl-dev libatlas-base-dev libopencv-dev python-numpy +``` + +The Amazon Machine Image (AMI) [ami-12fd8178](https://console.aws.amazon.com/ec2/v2/home?region=us-east-1#LaunchInstanceWizard:ami=ami-12fd8178) has the packages listed above installed. + + +### Build and Run MXNet on a GPU Instance + +The following commands build _MXNet_ with CUDA/CUDNN, Amazon S3, and distributed +training. + +```bash +git clone --recursive https://github.com/dmlc/mxnet +cd mxnet; cp make/config.mk . +echo "USE_CUDA=1" >>config.mk +echo "USE_CUDA_PATH=/usr/local/cuda" >>config.mk +echo "USE_CUDNN=1" >>config.mk +echo "USE_BLAS=atlas" >> config.mk +echo "USE_DIST_KVSTORE = 1" >>config.mk +echo "USE_S3=1" >>config.mk +make -j$(nproc) +``` + +To test whether everything is installed properly, we can try training a convolutional neural network (CNN) on the MNIST dataset using a GPU: + +```bash +python example/image-classification/train_mnist.py +``` + +If you've placed the MNIST data on `s3://dmlc/mnist`, you can read the data stored on Amazon S3 directly with the following command: + +```bash +sed -i.bak "s!data_dir = 'data'!data_dir = 's3://dmlc/mnist'!" example/image-classification/train_mnist.py +``` + +***Note:*** You can use `sudo ln /dev/null /dev/raw1394` to fix the opencv error `libdc1394 error: Failed to initialize libdc1394`. + +### Set Up an EC2 GPU Cluster for Distributed Training + +A cluster consists of multiple computers. +You can use one computer with _MXNet_ installed as the root computer for submitting jobs,and then launch several +slave computers to run the jobs. For example, launch multiple instances using an +AMI, e.g., +[ami-12fd8178](https://console.aws.amazon.com/ec2/v2/home?region=us-east-1#LaunchInstanceWizard:ami=ami-12fd8178), +with dependencies installed. There are two options: + +- Make all slaves' ports accessible (same for the root) by setting type: All TCP, + Source: Anywhere in Configure Security Group. + +- Use the same `pem` as the root computer to access all slave computers, and + then copy the `pem` file into the root computer's `~/.ssh/id_rsa`. If you do this, all slave computers can be accessed with SSH from the root. + +Now, run the CNN on multiple computers. Assume that we are on a working +directory of the root computer, such as `~/train`, and MXNet is built as `~/mxnet`. + +1. Pack the _MXNet_ Python library into this working directory for easy + synchronization: + + ```bash + cp -r ~/mxnet/python/mxnet . + cp ~/mxnet/lib/libmxnet.so mxnet/ + ``` + + And then copy the training program: + + ```bash + cp ~/mxnet/example/image-classification/*.py . + cp -r ~/mxnet/example/image-classification/common . + ``` + +2. Prepare a host file with all slaves private IPs. For example, `cat hosts`: + + ```bash + 172.30.0.172 + 172.30.0.171 + ``` + +3. Assuming that there are two computers, train the CNN using two workers: + + ```bash + ../../tools/launch.py -n 2 -H hosts --sync-dir /tmp/mxnet python train_mnist.py --kv-store dist_sync + ``` + +***Note:*** Sometimes the jobs linger at the slave computers even though you've pressed `Ctrl-c` +at the root node. To terminate them, use the following command: + +```bash +cat hosts | xargs -I{} ssh -o StrictHostKeyChecking=no {} 'uname -a; pgrep python | xargs kill -9' +``` + +***Note:*** The preceding example is very simple to train and therefore isn't a good +benchmark for distributed training. Consider using other [examples](https://github.com/dmlc/mxnet/tree/master/example/image-classification). + +### More Options +#### Use Multiple Data Shards +It is common to pack a dataset into multiple files, especially when working in a distributed environment. +_MXNet_ supports direct loading from multiple data shards. +Put all of the record files into a folder, and point the data path to the folder. + +#### Use YARN and SGE +Although using SSH can be simple when you don't have a cluster scheduling framework, +_MXNet_ is designed to be portable to various platforms. +We provide scripts available in [tracker](https://github.com/dmlc/dmlc-core/tree/master/tracker) +to allow running on other cluster frameworks, including Hadoop (YARN) and SGE. +We welcome contributions from the community of examples of running _MXNet_ on your favorite distributed platform. diff --git a/docs/how_to/env_var.md b/docs/how_to/env_var.md index 40423b55b5ee..cb993192bc7f 100644 --- a/docs/how_to/env_var.md +++ b/docs/how_to/env_var.md @@ -3,85 +3,116 @@ Environment Variables MXNet has several settings that you can change with environment variables. Typically, you wouldn't need to change these settings, but they are listed here for reference. +For example, you can set these environment variables in Linux or macOS as follows: +``` +export MXNET_GPU_WORKER_NTHREADS=3 +``` + ## Set the Number of Threads -* MXNET_GPU_WORKER_NTHREADS (default=2) - - The maximum number of threads that do the computation job on each GPU. -* MXNET_GPU_COPY_NTHREADS (default=1) - - The maximum number of threads that do the memory copy job on each GPU. -* MXNET_CPU_WORKER_NTHREADS (default=1) - - The maximum number of threads that do the CPU computation job. -* MXNET_CPU_PRIORITY_NTHREADS (default=4) - - The number of threads given to prioritized CPU jobs. -* MXNET_CPU_NNPACK_NTHREADS (default=4) - - The number of threads used for NNPACK. +* MXNET_GPU_WORKER_NTHREADS + - Values: Int ```(default=2)``` + - The maximum number of threads to use on each GPU. This parameter is used to parallelize the computation within a single GPU card. +* MXNET_GPU_COPY_NTHREADS + - Values: Int ```(default=1)``` + - The maximum number of concurrent threads that do the memory copy job on each GPU. +* MXNET_CPU_WORKER_NTHREADS + - Values: Int ```(default=1)``` + - The maximum number of scheduling threads on CPU. It specifies how many operators can be run in parallel. +* MXNET_CPU_PRIORITY_NTHREADS + - Values: Int ```(default=4)``` + - The number of threads given to prioritized CPU jobs. +* MXNET_CPU_NNPACK_NTHREADS + - Values: Int ```(default=4)``` + - The number of threads used for NNPACK. NNPACK package aims to provide high-performance implementations of some layers for multi-core CPUs. Checkout [NNPACK](http://mxnet.io/how_to/nnpack.html) to know more about it. ## Memory Options -* MXNET_EXEC_ENABLE_INPLACE (default=true) - - Whether to enable in-place optimization in symbolic execution. -* NNVM_EXEC_MATCH_RANGE (default=16) - - The rough matching scale in the symbolic execution memory allocator. +* MXNET_EXEC_ENABLE_INPLACE + - Values: true or false ```(default=true)``` + - Whether to enable in-place optimization in symbolic execution. Checkout [in-place optimization](http://mxnet.io/architecture/note_memory.html#in-place-operations) to know more about it. +* NNVM_EXEC_MATCH_RANGE + - Values: Int ```(default=16)``` + - The approximate matching scale in the symbolic execution memory allocator. - Set this to 0 if you don't want to enable memory sharing between graph nodes(for debugging purposes). -* MXNET_EXEC_NUM_TEMP (default=1) - - The maximum number of temp workspaces to allocate to each device. + - This variable has impact on the result of memory planning. So, MXNet sweep between [1, NNVM_EXEC_MATCH_RANGE], and selects the best value. +* MXNET_EXEC_NUM_TEMP + - Values: Int ```(default=1)``` + - The maximum number of temporary workspaces to allocate to each device. This controls space replicas and in turn reduces the memory usage. - Setting this to a small number can save GPU memory. It will also likely decrease the level of parallelism, which is usually acceptable. -* MXNET_GPU_MEM_POOL_RESERVE (default=5) + - MXNet internally uses graph coloring algorithm to [optimize memory consumption](http://mxnet.io/architecture/note_memory.html). + - This parameter is also used to get number of matching colors in graph and in turn how much parallelism one can get in each GPU. Color based match usually costs more memory but also enables more parallelism. +* MXNET_GPU_MEM_POOL_RESERVE + - Values: Int ```(default=5)``` - The percentage of GPU memory to reserve for things other than the GPU array, such as kernel launch or cudnn handle space. - If you see a strange out-of-memory error from the kernel launch, after multiple iterations, try setting this to a larger value. ## Engine Type -* MXNET_ENGINE_TYPE (default=ThreadedEnginePerDevice) +* MXNET_ENGINE_TYPE + - Values: String ```(default=ThreadedEnginePerDevice)``` - The type of underlying execution engine of MXNet. - Choices: - - NaiveEngine: A very simple engine that uses the master thread to do computation. + - NaiveEngine: A very simple engine that uses the master thread to do the computation synchronously. Setting this engine disables multi-threading. You can use this type for debugging in case of any error. Backtrace will give you the series of calls that lead to the error. Remember to set MXNET_ENGINE_TYPE back to empty after debugging. - ThreadedEngine: A threaded engine that uses a global thread pool to schedule jobs. - - ThreadedEnginePerDevice: A threaded engine that allocates thread per GPU. + - ThreadedEnginePerDevice: A threaded engine that allocates thread per GPU and executes jobs asynchronously. ## Execution Options -* MXNET_EXEC_BULK_EXEC_INFERENCE (default=1) +* MXNET_EXEC_BULK_EXEC_INFERENCE + - Values: 0(false) or 1(true) ```(default=1)``` - If set to `1`, during inference MXNet executes the entire computation graph in bulk mode, which reduces kernel launch gaps in between symbolic operators. -* MXNET_EXEC_BULK_EXEC_TRAIN (default=1) +* MXNET_EXEC_BULK_EXEC_TRAIN + - Values: 0(false) or 1(true) ```(default=1)``` - If set to `1`, during training MXNet executes the computation graph as several subgraphs in bulk mode. -* MXNET_EXEC_BULK_EXEC_MAX_NODE_TRAIN (default=15) +* MXNET_EXEC_BULK_EXEC_MAX_NODE_TRAIN + - Values: Int ```(default=15)``` - The maximum number of nodes in the subgraph executed in bulk during training(not inference). Setting this to a larger number may reduce the degree of parallelism for multi-GPU training. ## Control the Data Communication -* MXNET_KVSTORE_REDUCTION_NTHREADS (default=4) +* MXNET_KVSTORE_REDUCTION_NTHREADS + - Values: Int ```(default=4)``` - The number of CPU threads used for summing big arrays. -* MXNET_KVSTORE_BIGARRAY_BOUND (default=1e6) - - The minimum size of a "big array." - - When the array size is bigger than this threshold, MXNET_KVSTORE_REDUCTION_NTHREADS threads are used for reduction. -* MXNET_ENABLE_GPU_P2P (default=1) - - If true, MXNet tries to use GPU peer-to-peer communication, if available, - when kvstore's type is `device` +* MXNET_KVSTORE_BIGARRAY_BOUND + - Values: Int ```(default=1000000)``` + - The minimum size of a "big array". + - When the array size is bigger than this threshold, MXNET_KVSTORE_REDUCTION_NTHREADS threads are used for reduction. + - This parameter is also used as a load balancer in kvstore. It controls when to partition a single weight to all the servers. If the size of a single weight is less than MXNET_KVSTORE_BIGARRAY_BOUND then, it is sent to a single randomly picked server otherwise it is partitioned to all the servers. +* MXNET_ENABLE_GPU_P2P + - Values: 0(false) or 1(true) ```(default=1)``` + - If true, MXNet tries to use GPU peer-to-peer communication, if available on your device, + when kvstore's type is `device`. ## Memonger -* MXNET_BACKWARD_DO_MIRROR (default=0) - - whether do `mirror` during training for saving device memory. - - when set to `1`, then during forward propagation, graph executor will `mirror` some layer's feature map and drop others, but it will re-compute this dropped feature maps when needed. `MXNET_BACKWARD_DO_MIRROR=1` will save 30%~50% of device memory, but retains about 95% of running speed. - - one extension of `mirror` in MXNet is called [memonger technology](https://arxiv.org/abs/1604.06174), it will only use O(sqrt(N)) memory at 75% running speed. +* MXNET_BACKWARD_DO_MIRROR + - Values: 0(false) or 1(true) ```(default=0)``` + - MXNet uses mirroring concept to save memory. Normally backward pass needs some forward input and it is stored in memory but you can choose to release this saved input and recalculate it in backward pass when needed. This basically trades off the computation for memory consumption. + - This parameter decides whether to do `mirror` during training for saving device memory. + - When set to `1`, during forward propagation, graph executor will `mirror` some layer's feature map and drop others, but it will re-compute this dropped feature maps when needed. + - `MXNET_BACKWARD_DO_MIRROR=1` will save 30%~50% of device memory, but retains about 95% of running speed. + - One extension of `mirror` in MXNet is called [memonger technology](https://arxiv.org/abs/1604.06174), it will only use O(sqrt(N)) memory at 75% running speed. Checkout the code [here](https://github.com/dmlc/mxnet-memonger). ## Control the profiler When USE_PROFILER is enabled in Makefile or CMake, the following environments can be used to profile the application without changing code. Execution options may affect the granularity of profiling result. If you need profiling result of every operator, please set MXNET_EXEC_BULK_EXEC_INFERENCE and MXNET_EXEC_BULK_EXEC_TRAIN to 0. -* MXNET_PROFILER_AUTOSTART (default=0) +* MXNET_PROFILER_AUTOSTART + - Values: 0(false) or 1(true) ```(default=0)``` - Set to 1, MXNet starts the profiler automatically. The profiling result is stored into profile.json in the working directory. -* MXNET_PROFILER_MODE (default=0) +* MXNET_PROFILER_MODE + - Values: 0(false) or 1(true) ```(default=0)``` - If set to '0', profiler records the events of the symbolic operators. - If set to '1', profiler records the events of all operators. ## Other Environment Variables -* MXNET_CUDNN_AUTOTUNE_DEFAULT (default=0) - - The default value of cudnn_tune for convolution layers. - - Auto tuning is turn off by default. For benchmarking, set this to 1 to turn it on by default. +* MXNET_CUDNN_AUTOTUNE_DEFAULT + - Values: 0(false) or 1(true) ```(default=0)``` + - The default value of cudnn auto tunning for convolution layers. + - Auto tuning is turned off by default. For benchmarking, set this to 1 to turn it on by default. Settings for Minimum Memory Usage --------------------------------- @@ -92,4 +123,4 @@ Settings for More GPU Parallelism --------------------------------- - Set ```MXNET_GPU_WORKER_NTHREADS``` to a larger number (e.g., 2) - To reduce memory usage, consider setting ```MXNET_EXEC_NUM_TEMP```. -- This might not speed things up, especially for image applications, because GPU is usually fully utilized even with serialized jobs. + - This might not speed things up, especially for image applications, because GPU is usually fully utilized even with serialized jobs. diff --git a/docs/how_to/finetune.md b/docs/how_to/finetune.md index 79d06cb5bb77..f6c164c28db9 100644 --- a/docs/how_to/finetune.md +++ b/docs/how_to/finetune.md @@ -45,6 +45,8 @@ training set, and the rest for the validation set. We resize images into 256x256 size and pack them into the rec file. The scripts to prepare the data is as following. +> In order to successfully run the following bash script on Windows please use https://cygwin.com/install.html . + ```sh wget http://www.vision.caltech.edu/Image_Datasets/Caltech256/256_ObjectCategories.tar tar -xf 256_ObjectCategories.tar diff --git a/docs/how_to/index.md b/docs/how_to/index.md index c6a8cf67ad1d..4920e1cd3f78 100644 --- a/docs/how_to/index.md +++ b/docs/how_to/index.md @@ -11,8 +11,6 @@ and full working examples, visit the [tutorials section](../tutorials/index.md). * [How do I work with variable-length input in MXNet (bucketing)?](http://mxnet.io/how_to/bucketing.html) -* [How do I create new operators with MXNet?](new_op.md) - * [How do I visualize neural networks as computation graphs?](http://mxnet.io/how_to/visualize_graph.html) @@ -38,6 +36,10 @@ and full working examples, visit the [tutorials section](../tutorials/index.md). * [How do I run MXNet on a Raspberry Pi for computer vision?](http://mxnet.io/tutorials/embedded/wine_detector.html) +* [How do I run Keras 1.2.2 with mxnet backend?](https://github.com/dmlc/keras/wiki/Installation) + +* [How to convert MXNet models to Apple CoreML format?](https://github.com/apache/incubator-mxnet/tree/master/tools/coreml) + ## Extend and Contribute to MXNet * [How do I join the MXNet development discussion?](http://mxnet.io/community/mxnet_channels.html) @@ -49,3 +51,12 @@ and full working examples, visit the [tutorials section](../tutorials/index.md). * [How do I set MXNet's environmental variables?](http://mxnet.io/how_to/env_var.html) * [How do I use MXNet as a front end for Torch?](http://mxnet.io/how_to/torch.html) + +## Questions about Using MXNet +If you are not sure of how to use MXNet for something, or have questions about applying it to a particular kind of problem, please post a question at [Stackoverflow](http://stackoverflow.com/) with tag - ```mxnet```. You can view StackOverflow questions about mxnet [here](http://stackoverflow.com/questions/tagged/mxnet). + +## Issue Tracker +We track bugs and new feature requests in the MXNet Github repo in the issues folder: [mxnet/issues](https://github.com/dmlc/mxnet/issues). + +## Roadmap +MXNet is evolving fast. To see what's next and what we are working on internally, go to the [MXNet Roadmap](https://github.com/dmlc/mxnet/labels/Roadmap). diff --git a/docs/how_to/multi_devices.md b/docs/how_to/multi_devices.md index 7e9a5b31445d..327206224383 100644 --- a/docs/how_to/multi_devices.md +++ b/docs/how_to/multi_devices.md @@ -37,13 +37,13 @@ gradients are then summed over all GPUs before updating the model. If a machine has one or more GPU cards installed, then each card is labeled by a number starting from 0. To use a particular GPU, one can either -specify the context `ctx` in code +specify the context `context` in code or pass `--gpus` at the command line. For example, to use GPU 0 and 2 in python, -one can typically create a model with +one can typically create a module with ```python import mxnet as mx -model = mx.model.FeedForward(ctx=[mx.gpu(0), mx.gpu(2)], ...) +module = mx.module.Module(context=[mx.gpu(0), mx.gpu(2)], ...) ``` while if the program accepts a `--gpus` flag (as seen in [example/image-classification](https://github.com/dmlc/mxnet/tree/master/example/image-classification)), @@ -57,7 +57,7 @@ If the available GPUs are not all equally powerful, we can partition the workload accordingly. For example, if GPU 0 is 3 times faster than GPU 2, then we might use the workload option `work_load_list=[3, 1]`, -see [model.fit](../api/python/model.html#mxnet.model.FeedForward.fit) +see [Module](../api/python/module.html#mxnet.module.Module) for more details. Training with multiple GPUs should yield the same results @@ -101,7 +101,7 @@ When using a large number of GPUs, e.g. >=4, we suggest using `device` for bette ### How to Launch a Job > To use distributed training, we need to compile with `USE_DIST_KVSTORE=1` -> (see [MXNet installation guide](http://mxnet.io/get_started/setup.html) for more options). +> (see [MXNet installation guide](http://mxnet.io/get_started/install.html) for more options). Launching a distributed job is a bit different from running on a single machine. MXNet provides diff --git a/docs/model_zoo/index.md b/docs/model_zoo/index.md index a5a2b327937a..19811f22552d 100644 --- a/docs/model_zoo/index.md +++ b/docs/model_zoo/index.md @@ -32,7 +32,7 @@ Convolutional neural networks are the state-of-art architecture for many image a * [Places2](http://places2.csail.mit.edu/download.html): There are 1.6 million train images from 365 scene categories in the Places365-Standard, which are used to train the Places365 CNNs. There are 50 images per category in the validation set and 900 images per category in the testing set. Compared to the train set of Places365-Standard, the train set of Places365-Challenge has 6.2 million extra images, leading to totally 8 million train images for the Places365 challenge 2016. The validation set and testing set are the same as the Places365-Standard. * [Multimedia Commons](https://aws.amazon.com/public-datasets/multimedia-commons/): YFCC100M (99.2 million images and 0.8 million videos from Flickr) and supplemental material (pre-extracted features, additional annotations). -For instructions on using these models, see [the python tutorial on using pre-trained ImageNet models](http://mxnet.io/tutorials/python/predict_imagenet.html). +For instructions on using these models, see [the python tutorial on using pre-trained ImageNet models](https://mxnet.incubator.apache.org/tutorials/python/predict_image.html). | Model Definition | Dataset | Model Weights | Research Basis | Contributors | | --- | --- | --- | --- | --- | @@ -53,19 +53,19 @@ For instructions on using these models, see [the python tutorial on using pre-tr ## Recurrent Neural Networks (RNNs) including LSTMs -MXNet supports many types of recurrent neural networks (RNNs), including Long Short-Term Memory ([LSTM](http://deeplearning.cs.cmu.edu/pdfs/Hochreiter97_lstm.pdf)) +MXNet supports many types of recurrent neural networks (RNNs), including Long Short-Term Memory ([LSTM](http://www.bioinf.jku.at/publications/older/2604.pdf)) and Gated Recurrent Units (GRU) networks. Some available datasets include: -* [Penn Treebank (PTB)](https://www.cis.upenn.edu/~treebank/): Text corpus with ~1 million words. Vocabulary is limited to 10,000 words. The task is predicting downstream words/characters. +* [Penn Treebank (PTB)](https://catalog.ldc.upenn.edu/LDC95T7): Text corpus with ~1 million words. Vocabulary is limited to 10,000 words. The task is predicting downstream words/characters. * [Shakespeare](http://cs.stanford.edu/people/karpathy/char-rnn/): Complete text from Shakespeare's works. -* [IMDB reviews](https://s3.amazonaws.com/text-datasets): 25,000 movie reviews, labeled as positive or negative +* [IMDB reviews](https://getsatisfaction.com/imdb/topics/imdb-data-now-available-in-amazon-s3): 25,000 movie reviews, labeled as positive or negative * [Facebook bAbI](https://research.facebook.com/researchers/1543934539189348): As a set of 20 question & answer tasks, each with 1,000 training examples. * [Flickr8k, COCO](http://mscoco.org/): Images with associated caption (sentences). Flickr8k consists of 8,092 images captioned by AmazonTurkers with ~40,000 captions. COCO has 328,000 images, each with 5 captions. The COCO images also come with labeled objects using segmentation algorithms. | Model Definition | Dataset | Model Weights | Research Basis | Contributors | | --- | --- | --- | --- | --- | -| LSTM - Image Captioning | Flickr8k, MS COCO | | [Vinyals et al.., 2015](https://arxiv.org/pdf/ 1411.4555v2.pdf) | @... | +| LSTM - Image Captioning | Flickr8k, MS COCO | | [Vinyals et al.., 2015](https://arxiv.org/pdf/1411.4555.pdf) | @... | | LSTM - Q&A System| bAbl | | [Weston et al.., 2015](https://arxiv.org/pdf/1502.05698v10.pdf) | | | LSTM - Sentiment Analysis| IMDB | | [Li et al.., 2015](http://arxiv.org/pdf/1503.00185v5.pdf) | | diff --git a/docs/mxdoc.py b/docs/mxdoc.py index db6bdc699a11..2726a1ca0676 100644 --- a/docs/mxdoc.py +++ b/docs/mxdoc.py @@ -1,10 +1,33 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + """A sphnix-doc plugin to build mxnet docs""" import subprocess import re import os import json +import sys from recommonmark import transform import pypandoc +import StringIO +import contextlib + +# white list to evaluate the code block output, such as ['tutorials/gluon'] +_EVAL_WHILTELIST = [] # start or end of a code block _CODE_MARK = re.compile('^([ ]*)```([\w]*)') @@ -17,7 +40,6 @@ 'perl' : ('pl', '#'), 'cpp' : ('cc', '//'), 'bash' : ('sh', '#')} - _LANG_SELECTION_MARK = 'INSERT SELECTION BUTTONS' _SRC_DOWNLOAD_MARK = 'INSERT SOURCE DOWNLOAD BUTTONS' @@ -157,12 +179,20 @@ def _get_lang_selection_btn(langs): btngroup += '
    \n
    ' return btngroup -def _get_blocks(lang, lines): +def _get_blocks(lines): + """split lines into code and non-code blocks + + Returns + ------- + iterator of (bool, str, list of str) + - if it is a code block + - source language + - lines of source + """ cur_block = [] + pre_lang = None pre_in_code = None for (l, in_code, cur_lang, _) in _parse_code_lines(lines): - if in_code and cur_lang != lang: - in_code = False if in_code != pre_in_code: if pre_in_code and len(cur_block) >= 2: cur_block = cur_block[1:-1] # remove ``` @@ -179,20 +209,67 @@ def _get_blocks(lang, lines): else: break if len(cur_block): - yield (pre_in_code, cur_block) + yield (pre_in_code, pre_lang, cur_block) cur_block = [] cur_block.append(l) + pre_lang = cur_lang pre_in_code = in_code if len(cur_block): - yield (pre_in_code, cur_block) + yield (pre_in_code, pre_lang, cur_block) + +def _get_mk_code_block(src, lang): + """Return a markdown code block + + E.g. + ```python + import mxnet + ```` + """ + if lang is None: + lang = '' + return '```'+lang+'\n'+src.rstrip()+'\n'+'```\n' + +@contextlib.contextmanager +def _string_io(): + oldout = sys.stdout + olderr = sys.stderr + strio = StringIO.StringIO() + sys.stdout = strio + sys.stderr = strio + yield strio + sys.stdout = oldout + sys.stderr = olderr + +def _get_python_block_output(src, global_dict, local_dict): + """Evaluate python source codes + + Returns + (bool, str): + - True if success + - output + """ + src = '\n'.join([l for l in src.split('\n') + if not l.startswith('%') and not 'plt.show()' in l]) + ret_status = True + err = '' + with _string_io() as s: + try: + exec(src, global_dict, global_dict) + except Exception as e: + err = str(e) + ret_status = False + return (ret_status, s.getvalue()+err) def _get_jupyter_notebook(lang, lines): cells = [] - for in_code, lines in _get_blocks(lang, lines): + for in_code, blk_lang, lines in _get_blocks(lines): + if blk_lang != lang: + in_code = False + src = '\n'.join(lines) cell = { "cell_type": "code" if in_code else "markdown", "metadata": {}, - "source": '\n'.join(lines) + "source": src } if in_code: cell.update({ @@ -214,11 +291,13 @@ def _get_source(lang, lines): out.append('') for l in lines: if in_code: - out.append(l) + if '%matplotlib' not in l: + out.append(l) else: if ('
    ' in l or '
    ' in l or '' in l or - '' in l): + '' in l or + '%matplotlib' in l ): continue out.append(cmt+l) if in_code: @@ -229,16 +308,16 @@ def _get_source(lang, lines): def _get_src_download_btn(out_prefix, langs, lines): btn = '
    \n' for lang in langs: - ipynb = out_prefix + '_' + lang + '.ipynb' + ipynb = out_prefix + if lang == 'python': + ipynb += '.ipynb' + else: + ipynb += '_' + lang + '.ipynb' with open(ipynb, 'w') as f: json.dump(_get_jupyter_notebook(lang, lines), f) - src = out_prefix + '.' + _LANGS[lang][0] - with open(src, 'w') as f: - f.write('\n'.join(_get_source(lang, lines))) - for f in [ipynb, src]: - f = f.split('/')[-1] - btn += '\n' % (f, f) + f = ipynb.split('/')[-1] + btn += '' % (f, f, f) btn += '
    \n' return btn @@ -249,6 +328,8 @@ def add_buttons(app, docname, source): os.makedirs(dirname) for i,j in enumerate(source): + local_dict = {} + global_dict = {} lines = j.split('\n') langs = set([l for (_, _, l, _) in _parse_code_lines(lines) if l is not None and l in _LANGS]) @@ -257,11 +338,26 @@ def add_buttons(app, docname, source): if _SRC_DOWNLOAD_MARK in l: lines[k] = _get_src_download_btn( out_prefix, langs, lines) - # then add lang buttons - for k,l in enumerate(lines): - if _LANG_SELECTION_MARK in l: - lines[k] = _get_lang_selection_btn(langs) - source[i] = '\n'.join(lines) + # # then add lang buttons + # for k,l in enumerate(lines): + # if _LANG_SELECTION_MARK in l: + # lines[k] = _get_lang_selection_btn(langs) + + output = '' + for in_code, lang, lines in _get_blocks(lines): + src = '\n'.join(lines)+'\n' + if in_code: + output += _get_mk_code_block(src, lang) + if lang == 'python' and any([w in docname for w in _EVAL_WHILTELIST]): + status, blk_out = _get_python_block_output(src, global_dict, local_dict) + if len(blk_out): + output += '
    Output:
    \n\n' + output += _get_mk_code_block(blk_out, 'results') + else: + output += src + source[i] = output + + # source[i] = '\n'.join(lines) def setup(app): app.connect("builder-inited", build_mxnet) diff --git a/docs/tutorials/basic/data.md b/docs/tutorials/basic/data.md index 213b45f69062..d4db7d0de1b6 100644 --- a/docs/tutorials/basic/data.md +++ b/docs/tutorials/basic/data.md @@ -1,21 +1,39 @@ # Iterators - Loading data -In this tutorial we focus on how to feed data into a training or inference program. +In this tutorial, we focus on how to feed data into a training or inference program. Most training and inference modules in MXNet accept data iterators, -which simplifies this procedure, especially when reading large datasets. +which simplifies this procedure, especially when reading large datasets. Here we discuss the API conventions and several provided iterators. +## Prerequisites + +To complete this tutorial, we need: + +- MXNet. See the instructions for your operating system in [Setup and Installation](http://mxnet.io/get_started/install.html). + +- [OpenCV Python library](http://opencv.org/opencv-3-2.html), [Python Requests](http://docs.python-requests.org/en/master/), [Matplotlib](https://matplotlib.org/) and [Jupyter Notebook](http://jupyter.org/index.html). + +``` +$ pip install opencv-python requests matplotlib jupyter +``` +- Set the environment variable `MXNET_HOME` to the root of the MXNet source folder. + +``` +$ git clone https://github.com/dmlc/mxnet ~/mxnet +$ export MXNET_HOME='~/mxnet' +``` + ## MXNet Data Iterator Data Iterators in *MXNet* are similar to Python iterator objects. -In Python the function `iter` allows fetching items sequentially by calling `next()` on +In Python, the function `iter` allows fetching items sequentially by calling `next()` on iterable objects such as a Python `list`. Iterators provide an abstract interface for traversing various types of iterable collections without needing to expose details about the underlying data source. In MXNet, data iterators return a batch of data as `DataBatch` on each call to `next`. -A `DataBatch` often contains *n* training examples and their corresponding labels. Here *n* is the `batch_size` of the iterator. At the end of the data stream when there is no more data to read, the iterator raises ``StopIteration`` exception like Python `iter`. +A `DataBatch` often contains *n* training examples and their corresponding labels. Here *n* is the `batch_size` of the iterator. At the end of the data stream when there is no more data to read, the iterator raises ``StopIteration`` exception like Python `iter`. The structure of `DataBatch` is defined [here](http://mxnet.io/api/python/io.html#mxnet.io.DataBatch). -Information such as name, shape, type and layout on each training example and their corresponding label can be provided as Data descriptors `DataDesc` objects via the `provide_data` and `provide_label` properties in `DataBatch`. +Information such as name, shape, type and layout on each training example and their corresponding label can be provided as `DataDesc` data descriptor objects via the `provide_data` and `provide_label` properties in `DataBatch`. The structure of `DataDesc` is defined [here](http://mxnet.io/api/python/io.html#mxnet.io.DataDesc). All IO in MXNet is handled via `mx.io.DataIter` and its subclasses. In this tutorial, we'll discuss a few commonly used iterators provided by MXNet. @@ -62,7 +80,7 @@ for batch in data_iter: ``` ## Custom Iterator -When the built-in iterators do not suit your application needs, +When the built-in iterators do not suit your application needs, you can create your own custom data iterator. An iterator in _MXNet_ should @@ -141,10 +159,10 @@ two variables for input data: *data* for the training examples and *softmax_label* contains the respective labels and the *softmax_output*. The *data* variables are called free variables in MXNet's Symbol API. -To execute a Symbol, they need to bound with data. +To execute a Symbol, they need to be bound with data. [Click here learn more about Symbol](http://mxnet.io/tutorials/basic/symbol.html). -We use the data iterator to feed examples to a neural networks via MXNet's `module` API. +We use the data iterator to feed examples to a neural network via MXNet's `module` API. [Click here to learn more about Module](http://mxnet.io/tutorials/basic/module.html). @@ -167,7 +185,7 @@ Record IO is a file format used by MXNet for data IO. It compactly packs the data for efficient read and writes from distributed file system like Hadoop HDFS and AWS S3. You can learn more about the design of `RecordIO` [here](http://mxnet.io/architecture/note_data_loading.html). -MXNet provides [__`MXRecordIO`__](http://mxnet.io/api/python/io.html#mxnet.recordio.MXRecordIO) +MXNet provides [__`MXRecordIO`__](http://mxnet.io/api/python/io.html#mxnet.recordio.MXRecordIO) and [__`MXIndexedRecordIO`__](http://mxnet.io/api/python/io.html#mxnet.recordio.MXIndexedRecordIO) for sequential access of data and random access of the data. @@ -183,7 +201,7 @@ for i in range(5): record.close() ``` -We can read the data back by opening the file with a option `r` as below: +We can read the data back by opening the file with an option `r` as below: ```python record = mx.recordio.MXRecordIO('tmp.rec', 'r') @@ -224,10 +242,10 @@ record.keys ### Packing and Unpacking data -Each record in a .rec file can contain arbitrary binary data. However most deep learning tasks require data to be input in label/data format. +Each record in a .rec file can contain arbitrary binary data. However, most deep learning tasks require data to be input in label/data format. The `mx.recordio` package provides a few utility functions for such operations, namely: `pack`, `unpack`, `pack_img`, and `unpack_img`. -#### Packing/Unpacking Binary Data. +#### Packing/Unpacking Binary Data [__`pack`__](http://mxnet.io/api/python/io.html#mxnet.recordio.pack) and [__`unpack`__](http://mxnet.io/api/python/io.html#mxnet.recordio.unpack) are used for storing float (or 1d array of float) label and binary data. The data is packed along with a header. The header structure is defined [here](http://mxnet.io/api/python/io.html#mxnet.recordio.IRHeader). @@ -250,7 +268,7 @@ print(mx.recordio.unpack(s1)) print(mx.recordio.unpack(s2)) ``` -#### Packing/Unpacking Image Data. +#### Packing/Unpacking Image Data MXNet provides [__`pack_img`__](http://mxnet.io/api/python/io.html#mxnet.recordio.pack_img) and [__`unpack_img`__](http://mxnet.io/api/python/io.html#mxnet.recordio.unpack_img) to pack/unpack image data. Records packed by `pack_img` can be loaded by `mx.io.ImageRecordIter`. @@ -274,7 +292,7 @@ An example of how to use the script for converting to *RecordIO* format is shown ## Image IO -In this section we will learn how to preprocess and load image data in MXNet. +In this section, we will learn how to preprocess and load image data in MXNet. There are 4 ways of loading image data in MXNet. 1. Using [__mx.image.imdecode__](http://mxnet.io/api/python/io.html#mxnet.image.imdecode) to load raw image files. @@ -283,39 +301,31 @@ There are 4 ways of loading image data in MXNet. 4. Creating a Custom iterator inheriting `mx.io.DataIter` -First, set the environment variable `MXNET_HOME` to the root of the MXNet source folder: - -```python -# change this to your mxnet location -MXNET_HOME = '/scratch/mxnet' -``` - ### Preprocessing Images Images can be preprocessed in different ways. We list some of them below: - Using `mx.io.ImageRecordIter` which is fast but not very flexible. It is great for simple tasks like image recognition but won't work for more complex tasks like detection and segmentation. - Using `mx.recordio.unpack_img` (or `cv2.imread`, `skimage`, etc) + `numpy` is flexible but slow due to Python Global Interpreter Lock (GIL). - Using MXNet provided `mx.image` package. It stores images in [__`NDArray`__](http://mxnet.io/tutorials/basic/ndarray.html) format and leverages MXNet's [dependency engine](http://mxnet.io/architecture/note_engine.html) to automatically parallelize processing and circumvent GIL. -Below, we demonstrrate some of the frequently used preprocessing routines provided by the `mx.image` package. +Below, we demonstrate some of the frequently used preprocessing routines provided by the `mx.image` package. Let's download sample images that we can work with. ```python -fname = mx.test_utils.download(url='http://data.mxnet.io/data/test_images.tar.gz') +fname = mx.test_utils.download(url='http://data.mxnet.io/data/test_images.tar.gz', dirname='data', overwrite=False) tar = tarfile.open(fname) -tar.extractall() +tar.extractall(path='./data') tar.close() ``` #### Loading raw images -`mx.image.imdecode` lets us load the images. `imdecode` provides a similar interface to ``OpenCV``. -**Note: ** You will still need ``OpenCV``(not the CV2 Python library) installed to use `mx.image.imdecode`. +`mx.image.imdecode` lets us load the images. `imdecode` provides a similar interface to ``OpenCV``. +**Note:** You will still need ``OpenCV``(not the CV2 Python library) installed to use `mx.image.imdecode`. ```python -import cv2 -img = mx.image.imdecode(open('test_images/ILSVRC2012_val_00000001.JPEG').read()) +img = mx.image.imdecode(open('data/test_images/ILSVRC2012_val_00000001.JPEG').read()) plt.imshow(img.asnumpy()); plt.show() ``` @@ -338,7 +348,7 @@ plt.imshow(tmp.asnumpy()); plt.show() ### Loading Data using Image Iterators Before we see how to read data using the two built-in Image iterators, - lets get a sample dataset __Caltech 101__ dataset + lets get a sample __Caltech 101__ dataset that contains 101 classes of objects and converts them into record io format. Download and unzip @@ -346,30 +356,29 @@ Download and unzip ```python fname = mx.test_utils.download(url='http://www.vision.caltech.edu/Image_Datasets/Caltech101/101_ObjectCategories.tar.gz', dirname='data', overwrite=False) tar = tarfile.open(fname) -tar.extracall() +tar.extractall(path='./data') tar.close() -os.chdir('../') ``` -Let's take a look at the data. As you can see, under the [root folder](./data/101_ObjectCategories) every category has a [subfolder](./data/101_ObjectCategories/yin_yang). +Let's take a look at the data. As you can see, under the root folder (./data/101_ObjectCategories) every category has a subfolder(./data/101_ObjectCategories/yin_yang). -Now let's convert them into record io format using the `im2rec.py` utility scipt. -First we need to make a list that contains all the image files and their categories: +Now let's convert them into record io format using the `im2rec.py` utility script. +First, we need to make a list that contains all the image files and their categories: ```python -os.system('python %s/tools/im2rec.py --list=1 --recursive=1 --shuffle=1 --test-ratio=0.2 data/caltech data/101_ObjectCategories'%MXNET_HOME) +os.system('python %s/tools/im2rec.py --list=1 --recursive=1 --shuffle=1 --test-ratio=0.2 data/caltech data/101_ObjectCategories'%os.environ['MXNET_HOME']) ``` -The resulting [list file](./data/caltech_train.lst) is in the format `index\t(one or more label)\tpath`. In this case there is only one label for each image but you can modify the list to add in more for multi label training. +The resulting list file (./data/caltech_train.lst) is in the format `index\t(one or more label)\tpath`. In this case, there is only one label for each image but you can modify the list to add in more for multi-label training. Then we can use this list to create our record io file: ```python -os.system("python %s/tools/im2rec.py --num-thread=4 --pass-through=1 data/caltech data/101_ObjectCategories"%MXNET_HOME) +os.system("python %s/tools/im2rec.py --num-thread=4 --pass-through=1 data/caltech data/101_ObjectCategories"%os.environ['MXNET_HOME']) ``` -The record io files are now saved at [here](./data) +The record io files are now saved at here (./data) #### Using ImageRecordIter [__`ImageRecordIter`__](http://mxnet.io/api/python/io.html#mxnet.io.ImageRecordIter) can be used for loading image data saved in record io format. To use ImageRecordIter, simply create an instance by loading your record file: @@ -393,7 +402,7 @@ plt.show() ``` #### Using ImageIter -[__ImageIter__](http://mxnet.io/api/python/io.html#mxnet.io.ImageIter) is a flexible interface that supports loading of images from both in RecordIO and Raw format. +[__ImageIter__](http://mxnet.io/api/python/io.html#mxnet.io.ImageIter) is a flexible interface that supports loading of images in both RecordIO and Raw format. ```python @@ -410,4 +419,3 @@ plt.show() ``` - diff --git a/docs/tutorials/basic/module.md b/docs/tutorials/basic/module.md index b42993aec22f..e0618ca65e4a 100644 --- a/docs/tutorials/basic/module.md +++ b/docs/tutorials/basic/module.md @@ -8,14 +8,28 @@ steps. All this can be quite daunting to both newcomers as well as experienced developers. Luckily, MXNet modularizes commonly used code for training and inference in -the `module` (`mod` for short) package. `module` provides both a -high-level and intermediate-level interfaces for executing predefined networks. +the `module` (`mod` for short) package. `Module` provides both high-level and +intermediate-level interfaces for executing predefined networks. One can use +both interfaces interchangeably. We will show the usage of both interfaces in +this tutorial. + +## Prerequisites + +To complete this tutorial, we need: + +- MXNet. See the instructions for your operating system in [Setup and Installation](http://mxnet.io/get_started/install.html). + +- [Jupyter Notebook](http://jupyter.org/index.html) and [Python Requests](http://docs.python-requests.org/en/master/) packages. +``` +pip install jupyter requests +``` ## Preliminary In this tutorial we will demonstrate `module` usage by training a [Multilayer Perceptron](https://en.wikipedia.org/wiki/Multilayer_perceptron) (MLP) -on the [UCI letter recognition](https://archive.ics.uci.edu/ml/datasets/letter+recognition) dataset. +on the [UCI letter recognition](https://archive.ics.uci.edu/ml/datasets/letter+recognition) +dataset. The following code downloads the dataset and creates an 80:20 train:test split. It also initializes a training data iterator to return a batch of 32 @@ -48,9 +62,7 @@ net = mx.sym.SoftmaxOutput(net, name='softmax') mx.viz.plot_network(net) ``` -## High-level Interface - -### Creating a Module +## Creating a Module Now we are ready to introduce module. The commonly used module class is `Module`. We can construct a module by specifying the following parameters: @@ -70,12 +82,69 @@ mod = mx.mod.Module(symbol=net, label_names=['softmax_label']) ``` -### Train, Predict, and Evaluate +## Intermediate-level Interface + +We have created module. Now let us see how to run training and inference using module's intermediate-level APIs. These APIs give developers flexibility to do step-by-step +computation by running `forward` and `backward` passes. It's also useful for debugging. -Module provides high-level APIs for training, predicting and evaluating. -To fit a module, simply call the `fit` function. +To train a module, we need to perform following steps: + +- `bind` : Prepares environment for the computation by allocating memory. +- `init_params` : Assigns and initializes parameters. +- `init_optimizer` : Initializes optimizers. Defaults to `sgd`. +- `metric.create` : Creates evaluation metric from input metric name. +- `forward` : Forward computation. +- `update_metric` : Evaluates and accumulates evaluation metric on outputs of the last forward computation. +- `backward` : Backward computation. +- `update` : Updates parameters according to the installed optimizer and the gradients computed in the previous forward-backward batch. + +This can be used as follows: ```python +# allocate memory given the input data and label shapes +mod.bind(data_shapes=train_iter.provide_data, label_shapes=train_iter.provide_label) +# initialize parameters by uniform random numbers +mod.init_params(initializer=mx.init.Uniform(scale=.1)) +# use SGD with learning rate 0.1 to train +mod.init_optimizer(optimizer='sgd', optimizer_params=(('learning_rate', 0.1), )) +# use accuracy as the metric +metric = mx.metric.create('acc') +# train 5 epochs, i.e. going over the data iter one pass +for epoch in range(5): + train_iter.reset() + metric.reset() + for batch in train_iter: + mod.forward(batch, is_train=True) # compute predictions + mod.update_metric(metric, batch.label) # accumulate prediction accuracy + mod.backward() # compute gradients + mod.update() # update parameters + print('Epoch %d, Training %s' % (epoch, metric.get())) +``` + +To learn more about these APIs, visit [Module API](http://mxnet.io/api/python/module.html). + +## High-level Interface + +### Train + +Module also provides high-level APIs for training, predicting and evaluating for +user convenience. Instead of doing all the steps mentioned in the above section, +one can simply call [fit API](http://mxnet.io/api/python/module.html#mxnet.module.BaseModule.fit) +and it internally executes the same steps. + +To fit a module, call the `fit` function as follows: + +```python +# reset train_iter to the beginning +train_iter.reset() + +# create a module +mod = mx.mod.Module(symbol=net, + context=mx.cpu(), + data_names=['data'], + label_names=['softmax_label']) + +# fit the module mod.fit(train_iter, eval_data=val_iter, optimizer='sgd', @@ -84,7 +153,12 @@ mod.fit(train_iter, num_epoch=8) ``` -To predict with module, simply call `predict()`. It will collect and +By default, `fit` function has `eval_metric` set to `accuracy`, `optimizer` to `sgd` +and optimizer_params to `(('learning_rate', 0.01),)`. + +### Predict and Evaluate + +To predict with module, we can call `predict()`. It will collect and return all the prediction results. ```python @@ -93,12 +167,23 @@ assert y.shape == (4000, 26) ``` If we do not need the prediction outputs, but just need to evaluate on a test -set, we can call the `score()` function: +set, we can call the `score()` function. It runs prediction in the input validation +dataset and evaluates the performance according to the given input metric. + +It can be used as follows: ```python -mod.score(val_iter, ['mse', 'acc']) +score = mod.score(val_iter, ['acc']) +print("Accuracy score is %f" % (score[0][1])) ``` +Some of the other metrics which can be used are `top_k_acc`(top-k-accuracy), +`F1`, `RMSE`, `MSE`, `MAE`, `ce`(CrossEntropy). To learn more about the metrics, +visit [Evaluation metric](http://mxnet.io/api/python/metric.html). + +One can vary number of epochs, learning_rate, optimizer parameters to change the score +and tune these parameters to get best score. + ### Save and Load We can save the module parameters after each training epoch by using a checkpoint callback. @@ -139,34 +224,4 @@ mod.fit(train_iter, begin_epoch=3) ``` -## Intermediate-level Interface - -We already saw how to use module for basic training and inference. Now we are -going to see a more flexible usage of module. Instead of calling -the high-level `fit` and `predict` APIs, one can write a training program with the intermediate-level APIs -`forward` and `backward`. - -```python -# create module -mod = mx.mod.Module(symbol=net) -# allocate memory by given the input data and label shapes -mod.bind(data_shapes=train_iter.provide_data, label_shapes=train_iter.provide_label) -# initialize parameters by uniform random numbers -mod.init_params(initializer=mx.init.Uniform(scale=.1)) -# use SGD with learning rate 0.1 to train -mod.init_optimizer(optimizer='sgd', optimizer_params=(('learning_rate', 0.1), )) -# use accuracy as the metric -metric = mx.metric.create('acc') -# train 5 epochs, i.e. going over the data iter one pass -for epoch in range(5): - train_iter.reset() - metric.reset() - for batch in train_iter: - mod.forward(batch, is_train=True) # compute predictions - mod.update_metric(metric, batch.label) # accumulate prediction accuracy - mod.backward() # compute gradients - mod.update() # update parameters - print('Epoch %d, Training %s' % (epoch, metric.get())) -``` - diff --git a/docs/tutorials/basic/ndarray.md b/docs/tutorials/basic/ndarray.md index e69565e07405..bd76702aa376 100644 --- a/docs/tutorials/basic/ndarray.md +++ b/docs/tutorials/basic/ndarray.md @@ -10,7 +10,7 @@ to `numpy.ndarray`. Like the corresponding NumPy data structure, MXNet's So you might wonder, why not just use NumPy? MXNet offers two compelling advantages. First, MXNet's `NDArray` supports fast execution on a wide range of hardware configurations, including CPU, GPU, and multi-GPU machines. _MXNet_ -also scales to distribute systems in the cloud. Second, MXNet's NDArray +also scales to distributed systems in the cloud. Second, MXNet's `NDArray` executes code lazily, allowing it to automatically parallelize multiple operations across the available hardware. @@ -38,6 +38,19 @@ Each NDArray supports some important attributes that you'll often want to query: - **ndarray.context**: The device on which this array is stored, e.g. `cpu()` or `gpu(1)`. +## Prerequisites + +To complete this tutorial, we need: + +- MXNet. See the instructions for your operating system in [Setup and Installation](http://mxnet.io/get_started/install.html) +- [Jupyter](http://jupyter.org/) + ``` + pip install jupyter + ``` +- GPUs - A section of this tutorial uses GPUs. If you don't have GPUs on your +machine, simply set the variable gpu_device (set in the GPUs section of this +tutorial) to mx.cpu(). + ## Array Creation There are a few different ways to create an `NDArray`. @@ -53,7 +66,7 @@ b = mx.nd.array([[1,2,3], [2,3,4]]) {'a.shape':a.shape, 'b.shape':b.shape} ``` -* We can also create an MXNet NDArray from an `numpy.ndarray` object: +* We can also create an MXNet NDArray from a `numpy.ndarray` object: ```python import numpy as np @@ -182,7 +195,7 @@ shapes must be the same along the other axes. ```python a = mx.nd.ones((2,3)) b = mx.nd.ones((2,3))*2 -c = mx.nd.concat([a,b]) +c = mx.nd.concat(a,b) c.asnumpy() ``` @@ -279,7 +292,11 @@ can cause all computations to run on GPU 0 by using context `mx.gpu(0)`, or simply `mx.gpu()`. When we have access to two or more GPUs, the 2nd GPU is represented by `mx.gpu(1)`, etc. +**Note** In order to execute the following section on a cpu set gpu_device to mx.cpu(). ```python +gpu_device=mx.gpu() # Change this to mx.cpu() in absence of GPUs. + + def f(): a = mx.nd.ones((100,100)) b = mx.nd.ones((100,100)) @@ -288,14 +305,14 @@ def f(): # in default mx.cpu() is used f() # change the default context to the first GPU -with mx.Context(mx.gpu()): +with mx.Context(gpu_device): f() ``` We can also explicitly specify the context when creating an array: ```python -a = mx.nd.ones((100, 100), mx.gpu(0)) +a = mx.nd.ones((100, 100), gpu_device) a ``` @@ -304,8 +321,8 @@ computation. There are several methods for copying data between devices. ```python a = mx.nd.ones((100,100), mx.cpu()) -b = mx.nd.ones((100,100), mx.gpu()) -c = mx.nd.ones((100,100), mx.gpu()) +b = mx.nd.ones((100,100), gpu_device) +c = mx.nd.ones((100,100), gpu_device) a.copyto(c) # copy from CPU to GPU d = b + c e = b.as_in_context(c.context) + c # same to above @@ -353,12 +370,12 @@ c The `load` and `save` methods are preferable to pickle in two respects 1. When using these methods, you can save data from within the Python interface - and then use it later from another lanuage's binding. For example, if we save + and then use it later from another language's binding. For example, if we save the data in Python: ```python a = mx.nd.ones((2, 3)) -mx.save("temp.ndarray", [a,]) +mx.nd.save("temp.ndarray", [a,]) ``` we can later load it from R: @@ -432,7 +449,7 @@ first runs on CPU and then on GPU: ```python n = 10 a = mx.nd.ones((1000,1000)) -b = mx.nd.ones((6000,6000), mx.gpu()) +b = mx.nd.ones((6000,6000), gpu_device) tic = time.time() c = do(a, n) wait(c) diff --git a/docs/tutorials/basic/symbol.md b/docs/tutorials/basic/symbol.md index 00a6b1b8d931..dc7daaea857e 100644 --- a/docs/tutorials/basic/symbol.md +++ b/docs/tutorials/basic/symbol.md @@ -45,6 +45,18 @@ For a visual explanation of these concepts, see To make things concrete, let's take a hands-on look at the Symbol API. There are a few different ways to compose a `Symbol`. +## Prerequisites + +To complete this tutorial, we need: + +- MXNet. See the instructions for your operating system in [Setup and Installation](http://mxnet.io/get_started/install.html) +- [Jupyter](http://jupyter.org/) + ``` + pip install jupyter + ``` +- GPUs - A section of this tutorial uses GPUs. If you don't have GPUs on your machine, simply +set the variable gpu_device to mx.cpu(). + ## Basic Symbol Composition ### Basic Operators @@ -173,8 +185,8 @@ data = mx.sym.Variable("data") net = data n_layer = 2 for i in range(n_layer): - with mx.name.Prefix("layer%d_" % (i + 1)): - net = mx.sym.FullyConnected(data=net, name="fc", num_hidden=100) + with mx.name.Prefix("layer%d_" % (i + 1)): + net = mx.sym.FullyConnected(data=net, name="fc", num_hidden=100) net.list_arguments() ``` @@ -314,15 +326,18 @@ executor. The executor provides `forward` method for evaluation and an attribute ex = c.bind(ctx=mx.cpu(), args={'a' : mx.nd.ones([2,3]), 'b' : mx.nd.ones([2,3])}) ex.forward() -print 'number of outputs = %d\nthe first output = \n%s' % ( - len(ex.outputs), ex.outputs[0].asnumpy()) +print('number of outputs = %d\nthe first output = \n%s' % ( + len(ex.outputs), ex.outputs[0].asnumpy())) ``` We can evaluate the same symbol on GPU with different data. +**Note** In order to execute the following section on a cpu set gpu_device to mx.cpu(). ```python -ex_gpu = c.bind(ctx=mx.gpu(), args={'a' : mx.nd.ones([3,4], mx.gpu())*2, - 'b' : mx.nd.ones([3,4], mx.gpu())*3}) +gpu_device=mx.gpu() # Change this to mx.cpu() in absence of GPUs. + +ex_gpu = c.bind(ctx=gpu_device, args={'a' : mx.nd.ones([3,4], gpu_device)*2, + 'b' : mx.nd.ones([3,4], gpu_device)*3}) ex_gpu.forward() ex_gpu.outputs[0].asnumpy() ``` @@ -332,8 +347,8 @@ and `forward` methods. ```python ex = c.eval(ctx = mx.cpu(), a = mx.nd.ones([2,3]), b = mx.nd.ones([2,3])) -print 'number of outputs = %d\nthe first output = \n%s' % ( - len(ex), ex[0].asnumpy()) +print('number of outputs = %d\nthe first output = \n%s' % ( + len(ex), ex[0].asnumpy())) ``` For neural nets, a more commonly used pattern is ```simple_bind```, which diff --git a/docs/tutorials/c++/basics.md b/docs/tutorials/c++/basics.md index ae8addbd681d..cdf1a28ecd82 100644 --- a/docs/tutorials/c++/basics.md +++ b/docs/tutorials/c++/basics.md @@ -14,7 +14,7 @@ and decompress them into `mnist_data` folder. Except linking the MXNet shared library, the C++ package itself is a header-only package, which means all you need to do is to include the header files. Among the header files, -`op.h` is special since it is genereated dynamically. The generation should be done when +`op.h` is special since it is generated dynamically. The generation should be done when [building the C++ package](http://mxnet.io/get_started/build_from_source.html#build-the-c++-package). After that, you also need to copy the shared library (`libmxnet.so` in linux, `libmxnet.dll` in windows) from `/path/to/mxnet/lib` to the working directory. @@ -126,7 +126,7 @@ for (int iter = 0; iter < max_epoch; ++iter) { args["X"] = data_batch.data; args["label"] = data_batch.label; - // Create executor by binding parmeters to the model + // Create executor by binding parameters to the model auto *exec = net.SimpleBind(ctx, args); // Compute gradients exec->Forward(true); diff --git a/docs/tutorials/gluon/autograd.md b/docs/tutorials/gluon/autograd.md new file mode 100644 index 000000000000..4b296dd2dd5b --- /dev/null +++ b/docs/tutorials/gluon/autograd.md @@ -0,0 +1,55 @@ +# Automatic differentiation + +MXNet supports automatic differentiation with the `autograd` package. +`autograd` allows you to differentiate a graph of NDArray operations +with the chain rule. +This is called define-by-run, i.e., the network is defined on-the-fly by +running forward computation. You can define exotic network structures +and differentiate them, and each iteration can have a totally different +network structure. + +```python +import mxnet as mx +from mxnet import autograd +``` + +To use `autograd`, we must first mark variables that require gradient and +attach gradient buffers to them: + +```python +x = mx.nd.array([[1, 2], [3, 4]]) +x.attach_grad() +``` + +Now we can define the network while running forward computation by wrapping +it inside a `record` (operations out of `record` does not define +a graph and cannot be differentiated): + +```python +with autograd.record(): + y = x * 2 + z = y * x +``` + +Let's backprop with `z.backward()`, which is equivalent to +`z.backward(mx.nd.ones_like(z))`. When z has more than one entry, `z.backward()` +is equivalent to `mx.nd.sum(z).backward()`: + +```python +z.backward() +print(x.grad) +``` + +Now, let's see if this is the expected output. + +Here, y = f(x), z = f(y) = f(g(x)) +which means y = 2 * x and z = 2 * x * x. + +After, doing backprop with `z.backward()`, we will get gradient dz/dx as follows: + +dy/dx = 2, +dz/dx = 4 * x + +So, we should get x.grad as an array of [[4, 8],[12, 16]]. + + diff --git a/docs/tutorials/gluon/customop.md b/docs/tutorials/gluon/customop.md new file mode 100644 index 000000000000..dbb1907badb1 --- /dev/null +++ b/docs/tutorials/gluon/customop.md @@ -0,0 +1,198 @@ + +# Creating custom operators with numpy + +In this tutorial, we will learn how to build custom operators with numpy in python. We will go through two examples: +- Custom operator without any `Parameter`s +- Custom operator with `Parameter`s + +Custom operator in python is easy to develop and good for prototyping, but may hurt performance. If you find it to be a bottleneck, please consider moving to a C++ based implementation in the backend. + + + +```python +import numpy as np +import mxnet as mx +from mxnet import gluon, autograd +``` + +## Parameter-less operators + +This operator implements the standard sigmoid activation function. This is only for illustration purposes, in real life you would use the built-in operator `mx.nd.relu`. + +### Forward & backward implementation + +First we implement the forward and backward computation by sub-classing `mx.operator.CustomOp`: + + +```python +class Sigmoid(mx.operator.CustomOp): + def forward(self, is_train, req, in_data, out_data, aux): + """Implements forward computation. + + is_train : bool, whether forwarding for training or testing. + req : list of {'null', 'write', 'inplace', 'add'}, how to assign to out_data. 'null' means skip assignment, etc. + in_data : list of NDArray, input data. + out_data : list of NDArray, pre-allocated output buffers. + aux : list of NDArray, mutable auxiliary states. Usually not used. + """ + x = in_data[0].asnumpy() + y = 1.0 / (1.0 + np.exp(-x)) + self.assign(out_data[0], req[0], mx.nd.array(y)) + + def backward(self, req, out_grad, in_data, out_data, in_grad, aux): + """Implements backward computation + + req : list of {'null', 'write', 'inplace', 'add'}, how to assign to in_grad + out_grad : list of NDArray, gradient w.r.t. output data. + in_grad : list of NDArray, gradient w.r.t. input data. This is the output buffer. + """ + y = out_data[0].asnumpy() + dy = out_grad[0].asnumpy() + dx = dy*(1.0 - y)*y + self.assign(in_grad[0], req[0], mx.nd.array(dx)) +``` + +### Register custom operator + +Then we need to register the custom op and describe it's properties like input and output shapes so that mxnet can recognize it. This is done by sub-classing `mx.operator.CustomOpProp`: + + +```python +@mx.operator.register("sigmoid") # register with name "sigmoid" +class SigmoidProp(mx.operator.CustomOpProp): + def __init__(self): + super(SigmoidProp, self).__init__(True) + + def list_arguments(self): + # this can be omitted if you only have 1 input. + return ['data'] + + def list_outputs(self): + # this can be omitted if you only have 1 output. + return ['output'] + + def infer_shape(self, in_shapes): + """Calculate output shapes from input shapes. This can be + omited if all your inputs and outputs have the same shape. + + in_shapes : list of shape. Shape is described by a tuple of int. + """ + data_shape = in_shapes[0] + output_shape = data_shape + # return 3 lists representing inputs shapes, outputs shapes, and aux data shapes. + return (data_shape,), (output_shape,), () + + def create_operator(self, ctx, in_shapes, in_dtypes): + # create and return the CustomOp class. + return Sigmoid() +``` + +### Example Usage + +We can now use this operator by calling `mx.nd.Custom`: + + +```python +x = mx.nd.array([0, 1, 2, 3]) +# attach gradient buffer to x for autograd +x.attach_grad() +# forward in a record() section to save computation graph for backward +# see autograd tutorial to learn more. +with autograd.record(): + y = mx.nd.Custom(x, op_type='sigmoid') +print(y) +``` + +```python +# call backward computation +y.backward() +# gradient is now saved to the grad buffer we attached previously +print(x.grad) +``` + +## Parametrized Operator + +In the second use case we implement an operator with learnable weights. We implement the dense (or fully connected) layer that has one input, one output, and two learnable parameters: weight and bias. + +The dense operator performs a dot product between data and weight, then add bias to it. + +### Forward & backward implementation + + +```python +class Dense(mx.operator.CustomOp): + def __init__(self, bias): + self._bias = bias + + def forward(self, is_train, req, in_data, out_data, aux): + x = in_data[0].asnumpy() + weight = in_data[1].asnumpy() + y = x.dot(weight.T) + self._bias + self.assign(out_data[0], req[0], mx.nd.array(y)) + + def backward(self, req, out_grad, in_data, out_data, in_grad, aux): + x = in_data[0].asnumpy() + dy = out_grad[0].asnumpy() + dx = dy.T.dot(x) + self.assign(in_grad[0], req[0], mx.nd.array(dx)) +``` + +### Registration + + +```python +@mx.operator.register("dense") # register with name "sigmoid" +class DenseProp(mx.operator.CustomOpProp): + def __init__(self, bias): + super(DenseProp, self).__init__(True) + # we use constant bias here to illustrate how to pass arguments + # to operators. All arguments are in string format so you need + # to convert them back to the type you want. + self._bias = float(bias) + + def list_arguments(self): + return ['data', 'weight'] + + def list_outputs(self): + # this can be omitted if you only have 1 output. + return ['output'] + + def infer_shape(self, in_shapes): + data_shape = in_shapes[0] + weight_shape = in_shapes[1] + output_shape = (data_shape[0], weight_shape[0]) + # return 3 lists representing inputs shapes, outputs shapes, and aux data shapes. + return (data_shape, weight_shape), (output_shape,), () + + def create_operator(self, ctx, in_shapes, in_dtypes): + # create and return the CustomOp class. + return Dense(self._bias) +``` + +### Use CustomOp together with Block + +Parameterized CustomOp are ususally used together with Blocks, which holds the parameter. + + +```python +class DenseBlock(mx.gluon.Block): + def __init__(self, in_channels, channels, bias, **kwargs): + super(DenseBlock, self).__init__(**kwargs) + self._bias = bias + self.weight = self.params.get('weight', shape=(channels, in_channels)) + + def forward(self, x): + ctx = x.context + return mx.nd.Custom(x, self.weight.data(ctx), bias=self._bias, op_type='dense') +``` + +### Example usage + + +```python +dense = DenseBlock(3, 5, 0.1) +dense.initialize() +x = mx.nd.uniform(shape=(4, 3)) +y = dense(x) +print(y) +``` diff --git a/docs/tutorials/gluon/gluon.md b/docs/tutorials/gluon/gluon.md new file mode 100644 index 000000000000..ac1aa3f60f5e --- /dev/null +++ b/docs/tutorials/gluon/gluon.md @@ -0,0 +1,140 @@ +# Gluon - Neural network building blocks + +Gluon package is a high-level interface for MXNet designed to be easy to use while +keeping most of the flexibility of low level API. Gluon supports both imperative +and symbolic programming, making it easy to train complex models imperatively +in Python and then deploy with symbolic graph in C++ and Scala. + + +```python +# import dependencies +from __future__ import print_function +import numpy as np +import mxnet as mx +import mxnet.ndarray as F +import mxnet.gluon as gluon +from mxnet.gluon import nn +from mxnet import autograd +``` + +Neural networks (and other machine learning models) can be defined and trained +with `gluon.nn` and `gluon.rnn` package. A typical training script has the following +steps: + +- Define network +- Initialize parameters +- Loop over inputs +- Forward input through network to get output +- Compute loss with output and label +- Backprop gradient +- Update parameters with gradient descent. + + +## Define Network + +`gluon.Block` is the basic building block of models. You can define networks by +composing and inheriting `Block`: + +```python +class Net(gluon.Block): + def __init__(self, **kwargs): + super(Net, self).__init__(**kwargs) + with self.name_scope(): + # layers created in name_scope will inherit name space + # from parent layer. + self.conv1 = nn.Conv2D(6, kernel_size=5) + self.pool1 = nn.MaxPool2D(pool_size=(2,2)) + self.conv2 = nn.Conv2D(16, kernel_size=5) + self.pool2 = nn.MaxPool2D(pool_size=(2,2)) + self.fc1 = nn.Dense(120) + self.fc2 = nn.Dense(84) + self.fc3 = nn.Dense(10) + + def forward(self, x): + x = self.pool1(F.relu(self.conv1(x))) + x = self.pool2(F.relu(self.conv2(x))) + # 0 means copy over size from corresponding dimension. + # -1 means infer size from the rest of dimensions. + x = x.reshape((0, -1)) + x = F.relu(self.fc1(x)) + x = F.relu(self.fc2(x)) + x = self.fc3(x) + return x +``` + +## Initialize Parameters + +A network must be created and initialized before it can be used: + +```python +net = Net() +# Initialize on CPU. Replace with `mx.gpu(0)`, or `[mx.gpu(0), mx.gpu(1)]`, +# etc to use one or more GPUs. +net.collect_params().initialize(mx.init.Xavier(), ctx=mx.cpu()) +``` + +Note that because we didn't specify input size to layers in Net's constructor, +the shape of parameters cannot be determined at this point. Actual initialization +is deferred to the first forward pass, i.e. if you access `net.fc1.weight.data()` +now an exception will be raised. + +You can actually initialize the weights by running a forward pass: + +```python +data = mx.nd.random_normal(shape=(10, 1, 32, 32)) # dummy data +output = net(data) +``` + +Or you can specify input size when creating layers, i.e. `nn.Dense(84, in_units=120)` +instead of `nn.Dense(84)`. + +## Loss Functions + +Loss functions take (output, label) pairs and compute a scalar loss for each sample +in the mini-batch. The scalars measure how far each output is from the label. + +There are many predefined loss functions in `gluon.loss`. Here we use +`softmax_cross_entropy_loss` for digit classification. + +To compute loss and backprop for one iteration, we do: + +```python +label = mx.nd.arange(10) # dummy label +with autograd.record(): + output = net(data) + loss = gluon.loss.softmax_cross_entropy_loss(output, label) + loss.backward() +print('loss:', loss) +print('grad:', net.fc1.weight.grad()) +``` + +## Updating the weights + +Now that gradient is computed, we just need to update the weights. This is usually +done with formulas like `weight = weight - learning_rate * grad / batch_size`. +Note we divide gradient by batch_size because gradient is aggregated over the +entire batch. For example, + +```python +lr = 0.01 +for p in net.collect_params().values(): + p.data()[:] -= lr / data.shape[0] * p.grad() +``` + +But sometimes you want more fancy updating rules like momentum and Adam, and since +this is a commonly used functionality, gluon provide a `Trainer` class for it: + +```python +trainer = gluon.Trainer(net.collect_params(), 'sgd', {'learning_rate': 0.01}) + +with record(): + output = net(data) + loss = gluon.loss.softmax_cross_entropy_loss(output, label) + loss.backward() + +# do the update. Trainer needs to know the batch size of data to normalize +# the gradient by 1/batch_size. +trainer.step(data.shape[0]) +``` + + diff --git a/docs/tutorials/gluon/hybrid.md b/docs/tutorials/gluon/hybrid.md new file mode 100644 index 000000000000..7e043c816402 --- /dev/null +++ b/docs/tutorials/gluon/hybrid.md @@ -0,0 +1,125 @@ +# Hybrid - Faster training and easy deployment + +*Note: a newer version is available [here](http://gluon.mxnet.io/P14-C05-hybridize.html).* + +Deep learning frameworks can be roughly divided into two categories: declarative +and imperative. With declarative frameworks (including Tensorflow, Theano, etc) +users first declare a fixed computation graph and then execute it end-to-end. +The benefit of fixed computation graph is it's portable and runs more +efficiently. However, it's less flexible because any logic must be encoded +into the graph as special operators like `scan`, `while_loop` and `cond`. +It's also hard to debug. + +Imperative frameworks (including PyTorch, Chainer, etc) are just the opposite: +they execute commands one-by-one just like old fashioned Matlab and Numpy. +This style is more flexible, easier to debug, but less efficient. + +`HybridBlock` seamlessly combines declarative programming and imperative programming +to offer the benefit of both. Users can quickly develop and debug models with +imperative programming and switch to efficient declarative execution by simply +calling: `HybridBlock.hybridize()`. + +## HybridBlock + +`HybridBlock` is very similar to `Block` but has a few restrictions: + +- All children layers of `HybridBlock` must also be `HybridBlock`. +- Only methods that are implemented for both `NDArray` and `Symbol` can be used. + For example you cannot use `.asnumpy()`, `.shape`, etc. +- Operations cannot change from run to run. For example, you cannot do `if x:` + if `x` is different for each iteration. + +To use hybrid support, we subclass the `HybridBlock`: + +```python +import mxnet as mx +from mxnet import gluon +from mxnet.gluon import nn + +class Net(gluon.HybridBlock): + def __init__(self, **kwargs): + super(Net, self).__init__(**kwargs) + with self.name_scope: + # layers created in name_scope will inherit name space + # from parent layer. + self.conv1 = nn.Conv2D(6, kernel_size=5) + self.pool1 = nn.Pool2D(kernel_size=2) + self.conv2 = nn.Conv2D(16, kernel_size=5) + self.pool2 = nn.Pool2D(kernel_size=2) + self.fc1 = nn.Dense(120) + self.fc2 = nn.Dense(84) + # You can use a Dense layer for fc3 but we do dot product manually + # here for illustration purposes. + self.fc3_weight = self.params.get('fc3_weight', shape=(10, 84)) + + def hybrid_forward(self, F, x, fc3_weight): + # Here `F` can be either mx.nd or mx.sym, x is the input data, + # and fc3_weight is either self.fc3_weight.data() or + # self.fc3_weight.var() depending on whether x is Symbol or NDArray + print(x) + x = self.pool1(F.relu(self.conv1(x))) + x = self.pool2(F.relu(self.conv2(x))) + # 0 means copy over size from corresponding dimension. + # -1 means infer size from the rest of dimensions. + x = x.reshape((0, -1)) + x = F.relu(self.fc1(x)) + x = F.relu(self.fc2(x)) + x = F.dot(x, fc3_weight, transpose_b=True) + return x +``` + +## Hybridize + +By default, `HybridBlock` runs just like a standard `Block`. Each time a layer +is called, its `hybrid_forward` will be run: + +```python +net = Net() +net.collect_params().initialize() +x = mx.nd.random_normal(shape=(16, 1, 28, 28)) +net(x) +x = mx.nd.random_normal(shape=(16, 1, 28, 28)) +net(x) +``` + +Hybrid execution can be activated by simply calling `.hybridize()` on the top +level layer. The first forward call after activation will try to build a +computation graph from `hybrid_forward` and cache it. On subsequent forward +calls the cached graph instead of `hybrid_forward` will be invoked: + +```python +net.hybridize() +x = mx.nd.random_normal(shape=(16, 1, 28, 28)) +net(x) +x = mx.nd.random_normal(shape=(16, 1, 28, 28)) +net(x) +``` + +Note that before hybridize, `print(x)` printed out one NDArray for forward, +but after hybridize, only the first forward printed out a Symbol. On subsequent +forward `hybrid_forward` is not called so nothing was printed. + +Hybridize will speed up execution and save memory. If the top level layer is +not a `HybridBlock`, you can still call `.hybridize()` on it and Gluon will try +to hybridize its children layers instead. + +## Serializing trained model for deployment + +Models implemented as `HybridBlock` can be easily serialized for deployment +using other language front-ends like C, C++ and Scala. To this end, we simply +forward the model with symbolic variables instead of NDArrays and save the +output Symbol(s): + +```python +x = mx.sym.var('data') +y = net(x) +print(y) +y.save('model.json') +net.collect_params().save('model.params') +``` + +If your network outputs more than one value, you can use `mx.sym.Group` to +combine them into a grouped Symbol and then save. The saved json and params +files can then be loaded with C, C++ and Scala interface for prediction. + + diff --git a/docs/tutorials/gluon/mnist.md b/docs/tutorials/gluon/mnist.md new file mode 100644 index 000000000000..0abb8ea41fc2 --- /dev/null +++ b/docs/tutorials/gluon/mnist.md @@ -0,0 +1,325 @@ +# Handwritten Digit Recognition + +In this tutorial, we'll give you a step by step walk-through of how to build a hand-written digit classifier using the [MNIST](https://en.wikipedia.org/wiki/MNIST_database) dataset. + +MNIST is a widely used dataset for the hand-written digit classification task. It consists of 70,000 labeled 28x28 pixel grayscale images of hand-written digits. The dataset is split into 60,000 training images and 10,000 test images. There are 10 classes (one for each of the 10 digits). The task at hand is to train a model using the 60,000 training images and subsequently test its classification accuracy on the 10,000 test images. + +![png](https://raw.githubusercontent.com/dmlc/web-data/master/mxnet/example/mnist.png) + +**Figure 1:** Sample images from the MNIST dataset. + +This tutorial uses MXNet's new high-level interface, gluon package to implement MLP using +imperative fashion. + +This is based on the Mnist tutorial with symbolic approach. You can find it [here](http://mxnet.io/tutorials/python/mnist.html). + +## Prerequisites +To complete this tutorial, we need: + +- MXNet. See the instructions for your operating system in [Setup and Installation](http://mxnet.io/get_started/install.html). + +- [Python Requests](http://docs.python-requests.org/en/master/) and [Jupyter Notebook](http://jupyter.org/index.html). + +``` +$ pip install requests jupyter +``` + +## Loading Data + +Before we define the model, let's first fetch the [MNIST](http://yann.lecun.com/exdb/mnist/) dataset. + +The following source code downloads and loads the images and the corresponding labels into memory. + +```python +import mxnet as mx +mnist = mx.test_utils.get_mnist() +``` + +After running the above source code, the entire MNIST dataset should be fully loaded into memory. Note that for large datasets it is not feasible to pre-load the entire dataset first like we did here. What is needed is a mechanism by which we can quickly and efficiently stream data directly from the source. MXNet Data iterators come to the rescue here by providing exactly that. Data iterator is the mechanism by which we feed input data into an MXNet training algorithm and they are very simple to initialize and use and are optimized for speed. During training, we typically process training samples in small batches and over the entire training lifetime will end up processing each training example multiple times. In this tutorial, we'll configure the data iterator to feed examples in batches of 100. Keep in mind that each example is a 28x28 grayscale image and the corresponding label. + +Image batches are commonly represented by a 4-D array with shape `(batch_size, num_channels, width, height)`. For the MNIST dataset, since the images are grayscale, there is only one color channel. Also, the images are 28x28 pixels, and so each image has width and height equal to 28. Therefore, the shape of input is `(batch_size, 1, 28, 28)`. Another important consideration is the order of input samples. When feeding training examples, it is critical that we don't feed samples with the same label in succession. Doing so can slow down training. +Data iterators take care of this by randomly shuffling the inputs. Note that we only need to shuffle the training data. The order does not matter for test data. + +The following source code initializes the data iterators for the MNIST dataset. Note that we initialize two iterators: one for train data and one for test data. + +```python +batch_size = 100 +train_data = mx.io.NDArrayIter(mnist['train_data'], mnist['train_label'], batch_size, shuffle=True) +val_data = mx.io.NDArrayIter(mnist['test_data'], mnist['test_label'], batch_size) +``` + +## Approaches + +We will cover a couple of approaches for performing the hand written digit recognition task. The first approach makes use of a traditional deep neural network architecture called Multilayer Percepton (MLP). We'll discuss its drawbacks and use that as a motivation to introduce a second more advanced approach called Convolution Neural Network (CNN) that has proven to work very well for image classification tasks. + +Now, let's import required nn modules + +```python +from __future__ import print_function +import mxnet as mx +from mxnet import gluon +from mxnet.gluon import nn +from mxnet import autograd as ag +``` + +### Define a network: Multilayer Perceptron + +The first approach makes use of a [Multilayer Perceptron](https://en.wikipedia.org/wiki/Multilayer_perceptron) to solve this problem. We'll define the MLP using MXNet's imperative approach. + +MLPs contains several fully connected layers. A fully connected layer or FC layer for short, is one where each neuron in the layer is connected to every neuron in its preceding layer. From a linear algebra perspective, an FC layer applies an [affine transform](https://en.wikipedia.org/wiki/Affine_transformation) to the *n x m* input matrix *X* and outputs a matrix *Y* of size *n x k*, where *k* is the number of neurons in the FC layer. *k* is also referred to as the hidden size. The output *Y* is computed according to the equation *Y = W X + b*. The FC layer has two learnable parameters, the *m x k* weight matrix *W* and the *m x 1* bias vector *b*. + +In an MLP, the outputs of most FC layers are fed into an activation function, which applies an element-wise non-linearity. This step is critical and it gives neural networks the ability to classify inputs that are not linearly separable. Common choices for activation functions are sigmoid, tanh, and [rectified linear unit](https://en.wikipedia.org/wiki/Rectifier_%28neural_networks%29) (ReLU). In this example, we'll use the ReLU activation function which has several desirable properties and is typically considered a default choice. + +The following code declares three fully connected layers with 128, 64 and 10 neurons each. +The last fully connected layer often has its hidden size equal to the number of output classes in the dataset. Furthermore, these FC layers uses ReLU activation for performing an element-wise ReLU transformation on the FC layer output. + +To do this, we will use [Sequential layer](http://mxnet.io/api/python/gluon.html#mxnet.gluon.nn.Sequential) type. This is simply a linear stack of neural network layers. `nn.Dense` layers are nothing but the fully connected layers we discussed above. + +```python +# define network +net = nn.Sequential() +with net.name_scope(): + net.add(nn.Dense(128, activation='relu')) + net.add(nn.Dense(64, activation='relu')) + net.add(nn.Dense(10)) +``` + +#### Initialize parameters and optimizer + +The following source code initializes all parameters received from parameter dict using [Xavier](http://mxnet.io/api/python/optimization.html#mxnet.initializer.Xavier) initializer +to train the MLP network we defined above. + +For our training, we will make use of the stochastic gradient descent (SGD) optimizer. In particular, we'll be using mini-batch SGD. Standard SGD processes train data one example at a time. In practice, this is very slow and one can speed up the process by processing examples in small batches. In this case, our batch size will be 100, which is a reasonable choice. Another parameter we select here is the learning rate, which controls the step size the optimizer takes in search of a solution. We'll pick a learning rate of 0.1, again a reasonable choice. Settings such as batch size and learning rate are what are usually referred to as hyper-parameters. What values we give them can have a great impact on training performance. + +We will use [Trainer](http://mxnet.io/api/python/gluon.html#trainer) class to apply the +[SGD optimizer](http://mxnet.io/api/python/optimization.html#mxnet.optimizer.SGD) on the +initialized parameters. + +```python +ctx = [mx.cpu(0), mx.cpu(1)] +net.collect_params().initialize(mx.init.Xavier(magnitude=2.24), ctx=ctx) +trainer = gluon.Trainer(net.collect_params(), 'sgd', {'learning_rate': 0.1}) +``` + +#### Train the network + +Typically, one runs the training until convergence, which means that we have learned a good set of model parameters (weights + biases) from the train data. For the purpose of this tutorial, we'll run training for 10 epochs and stop. An epoch is one full pass over the entire train data. + +We will take following steps for training: + +- Define [Accuracy evaluation metric](http://mxnet.io/api/python/metric.html#mxnet.metric.Accuracy) over training data. +- Loop over inputs for every epoch. +- Forward input through network to get output. +- Compute loss with output and label inside record scope. +- Backprop gradient inside record scope. +- Update evaluation metric and parameters with gradient descent. + +Loss function takes (output, label) pairs and computes a scalar loss for each sample in the mini-batch. The scalars measure how far each output is from the label. +There are many predefined loss functions in gluon.loss. Here we use +[softmax_cross_entropy_loss](http://mxnet.io/api/python/gluon.html#mxnet.gluon.loss.softmax_cross_entropy_loss) for digit classification. We will compute loss and do backward propagation inside +training scope which is defined by `autograd.record()`. + +```python +epoch = 10 +# Use Accuracy as the evaluation metric. +metric = mx.metric.Accuracy() + +for i in range(epoch): + # Reset the train data iterator. + train_data.reset() + # Loop over the train data iterator. + for batch in train_data: + # Splits train data into multiple slices along batch_axis + # and copy each slice into a context. + data = gluon.utils.split_and_load(batch.data[0], ctx_list=ctx, batch_axis=0) + # Splits train labels into multiple slices along batch_axis + # and copy each slice into a context. + label = gluon.utils.split_and_load(batch.label[0], ctx_list=ctx, batch_axis=0) + outputs = [] + # Inside training scope + with ag.record(): + for x, y in zip(data, label): + z = net(x) + # Computes softmax cross entropy loss. + loss = gluon.loss.softmax_cross_entropy_loss(z, y) + # Backpropogate the error for one iteration. + ag.backward([loss]) + outputs.append(z) + # Updates internal evaluation + metric.update(label, outputs) + # Make one step of parameter update. Trainer needs to know the + # batch size of data to normalize the gradient by 1/batch_size. + trainer.step(batch.data[0].shape[0]) + # Gets the evaluation result. + name, acc = metric.get() + # Reset evaluation result to initial state. + metric.reset() + print('training acc at epoch %d: %s=%f'%(i, name, acc)) +``` + +#### Prediction + +After the above training completes, we can evaluate the trained model by running predictions on validation dataset. Since the dataset also has labels for all test images, we can compute the accuracy metric over validation data as follows: + +```python +# Use Accuracy as the evaluation metric. +metric = mx.metric.Accuracy() +# Reset the validation data iterator. +val_data.reset() +# Loop over the validation data iterator. +for batch in val_data: + # Splits validation data into multiple slices along batch_axis + # and copy each slice into a context. + data = gluon.utils.split_and_load(batch.data[0], ctx_list=ctx, batch_axis=0) + # Splits validation label into multiple slices along batch_axis + # and copy each slice into a context. + label = gluon.utils.split_and_load(batch.label[0], ctx_list=ctx, batch_axis=0) + outputs = [] + for x in data: + outputs.append(net(x)) + # Updates internal evaluation + metric.update(label, outputs) +print('validation acc: %s=%f'%metric.get()) +assert metric.get()[1] > 0.96 +``` + +If everything went well, we should see an accuracy value that is around 0.96, which means that we are able to accurately predict the digit in 96% of test images. This is a pretty good result. But as we will see in the next part of this tutorial, we can do a lot better than that. + +### Convolutional Neural Network + +Earlier, we briefly touched on a drawback of MLP when we said we need to discard the input image's original shape and flatten it as a vector before we can feed it as input to the MLP's first fully connected layer. Turns out this is an important issue because we don't take advantage of the fact that pixels in the image have natural spatial correlation along the horizontal and vertical axes. A convolutional neural network (CNN) aims to address this problem by using a more structured weight representation. Instead of flattening the image and doing a simple matrix-matrix multiplication, it employs one or more convolutional layers that each performs a 2-D convolution on the input image. + +A single convolution layer consists of one or more filters that each play the role of a feature detector. During training, a CNN learns appropriate representations (parameters) for these filters. Similar to MLP, the output from the convolutional layer is transformed by applying a non-linearity. Besides the convolutional layer, another key aspect of a CNN is the pooling layer. A pooling layer serves to make the CNN translation invariant: a digit remains the same even when it is shifted left/right/up/down by a few pixels. A pooling layer reduces a *n x m* patch into a single value to make the network less sensitive to the spatial location. Pooling layer is always included after each conv (+ activation) layer in the CNN. + +The following source code defines a convolutional neural network architecture called LeNet. LeNet is a popular network known to work well on digit classification tasks. We will use a slightly different version from the original LeNet implementation, replacing the sigmoid activations with tanh activations for the neurons. + +A typical way to write your network is creating a new class inherited from `gluon.Block` +class. We can define the network by composing and inheriting Block class as follows: + +```python +import mxnet.ndarray as F + +class Net(gluon.Block): + def __init__(self, **kwargs): + super(Net, self).__init__(**kwargs) + with self.name_scope(): + # layers created in name_scope will inherit name space + # from parent layer. + self.conv1 = nn.Conv2D(20, kernel_size=(5,5)) + self.pool1 = nn.MaxPool2D(pool_size=(2,2), strides = (2,2)) + self.conv2 = nn.Conv2D(50, kernel_size=(5,5)) + self.pool2 = nn.MaxPool2D(pool_size=(2,2), strides = (2,2)) + self.fc1 = nn.Dense(500) + self.fc2 = nn.Dense(10) + + def forward(self, x): + x = self.pool1(F.tanh(self.conv1(x))) + x = self.pool2(F.tanh(self.conv2(x))) + # 0 means copy over size from corresponding dimension. + # -1 means infer size from the rest of dimensions. + x = x.reshape((0, -1)) + x = F.tanh(self.fc1(x)) + x = F.tanh(self.fc2(x)) + return x +``` + +We just defined the forward function here, and the backward function to compute gradients +is automatically defined for you using autograd. +We also imported `mxnet.ndarray` package to use activation functions from `ndarray` API. + +Now, We will create the network as follows: + +```python +net = Net() +``` + +![png](https://raw.githubusercontent.com/dmlc/web-data/master/mxnet/image/conv_mnist.png) + +**Figure 3:** First conv + pooling layer in LeNet. + +Now we train LeNet with the same hyper-parameters as before. Note that, if a GPU is available, we recommend using it. This greatly speeds up computation given that LeNet is more complex and compute-intensive than the previous multilayer perceptron. To do so, we only need to change `mx.cpu()` to `mx.gpu()` and MXNet takes care of the rest. Just like before, we'll stop training after 10 epochs. + +Training and prediction can be done in the similar way as we did for MLP. + +#### Initialize parameters and optimizer + +We will initialize the network parameters as follows: + +```python +ctx = [mx.cpu(0), mx.cpu(1)] +net.collect_params().initialize(mx.init.Xavier(magnitude=2.24), ctx=ctx) +trainer = gluon.Trainer(net.collect_params(), 'sgd', {'learning_rate': 0.1}) +``` + +#### Training + +```python +# Use Accuracy as the evaluation metric. +metric = mx.metric.Accuracy() + +for i in range(epoch): + # Reset the train data iterator. + train_data.reset() + # Loop over the train data iterator. + for batch in train_data: + # Splits train data into multiple slices along batch_axis + # and copy each slice into a context. + data = gluon.utils.split_and_load(batch.data[0], ctx_list=ctx, batch_axis=0) + # Splits train labels into multiple slices along batch_axis + # and copy each slice into a context. + label = gluon.utils.split_and_load(batch.label[0], ctx_list=ctx, batch_axis=0) + outputs = [] + # Inside training scope + with ag.record(): + for x, y in zip(data, label): + z = net(x) + # Computes softmax cross entropy loss. + loss = gluon.loss.softmax_cross_entropy_loss(z, y) + # Backpropogate the error for one iteration. + ag.backward([loss]) + outputs.append(z) + # Updates internal evaluation + metric.update(label, outputs) + # Make one step of parameter update. Trainer needs to know the + # batch size of data to normalize the gradient by 1/batch_size. + trainer.step(batch.data[0].shape[0]) + # Gets the evaluation result. + name, acc = metric.get() + # Reset evaluation result to initial state. + metric.reset() + print('training acc at epoch %d: %s=%f'%(i, name, acc)) +``` + +#### Prediction + +Finally, we'll use the trained LeNet model to generate predictions for the test data. + +```python +# Use Accuracy as the evaluation metric. +metric = mx.metric.Accuracy() +# Reset the validation data iterator. +val_data.reset() +# Loop over the validation data iterator. +for batch in val_data: + # Splits validation data into multiple slices along batch_axis + # and copy each slice into a context. + data = gluon.utils.split_and_load(batch.data[0], ctx_list=ctx, batch_axis=0) + # Splits validation label into multiple slices along batch_axis + # and copy each slice into a context. + label = gluon.utils.split_and_load(batch.label[0], ctx_list=ctx, batch_axis=0) + outputs = [] + for x in data: + outputs.append(net(x)) + # Updates internal evaluation + metric.update(label, outputs) +print('validation acc: %s=%f'%metric.get()) +assert metric.get()[1] > 0.98 +``` + +If all went well, we should see a higher accuracy metric for predictions made using LeNet. With CNN we should be able to correctly predict around 98% of all test images. + +## Summary + +In this tutorial, we have learned how to use MXNet to solve a standard computer vision problem: classifying images of hand written digits. You have seen how to quickly and easily build, train and evaluate models such as MLP and CNN with MXNet Gluon package. + + diff --git a/docs/tutorials/gluon/ndarray.md b/docs/tutorials/gluon/ndarray.md new file mode 100644 index 000000000000..7cf08a88cbf3 --- /dev/null +++ b/docs/tutorials/gluon/ndarray.md @@ -0,0 +1,145 @@ +# NDArray - Scientific computing on CPU and GPU + +NDArray is a tensor data structure similar to numpy's multi-dimensional array. +In addition, it supports asynchronous computation on CPU and GPU. + +First, let's import MXNet: + +```python +from __future__ import print_function +import numpy as np +import mxnet as mx +``` + +## Creating NDArray + +There are many ways to create NDArray. + +Construct from (nested) list: +```python +x = mx.nd.array([[1, 2, 3], [4, 5, 6]]) +print(x) +``` + +Construct from numpy array: +```python +x_numpy = np.ones((2, 3)) +x = mx.nd.array(x_numpy) +print(x) +``` + +Array construction routines: +```python +# create an 2x3 array of ones +x = mx.nd.ones((2, 3)) +print(x) +# create an 2x3 array of zeros +x = mx.nd.zeros((2, 3)) +print(x) +# create an 1d-array of 0 to 5 and reshape to 2x3 +x = mx.nd.arange(6).reshape((2, 3)) +print(x) +``` + +You can convert an NDArray to numpy array to retrieve its data with `.asnumpy()`: +```python +z = x.asnumpy() +print(z) +``` + +## Basic attributes + +NDArray has some basic attributes that you often want to query: + +**NDArray.shape**: The dimensions of the array. It is a tuple of integers +indicating the length of the array along each axis. For a matrix with `n` rows +and `m` columns, its `shape` will be `(n, m)`. + +```python +print('x.shape:', x.shape) +``` + +**NDArray.dtype**: A `numpy` _type_ object describing the type of array +elements. + +```python +print('x.dtype:', x.dtype) +``` + +**NDArray.size**: the total number of components in the array - equals to the +product of the components of its `shape` + +```python +print('x.size:', x.size) +``` + +**NDArray.context**: The device on which this array is stored, e.g. `mx.cpu()` +or `mx.gpu(1)`. + +```python +print('x.context:', x.context) +``` + +## NDArray Operations + +NDArray supports a wide range of operations. Simple operations can be called +with python syntax: + +```python +x = mx.nd.array([[1, 2], [3, 4]]) +y = mx.nd.array([[4, 3], [2, 1]]) +print(x + y) +``` + +You can also call operators from the `mxnet.ndarray` (or `mx.nd` for short) name space: + +```python +z = mx.nd.add(x, y) +print(z) +``` + +You can also pass additional flags to operators: + +```python +z = mx.nd.sum(x, axis=0) +print('axis=0:', z) +z = mx.nd.sum(x, axis=1) +print('axis=1:', z) +``` + +## Using GPU + +Each NDArray lives on a `Context`. MXNet supports `mx.cpu()` for CPU and `mx.gpu(0)`, +`mx.gpu(1)`, etc for GPU. You can specify context when creating NDArray: + +```python +# creates on CPU (the default). +# Replace mx.cpu() with mx.gpu(0) if you have a GPU. +x = mx.nd.zeros((2, 2), ctx=mx.cpu()) +print(x) +``` + +```python +x = mx.nd.array([[1, 2], [3, 4]], ctx=mx.cpu()) +print(x) +``` + +You can copy arrays between devices with `.copyto()`: + +```python +# Copy x to cpu. Replace with mx.gpu(0) if you have GPU. +y = x.copyto(mx.cpu()) +print(y) +``` + +```python +# Copy x to another NDArray, possibly on another Context. +y = mx.nd.zeros_like(x) +x.copyto(y) +print(y) +``` + +See the [Advanced NDArray tutorial](../basic/ndarray.md) for a more detailed +introduction to NDArray API. + + diff --git a/docs/tutorials/index.md b/docs/tutorials/index.md index fbe123ed3e49..32d8bd8ae9d1 100644 --- a/docs/tutorials/index.md +++ b/docs/tutorials/index.md @@ -1,10 +1,12 @@ # Tutorials -These tutorials introduce the fundamental concepts in deep learning and how to implement them in _MXNet_. The _Basics_ section contains tutorials on manipulating arrays, building networks, loading/preprocessing data, etc. The _Training and Inference_ section talks about implementing Linear Regression, training a Handwritten digit classifier using MLP and CNN, running inferences using a pre-trained model, and lastly, efficiently training a large scale image classifier. +These tutorials introduce a few fundamental concepts in deep learning and how to implement them in _MXNet_. The _Basics_ section contains tutorials on manipulating arrays, building networks, loading/preprocessing data, etc. The _Training and Inference_ section talks about implementing Linear Regression, training a Handwritten digit classifier using MLP and CNN, running inferences using a pre-trained model, and lastly, efficiently training a large scale image classifier. + +**Note:** We are working on a set of tutorials for the new imperative interface called Gluon. A preview version is hosted at [thestraightdope.mxnet.io](http://thestraightdope.mxnet.io). ## Python -### Basics +### Basic ```eval_rst .. toctree:: diff --git a/docs/tutorials/nlp/rnn.md b/docs/tutorials/nlp/rnn.md index 0382b2cc23c5..e2d2265ecedf 100644 --- a/docs/tutorials/nlp/rnn.md +++ b/docs/tutorials/nlp/rnn.md @@ -3,12 +3,8 @@ This folder contains RNN examples using a low-level symbol interface. You can ge ## Python -- [lstm.py](lstm.py). Functions for building an LSTM Network -- [gru.py](gru.py). Functions for building a GRU Network -- [lstm_bucketing.py](lstm_bucketing.py). A PennTreeBank language model using LSTM -- [gru_bucketing.py](gru_bucketing.py). A PennTreeBank language model using GRU -- [char-rnn.ipynb](char-rnn.ipynb). A notebook that demonstrates how to train a character LSTM by using ```lstm.py``` - +- [https://github.com/dmlc/mxnet/blob/master/example/rnn/lstm_bucketing.py](lstm_bucketing.py). A PennTreeBank language model using LSTM +- [https://github.com/dmlc/mxnet/blob/master/example/rnn/cudnn_lstm_bucketing.py](cudnn_lstm_bucketing.py). A PennTreeBank language model using LSTM and CUDNN Performance Note: diff --git a/docs/tutorials/python/linear-regression.md b/docs/tutorials/python/linear-regression.md index 75491ab95093..c26435dec6cc 100644 --- a/docs/tutorials/python/linear-regression.md +++ b/docs/tutorials/python/linear-regression.md @@ -1,14 +1,29 @@ # Linear Regression -In this tutorial we'll walk though how one can implement *linear regression* using MXNet APIs. +In this tutorial we'll walk through how one can implement *linear regression* using MXNet APIs. The function we are trying to learn is: *y = x1 + 2x2*, where *(x1,x2)* are input features and *y* is the corresponding label. +## Prerequisites + +To complete this tutorial, we need: + +- MXNet. See the instructions for your operating system in [Setup and Installation](http://mxnet.io/get_started/install.html). + +- [Jupyter Notebook](http://jupyter.org/index.html). + +``` +$ pip install jupyter +``` + To begin, the following code imports the necessary packages we'll need for this exercise. ```python import mxnet as mx import numpy as np + +import logging +logging.getLogger().setLevel(logging.DEBUG) ``` ## Preparing the Data @@ -18,8 +33,8 @@ how to encode a dataset into an iterator that MXNet can use. The data used in th ```python #Training data -train_data = np.array([[1,2],[3,4],[5,6],[3,2],[7,1],[6,9]]) -train_label = np.array([5,11,17,7,9,24]) +train_data = np.random.uniform(0, 1, [100, 2]) +train_label = np.array([train_data[i][0] + 2 * train_data[i][1] for i in range(100)]) batch_size = 1 #Evaluation Data @@ -71,7 +86,7 @@ and make up various components of the model. Symbols are used to define: One such example is the `FullyConnected` symbol which specifies a fully connected layer of a neural network. 3. **Outputs:** Output symbols are MXNet's way of defining a loss. They are - suffixed with the word "Output" (eg. the `SoftmaxOutput` layer. You can also + suffixed with the word "Output" (eg. the `SoftmaxOutput` layer). You can also [create your own loss function](https://github.com/dmlc/mxnet/blob/master/docs/tutorials/r/CustomLossFunction.md#how-to-use-your-own-loss-function). Some examples of existing losses are: `LinearRegressionOutput`, which computes the l2-loss between it's input symbol and the labels provided to it; @@ -140,8 +155,9 @@ parameters of the model to fit the training data. This is accomplished using the ```python model.fit(train_iter, eval_iter, - optimizer_params={'learning_rate':0.01, 'momentum': 0.9}, - num_epoch=1000, + optimizer_params={'learning_rate':0.005, 'momentum': 0.9}, + num_epoch=50, + eval_metric='mse', batch_end_callback = mx.callback.Speedometer(batch_size, 2)) ``` @@ -155,7 +171,7 @@ model.predict(eval_iter).asnumpy() ``` We can also evaluate our model according to some metric. In this example, we are -evaulating our model's mean squared error (MSE) on the evaluation data. +evaluating our model's mean squared error (MSE) on the evaluation data. ```python metric = mx.metric.MSE() @@ -171,7 +187,7 @@ eval_iter = mx.io.NDArrayIter(eval_data, eval_label, batch_size, shuffle=False) model.score(eval_iter, metric) ``` -We also can create a custom metric and use it to evauate the model. More -information on metrics can be found [here](http://mxnet-test.readthedocs.io/en/latest/api/metric.html). +We can also create a custom metric and use it to evaluate a model. More +information on metrics can be found in the [API documentation](http://mxnet.io/api/python/model.html#evaluation-metric-api-reference). diff --git a/docs/tutorials/python/mnist.md b/docs/tutorials/python/mnist.md index f3ce3f26dbf4..4fdf372964a1 100644 --- a/docs/tutorials/python/mnist.md +++ b/docs/tutorials/python/mnist.md @@ -8,6 +8,17 @@ MNIST is a widely used dataset for the hand-written digit classification task. I **Figure 1:** Sample images from the MNIST dataset. +## Prerequisites +To complete this tutorial, we need: + +- MXNet version 0.10 or later. See the installation instructions for your operating system in [Setup and Installation](http://mxnet.io/get_started/install.html). + +- [Python Requests](http://docs.python-requests.org/en/master/) and [Jupyter Notebook](http://jupyter.org/index.html). + +``` +$ pip install requests jupyter +``` + ## Loading Data Before we define the model, let's first fetch the [MNIST](http://yann.lecun.com/exdb/mnist/) dataset. diff --git a/docs/tutorials/python/predict_image.md b/docs/tutorials/python/predict_image.md index c97f6ade5997..1c6cfa8e2e27 100644 --- a/docs/tutorials/python/predict_image.md +++ b/docs/tutorials/python/predict_image.md @@ -3,12 +3,24 @@ This tutorial explains how to recognize objects in an image with a pre-trained model, and how to perform feature extraction. +## Prerequisites + +To complete this tutorial, we need: + +- MXNet. See the instructions for your operating system in [Setup and Installation](http://mxnet.io/get_started/install.html) + +- [Python Requests](http://docs.python-requests.org/en/master/), [Matplotlib](https://matplotlib.org/) and [Jupyter Notebook](http://jupyter.org/index.html). + +``` +$ pip install requests matplotlib jupyter +``` + ## Loading We first download a pre-trained ResNet 152 layer that is trained on the full -Imagenet dataset with over 10 million images and 10 thousand classes. A +ImageNet dataset with over 10 million images and 10 thousand classes. A pre-trained model contains two parts, a json file containing the model -definition and a binary file containing the parameters. In addition there may be +definition and a binary file containing the parameters. In addition, there may be a text file for the labels. ```python @@ -20,13 +32,14 @@ path='http://data.mxnet.io/models/imagenet-11k/' ``` Next, we load the downloaded model. *Note:* If GPU is available, we can replace all -occurances of `mx.cpu()` with `mx.gpu()` to accelerate the computation. +occurrences of `mx.cpu()` with `mx.gpu()` to accelerate the computation. ```python sym, arg_params, aux_params = mx.model.load_checkpoint('resnet-152', 0) -mod = mx.mod.Module(symbol=sym, context=mx.cpu()) -mod.bind(for_training=False, data_shapes=[('data', (1,3,224,224))]) -mod.set_params(arg_params, aux_params) +mod = mx.mod.Module(symbol=sym, context=mx.cpu(), label_names=None) +mod.bind(for_training=False, data_shapes=[('data', (1,3,224,224))], + label_shapes=mod._label_shapes) +mod.set_params(arg_params, aux_params, allow_missing=True) with open('synset.txt', 'r') as f: labels = [l.rstrip() for l in f] ``` @@ -68,8 +81,8 @@ def predict(url): prob = mod.get_outputs()[0].asnumpy() # print the top-5 prob = np.squeeze(prob) - prob = np.argsort(prob)[::-1] - for i in prob[0:5]: + a = np.argsort(prob)[::-1] + for i in a[0:5]: print('probability=%f, class=%s' %(prob[i], labels[i])) ``` @@ -80,12 +93,12 @@ predict('http://writm.com/wp-content/uploads/2016/08/Cat-hd-wallpapers.jpg') ``` ```python -predict('http://images-na.ssl-images-amazon.com/images/G/01/img15/pet-products/small-tiles/23695_pets_vertical_store_dogs_small_tile_8._CB312176604_.jpg') +predict('http://thenotoriouspug.com/wp-content/uploads/2015/01/Pug-Cookie-1920x1080-1024x576.jpg') ``` ## Feature extraction -By feature extraction we mean presenting the input images by the output of an +By feature extraction, we mean presenting the input images by the output of an internal layer rather than the last softmax layer. These outputs, which can be viewed as the feature of the raw input image, can then be used by other applications such as object detection. @@ -99,11 +112,11 @@ all_layers = sym.get_internals() all_layers.list_outputs()[-10:] ``` -A often used layer for feature extraction is the one before the last fully -connected layer. For ResNet, and also Inception, it is the flatten layer with +An often used layer for feature extraction is the one before the last fully +connected layer. For ResNet, and also Inception, it is the flattened layer with name `flatten0` which reshapes the 4-D convolutional layer output into 2-D for the fully connected layer. The following source code extracts a new Symbol which -outputs the flatten layer and creates a model. +outputs the flattened layer and creates a model. ```python fe_sym = all_layers['flatten0_output'] diff --git a/docs/tutorials/r/CatsDogsFinetune.rmd b/docs/tutorials/r/CatsDogsFinetune.rmd deleted file mode 100644 index a99e7042804e..000000000000 --- a/docs/tutorials/r/CatsDogsFinetune.rmd +++ /dev/null @@ -1,305 +0,0 @@ ---- -title: "Dogs vs. Cats classification with mxnet and R" -author: "Andrey Ogurtsov (https://github.com/statist-bhfz/)" -date: "February 25, 2017" ---- - -## 1. Packages and prerequisites - -Ubuntu 16, **mxnet** 0.9.4 (compiled with GPU support), **imager** for image processind, **abind** for manipulations with arrays. It is almost end-to-end R solution for Kaggle competition https://www.kaggle.com/c/dogs-vs-cats-redux-kernels-edition/, we will use Python only for creating .rec-files. - -Thanks to [jeremiedb](https://github.com/jeremiedb), my code for fine-tuning is largely based on his [answers](https://github.com/dmlc/mxnet/issues/4817). - -```{r} -knitr::opts_chunk$set(eval = FALSE) -``` - -```{r} -library(imager) -library(mxnet) -library(abind) -``` - - -## 2. Image processing - -### 2.1. Renaming train files - -```{r} -files <- list.files("train") -old_names <- sapply(files, strsplit, split = ".", fixed = TRUE) -max_length <- max(sapply(old_names, function(x) nchar(x[[2]]))) -zeros <- max_length - sapply(old_names, function(x) nchar(x[[2]])) -zeros <- sapply(zeros, function(x) paste(rep(0, x), collapse = "")) -new_names <- Map(function(x, y) {paste0("./train/", - x[1], - "/", - y, - x[2], - ".jpg")}, - x = old_names, y = zeros - ) - -# Full names -files <- paste0("./train/", files) - -dir.create("./train/cat") -dir.create("./train/dog") - -# New names will be in 00001.jpg format -Map(function(x, y) file.rename(from = x, to = y), files, new_names) -``` - -### 2.2. Train images: 224x224, padded with empty space - -```{r} -files <- list.files("train", recursive = TRUE) -new_names <- paste0("train_pad_224x224/", files) -files <- paste0("./train/", files) -dir.create("./train_pad_224x224/") -dir.create("./train_pad_224x224/cat") -dir.create("./train_pad_224x224/dog") - -padImage <- function(x) { - long_side <- max(dim(x)[1:2]) - short_side <- min(dim(x)[1:2]) - pad_img <- pad(x, - nPix = long_side - short_side, - axes = ifelse(dim(x)[1] < dim(x)[2], "x", "y")) - return(pad_img) -} - -Map(function(x, y) { - pad_img <- padImage(load.image(x)) - res_img <- resize(pad_img, size_x = 224, size_y = 224) - imager::save.image(res_img, y) -}, x = files, y = new_names) -``` - -### 2.3. Renaming test files - -```{r} -files <- list.files("test") -max_length <- max(sapply(files, nchar)) -zeros <- max_length - sapply(files, nchar) -zeros <- sapply(zeros, function(x) paste(rep(0, x), collapse = "")) -newnames <- paste0("./test/", zeros, files) - -files <- paste0("./test/", files) - -Map(function(x, y) file.rename(from = x, to = y), files, newnames) -``` - - -### 2.4. Test images: 224x224, padded with empty space - -```{r} -files <- list.files("test") -new_names <- paste0("test_pad_224x224/", files) -files <- paste0("./test/", files) -dir.create("./test_pad_224x224/") - -Map(function(x, y) { - pad_img <- padImage(load.image(x)) - res_img <- resize(pad_img, size_x = 224, size_y = 224) - imager::save.image(res_img, y) -}, x = files, y = new_names) -``` - -### 2.5. Creating .rec files - -```{bash, eval = FALSE} -python ~/mxnet/tools/im2rec.py --list=1 --recursive=1 --train-ratio=0.8 cats_dogs train_pad_224x224 -python ~/mxnet/tools/im2rec.py --num-thread=4 --pass-through=1 cats_dogs_train.lst train_pad_224x224 -python ~/mxnet/tools/im2rec.py --num-thread=4 --pass-through=1 cats_dogs_val.lst train_pad_224x224 -``` - - -## 3. Iterators - -```{r} -get_iterator <- function(data_shape, - train_data, - val_data, - batch_size = 128) { - train <- mx.io.ImageRecordIter( - path.imgrec = train_data, - batch.size = batch_size, - data.shape = data_shape, - rand.crop = TRUE, - rand.mirror = TRUE) - - val <- mx.io.ImageRecordIter( - path.imgrec = val_data, - batch.size = batch_size, - data.shape = data_shape, - rand.crop = FALSE, - rand.mirror = FALSE - ) - - return(list(train = train, val = val)) -} -``` - - -```{r} -data <- get_iterator(data_shape = c(224, 224, 3), - train_data = "/media/andrey/Data/KAGGLE/cats_dogs/cats_dogs_train.rec", - val_data = "/media/andrey/Data/KAGGLE/cats_dogs/cats_dogs_val.rec", - batch_size = 8) -train <- data$train -val <- data$val -``` - - -## 4. Load pretrained model - -Model from http://data.dmlc.ml/models/imagenet/ -Last fully connected layes for 1000 classes replaced with new layer for 2 classes. - - -```{r} -inception_bn <- mx.model.load("models/inception_bn/Inception-BN", - iteration = 126) - -symbol <- inception_bn$symbol -# check symbol$arguments for layer names -internals <- symbol$get.internals() -outputs <- internals$outputs - -flatten <- internals$get.output(which(outputs == "flatten_output")) - -new_fc <- mx.symbol.FullyConnected(data = flatten, - num_hidden = 2, - name = "fc1") - # set name to original name in symbol$arguments -new_soft <- mx.symbol.SoftmaxOutput(data = new_fc, - name = "softmax") - # set name to original name in symbol$arguments - -arg_params_new <- mxnet:::mx.model.init.params( - symbol = new_soft, - input.shape = c(224, 224, 3, 8), - initializer = mxnet:::mx.init.uniform(0.1), - ctx = mx.gpu(0) - )$arg.params -fc1_weights_new <- arg_params_new[["fc1_weight"]] -fc1_bias_new <- arg_params_new[["fc1_bias"]] - -arg_params_new <- inception_bn$arg.params - -arg_params_new[["fc1_weight"]] <- fc1_weights_new -arg_params_new[["fc1_bias"]] <- fc1_bias_new -``` - - -## 5. Train (fine-tune) model - -```{r} -model <- mx.model.FeedForward.create( - symbol = new_soft, - X = train, - eval.data = val, - ctx = mx.gpu(0), - eval.metric = mx.metric.accuracy, - num.round = 1, - learning.rate = 0.05, - momentum = 0.9, - wd = 0.00001, - kvstore = "local", - array.batch.size = 128, - epoch.end.callback = mx.callback.save.checkpoint("inception_bn"), - batch.end.callback = mx.callback.log.train.metric(150), - initializer = mx.init.Xavier(factor_type = "in", magnitude = 2.34), - optimizer = "sgd", - arg.params = arg_params_new, - aux.params = inception_bn$aux.params -) -``` - -```{r} -model <- mx.model.load("inception_bn", 1) -``` - -Continue training with decreased speed (`learning.rate = 0.03`): - -```{r} -model <- mx.model.FeedForward.create( - symbol = model$symbol, - X = train, - eval.data = val, - ctx = mx.gpu(0), - eval.metric = mx.metric.accuracy, - num.round = 5, - learning.rate = 0.03, - momentum = 0.9, - wd = 0.00001, - kvstore = "local", - array.batch.size = 100, - epoch.end.callback = mx.callback.save.checkpoint("inception_bn"), - batch.end.callback = mx.callback.log.train.metric(150), - initializer = mx.init.Xavier(factor_type = "in", magnitude = 2.34), - optimizer = "sgd", - arg.params = model$arg.params, - aux.params = model$aux.params -) -``` - -```{r} -model <- mx.model.load("inception_bn", 1) -``` - -My R session crashed after each iteration, so I made some iterations manually. - - -## 6. Make predictions - -```{r} -preprocImage<- function(src, # URL or file location - height = 224, - width = 224, - num_channels = 3, # 3 for RGB, 1 for grayscale - mult_by = 1, # set to 255 for normalized image - crop = FALSE) { # no crop by default - - im <- load.image(src) - - if (crop) { - shape <- dim(im) - short_edge <- min(shape[1:2]) - xx <- floor((shape[1] - short_edge) / 2) - yy <- floor((shape[2] - short_edge) / 2) - im <- crop.borders(im, xx, yy) - } - - resized <- resize(im, size_x = width, size_y = height) - arr <- as.array(resized) * mult_by - dim(arr) <- c(width, height, num_channels, 1) - return(arr) -} -``` - -```{r} -files <- list.files("test_pad_224x224/") -files <- paste0("./test_pad_224x224/", files) - -# ind <- seq(1, 12500, 1250) -# probs <- numeric() -# for (i in ind) { -# images <- lapply(files[i:i+1249], preprocImage, mult_by = 255) -# images <- do.call(abind, images) -# probs[i:i+1249] <- predict(model, X = images, ctx = mx.gpu(0)) -# } - -files <- split(files, rep(1:1250, each = 10)) -probs <- lapply(files, function(x) { - images <- lapply(x, preprocImage, mult_by = 255) - images <- do.call(abind, images) - probs <- predict(model, X = images, ctx = mx.gpu(0)) -}) -saveRDS(probs, "probs.rds") -probs <- t(do.call(cbind, probs)) - -preds <- data.frame(id = 1:12500, label = probs[, 2]) -write.csv(preds, "subm.csv", row.names = FALSE, quote = FALSE) -``` diff --git a/docs/tutorials/r/CustomLossFunction.md b/docs/tutorials/r/CustomLossFunction.md index a7104803cacb..afb99518894c 100644 --- a/docs/tutorials/r/CustomLossFunction.md +++ b/docs/tutorials/r/CustomLossFunction.md @@ -3,57 +3,201 @@ Customized loss function This tutorial provides guidelines for using customized loss function in network construction. - Model Training Example ----------- +---------------------- Let's begin with a small regression example. We can build and train a regression model with the following code: +``` r +data(BostonHousing, package = "mlbench") +BostonHousing[, sapply(BostonHousing, is.factor)] <- + as.numeric(as.character(BostonHousing[, sapply(BostonHousing, is.factor)])) +BostonHousing <- data.frame(scale(BostonHousing)) + +test.ind = seq(1, 506, 5) # 1 pt in 5 used for testing +train.x = data.matrix(BostonHousing[-test.ind,-14]) +train.y = BostonHousing[-test.ind, 14] +test.x = data.matrix(BostonHousing[--test.ind,-14]) +test.y = BostonHousing[--test.ind, 14] + +require(mxnet) +``` + + ## Loading required package: mxnet + +``` r +data <- mx.symbol.Variable("data") +label <- mx.symbol.Variable("label") +fc1 <- mx.symbol.FullyConnected(data, num_hidden = 14, name = "fc1") +tanh1 <- mx.symbol.Activation(fc1, act_type = "tanh", name = "tanh1") +fc2 <- mx.symbol.FullyConnected(tanh1, num_hidden = 1, name = "fc2") +lro <- mx.symbol.LinearRegressionOutput(fc2, name = "lro") + +mx.set.seed(0) +model <- mx.model.FeedForward.create(lro, X = train.x, y = train.y, + ctx = mx.cpu(), + num.round = 5, + array.batch.size = 60, + optimizer = "rmsprop", + verbose = TRUE, + array.layout = "rowmajor", + batch.end.callback = NULL, + epoch.end.callback = NULL) +``` + + ## Start training with 1 devices + +``` r +pred <- predict(model, test.x) +``` + + ## Warning in mx.model.select.layout.predict(X, model): Auto detect layout of input matrix, use rowmajor.. + +``` r +sum((test.y - pred[1,])^2) / length(test.y) +``` - ```r - library(mxnet) - data(BostonHousing, package="mlbench") - train.ind = seq(1, 506, 3) - train.x = data.matrix(BostonHousing[train.ind, -14]) - train.y = BostonHousing[train.ind, 14] - test.x = data.matrix(BostonHousing[-train.ind, -14]) - test.y = BostonHousing[-train.ind, 14] - data <- mx.symbol.Variable("data") - fc1 <- mx.symbol.FullyConnected(data, num_hidden=1) - lro <- mx.symbol.LinearRegressionOutput(fc1) - mx.set.seed(0) - model <- mx.model.FeedForward.create( - lro, X=train.x, y=train.y, - eval.data=list(data=test.x, label=test.y), - ctx=mx.cpu(), num.round=10, array.batch.size=20, - learning.rate=2e-6, momentum=0.9, eval.metric=mx.metric.rmse) - ``` - -Besides the `LinearRegressionOutput`, we also provide `LogisticRegressionOutput` and `MAERegressionOutput`. -However, this might not be enough for real-world models. You can provide your own loss function -by using `mx.symbol.MakeLoss` when constructing the network. + ## [1] 0.2485236 +Besides the `LinearRegressionOutput`, we also provide `LogisticRegressionOutput` and `MAERegressionOutput`. However, this might not be enough for real-world models. You can provide your own loss function by using `mx.symbol.MakeLoss` when constructing the network. How to Use Your Own Loss Function ---------- +--------------------------------- + +We still use our previous example, but this time we use `mx.symbol.MakeLoss` to minimize the `(pred-label)^2` + +``` r +data <- mx.symbol.Variable("data") +label <- mx.symbol.Variable("label") +fc1 <- mx.symbol.FullyConnected(data, num_hidden = 14, name = "fc1") +tanh1 <- mx.symbol.Activation(fc1, act_type = "tanh", name = "tanh1") +fc2 <- mx.symbol.FullyConnected(tanh1, num_hidden = 1, name = "fc2") +lro2 <- mx.symbol.MakeLoss(mx.symbol.square(mx.symbol.Reshape(fc2, shape = 0) - label), name="lro2") +``` + +Then we can train the network just as usual. + +``` r +mx.set.seed(0) +model2 <- mx.model.FeedForward.create(lro2, X = train.x, y = train.y, + ctx = mx.cpu(), + num.round = 5, + array.batch.size = 60, + optimizer = "rmsprop", + verbose = TRUE, + array.layout = "rowmajor", + batch.end.callback = NULL, + epoch.end.callback = NULL) +``` + + ## Start training with 1 devices + +We should get very similar results because we are actually minimizing the same loss function. However, the result is quite different. + +``` r +pred2 <- predict(model2, test.x) +``` + + ## Warning in mx.model.select.layout.predict(X, model): Auto detect layout of input matrix, use rowmajor.. + +``` r +sum((test.y - pred2)^2) / length(test.y) +``` + + ## [1] 1.234584 + +This is because output of `mx.symbol.MakeLoss` is the gradient of loss with respect to the input data. We can get the real prediction as below. + +``` r +internals = internals(model2$symbol) +fc_symbol = internals[[match("fc2_output", outputs(internals))]] + +model3 <- list(symbol = fc_symbol, + arg.params = model2$arg.params, + aux.params = model2$aux.params) + +class(model3) <- "MXFeedForwardModel" + +pred3 <- predict(model3, test.x) +``` + + ## Warning in mx.model.select.layout.predict(X, model): Auto detect layout of input matrix, use rowmajor.. + +``` r +sum((test.y - pred3[1,])^2) / length(test.y) +``` + + ## [1] 0.248294 + +We have provided many operations on the symbols. An example of `|pred-label|` can be found below. + +``` r +lro_abs <- mx.symbol.MakeLoss(mx.symbol.abs(mx.symbol.Reshape(fc2, shape = 0) - label)) +mx.set.seed(0) +model4 <- mx.model.FeedForward.create(lro_abs, X = train.x, y = train.y, + ctx = mx.cpu(), + num.round = 20, + array.batch.size = 60, + optimizer = "sgd", + learning.rate = 0.001, + verbose = TRUE, + array.layout = "rowmajor", + batch.end.callback = NULL, + epoch.end.callback = NULL) +``` + + ## Start training with 1 devices + +``` r +internals = internals(model4$symbol) +fc_symbol = internals[[match("fc2_output", outputs(internals))]] + +model5 <- list(symbol = fc_symbol, + arg.params = model4$arg.params, + aux.params = model4$aux.params) + +class(model5) <- "MXFeedForwardModel" + +pred5 <- predict(model5, test.x) +``` + + ## Warning in mx.model.select.layout.predict(X, model): Auto detect layout of input matrix, use rowmajor.. + +``` r +sum(abs(test.y - pred5[1,])) / length(test.y) +``` + + ## [1] 0.7056902 + +``` r +lro_mae <- mx.symbol.MAERegressionOutput(fc2, name = "lro") +mx.set.seed(0) +model6 <- mx.model.FeedForward.create(lro_mae, X = train.x, y = train.y, + ctx = mx.cpu(), + num.round = 20, + array.batch.size = 60, + optimizer = "sgd", + learning.rate = 0.001, + verbose = TRUE, + array.layout = "rowmajor", + batch.end.callback = NULL, + epoch.end.callback = NULL) +``` + + ## Start training with 1 devices -We still use our previous example. +``` r +pred6 <- predict(model6, test.x) +``` - ```r - library(mxnet) - data <- mx.symbol.Variable("data") - fc1 <- mx.symbol.FullyConnected(data, num_hidden=1) - lro <- mx.symbol.MakeLoss(mx.symbol.square(mx.symbol.Reshape(fc1, shape = 0) - label)) - ``` + ## Warning in mx.model.select.layout.predict(X, model): Auto detect layout of input matrix, use rowmajor.. -In the last line of network definition, we do not use the predefined loss function. We define the loss -by ourselves, which is `(pred-label)^2`. +``` r +sum(abs(test.y - pred6[1,])) / length(test.y) +``` -We have provided many operations on the symbols, so you can also define `|pred-label|` using the line below. + ## [1] 0.7056902 - ```r - lro <- mx.symbol.MakeLoss(mx.symbol.abs(mx.symbol.Reshape(fc1, shape = 0) - label)) - ``` ## Next Steps * [Neural Networks with MXNet in Five Minutes](http://mxnet.io/tutorials/r/fiveMinutesNeuralNetwork.html) diff --git a/docs/tutorials/scala/mnist.md b/docs/tutorials/scala/mnist.md index e01ac49ed0c1..ad55ee4c0257 100644 --- a/docs/tutorials/scala/mnist.md +++ b/docs/tutorials/scala/mnist.md @@ -4,6 +4,12 @@ This Scala tutorial guides you through a classic computer vision application: id Let's train a 3-layer network (i.e multilayer perceptron network) on the MNIST dataset to classify handwritten digits. +## Prerequisites +To complete this tutorial, we need: + +- to compile the latest MXNet version. See the MXNet installation instructions for your operating system in [Setup and Installation](http://mxnet.io/get_started/install.html). +- to compile the Scala API. See Scala API build instructions in [Build](https://github.com/dmlc/mxnet/tree/master/scala-package). + ## Define the Network First, define the neural network's architecture using the Symbol API: @@ -87,7 +93,7 @@ while (valDataIter.hasNext) { val y = NDArray.concatenate(labels) // get predicted labels -val predictedY = NDArray.argmaxChannel(prob) +val predictedY = NDArray.argmax_channel(prob) require(y.shape == predictedY.shape) // calculate accuracy diff --git a/docs/tutorials/unsupervised_learning/gan.md b/docs/tutorials/unsupervised_learning/gan.md index 6491806c0acc..709e1323c6f6 100644 --- a/docs/tutorials/unsupervised_learning/gan.md +++ b/docs/tutorials/unsupervised_learning/gan.md @@ -1,5 +1,383 @@ -# Generative Adversarial Network -Get the source code for an example of a generative adversarial network (GAN) running on MXNet on GitHub in the [gan](https://github.com/dmlc/mxnet/tree/master/example/gan) folder. +# Generative Adversarial Networks -## Next Steps -* [MXNet tutorials index](http://mxnet.io/tutorials/index.html) \ No newline at end of file +GANs are an application of unsupervised learning - you don't need labels for your dataset in order to train a GAN. + +The GAN framework composes of two neural networks: a generator network and a discriminator network. + +The generator's job is to take a set of random numbers and produce data (such as images or text). + +The discriminator then takes in that data as well as samples of that data from a dataset and tries to determine if is "fake" (created by the generator network) or "real" (from the original dataset). + +During training, the two networks play a game against each other. The generator tries to create realistic data, so that it can fool the discriminator into thinking that the data it generated is from the original dataset. At the same time, the discriminator tries to not be fooled - it learns to become better at determining if data is real or fake. + +Since the two networks are fighting in this game, they can be seen as as adversaries, which is where the term "Generative Adverserial Network" comes from. + +## Deep Convolutional Generative Adversarial Networks + +This tutorial takes a look at Deep Convolutional Generative Adversarial Networks (DCGAN), which combines Convolutional Neural Networks (CNNs) and GANs. + +We will create a DCGAN that is able to create images of handwritten digits from random numbers.The tutorial uses the neural net architecture and guidelines outlined in [this paper](https://arxiv.org/abs/1511.06434), and the MNIST dataset. + +##How to Use This Tutorial +You can use this tutorial by executing each snippet of python code in order as it appears in the tutorial. + + +1. The first net is the "generator" and creates images of handwritten digits from random numbers. +2. The second net is the "discriminator" and determines if the image created by the generator is real (a realistic looking image of handwritten digits) or fake (an image that doesn't look like it came from the original dataset). + +Apart from creating a DCGAN, you'll also learn: + +- How to manipulate and iterate through batches images that you can feed into your neural network. + +- How to create a custom MXNet data iterator that generates random numbers from a normal distribution. + +- How to create a custom training process in MXNet, using lower level functions from the MXNet Module API such as .bind() .forward() and .backward(). The training process for a DCGAN is more complex than many other neural net's, so we need to use these functions instead of using the higher level .fit() function. + +- How to visualize images as they are going through the training process + +## Prerequisites + +This tutorial assumes you're familiar with the concept of CNN's and have implemented one in MXNet. You should also be familiar with the concept of logistic regression. Having a basic understanding for MXNet data iterators helps, since we'll create a custom Data Iterator to iterate though random numbers as inputs to our generator network. + +This example is designed to be trained on a single GPU. Training this network on CPU can be slow, so it's recommended that you use a GPU for training. + +To complete this tutorial, you need: + +- MXNet +- Python 2.7, and the following libraries for Python: + - Numpy - for matrix math + - OpenCV - for image manipulation + - Scikit-learn - to easily get our dataset + - Matplotlib - to visualize our output + +## The Data +We need two pieces of data to train our DCGAN: + 1. Images of handwritten digits from the MNIST dataset + 2. Random numbers from a normal distribution + +Our generator network will use the random numbers as the input to produce images of handwritten digits, and out discriminator network will use images of handwritten digits from the MNIST dataset to determine if images produced by our generator are realistic. + +We are going to use the python library, scikit-learn, to get the MNIST dataset. Scikit-learn comes with a function that gets the dataset for us, which we will then manipulate to create our training and testing inputs. + +The MNIST dataset contains 70,000 images of handwritten digits. Each image is 28x28 pixels in size. To create random numbers, we're going to create a custom MXNet data iterator, which will returns random numbers from a normal distribution as we need then. + +## Prepare the Data + +### 1. Preparing the MNSIT dataset + +Let's start by preparing our handwritten digits from the MNIST dataset. We import the fetch_mldata function from scikit-learn, and use it to get the MNSIT dataset. Notice that it's shape is 70000x784. This contains the 70000 images on every row and 784 pixels of each image in the columns of each row. Each image is 28x28 pixels, but has been flattened so that all 784 images are represented in a single list. +```python +from sklearn.datasets import fetch_mldata +mnist = fetch_mldata('MNIST original') +``` + +Next, we'll randomize the handwritten digits by using numpy to create random permutations on the dataset on our rows (images). We'll then reshape our dataset from 70000x786 to 70000x28x28, so that every image in our dataset is arranged into a 28x28 grid, where each cell in the grid represents 1 pixel of the image. + +```python +import numpy as np +#Use a seed so that we get the same random permutation each time +np.random.seed(1) +p = np.random.permutation(mnist.data.shape[0]) +X = mnist.data[p] +X = X.reshape((70000, 28, 28)) +``` +Since the DCGAN that we're creating takes in a 64x64 image as the input, we'll use OpenCV to resize the each 28x28 image to 64x64 images: +```python +import cv2 +X = np.asarray([cv2.resize(x, (64,64)) for x in X]) +``` +Each pixel in our 64x64 image is represented by a number between 0-255, that represents the intensity of the pixel. However, we want to input numbers between -1 and 1 into our DCGAN, as suggested by the research paper. To rescale our pixels to be in the range of -1 to 1, we'll divide each pixel by (255/2). This put our images on a scale of 0-2. We can then subtract by 1, to get them in the range of -1 to 1. +```python +X = X.astype(np.float32)/(255.0/2) - 1.0 +``` +Ultimately, images are inputted into our neural net from a 70000x3x64x64 array, and they are currently in a 70000x64x64 array. We need to add 3 channels to our images. Typically when we are working with images, the 3 channels represent the red, green, and blue components of each image. Since the MNIST dataset is grayscale, we only need 1 channel to represent our dataset. We will pad the other channels with 0's: + +```python +X = X.reshape((70000, 1, 64, 64)) +X = np.tile(X, (1, 3, 1, 1)) +``` +Finally, we'll put our images into MXNet's NDArrayIter, which will allow MXNet to easily iterate through our images during training. We'll also split up them images into a batches, with 64 images in each batch. Every time we iterate, we'll get a 4 dimensional array with size (64, 3, 64, 64), representing a batch of 64 images. +```python +import mxnet as mx +batch_size = 64 +image_iter = mx.io.NDArrayIter(X, batch_size=batch_size) +``` +### 2. Preparing Random Numbers + +We need to input random numbers from a normal distribution to our generator network, so we'll create an MXNet DataIter that produces random numbers for each training batch. The DataIter is the base class of MXNet's Data Loading API. Below, we create a class called RandIter which is a subclass of DataIter. We use MXNet's built in mx.random.normal function in order to return the normally distributed random numbers every time we iterate. +```python +class RandIter(mx.io.DataIter): + def __init__(self, batch_size, ndim): + self.batch_size = batch_size + self.ndim = ndim + self.provide_data = [('rand', (batch_size, ndim, 1, 1))] + self.provide_label = [] + + def iter_next(self): + return True + + def getdata(self): + #Returns random numbers from a gaussian (normal) distribution + #with mean=0 and standard deviation = 1 + return [mx.random.normal(0, 1.0, shape=(self.batch_size, self.ndim, 1, 1))] +``` +When we initalize our RandIter, we need to provide two numbers: the batch size and how many random numbers we want to produce a single image from. This number is referred to as Z, and we'll set this to 100. This value comes from the research paper on the topic. Every time we iterate and get a batch of random numbers, we will get a 4 dimensional array with shape: (batch_size, Z, 1, 1), which in our example is (64, 100, 1, 1). +```python +Z = 100 +rand_iter = RandIter(batch_size, Z) +``` +## Create the Model + +Our model has two networks that we will train together - the generator network and the disciminator network. + +### The Generator + +Let's start off by defining the generator network, which uses deconvolutional layers (also callled fractionally strided layers) to generate an image form random numbers : +```python +no_bias = True +fix_gamma = True +epsilon = 1e-5 + 1e-12 + +rand = mx.sym.Variable('rand') + +g1 = mx.sym.Deconvolution(rand, name='g1', kernel=(4,4), num_filter=1024, no_bias=no_bias) +gbn1 = mx.sym.BatchNorm(g1, name='gbn1', fix_gamma=fix_gamma, eps=epsilon) +gact1 = mx.sym.Activation(gbn1, name='gact1', act_type='relu') + +g2 = mx.sym.Deconvolution(gact1, name='g2', kernel=(4,4), stride=(2,2), pad=(1,1), num_filter=512, no_bias=no_bias) +gbn2 = mx.sym.BatchNorm(g2, name='gbn2', fix_gamma=fix_gamma, eps=epsilon) +gact2 = mx.sym.Activation(gbn2, name='gact2', act_type='relu') + +g3 = mx.sym.Deconvolution(gact2, name='g3', kernel=(4,4), stride=(2,2), pad=(1,1), num_filter=256, no_bias=no_bias) +gbn3 = mx.sym.BatchNorm(g3, name='gbn3', fix_gamma=fix_gamma, eps=epsilon) +gact3 = mx.sym.Activation(gbn3, name='gact3', act_type='relu') + +g4 = mx.sym.Deconvolution(gact3, name='g4', kernel=(4,4), stride=(2,2), pad=(1,1), num_filter=128, no_bias=no_bias) +gbn4 = mx.sym.BatchNorm(g4, name='gbn4', fix_gamma=fix_gamma, eps=epsilon) +gact4 = mx.sym.Activation(gbn4, name='gact4', act_type='relu') + +g5 = mx.sym.Deconvolution(gact4, name='g5', kernel=(4,4), stride=(2,2), pad=(1,1), num_filter=3, no_bias=no_bias) +generatorSymbol = mx.sym.Activation(g5, name='gact5', act_type='tanh') +``` + +Our generator image starts with random numbers that will be obtained from the RandIter we created earlier, so we created the rand variable for this input. +We then start creating the model starting with a Deconvolution layer (sometimes called 'fractionally strided layer'). We apply batch normalization and ReLU activation after the Deconvolution layer. + +We repeat this process 4 times, applying a (2,2) stride and (1,1) pad at each Deconvolutional layer, which doubles the size of our image at each layer. By creating these layers, our generator network will have to learn to upsample our input vector of random numbers, Z at each layer, so that network output a final image. We also reduce half the number of filters at each layer, reducing dimensionality at each layer. Ultimatley, our output layer is a 64x64x3 layer, representing the size and channels of our image. We use tanh activation instead of relu on the last layer, as recommended by the research on DCGANs. The output of neurons in the final gout layer represent the pixels of generated image. + +Notice we used 3 parameters to help us create our model: no_bias, fixed_gamma, and epsilon. Neurons in our network won't have a bias added to them, this seems to work better in practice for the DCGAN. In our batch norm layer, we set fixed_gamma=True, which means gamma=1 for all of our batch norm layers. epsilon is a small number that gets added to our batch norm so that we don't end up dividing by zero. By default, CuDNN requires that this number is greater than 1e-5, so we add a small number to this value, ensuring this values stays small. + +### The Discriminator + +Let's now create our discriminator network, which will take in images of handwritten digits from the MNIST dataset and images created by the generator network: +```python +data = mx.sym.Variable('data') + +d1 = mx.sym.Convolution(data, name='d1', kernel=(4,4), stride=(2,2), pad=(1,1), num_filter=128, no_bias=no_bias) +dact1 = mx.sym.LeakyReLU(d1, name='dact1', act_type='leaky', slope=0.2) + +d2 = mx.sym.Convolution(dact1, name='d2', kernel=(4,4), stride=(2,2), pad=(1,1), num_filter=256, no_bias=no_bias) +dbn2 = mx.sym.BatchNorm(d2, name='dbn2', fix_gamma=fix_gamma, eps=epsilon) +dact2 = mx.sym.LeakyReLU(dbn2, name='dact2', act_type='leaky', slope=0.2) + +d3 = mx.sym.Convolution(dact2, name='d3', kernel=(4,4), stride=(2,2), pad=(1,1), num_filter=512, no_bias=no_bias) +dbn3 = mx.sym.BatchNorm(d3, name='dbn3', fix_gamma=fix_gamma, eps=epsilon) +dact3 = mx.sym.LeakyReLU(dbn3, name='dact3', act_type='leaky', slope=0.2) + +d4 = mx.sym.Convolution(dact3, name='d4', kernel=(4,4), stride=(2,2), pad=(1,1), num_filter=1024, no_bias=no_bias) +dbn4 = mx.sym.BatchNorm(d4, name='dbn4', fix_gamma=fix_gamma, eps=epsilon) +dact4 = mx.sym.LeakyReLU(dbn4, name='dact4', act_type='leaky', slope=0.2) + +d5 = mx.sym.Convolution(dact4, name='d5', kernel=(4,4), num_filter=1, no_bias=no_bias) +d5 = mx.sym.Flatten(d5) + +label = mx.sym.Variable('label') +discriminatorSymbol = mx.sym.LogisticRegressionOutput(data=d5, label=label, name='dloss') +``` + +We start off by creating the data variable, which is used to hold our input images to the discriminator. + +The discriminator then goes through a series of 5 convolutional layers, each with a 4x4 kernel, 2x2 stride, and 1x1 pad. These layers half the size of the image (which starts at 64x64) at each convolutional layer. Our model also increases dimensionality at each layer by doubling the number of filters per convolutional layer, starting at 128 filters and ending at 1024 filters before we flatten the output. + +At the final convolution, we flatten the neural net to get one number as the final output of discriminator network. This number is the probability the image is real, as determined by our discriminator. We use logistic regression to determine this probability. When we pass in "real" images from the MNIST dataset, we can label these as 1 and we can label the "fake" images from the generator net as 0 to perform logistic regression on the discriminator network. +Prepare the models using the Module API + +So far we have defined a MXNet Symbol for both the generator and the discriminator network. Before we can train our model, we need to bind these symbols using the Module API, which creates the computation graph for our models. It also allows us to decide how we want to initialize our model and what type of optimizer we want to use. Let's set up Module for both of our networks: +```python +#Hyperperameters +sigma = 0.02 +lr = 0.0002 +beta1 = 0.5 +ctx = mx.gpu(0) + +#=============Generator Module============= +generator = mx.mod.Module(symbol=generatorSymbol, data_names=('rand',), label_names=None, context=ctx) +generator.bind(data_shapes=rand_iter.provide_data) +generator.init_params(initializer=mx.init.Normal(sigma)) +generator.init_optimizer( + optimizer='adam', + optimizer_params={ + 'learning_rate': lr, + 'beta1': beta1, + }) +mods = [generator] + +# =============Discriminator Module============= +discriminator = mx.mod.Module(symbol=discriminatorSymbol, data_names=('data',), label_names=('label',), context=ctx) +discriminator.bind(data_shapes=image_iter.provide_data, + label_shapes=[('label', (batch_size,))], + inputs_need_grad=True) +discriminator.init_params(initializer=mx.init.Normal(sigma)) +discriminator.init_optimizer( + optimizer='adam', + optimizer_params={ + 'learning_rate': lr, + 'beta1': beta1, + }) +mods.append(discriminator) +``` +First, we create Modules for our networks and then bind the symbols that we've created in the previous steps to our modules. +We use rand_iter.provide_data as the data_shape to bind our generator network. This means that as we iterate though batches of data on the generator Module, our RandIter will provide us with random numbers to feed our Module using it's provide_data function. + +Similarly, we bind the discriminator Module to image_iter.provide_data, which gives us images from MNIST from the NDArrayIter we had set up earlier, called image_iter. + +Notice that we're using the Normal initialization, with the hyperparameter sigma=0.02. This means our weight initializations for the neurons in our networks will random numbers from a Gaussian (normal) distribution with a mean of 0 and a standard deviation of 0.02. + +We also use the adam optimizer for gradient decent. We've set up two hyperparameters, lr and beta1 based on the values used in the DCGAN paper. We're using a single gpu, gpu(0) for training. + +### Visualizing Our Training +Before we train the model, let's set up some helper functions that will help visualize what our generator is producing, compared to what the real image is: +```python +from matplotlib import pyplot as plt + +#Takes the images in our batch and arranges them in an array so that they can be +#Plotted using matplotlib +def fill_buf(buf, num_images, img, shape): + width = buf.shape[0]/shape[1] + height = buf.shape[1]/shape[0] + img_width = (num_images%width)*shape[0] + img_hight = (num_images/height)*shape[1] + buf[img_hight:img_hight+shape[1], img_width:img_width+shape[0], :] = img + +#Plots two images side by side using matplotlib +def visualize(fake, real): + #64x3x64x64 to 64x64x64x3 + fake = fake.transpose((0, 2, 3, 1)) + #Pixel values from 0-255 + fake = np.clip((fake+1.0)*(255.0/2.0), 0, 255).astype(np.uint8) + #Repeat for real image + real = real.transpose((0, 2, 3, 1)) + real = np.clip((real+1.0)*(255.0/2.0), 0, 255).astype(np.uint8) + + #Create buffer array that will hold all the images in our batch + #Fill the buffer so to arrange all images in the batch onto the buffer array + n = np.ceil(np.sqrt(fake.shape[0])) + fbuff = np.zeros((int(n*fake.shape[1]), int(n*fake.shape[2]), int(fake.shape[3])), dtype=np.uint8) + for i, img in enumerate(fake): + fill_buf(fbuff, i, img, fake.shape[1:3]) + rbuff = np.zeros((int(n*real.shape[1]), int(n*real.shape[2]), int(real.shape[3])), dtype=np.uint8) + for i, img in enumerate(real): + fill_buf(rbuff, i, img, real.shape[1:3]) + + #Create a matplotlib figure with two subplots: one for the real and the other for the fake + #fill each plot with our buffer array, which creates the image + fig = plt.figure() + ax1 = fig.add_subplot(2,2,1) + ax1.imshow(fbuff) + ax2 = fig.add_subplot(2,2,2) + ax2.imshow(rbuff) + plt.show() +``` + +## Fit the Model +Training the DCGAN is a complex process that requires multiple steps. +To fit the model, for every batch of data in our dataset: + +1. Use the Z vector, which contains our random numbers to do a forward pass through our generator. This outputs the "fake" image, since it's created from our generator. + +2. Use the fake image as the input to do a forward and backwards pass through the discriminator network. We set our labels for our logistic regression to 0 to represent that this is a fake image. This trains the discriminator to learn what a fake image looks like. We save the gradient produced in backpropogation for the next step. + +3. Do a forwards and backwards pass through the discriminator using a real image from our dataset. Our label for logistic regression will now be 1 to represent real images, so our discriminator can learn to recognize a real image. + +4. Update the discriminator by adding the result of the gradient generated during backpropogation on the fake image with the gradient from backpropogation on the real image. + +5. Now that the discriminator has been updated for the this batch, we still need to update the generator. First, do a forward and backwards pass with the same batch on the updated discriminator, to produce a new gradient. Use the new gradient to do a backwards pass + +Here's the main training loop for our DCGAN: + +```python +# =============train=============== +print('Training...') +for epoch in range(1): + image_iter.reset() + for i, batch in enumerate(image_iter): + #Get a batch of random numbers to generate an image from the generator + rbatch = rand_iter.next() + #Forward pass on training batch + generator.forward(rbatch, is_train=True) + #Output of training batch is the 64x64x3 image + outG = generator.get_outputs() + + #Pass the generated (fake) image through the discriminator, and save the gradient + #Label (for logistic regression) is an array of 0's since this image is fake + label = mx.nd.zeros((batch_size,), ctx=ctx) + #Forward pass on the output of the discriminator network + discriminator.forward(mx.io.DataBatch(outG, [label]), is_train=True) + #Do the backwards pass and save the gradient + discriminator.backward() + gradD = [[grad.copyto(grad.context) for grad in grads] for grads in discriminator._exec_group.grad_arrays] + + #Pass a batch of real images from MNIST through the discriminator + #Set the label to be an array of 1's because these are the real images + label[:] = 1 + batch.label = [label] + #Forward pass on a batch of MNIST images + discriminator.forward(batch, is_train=True) + #Do the backwards pass and add the saved gradient from the fake images to the gradient + #generated by this backwards pass on the real images + discriminator.backward() + for gradsr, gradsf in zip(discriminator._exec_group.grad_arrays, gradD): + for gradr, gradf in zip(gradsr, gradsf): + gradr += gradf + #Update gradient on the discriminator + discriminator.update() + + #Now that we've updated the discriminator, let's update the generator + #First do a forward pass and backwards pass on the newly updated discriminator + #With the current batch + discriminator.forward(mx.io.DataBatch(outG, [label]), is_train=True) + discriminator.backward() + #Get the input gradient from the backwards pass on the discriminator, + #and use it to do the backwards pass on the generator + diffD = discriminator.get_input_grads() + generator.backward(diffD) + #Update the gradients on the generator + generator.update() + + #Increment to the next batch, printing every 50 batches + i += 1 + if i % 50 == 0: + print('epoch:', epoch, 'iter:', i) + print + print(" From generator: From MNIST:") + + visualize(outG[0].asnumpy(), batch.data[0].asnumpy()) +``` + +This causes our GAN to train and we can visualize the progress that we're making as our networks train. After every 25 iterations, we're calling the visualize function that we created earlier, which creates the visual plots during training. + +The plot on our left will represent what our generator created (the fake image) in the most recent iteration. The plot on the right will represent the original (real) image from the MNIST dataset that was inputted to the discriminator on the same iteration. + +As training goes on the generator becomes better at generating realistic images. You can see this happening since images on the left become closer to the original dataset with each iteration. + +## Summary + +We've now sucessfully used Apache MXNet to train a Deep Convolutional GAN using the MNIST dataset. + +As a result, we've created two neural nets: a generator, which is able to create images of handwritten digits from random numbers, and a discriminator, which is able to take an image and determine if it is an image of handwritten digits. + +Along the way, we've learned how to do the image manipulation and visualization that's associted with training deep neural nets. We've also learned how to some of MXNet's advanced training functionality to fit our model. + +## Acknowledgements +This tutorial is based on [MXNet DCGAN codebase](https://github.com/apache/incubator-mxnet/blob/master/example/gan/dcgan.py), +[The original paper on GANs](https://arxiv.org/abs/1406.2661), as well as [this paper on deep convolutional GANs](https://arxiv.org/abs/1511.06434). \ No newline at end of file diff --git a/docs/tutorials/vision/large_scale_classification.md b/docs/tutorials/vision/large_scale_classification.md index cf76c96b70a5..1cf22708efde 100644 --- a/docs/tutorials/vision/large_scale_classification.md +++ b/docs/tutorials/vision/large_scale_classification.md @@ -2,10 +2,19 @@ Training a neural network with a large number of images presents several challenges. Even with the latest GPUs, it is not possible to train large networks using a large number of images in a reasonable amount of time using a single GPU. This problem can be somewhat mitigated by using multiple GPUs in a single machine. But there is a limit to the number of GPUs that can be attached to one machine (typically 8 or 16). This tutorial explains how to train large networks with terabytes of data using multiple machines each containing multiple GPUs. +## Prerequisites +- MXNet. See the instructions for your operating system in [Setup and Installation](http://mxnet.io/get_started/install.html). + +- [OpenCV Python library](http://opencv.org/opencv-3-2.html) + +``` +$ pip install opencv-python +``` + ## Preprocessing ### Disk space -The first step in training with large data is downloading the data and preprocessing it. For this tutorial, we will be using the full imagenet dataset. Note that, at least 2 TB of disk space is required to download and preprocess this data. It is strongly recommended to use SSD instead of HDD. SSD is much better at dealing with a large number of small image files. After the preprocessing completes and images are packed into recordIO files, HDD should be fine for training. +The first step in training with large data is downloading the data and preprocessing it. For this tutorial, we will be using the full ImageNet dataset. Note that, at least 2 TB of disk space is required to download and preprocess this data. It is strongly recommended to use SSD instead of HDD. SSD is much better at dealing with a large number of small image files. After the preprocessing completes and images are packed into recordIO files, HDD should be fine for training. In this tutorial, we will use an AWS storage instance for data preprocessing. The storage instance `i3.4xlarge` has 3.8 TB of disk space across two NVMe SSD disks. We will use software RAID to combine them into one disk and mount it at `~/data`. @@ -20,9 +29,9 @@ sudo chown ${whoami} ~/data We now have sufficient disk space to download and preprocess the data. -### Download imagenet +### Download ImageNet -In this tutorial, we will be using the full imagenet dataset which can be downloaded from http://www.image-net.org/download-images. `fall11_whole.tar` contains all the images. This file is 1.2 TB in size and could take a long time to download. +In this tutorial, we will be using the full ImageNet dataset which can be downloaded from http://www.image-net.org/download-images. `fall11_whole.tar` contains all the images. This file is 1.2 TB in size and could take a long time to download. After downloading, untar the file. ``` @@ -51,7 +60,7 @@ n00120010 ``` ### Remove uncommon classes for transfer learning (optional) -A common reason to train a network on Imagenet data is to use it for transfer learning (including feature extraction or fine-tuning other models). According to [this](https://arxiv.org/pdf/1608.08614v1.pdf) study, classes with too few images don’t help in transfer learning. So, we could remove classes with fewer than a certain number of images. The following code will remove classes with less than 500 images. +A common reason to train a network on ImageNet data is to use it for transfer learning (including feature extraction or fine-tuning other models). According to [this](https://arxiv.org/pdf/1608.08614v1.pdf) study, classes with too few images don’t help in transfer learning. So, we could remove classes with fewer than a certain number of images. The following code will remove classes with less than 500 images. ``` BAK=${ROOT}_filtered @@ -83,7 +92,7 @@ done ``` ### Pack images into record files -While MXNet can read image files directly, it is recommended to pack the image files into a recordIO file for increased performance. MXNet provides a tool (tools/im2rec.py) to do this. To use this tool, MXNet and OpenCV’s python module needs to be installed in the system. OpenCV’s python module can be installed on Ubuntu using the command `sudo apt-get install python-opencv`. +While MXNet can read image files directly, it is recommended to pack the image files into a recordIO file for increased performance. MXNet provides a tool (tools/im2rec.py) to do this. To use this tool, MXNet and OpenCV’s python module needs to be installed in the system. Set the environment variable `MXNET` to point to the MXNet installation directory and `NAME` to the name of the dataset. Here, we assume MXNet is installed at `~/mxnet` @@ -132,7 +141,7 @@ We now have all training and validation images in recordIO format in `train` and [ResNet](https://arxiv.org/abs/1512.03385) has shown its effectiveness on ImageNet competition. Our experiments also [reproduced](https://github.com/tornadomeet/ResNet) the results reported in the paper. As we increase the number of layers from 18 to 152, we see steady improvement in validation accuracy. Given this is a huge dataset, we will use Resnet with 152 layers. -Due to the huge computational complexity, even the fastest GPU needs more than one day for a single pass of the data. We often need tens of epochs before the training converges to good validation accuracy. While we can use multiple GPUs in a machine, number of GPUs in a machine is often limited to 8 or 16. For faster training, in this tutorial, we will use multiple machines each containing multiple GPUs to train the model. +Due to the huge computational complexity, even the fastest GPU needs more than one day for a single pass of the data. We often need tens of epochs before the training converges to good validation accuracy. While we can use multiple GPUs in a machine, the number of GPUs in a machine is often limited to 8 or 16. For faster training, in this tutorial, we will use multiple machines each containing multiple GPUs to train the model. ### Setup @@ -151,7 +160,7 @@ If you are setting up your cluster manually, without using AWS CloudFormation, r deeplearning-worker2 deeplearning-worker3 ``` - It should be possible to ssh into any of these machines from master by invoking `ssh` with just a hostname from the file. For example, + It should be possible to ssh into any of these machines from the master by invoking `ssh` with just a hostname from the file. For example, ``` $ ssh deeplearning-worker2 =================================== @@ -160,7 +169,7 @@ If you are setting up your cluster manually, without using AWS CloudFormation, r ... ubuntu@ip-10-0-1-199:~$ ``` - One way to do this is to use ssh agent forwarding. Please check [this](https://aws.amazon.com/blogs/security/securely-connect-to-linux-instances-running-in-a-private-amazon-vpc/) page to learn how to set this up. In short, you’ll configure all machines to login using a particular certificate (mycert.pem) which is present on your local machine. You then login to the master using the certificate and the `-A` switch to enable agent forwarding. Now, from master, you should be able to login to any other machine in the cluster by providing just the hostname (example: `ssh deeplearning-worker2`). + One way to do this is to use ssh agent forwarding. Please check [this](https://aws.amazon.com/blogs/security/securely-connect-to-linux-instances-running-in-a-private-amazon-vpc/) page to learn how to set this up. In short, you’ll configure all machines to login using a particular certificate (mycert.pem) which is present on your local machine. You then login to the master using the certificate and the `-A` switch to enable agent forwarding. Now, from the master, you should be able to login to any other machine in the cluster by providing just the hostname (example: `ssh deeplearning-worker2`). ### Run Training After the cluster is setup, login to master and run the following command from ${MXNET}/example/image-classification diff --git a/example/README.md b/example/README.md index cd765affd567..12ada4d0ceef 100644 --- a/example/README.md +++ b/example/README.md @@ -74,6 +74,8 @@ If you want to contribute to this list and the examples, please open a new pull * [Fast Neural Style in Scala](https://github.com/Ldpe2G/DeepLearningForFun/tree/master/Mxnet-Scala/FastNeuralStyle) by [Ldpe2G](https://github.com/Ldpe2G) * [LSTM Human Activity Recognition](https://github.com/Ldpe2G/DeepLearningForFun/tree/master/Mxnet-Scala/HumanActivityRecognition) by [Ldpe2G](https://github.com/Ldpe2G) * [Visual Question Answering](https://github.com/liuzhi136/Visual-Question-Answering) by [liuzhi136](https://github.com/liuzhi136) +* [Deformable ConvNets](https://arxiv.org/abs/1703.06211) ([github](https://github.com/msracver/Deformable-ConvNets)) by [MSRACVer](https://github.com/msracver) + ### IPython Notebooks ----------------- diff --git a/example/adversary/data.py b/example/adversary/data.py index d39821f52145..0ca8e1fd6653 100644 --- a/example/adversary/data.py +++ b/example/adversary/data.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # pylint: skip-file """ data iterator for mnist """ import sys diff --git a/example/autoencoder/autoencoder.py b/example/autoencoder/autoencoder.py index ca8db7a70289..a84b2718f748 100644 --- a/example/autoencoder/autoencoder.py +++ b/example/autoencoder/autoencoder.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # pylint: skip-file import mxnet as mx from mxnet import misc diff --git a/example/autoencoder/data.py b/example/autoencoder/data.py index ecd117d86218..d6a25edce886 100644 --- a/example/autoencoder/data.py +++ b/example/autoencoder/data.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import os import numpy as np from sklearn.datasets import fetch_mldata diff --git a/example/autoencoder/mnist_sae.py b/example/autoencoder/mnist_sae.py index 538d8b976d0c..552594823a93 100644 --- a/example/autoencoder/mnist_sae.py +++ b/example/autoencoder/mnist_sae.py @@ -1,27 +1,44 @@ -# pylint: skip-file -from __future__ import print_function -import mxnet as mx -import numpy as np -import logging -import data -from autoencoder import AutoEncoderModel - - -if __name__ == '__main__': - # set to INFO to see less information during training - logging.basicConfig(level=logging.DEBUG) - ae_model = AutoEncoderModel(mx.gpu(0), [784,500,500,2000,10], pt_dropout=0.2, - internal_act='relu', output_act='relu') - - X, _ = data.get_mnist() - train_X = X[:60000] - val_X = X[60000:] - - ae_model.layerwise_pretrain(train_X, 256, 50000, 'sgd', l_rate=0.1, decay=0.0, - lr_scheduler=mx.misc.FactorScheduler(20000,0.1)) - ae_model.finetune(train_X, 256, 100000, 'sgd', l_rate=0.1, decay=0.0, - lr_scheduler=mx.misc.FactorScheduler(20000,0.1)) - ae_model.save('mnist_pt.arg') - ae_model.load('mnist_pt.arg') - print("Training error:", ae_model.eval(train_X)) - print("Validation error:", ae_model.eval(val_X)) +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +# pylint: skip-file +from __future__ import print_function +import mxnet as mx +import numpy as np +import logging +import data +from autoencoder import AutoEncoderModel + + +if __name__ == '__main__': + # set to INFO to see less information during training + logging.basicConfig(level=logging.DEBUG) + ae_model = AutoEncoderModel(mx.gpu(0), [784,500,500,2000,10], pt_dropout=0.2, + internal_act='relu', output_act='relu') + + X, _ = data.get_mnist() + train_X = X[:60000] + val_X = X[60000:] + + ae_model.layerwise_pretrain(train_X, 256, 50000, 'sgd', l_rate=0.1, decay=0.0, + lr_scheduler=mx.misc.FactorScheduler(20000,0.1)) + ae_model.finetune(train_X, 256, 100000, 'sgd', l_rate=0.1, decay=0.0, + lr_scheduler=mx.misc.FactorScheduler(20000,0.1)) + ae_model.save('mnist_pt.arg') + ae_model.load('mnist_pt.arg') + print("Training error:", ae_model.eval(train_X)) + print("Validation error:", ae_model.eval(val_X)) diff --git a/example/autoencoder/model.py b/example/autoencoder/model.py index 85fb48c5797b..1aaae1b5fdad 100644 --- a/example/autoencoder/model.py +++ b/example/autoencoder/model.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # pylint: skip-file import mxnet as mx import numpy as np diff --git a/example/autoencoder/solver.py b/example/autoencoder/solver.py index 21c5da2ed4c7..5589c5a14010 100644 --- a/example/autoencoder/solver.py +++ b/example/autoencoder/solver.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # pylint: skip-file import mxnet as mx import numpy as np diff --git a/example/bayesian-methods/algos.py b/example/bayesian-methods/algos.py index 81df9c5d8534..e47a18f398e9 100644 --- a/example/bayesian-methods/algos.py +++ b/example/bayesian-methods/algos.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + from __future__ import print_function import mxnet as mx import mxnet.ndarray as nd diff --git a/example/bayesian-methods/bdk.ipynb b/example/bayesian-methods/bdk.ipynb index e3dbaa40f450..8c98651063bc 100644 --- a/example/bayesian-methods/bdk.ipynb +++ b/example/bayesian-methods/bdk.ipynb @@ -277,8 +277,8 @@ " student_prior = 0.1\n", " perturb_deviation = 0.001\n", " teacher_net = get_mnist_sym(num_hidden=num_hidden)\n", - " crossentroy_softmax = CrossEntropySoftmax()\n", - " student_net = get_mnist_sym(output_op=crossentroy_softmax, num_hidden=num_hidden)\n", + " crossentropy_softmax = CrossEntropySoftmax()\n", + " student_net = get_mnist_sym(output_op=crossentropy_softmax, num_hidden=num_hidden)\n", " data_shape = (minibatch_size,) + X.shape[1::]\n", " teacher_data_inputs = {'data': nd.zeros(data_shape, ctx=dev()),\n", " 'softmax_label': nd.zeros((minibatch_size,), ctx=dev())}\n", diff --git a/example/bayesian-methods/bdk_demo.py b/example/bayesian-methods/bdk_demo.py index 77e9b945cb14..145dac10e2a6 100644 --- a/example/bayesian-methods/bdk_demo.py +++ b/example/bayesian-methods/bdk_demo.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + from __future__ import print_function import mxnet as mx import mxnet.ndarray as nd diff --git a/example/bayesian-methods/data_loader.py b/example/bayesian-methods/data_loader.py index 90b01e0144cc..2649eb560b68 100644 --- a/example/bayesian-methods/data_loader.py +++ b/example/bayesian-methods/data_loader.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + from __future__ import print_function import numpy import os diff --git a/example/bayesian-methods/utils.py b/example/bayesian-methods/utils.py index 4a2f41d7e149..a2744373e87d 100644 --- a/example/bayesian-methods/utils.py +++ b/example/bayesian-methods/utils.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import mxnet as mx import mxnet.ndarray as nd import numpy @@ -169,4 +186,4 @@ def pred_test(testing_data, exe, param_list=None, save_path=""): ret[i, 1] = pred.std()**2 numpy.savetxt(save_path, ret) mse = numpy.square(ret[:, 0] - testing_data[:, 0] **3).mean() - return mse, ret \ No newline at end of file + return mse, ret diff --git a/example/bi-lstm-sort/infer_sort.py b/example/bi-lstm-sort/infer_sort.py index 0f5ef07a269b..b074c03d1159 100644 --- a/example/bi-lstm-sort/infer_sort.py +++ b/example/bi-lstm-sort/infer_sort.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # pylint: disable=C0111,too-many-arguments,too-many-instance-attributes,too-many-locals,redefined-outer-name,fixme # pylint: disable=superfluous-parens, no-member, invalid-name import sys @@ -31,7 +48,7 @@ def MakeInput(char, vocab, arr): rvocab = {} for k, v in vocab.items(): rvocab[v] = k - + _, arg_params, __ = mx.model.load_checkpoint("sort", 1) model = BiLSTMInferenceModel(5, len(vocab), @@ -42,9 +59,9 @@ def MakeInput(char, vocab, arr): data = np.zeros((1, len(tks))) for k in range(len(tks)): data[0][k] = vocab[tks[k]] - + data = mx.nd.array(data) prob = model.forward(data) - for k in range(len(tks)): + for k in range(len(tks)): print(rvocab[np.argmax(prob, axis = 1)[k]]) - + diff --git a/example/bi-lstm-sort/lstm.py b/example/bi-lstm-sort/lstm.py index 61687124266d..a082092b0351 100644 --- a/example/bi-lstm-sort/lstm.py +++ b/example/bi-lstm-sort/lstm.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # pylint:skip-file import sys sys.path.insert(0, "../../python") @@ -64,7 +81,7 @@ def bi_lstm_unroll(seq_len, input_size, embed = mx.sym.Embedding(data=data, input_dim=input_size, weight=embed_weight, output_dim=num_embed, name='embed') wordvec = mx.sym.SliceChannel(data=embed, num_outputs=seq_len, squeeze_axis=1) - + forward_hidden = [] for seqidx in range(seq_len): hidden = wordvec[seqidx] @@ -87,7 +104,7 @@ def bi_lstm_unroll(seq_len, input_size, hidden = next_state.h last_states[1] = next_state backward_hidden.insert(0, hidden) - + hidden_all = [] for i in range(seq_len): hidden_all.append(mx.sym.Concat(*[forward_hidden[i], backward_hidden[i]], dim=1)) @@ -109,7 +126,7 @@ def bi_lstm_inference_symbol(input_size, seq_len, embed_weight=mx.sym.Variable("embed_weight") cls_weight = mx.sym.Variable("cls_weight") cls_bias = mx.sym.Variable("cls_bias") - last_states = [LSTMState(c = mx.sym.Variable("l0_init_c"), h = mx.sym.Variable("l0_init_h")), + last_states = [LSTMState(c = mx.sym.Variable("l0_init_c"), h = mx.sym.Variable("l0_init_h")), LSTMState(c = mx.sym.Variable("l1_init_c"), h = mx.sym.Variable("l1_init_h"))] forward_param = LSTMParam(i2h_weight=mx.sym.Variable("l0_i2h_weight"), i2h_bias=mx.sym.Variable("l0_i2h_bias"), @@ -143,7 +160,7 @@ def bi_lstm_inference_symbol(input_size, seq_len, hidden = next_state.h last_states[1] = next_state backward_hidden.insert(0, hidden) - + hidden_all = [] for i in range(seq_len): hidden_all.append(mx.sym.Concat(*[forward_hidden[i], backward_hidden[i]], dim=1)) diff --git a/example/bi-lstm-sort/lstm_sort.py b/example/bi-lstm-sort/lstm_sort.py index fe8c38b559bd..aef88b899ce3 100644 --- a/example/bi-lstm-sort/lstm_sort.py +++ b/example/bi-lstm-sort/lstm_sort.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # pylint: disable=C0111,too-many-arguments,too-many-instance-attributes,too-many-locals,redefined-outer-name,fixme # pylint: disable=superfluous-parens, no-member, invalid-name import sys diff --git a/example/bi-lstm-sort/rnn_model.py b/example/bi-lstm-sort/rnn_model.py index a253e862fcce..202aae608726 100644 --- a/example/bi-lstm-sort/rnn_model.py +++ b/example/bi-lstm-sort/rnn_model.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # pylint: disable=C0111,too-many-arguments,too-many-instance-attributes,too-many-locals,redefined-outer-name,fixme # pylint: disable=superfluous-parens, no-member, invalid-name import sys @@ -25,7 +42,7 @@ def __init__(self, batch_size = 1 init_c = [('l%d_init_c'%l, (batch_size, num_hidden)) for l in range(2)] init_h = [('l%d_init_h'%l, (batch_size, num_hidden)) for l in range(2)] - + data_shape = [("data", (batch_size, seq_len, ))] input_shapes = dict(init_c + init_h + data_shape) diff --git a/example/bi-lstm-sort/sort_io.py b/example/bi-lstm-sort/sort_io.py index 8e1152173ac7..8cb44c678a72 100644 --- a/example/bi-lstm-sort/sort_io.py +++ b/example/bi-lstm-sort/sort_io.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # pylint: disable=C0111,too-many-arguments,too-many-instance-attributes,too-many-locals,redefined-outer-name,fixme # pylint: disable=superfluous-parens, no-member, invalid-name from __future__ import print_function diff --git a/example/caffe/caffe_net.py b/example/caffe/caffe_net.py index c91d37bcbecb..0dc4770a24f0 100644 --- a/example/caffe/caffe_net.py +++ b/example/caffe/caffe_net.py @@ -1,5 +1,22 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import mxnet as mx -from data import get_iterator +from data import get_iterator import argparse import train_model diff --git a/example/caffe/data.py b/example/caffe/data.py index 723e7da02b85..fac8e11989dc 100644 --- a/example/caffe/data.py +++ b/example/caffe/data.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import sys import os # code to automatically download dataset diff --git a/example/caffe/train_model.py b/example/caffe/train_model.py index 9a51f07bda87..2eadd869e70e 100644 --- a/example/caffe/train_model.py +++ b/example/caffe/train_model.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import mxnet as mx import logging import os diff --git a/example/captcha/README.md b/example/captcha/README.md new file mode 100644 index 000000000000..02e87267ccba --- /dev/null +++ b/example/captcha/README.md @@ -0,0 +1,5 @@ +This is the R version of [captcha recognition](http://blog.xlvector.net/2016-05/mxnet-ocr-cnn/) example by xlvector and it can be used as an example of multi-label training. For a captcha below, we consider it as an image with 4 labels and train a CNN over the data set. + +![](captcha_example.png) + +You can download the images and `.rec` files from [here](https://s3-us-west-2.amazonaws.com/apache-mxnet/R/data/captcha_example.zip). Since each image has 4 labels, please remember to use `label_width=4` when generating the `.rec` files. diff --git a/example/captcha/captcha_example.png b/example/captcha/captcha_example.png new file mode 100644 index 000000000000..09b84f7190fa Binary files /dev/null and b/example/captcha/captcha_example.png differ diff --git a/example/captcha/mxnet_captcha.R b/example/captcha/mxnet_captcha.R new file mode 100644 index 000000000000..4874ad535419 --- /dev/null +++ b/example/captcha/mxnet_captcha.R @@ -0,0 +1,68 @@ +library(mxnet) + +data <- mx.symbol.Variable('data') +label <- mx.symbol.Variable('label') +conv1 <- mx.symbol.Convolution(data = data, kernel = c(5, 5), num_filter = 32) +pool1 <- mx.symbol.Pooling(data = conv1, pool_type = "max", kernel = c(2, 2), stride = c(1, 1)) +relu1 <- mx.symbol.Activation(data = pool1, act_type = "relu") + +conv2 <- mx.symbol.Convolution(data = relu1, kernel = c(5, 5), num_filter = 32) +pool2 <- mx.symbol.Pooling(data = conv2, pool_type = "avg", kernel = c(2, 2), stride = c(1, 1)) +relu2 <- mx.symbol.Activation(data = pool2, act_type = "relu") + +flatten <- mx.symbol.Flatten(data = relu2) +fc1 <- mx.symbol.FullyConnected(data = flatten, num_hidden = 120) +fc21 <- mx.symbol.FullyConnected(data = fc1, num_hidden = 10) +fc22 <- mx.symbol.FullyConnected(data = fc1, num_hidden = 10) +fc23 <- mx.symbol.FullyConnected(data = fc1, num_hidden = 10) +fc24 <- mx.symbol.FullyConnected(data = fc1, num_hidden = 10) +fc2 <- mx.symbol.Concat(c(fc21, fc22, fc23, fc24), dim = 0, num.args = 4) +label <- mx.symbol.transpose(data = label) +label <- mx.symbol.Reshape(data = label, target_shape = c(0)) +captcha_net <- mx.symbol.SoftmaxOutput(data = fc2, label = label, name = "softmax") + +mx.metric.acc2 <- mx.metric.custom("accuracy", function(label, pred) { + ypred <- max.col(t(pred)) - 1 + ypred <- matrix(ypred, nrow = nrow(label), ncol = ncol(label), byrow = TRUE) + return(sum(colSums(label == ypred) == 4) / ncol(label)) + }) + +data.shape <- c(80, 30, 3) + +batch_size <- 40 + +train <- mx.io.ImageRecordIter( + path.imgrec = "train.rec", + path.imglist = "train.lst", + batch.size = batch_size, + label.width = 4, + data.shape = data.shape, + mean.img = "mean.bin" +) + +val <- mx.io.ImageRecordIter( + path.imgrec = "test.rec", + path.imglist = "test.lst", + batch.size = batch_size, + label.width = 4, + data.shape = data.shape, + mean.img = "mean.bin" +) + +mx.set.seed(42) + +model <- mx.model.FeedForward.create( + X = train, + eval.data = val, + ctx = mx.gpu(), + symbol = captcha_net, + eval.metric = mx.metric.acc2, + num.round = 10, + learning.rate = 0.0001, + momentum = 0.9, + wd = 0.00001, + batch.end.callback = mx.callback.log.train.metric(50), + initializer = mx.init.Xavier(factor_type = "in", magnitude = 2.34), + optimizer = "sgd", + clip_gradient = 10 +) diff --git a/example/cnn_text_classification/data_helpers.py b/example/cnn_text_classification/data_helpers.py index b3ece2d4825b..3812683e7a5d 100644 --- a/example/cnn_text_classification/data_helpers.py +++ b/example/cnn_text_classification/data_helpers.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import numpy as np import re import itertools diff --git a/example/cnn_text_classification/old/text_cnn.py b/example/cnn_text_classification/old/text_cnn.py new file mode 100644 index 000000000000..8d82d6ef7945 --- /dev/null +++ b/example/cnn_text_classification/old/text_cnn.py @@ -0,0 +1,279 @@ +#!/usr/bin/env python + +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +# -*- coding: utf-8 -*- +from __future__ import print_function +import sys,os +import mxnet as mx +import numpy as np +import time +import math +import data_helpers +from collections import namedtuple + +import logging +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) # get a logger to accuracies are printed + +logs = sys.stderr + +CNNModel = namedtuple("CNNModel", ['cnn_exec', 'symbol', 'data', 'label', 'param_blocks']) + +def make_text_cnn(sentence_size, num_embed, batch_size, vocab_size, + num_label=2, filter_list=[3, 4, 5], num_filter=100, + dropout=0., with_embedding=True): + + input_x = mx.sym.Variable('data') # placeholder for input + input_y = mx.sym.Variable('softmax_label') # placeholder for output + + # embedding layer + if not with_embedding: + embed_layer = mx.sym.Embedding(data=input_x, input_dim=vocab_size, output_dim=num_embed, name='vocab_embed') + conv_input = mx.sym.Reshape(data=embed_layer, target_shape=(batch_size, 1, sentence_size, num_embed)) + else: + conv_input = input_x + + # create convolution + (max) pooling layer for each filter operation + pooled_outputs = [] + for i, filter_size in enumerate(filter_list): + convi = mx.sym.Convolution(data=conv_input, kernel=(filter_size, num_embed), num_filter=num_filter) + relui = mx.sym.Activation(data=convi, act_type='relu') + pooli = mx.sym.Pooling(data=relui, pool_type='max', kernel=(sentence_size - filter_size + 1, 1), stride=(1,1)) + pooled_outputs.append(pooli) + + # combine all pooled outputs + total_filters = num_filter * len(filter_list) + concat = mx.sym.Concat(*pooled_outputs, dim=1) + h_pool = mx.sym.Reshape(data=concat, target_shape=(batch_size, total_filters)) + + # dropout layer + if dropout > 0.0: + h_drop = mx.sym.Dropout(data=h_pool, p=dropout) + else: + h_drop = h_pool + + # fully connected + cls_weight = mx.sym.Variable('cls_weight') + cls_bias = mx.sym.Variable('cls_bias') + + fc = mx.sym.FullyConnected(data=h_drop, weight=cls_weight, bias=cls_bias, num_hidden=num_label) + + # softmax output + sm = mx.sym.SoftmaxOutput(data=fc, label=input_y, name='softmax') + + return sm + + +def setup_cnn_model(ctx, batch_size, sentence_size, num_embed, vocab_size, + dropout=0.5, initializer=mx.initializer.Uniform(0.1), with_embedding=True): + + cnn = make_text_cnn(sentence_size, num_embed, batch_size=batch_size, + vocab_size=vocab_size, dropout=dropout, with_embedding=with_embedding) + arg_names = cnn.list_arguments() + + input_shapes = {} + if with_embedding: + input_shapes['data'] = (batch_size, 1, sentence_size, num_embed) + else: + input_shapes['data'] = (batch_size, sentence_size) + + arg_shape, out_shape, aux_shape = cnn.infer_shape(**input_shapes) + arg_arrays = [mx.nd.zeros(s, ctx) for s in arg_shape] + args_grad = {} + for shape, name in zip(arg_shape, arg_names): + if name in ['softmax_label', 'data']: # input, output + continue + args_grad[name] = mx.nd.zeros(shape, ctx) + + cnn_exec = cnn.bind(ctx=ctx, args=arg_arrays, args_grad=args_grad, grad_req='add') + + param_blocks = [] + arg_dict = dict(zip(arg_names, cnn_exec.arg_arrays)) + for i, name in enumerate(arg_names): + if name in ['softmax_label', 'data']: # input, output + continue + initializer(name, arg_dict[name]) + + param_blocks.append( (i, arg_dict[name], args_grad[name], name) ) + + out_dict = dict(zip(cnn.list_outputs(), cnn_exec.outputs)) + + data = cnn_exec.arg_dict['data'] + label = cnn_exec.arg_dict['softmax_label'] + + return CNNModel(cnn_exec=cnn_exec, symbol=cnn, data=data, label=label, param_blocks=param_blocks) + + +def train_cnn(model, X_train_batch, y_train_batch, X_dev_batch, y_dev_batch, batch_size, + optimizer='rmsprop', max_grad_norm=5.0, learning_rate=0.0005, epoch=200): + m = model + # create optimizer + opt = mx.optimizer.create(optimizer) + opt.lr = learning_rate + + updater = mx.optimizer.get_updater(opt) + + for iteration in range(epoch): + tic = time.time() + num_correct = 0 + num_total = 0 + for begin in range(0, X_train_batch.shape[0], batch_size): + batchX = X_train_batch[begin:begin+batch_size] + batchY = y_train_batch[begin:begin+batch_size] + if batchX.shape[0] != batch_size: + continue + + m.data[:] = batchX + m.label[:] = batchY + + # forward + m.cnn_exec.forward(is_train=True) + + # backward + m.cnn_exec.backward() + + # eval on training data + num_correct += sum(batchY == np.argmax(m.cnn_exec.outputs[0].asnumpy(), axis=1)) + num_total += len(batchY) + + # update weights + norm = 0 + for idx, weight, grad, name in m.param_blocks: + grad /= batch_size + l2_norm = mx.nd.norm(grad).asscalar() + norm += l2_norm * l2_norm + + norm = math.sqrt(norm) + for idx, weight, grad, name in m.param_blocks: + if norm > max_grad_norm: + grad *= (max_grad_norm / norm) + + updater(idx, grad, weight) + + # reset gradient to zero + grad[:] = 0.0 + + # decay learning rate + if iteration % 50 == 0 and iteration > 0: + opt.lr *= 0.5 + print('reset learning rate to %g' % opt.lr,file=logs) + + # end of training loop + toc = time.time() + train_time = toc - tic + train_acc = num_correct * 100 / float(num_total) + + # saving checkpoint + if (iteration + 1) % 10 == 0: + prefix = 'cnn' + m.symbol.save('checkpoint/%s-symbol.json' % prefix) + save_dict = {('arg:%s' % k) :v for k, v in m.cnn_exec.arg_dict.items()} + save_dict.update({('aux:%s' % k) : v for k, v in m.cnn_exec.aux_dict.items()}) + param_name = 'checkpoint/%s-%04d.params' % (prefix, iteration) + mx.nd.save(param_name, save_dict) + print('Saved checkpoint to %s' % param_name,file=logs) + + + # evaluate on dev set + num_correct = 0 + num_total = 0 + for begin in range(0, X_dev_batch.shape[0], batch_size): + batchX = X_dev_batch[begin:begin+batch_size] + batchY = y_dev_batch[begin:begin+batch_size] + + if batchX.shape[0] != batch_size: + continue + + m.data[:] = batchX + m.cnn_exec.forward(is_train=False) + + num_correct += sum(batchY == np.argmax(m.cnn_exec.outputs[0].asnumpy(), axis=1)) + num_total += len(batchY) + + dev_acc = num_correct * 100 / float(num_total) + print('Iter [%d] Train: Time: %.3fs, Training Accuracy: %.3f \ + --- Dev Accuracy thus far: %.3f' % (iteration, train_time, train_acc, dev_acc), file=logs) + + +def main(): + print('Loading data...') + # word2vec = data_helpers.load_google_word2vec('data/GoogleNews-vectors-negative300.bin') + word2vec = data_helpers.load_pretrained_word2vec('data/rt.vec') + x, y = data_helpers.load_data_with_word2vec(word2vec) + + # randomly shuffle data + np.random.seed(10) + shuffle_indices = np.random.permutation(np.arange(len(y))) + x_shuffled = x[shuffle_indices] + y_shuffled = y[shuffle_indices] + + # split train/dev set + x_train, x_dev = x_shuffled[:-1000], x_shuffled[-1000:] + y_train, y_dev = y_shuffled[:-1000], y_shuffled[-1000:] + print('Train/Dev split: %d/%d' % (len(y_train), len(y_dev))) + print('train shape:', x_train.shape) + print('dev shape:', x_dev.shape) + + # reshpae for convolution input + x_train = np.reshape(x_train, (x_train.shape[0], 1, x_train.shape[1], x_train.shape[2])) + x_dev = np.reshape(x_dev, (x_dev.shape[0], 1, x_dev.shape[1], x_dev.shape[2])) + + num_embed = x_train.shape[-1] + sentence_size = x_train.shape[2] + print('sentence max words', sentence_size) + print('embedding size', num_embed) + batch_size = 50 + + cnn_model = setup_cnn_model(mx.gpu(1), batch_size, sentence_size, num_embed, dropout=0.5) + train_cnn(cnn_model, x_train, y_train, x_dev, y_dev, batch_size) + +def train_without_pretrained_embedding(): + x, y, vocab, vocab_inv = data_helpers.load_data() + vocab_size = len(vocab) + + # randomly shuffle data + np.random.seed(10) + shuffle_indices = np.random.permutation(np.arange(len(y))) + x_shuffled = x[shuffle_indices] + y_shuffled = y[shuffle_indices] + + # split train/dev set + x_train, x_dev = x_shuffled[:-1000], x_shuffled[-1000:] + y_train, y_dev = y_shuffled[:-1000], y_shuffled[-1000:] + print('Train/Dev split: %d/%d' % (len(y_train), len(y_dev))) + print('train shape:', x_train.shape) + print('dev shape:', x_dev.shape) + print('vocab_size', vocab_size) + + batch_size = 50 + num_embed = 300 + sentence_size = x_train.shape[1] + + print('batch size', batch_size) + print('sentence max words', sentence_size) + print('embedding size', num_embed) + + cnn_model = setup_cnn_model(mx.gpu(0), batch_size, sentence_size, num_embed, vocab_size, dropout=0.5, with_embedding=False) + train_cnn(cnn_model, x_train, y_train, x_dev, y_dev, batch_size) + + +if __name__ == '__main__': + if not os.path.exists("checkpoint"): + os.mkdir("checkpoint") + train_without_pretrained_embedding() diff --git a/example/cnn_text_classification/text_cnn.py b/example/cnn_text_classification/text_cnn.py index e41af36cf2ff..d88a8e699420 100644 --- a/example/cnn_text_classification/text_cnn.py +++ b/example/cnn_text_classification/text_cnn.py @@ -1,31 +1,111 @@ #!/usr/bin/env python + +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # -*- coding: utf-8 -*- -from __future__ import print_function -import sys,os + +import sys +import os import mxnet as mx import numpy as np -import time -import math +import argparse +import logging import data_helpers -from collections import namedtuple -import logging -logging.basicConfig(level=logging.INFO) -logger = logging.getLogger(__name__) # get a logger to accuracies are printed +logging.basicConfig(level=logging.DEBUG) + +parser = argparse.ArgumentParser(description="CNN for text classification", + formatter_class=argparse.ArgumentDefaultsHelpFormatter) +parser.add_argument('--pretrained-embedding', type=bool, default=False, + help='use pre-trained word2vec') +parser.add_argument('--num-embed', type=int, default=300, + help='embedding layer size') +parser.add_argument('--gpus', type=str, default='', + help='list of gpus to run, e.g. 0 or 0,2,5. empty means using cpu. ') +parser.add_argument('--kv-store', type=str, default='local', + help='key-value store type') +parser.add_argument('--num-epochs', type=int, default=200, + help='max num of epochs') +parser.add_argument('--batch-size', type=int, default=50, + help='the batch size.') +parser.add_argument('--optimizer', type=str, default='rmsprop', + help='the optimizer type') +parser.add_argument('--lr', type=float, default=0.0005, + help='initial learning rate') +parser.add_argument('--dropout', type=float, default=0.0, + help='dropout rate') +parser.add_argument('--disp-batches', type=int, default=50, + help='show progress for every n batches') +parser.add_argument('--save-period', type=int, default=10, + help='save checkpoint for every n epochs') + +def save_model(): + if not os.path.exists("checkpoint"): + os.mkdir("checkpoint") + return mx.callback.do_checkpoint("checkpoint/checkpoint", args.save_period) + +def data_iter(batch_size, num_embed, pre_trained_word2vec=False): + print('Loading data...') + if pre_trained_word2vec: + word2vec = data_helpers.load_pretrained_word2vec('data/rt.vec') + x, y = data_helpers.load_data_with_word2vec(word2vec) + # reshpae for convolution input + x = np.reshape(x, (x.shape[0], 1, x.shape[1], x.shape[2])) + embed_size = x.shape[-1] + sentence_size = x.shape[2] + vocab_size = -1 + else: + x, y, vocab, vocab_inv = data_helpers.load_data() + embed_size = num_embed + sentence_size = x.shape[1] + vocab_size = len(vocab) + + # randomly shuffle data + np.random.seed(10) + shuffle_indices = np.random.permutation(np.arange(len(y))) + x_shuffled = x[shuffle_indices] + y_shuffled = y[shuffle_indices] -logs = sys.stderr + # split train/valid set + x_train, x_dev = x_shuffled[:-1000], x_shuffled[-1000:] + y_train, y_dev = y_shuffled[:-1000], y_shuffled[-1000:] + print('Train/Valid split: %d/%d' % (len(y_train), len(y_dev))) + print('train shape:', x_train.shape) + print('valid shape:', x_dev.shape) + print('sentence max words', sentence_size) + print('embedding size', embed_size) + print('vocab size', vocab_size) -CNNModel = namedtuple("CNNModel", ['cnn_exec', 'symbol', 'data', 'label', 'param_blocks']) + train = mx.io.NDArrayIter( + x_train, y_train, batch_size, shuffle=True) + valid = mx.io.NDArrayIter( + x_dev, y_dev, batch_size) -def make_text_cnn(sentence_size, num_embed, batch_size, vocab_size, - num_label=2, filter_list=[3, 4, 5], num_filter=100, - dropout=0., with_embedding=True): + return (train, valid, sentence_size, embed_size, vocab_size) - input_x = mx.sym.Variable('data') # placeholder for input - input_y = mx.sym.Variable('softmax_label') # placeholder for output +def sym_gen(batch_size, sentence_size, num_embed, vocab_size, + num_label=2, filter_list=[3, 4, 5], num_filter=100, + dropout=0.0, pre_trained_word2vec=False): + input_x = mx.sym.Variable('data') + input_y = mx.sym.Variable('softmax_label') # embedding layer - if not with_embedding: + if not pre_trained_word2vec: embed_layer = mx.sym.Embedding(data=input_x, input_dim=vocab_size, output_dim=num_embed, name='vocab_embed') conv_input = mx.sym.Reshape(data=embed_layer, target_shape=(batch_size, 1, sentence_size, num_embed)) else: @@ -59,203 +139,37 @@ def make_text_cnn(sentence_size, num_embed, batch_size, vocab_size, # softmax output sm = mx.sym.SoftmaxOutput(data=fc, label=input_y, name='softmax') - return sm - - -def setup_cnn_model(ctx, batch_size, sentence_size, num_embed, vocab_size, - dropout=0.5, initializer=mx.initializer.Uniform(0.1), with_embedding=True): - - cnn = make_text_cnn(sentence_size, num_embed, batch_size=batch_size, - vocab_size=vocab_size, dropout=dropout, with_embedding=with_embedding) - arg_names = cnn.list_arguments() - - input_shapes = {} - if with_embedding: - input_shapes['data'] = (batch_size, 1, sentence_size, num_embed) - else: - input_shapes['data'] = (batch_size, sentence_size) - - arg_shape, out_shape, aux_shape = cnn.infer_shape(**input_shapes) - arg_arrays = [mx.nd.zeros(s, ctx) for s in arg_shape] - args_grad = {} - for shape, name in zip(arg_shape, arg_names): - if name in ['softmax_label', 'data']: # input, output - continue - args_grad[name] = mx.nd.zeros(shape, ctx) - - cnn_exec = cnn.bind(ctx=ctx, args=arg_arrays, args_grad=args_grad, grad_req='add') - - param_blocks = [] - arg_dict = dict(zip(arg_names, cnn_exec.arg_arrays)) - for i, name in enumerate(arg_names): - if name in ['softmax_label', 'data']: # input, output - continue - initializer(name, arg_dict[name]) - - param_blocks.append( (i, arg_dict[name], args_grad[name], name) ) - - out_dict = dict(zip(cnn.list_outputs(), cnn_exec.outputs)) - - data = cnn_exec.arg_dict['data'] - label = cnn_exec.arg_dict['softmax_label'] - - return CNNModel(cnn_exec=cnn_exec, symbol=cnn, data=data, label=label, param_blocks=param_blocks) - - -def train_cnn(model, X_train_batch, y_train_batch, X_dev_batch, y_dev_batch, batch_size, - optimizer='rmsprop', max_grad_norm=5.0, learning_rate=0.0005, epoch=200): - m = model - # create optimizer - opt = mx.optimizer.create(optimizer) - opt.lr = learning_rate - - updater = mx.optimizer.get_updater(opt) - - for iteration in range(epoch): - tic = time.time() - num_correct = 0 - num_total = 0 - for begin in range(0, X_train_batch.shape[0], batch_size): - batchX = X_train_batch[begin:begin+batch_size] - batchY = y_train_batch[begin:begin+batch_size] - if batchX.shape[0] != batch_size: - continue - - m.data[:] = batchX - m.label[:] = batchY - - # forward - m.cnn_exec.forward(is_train=True) - - # backward - m.cnn_exec.backward() - - # eval on training data - num_correct += sum(batchY == np.argmax(m.cnn_exec.outputs[0].asnumpy(), axis=1)) - num_total += len(batchY) - - # update weights - norm = 0 - for idx, weight, grad, name in m.param_blocks: - grad /= batch_size - l2_norm = mx.nd.norm(grad).asscalar() - norm += l2_norm * l2_norm - - norm = math.sqrt(norm) - for idx, weight, grad, name in m.param_blocks: - if norm > max_grad_norm: - grad *= (max_grad_norm / norm) - - updater(idx, grad, weight) - - # reset gradient to zero - grad[:] = 0.0 - - # decay learning rate - if iteration % 50 == 0 and iteration > 0: - opt.lr *= 0.5 - print('reset learning rate to %g' % opt.lr,file=logs) - - # end of training loop - toc = time.time() - train_time = toc - tic - train_acc = num_correct * 100 / float(num_total) - - # saving checkpoint - if (iteration + 1) % 10 == 0: - prefix = 'cnn' - m.symbol.save('checkpoint/%s-symbol.json' % prefix) - save_dict = {('arg:%s' % k) :v for k, v in m.cnn_exec.arg_dict.items()} - save_dict.update({('aux:%s' % k) : v for k, v in m.cnn_exec.aux_dict.items()}) - param_name = 'checkpoint/%s-%04d.params' % (prefix, iteration) - mx.nd.save(param_name, save_dict) - print('Saved checkpoint to %s' % param_name,file=logs) - - - # evaluate on dev set - num_correct = 0 - num_total = 0 - for begin in range(0, X_dev_batch.shape[0], batch_size): - batchX = X_dev_batch[begin:begin+batch_size] - batchY = y_dev_batch[begin:begin+batch_size] - - if batchX.shape[0] != batch_size: - continue - - m.data[:] = batchX - m.cnn_exec.forward(is_train=False) - - num_correct += sum(batchY == np.argmax(m.cnn_exec.outputs[0].asnumpy(), axis=1)) - num_total += len(batchY) - - dev_acc = num_correct * 100 / float(num_total) - print('Iter [%d] Train: Time: %.3fs, Training Accuracy: %.3f \ - --- Dev Accuracy thus far: %.3f' % (iteration, train_time, train_acc, dev_acc), file=logs) - - -def main(): - print('Loading data...') - # word2vec = data_helpers.load_google_word2vec('data/GoogleNews-vectors-negative300.bin') - word2vec = data_helpers.load_pretrained_word2vec('data/rt.vec') - x, y = data_helpers.load_data_with_word2vec(word2vec) - - # randomly shuffle data - np.random.seed(10) - shuffle_indices = np.random.permutation(np.arange(len(y))) - x_shuffled = x[shuffle_indices] - y_shuffled = y[shuffle_indices] - - # split train/dev set - x_train, x_dev = x_shuffled[:-1000], x_shuffled[-1000:] - y_train, y_dev = y_shuffled[:-1000], y_shuffled[-1000:] - print('Train/Dev split: %d/%d' % (len(y_train), len(y_dev))) - print('train shape:', x_train.shape) - print('dev shape:', x_dev.shape) - - # reshpae for convolution input - x_train = np.reshape(x_train, (x_train.shape[0], 1, x_train.shape[1], x_train.shape[2])) - x_dev = np.reshape(x_dev, (x_dev.shape[0], 1, x_dev.shape[1], x_dev.shape[2])) - - num_embed = x_train.shape[-1] - sentence_size = x_train.shape[2] - print('sentence max words', sentence_size) - print('embedding size', num_embed) - batch_size = 50 - - cnn_model = setup_cnn_model(mx.gpu(1), batch_size, sentence_size, num_embed, dropout=0.5) - train_cnn(cnn_model, x_train, y_train, x_dev, y_dev, batch_size) - -def train_without_pretrained_embedding(): - x, y, vocab, vocab_inv = data_helpers.load_data() - vocab_size = len(vocab) - - # randomly shuffle data - np.random.seed(10) - shuffle_indices = np.random.permutation(np.arange(len(y))) - x_shuffled = x[shuffle_indices] - y_shuffled = y[shuffle_indices] - - # split train/dev set - x_train, x_dev = x_shuffled[:-1000], x_shuffled[-1000:] - y_train, y_dev = y_shuffled[:-1000], y_shuffled[-1000:] - print('Train/Dev split: %d/%d' % (len(y_train), len(y_dev))) - print('train shape:', x_train.shape) - print('dev shape:', x_dev.shape) - print('vocab_size', vocab_size) - - batch_size = 50 - num_embed = 300 - sentence_size = x_train.shape[1] - - print('batch size', batch_size) - print('sentence max words', sentence_size) - print('embedding size', num_embed) - - cnn_model = setup_cnn_model(mx.gpu(0), batch_size, sentence_size, num_embed, vocab_size, dropout=0.5, with_embedding=False) - train_cnn(cnn_model, x_train, y_train, x_dev, y_dev, batch_size) - + return sm, ('data',), ('softmax_label',) + +def train(symbol, train_iter, valid_iter, data_names, label_names): + devs = mx.cpu() if args.gpus is None or args.gpus is '' else [ + mx.gpu(int(i)) for i in args.gpus.split(',')] + module = mx.mod.Module(symbol, data_names=data_names, label_names=label_names, context=devs) + module.fit(train_data = train_iter, + eval_data = valid_iter, + eval_metric = 'acc', + kvstore = args.kv_store, + optimizer = args.optimizer, + optimizer_params = { 'learning_rate': args.lr }, + initializer = mx.initializer.Uniform(0.1), + num_epoch = args.num_epochs, + batch_end_callback = mx.callback.Speedometer(args.batch_size, args.disp_batches), + epoch_end_callback = save_model()) if __name__ == '__main__': - if not os.path.exists("checkpoint"): - os.mkdir("checkpoint") - train_without_pretrained_embedding() + # parse args + args = parser.parse_args() + + # data iter + train_iter, valid_iter, sentence_size, embed_size, vocab_size = data_iter(args.batch_size, + args.num_embed, + args.pretrained_embedding) + # network symbol + symbol, data_names, label_names = sym_gen(args.batch_size, + sentence_size, + embed_size, + vocab_size, + num_label=2, filter_list=[3, 4, 5], num_filter=100, + dropout=args.dropout, pre_trained_word2vec=args.pretrained_embedding) + # train cnn model + train(symbol, train_iter, valid_iter, data_names, label_names) diff --git a/example/ctc/README.md b/example/ctc/README.md new file mode 100644 index 000000000000..e3a4d8c0857f --- /dev/null +++ b/example/ctc/README.md @@ -0,0 +1,116 @@ +# CTC with Mxnet +this is mx.contrib.sym.ctc_loss example. It was modified from example [warpctc](https://github.com/dmlc/mxnet/tree/master/example/warpctc) + +# Core code +this is core change in lstm.py +```Cython +def lstm_unroll(num_lstm_layer, seq_len, + num_hidden, num_label): + param_cells = [] + last_states = [] + for i in range(num_lstm_layer): + param_cells.append(LSTMParam(i2h_weight=mx.sym.Variable("l%d_i2h_weight" % i), + i2h_bias=mx.sym.Variable("l%d_i2h_bias" % i), + h2h_weight=mx.sym.Variable("l%d_h2h_weight" % i), + h2h_bias=mx.sym.Variable("l%d_h2h_bias" % i))) + state = LSTMState(c=mx.sym.Variable("l%d_init_c" % i), + h=mx.sym.Variable("l%d_init_h" % i)) + last_states.append(state) + assert (len(last_states) == num_lstm_layer) + + # embeding layer + data = mx.sym.Variable('data') + label = mx.sym.Variable('label') + wordvec = mx.sym.SliceChannel(data=data, num_outputs=seq_len, squeeze_axis=1) + + hidden_all = [] + for seqidx in range(seq_len): + hidden = wordvec[seqidx] + for i in range(num_lstm_layer): + next_state = lstm(num_hidden, indata=hidden, + prev_state=last_states[i], + param=param_cells[i], + seqidx=seqidx, layeridx=i) + hidden = next_state.h + last_states[i] = next_state + hidden_all.append(hidden) + + hidden_concat = mx.sym.Concat(*hidden_all, dim=0) + + pred_fc = mx.sym.FullyConnected(data=hidden_concat, num_hidden=11) + pred_ctc = mx.sym.Reshape(data=pred_fc, shape=(-4, seq_len, -1, 0)) + + loss = mx.contrib.sym.ctc_loss(data=pred_ctc, label=label) + ctc_loss = mx.sym.MakeLoss(loss) + + softmax_class = mx.symbol.SoftmaxActivation(data=pred_fc) + softmax_loss = mx.sym.MakeLoss(softmax_class) + softmax_loss = mx.sym.BlockGrad(softmax_loss) + + return mx.sym.Group([softmax_loss, ctc_loss]) +``` +# Some Result +If there were more training, the result would be better + +``` +2017-07-08 13:22:01,155 Epoch[94] Batch [50] Speed: 4273.43 samples/sec Accuracy=0.808747 +2017-07-08 13:22:13,141 Epoch[94] Batch [100] Speed: 4271.84 samples/sec Accuracy=0.786855 +2017-07-08 13:22:25,179 Epoch[94] Batch [150] Speed: 4253.81 samples/sec Accuracy=0.810625 +2017-07-08 13:22:37,198 Epoch[94] Batch [200] Speed: 4259.96 samples/sec Accuracy=0.808809 +2017-07-08 13:22:49,233 Epoch[94] Batch [250] Speed: 4254.13 samples/sec Accuracy=0.806426 +2017-07-08 13:23:01,308 Epoch[94] Batch [300] Speed: 4239.98 samples/sec Accuracy=0.817305 +2017-07-08 13:23:02,030 Epoch[94] Train-Accuracy=0.819336 +2017-07-08 13:23:02,030 Epoch[94] Time cost=73.092 +2017-07-08 13:23:02,101 Saved checkpoint to "ocr-0095.params" +2017-07-08 13:23:07,192 Epoch[94] Validation-Accuracy=0.819417 +2017-07-08 13:23:20,579 Epoch[95] Batch [50] Speed: 4288.76 samples/sec Accuracy=0.817459 +2017-07-08 13:23:32,573 Epoch[95] Batch [100] Speed: 4268.75 samples/sec Accuracy=0.815215 +2017-07-08 13:23:44,635 Epoch[95] Batch [150] Speed: 4244.85 samples/sec Accuracy=0.820215 +2017-07-08 13:23:56,670 Epoch[95] Batch [200] Speed: 4254.38 samples/sec Accuracy=0.823613 +2017-07-08 13:24:08,650 Epoch[95] Batch [250] Speed: 4273.83 samples/sec Accuracy=0.827109 +2017-07-08 13:24:20,680 Epoch[95] Batch [300] Speed: 4256.49 samples/sec Accuracy=0.824961 +2017-07-08 13:24:21,401 Epoch[95] Train-Accuracy=0.840495 +2017-07-08 13:24:21,401 Epoch[95] Time cost=73.008 +2017-07-08 13:24:21,441 Saved checkpoint to "ocr-0096.params" +2017-07-08 13:24:26,508 Epoch[95] Validation-Accuracy=0.834798 +2017-07-08 13:24:39,938 Epoch[96] Batch [50] Speed: 4259.32 samples/sec Accuracy=0.825578 +2017-07-08 13:24:51,987 Epoch[96] Batch [100] Speed: 4249.67 samples/sec Accuracy=0.826562 +2017-07-08 13:25:04,041 Epoch[96] Batch [150] Speed: 4247.44 samples/sec Accuracy=0.831855 +2017-07-08 13:25:16,058 Epoch[96] Batch [200] Speed: 4260.77 samples/sec Accuracy=0.830840 +2017-07-08 13:25:28,109 Epoch[96] Batch [250] Speed: 4248.44 samples/sec Accuracy=0.827168 +2017-07-08 13:25:40,057 Epoch[96] Batch [300] Speed: 4285.23 samples/sec Accuracy=0.832715 +2017-07-08 13:25:40,782 Epoch[96] Train-Accuracy=0.830729 +2017-07-08 13:25:40,782 Epoch[96] Time cost=73.098 +2017-07-08 13:25:40,821 Saved checkpoint to "ocr-0097.params" +2017-07-08 13:25:45,886 Epoch[96] Validation-Accuracy=0.840820 +2017-07-08 13:25:59,283 Epoch[97] Batch [50] Speed: 4271.85 samples/sec Accuracy=0.831648 +2017-07-08 13:26:11,243 Epoch[97] Batch [100] Speed: 4280.89 samples/sec Accuracy=0.835371 +2017-07-08 13:26:23,263 Epoch[97] Batch [150] Speed: 4259.89 samples/sec Accuracy=0.831094 +2017-07-08 13:26:35,230 Epoch[97] Batch [200] Speed: 4278.40 samples/sec Accuracy=0.827129 +2017-07-08 13:26:47,199 Epoch[97] Batch [250] Speed: 4277.77 samples/sec Accuracy=0.834258 +2017-07-08 13:26:59,257 Epoch[97] Batch [300] Speed: 4245.93 samples/sec Accuracy=0.833770 +2017-07-08 13:26:59,971 Epoch[97] Train-Accuracy=0.844727 +2017-07-08 13:26:59,971 Epoch[97] Time cost=72.908 +2017-07-08 13:27:00,020 Saved checkpoint to "ocr-0098.params" +2017-07-08 13:27:05,130 Epoch[97] Validation-Accuracy=0.827962 +2017-07-08 13:27:18,521 Epoch[98] Batch [50] Speed: 4281.06 samples/sec Accuracy=0.834118 +2017-07-08 13:27:30,537 Epoch[98] Batch [100] Speed: 4261.20 samples/sec Accuracy=0.835352 +2017-07-08 13:27:42,542 Epoch[98] Batch [150] Speed: 4264.88 samples/sec Accuracy=0.839395 +2017-07-08 13:27:54,544 Epoch[98] Batch [200] Speed: 4266.31 samples/sec Accuracy=0.836328 +2017-07-08 13:28:06,550 Epoch[98] Batch [250] Speed: 4264.50 samples/sec Accuracy=0.841465 +2017-07-08 13:28:18,622 Epoch[98] Batch [300] Speed: 4241.11 samples/sec Accuracy=0.831680 +2017-07-08 13:28:19,349 Epoch[98] Train-Accuracy=0.833984 +2017-07-08 13:28:19,349 Epoch[98] Time cost=73.018 +2017-07-08 13:28:19,393 Saved checkpoint to "ocr-0099.params" +2017-07-08 13:28:24,472 Epoch[98] Validation-Accuracy=0.818034 +2017-07-08 13:28:37,961 Epoch[99] Batch [50] Speed: 4242.14 samples/sec Accuracy=0.835861 +2017-07-08 13:28:50,031 Epoch[99] Batch [100] Speed: 4241.94 samples/sec Accuracy=0.846543 +2017-07-08 13:29:02,108 Epoch[99] Batch [150] Speed: 4239.22 samples/sec Accuracy=0.850645 +2017-07-08 13:29:14,160 Epoch[99] Batch [200] Speed: 4248.34 samples/sec Accuracy=0.844141 +2017-07-08 13:29:26,225 Epoch[99] Batch [250] Speed: 4243.71 samples/sec Accuracy=0.842129 +2017-07-08 13:29:38,277 Epoch[99] Batch [300] Speed: 4248.07 samples/sec Accuracy=0.851250 +2017-07-08 13:29:38,975 Epoch[99] Train-Accuracy=0.854492 +2017-07-08 13:29:38,976 Epoch[99] Time cost=73.315 +2017-07-08 13:29:39,023 Saved checkpoint to "ocr-0100.params" +2017-07-08 13:29:44,110 Epoch[99] Validation-Accuracy=0.851969 +``` diff --git a/example/ctc/lstm.py b/example/ctc/lstm.py new file mode 100644 index 000000000000..7e18c8699492 --- /dev/null +++ b/example/ctc/lstm.py @@ -0,0 +1,106 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +# pylint:skip-file +import sys + +from mxnet.symbol_doc import SymbolDoc + +sys.path.insert(0, "../../python") +import mxnet as mx +import numpy as np +from collections import namedtuple +import time +import math + +LSTMState = namedtuple("LSTMState", ["c", "h"]) +LSTMParam = namedtuple("LSTMParam", ["i2h_weight", "i2h_bias", + "h2h_weight", "h2h_bias"]) +LSTMModel = namedtuple("LSTMModel", ["rnn_exec", "symbol", + "init_states", "last_states", + "seq_data", "seq_labels", "seq_outputs", + "param_blocks"]) + + +def lstm(num_hidden, indata, prev_state, param, seqidx, layeridx): + """LSTM Cell symbol""" + i2h = mx.sym.FullyConnected(data=indata, + weight=param.i2h_weight, + bias=param.i2h_bias, + num_hidden=num_hidden * 4, + name="t%d_l%d_i2h" % (seqidx, layeridx)) + h2h = mx.sym.FullyConnected(data=prev_state.h, + weight=param.h2h_weight, + bias=param.h2h_bias, + num_hidden=num_hidden * 4, + name="t%d_l%d_h2h" % (seqidx, layeridx)) + gates = i2h + h2h + slice_gates = mx.sym.SliceChannel(gates, num_outputs=4, + name="t%d_l%d_slice" % (seqidx, layeridx)) + in_gate = mx.sym.Activation(slice_gates[0], act_type="sigmoid") + in_transform = mx.sym.Activation(slice_gates[1], act_type="tanh") + forget_gate = mx.sym.Activation(slice_gates[2], act_type="sigmoid") + out_gate = mx.sym.Activation(slice_gates[3], act_type="sigmoid") + next_c = (forget_gate * prev_state.c) + (in_gate * in_transform) + next_h = out_gate * mx.sym.Activation(next_c, act_type="tanh") + return LSTMState(c=next_c, h=next_h) + + +def lstm_unroll(num_lstm_layer, seq_len, + num_hidden, num_label): + param_cells = [] + last_states = [] + for i in range(num_lstm_layer): + param_cells.append(LSTMParam(i2h_weight=mx.sym.Variable("l%d_i2h_weight" % i), + i2h_bias=mx.sym.Variable("l%d_i2h_bias" % i), + h2h_weight=mx.sym.Variable("l%d_h2h_weight" % i), + h2h_bias=mx.sym.Variable("l%d_h2h_bias" % i))) + state = LSTMState(c=mx.sym.Variable("l%d_init_c" % i), + h=mx.sym.Variable("l%d_init_h" % i)) + last_states.append(state) + assert (len(last_states) == num_lstm_layer) + + # embeding layer + data = mx.sym.Variable('data') + label = mx.sym.Variable('label') + wordvec = mx.sym.SliceChannel(data=data, num_outputs=seq_len, squeeze_axis=1) + + hidden_all = [] + for seqidx in range(seq_len): + hidden = wordvec[seqidx] + for i in range(num_lstm_layer): + next_state = lstm(num_hidden, indata=hidden, + prev_state=last_states[i], + param=param_cells[i], + seqidx=seqidx, layeridx=i) + hidden = next_state.h + last_states[i] = next_state + hidden_all.append(hidden) + + hidden_concat = mx.sym.Concat(*hidden_all, dim=0) + + pred_fc = mx.sym.FullyConnected(data=hidden_concat, num_hidden=11) + pred_ctc = mx.sym.Reshape(data=pred_fc, shape=(-4, seq_len, -1, 0)) + + loss = mx.contrib.sym.ctc_loss(data=pred_ctc, label=label) + ctc_loss = mx.sym.MakeLoss(loss) + + softmax_class = mx.symbol.SoftmaxActivation(data=pred_fc) + softmax_loss = mx.sym.MakeLoss(softmax_class) + softmax_loss = mx.sym.BlockGrad(softmax_loss) + + return mx.sym.Group([softmax_loss, ctc_loss]) diff --git a/example/ctc/lstm_ocr.py b/example/ctc/lstm_ocr.py new file mode 100644 index 000000000000..c9928aa43ab8 --- /dev/null +++ b/example/ctc/lstm_ocr.py @@ -0,0 +1,254 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +# pylint: disable=C0111,too-many-arguments,too-many-instance-attributes,too-many-locals,redefined-outer-name,fixme +# pylint: disable=superfluous-parens, no-member, invalid-name +from __future__ import print_function +import sys, random +sys.path.insert(0, "../../python") +import numpy as np +import mxnet as mx + +from lstm import lstm_unroll + +from captcha.image import ImageCaptcha +import cv2, random + + +class SimpleBatch(object): + def __init__(self, data_names, data, label_names, label): + self.data = data + self.label = label + self.data_names = data_names + self.label_names = label_names + + self.pad = 0 + self.index = None # TODO: what is index? + + @property + def provide_data(self): + return [(n, x.shape) for n, x in zip(self.data_names, self.data)] + + @property + def provide_label(self): + return [(n, x.shape) for n, x in zip(self.label_names, self.label)] + + +def gen_rand(): + buf = "" + max_len = random.randint(3, 4) + for i in range(max_len): + buf += str(random.randint(0, 9)) + return buf + + +def get_label(buf): + ret = np.zeros(4) + for i in range(len(buf)): + ret[i] = 1 + int(buf[i]) + if len(buf) == 3: + ret[3] = 0 + return ret + + +class OCRIter(mx.io.DataIter): + def __init__(self, count, batch_size, num_label, init_states): + super(OCRIter, self).__init__() + global SEQ_LENGTH + # you can get this font from http://font.ubuntu.com/ + self.captcha = ImageCaptcha(fonts=['./data/Xerox.ttf']) + self.batch_size = batch_size + self.count = count + self.num_label = num_label + self.init_states = init_states + self.init_state_arrays = [mx.nd.zeros(x[1]) for x in init_states] + self.provide_data = [('data', (batch_size, 80, 30))] + init_states + self.provide_label = [('label', (self.batch_size, 4))] + self.cache_data = [] + self.cache_label = [] + + def __iter__(self): + print('iter') + init_state_names = [x[0] for x in self.init_states] + for k in range(self.count): + data = [] + label = [] + for i in range(self.batch_size): + num = gen_rand() + img = self.captcha.generate(num) + img = np.fromstring(img.getvalue(), dtype='uint8') + img = cv2.imdecode(img, cv2.IMREAD_GRAYSCALE) + img = cv2.resize(img, (80, 30)) + img = img.transpose(1, 0) + img = img.reshape((80, 30)) + img = np.multiply(img, 1 / 255.0) + data.append(img) + label.append(get_label(num)) + + data_all = [mx.nd.array(data)] + self.init_state_arrays + label_all = [mx.nd.array(label)] + data_names = ['data'] + init_state_names + label_names = ['label'] + + data_batch = SimpleBatch(data_names, data_all, label_names, label_all) + yield data_batch + + def reset(self): + self.cache_data.clear() + self.cache_label.clear() + pass + + +BATCH_SIZE = 1024 +SEQ_LENGTH = 80 + + +def ctc_label(p): + ret = [] + p1 = [0] + p + for i in range(len(p)): + c1 = p1[i] + c2 = p1[i + 1] + if c2 == 0 or c2 == c1: + continue + ret.append(c2) + return ret + + +def remove_blank(l): + ret = [] + for i in range(len(l)): + if l[i] == 0: + break + ret.append(l[i]) + return ret + + +def Accuracy(label, pred): + global BATCH_SIZE + global SEQ_LENGTH + hit = 0. + total = 0. + rp = np.argmax(pred, axis=1) + for i in range(BATCH_SIZE): + l = remove_blank(label[i]) + p = [] + for k in range(SEQ_LENGTH): + p.append(np.argmax(pred[k * BATCH_SIZE + i])) + p = ctc_label(p) + if len(p) == len(l): + match = True + for k in range(len(p)): + if p[k] != int(l[k]): + match = False + break + if match: + hit += 1.0 + total += 1.0 + return hit / total + + +def LCS(p, l): + # Dynamic Programming Finding LCS + if len(p) == 0: + return 0 + P = np.array(list(p)).reshape((1, len(p))) + L = np.array(list(l)).reshape((len(l), 1)) + M = np.int32(P == L) + for i in range(M.shape[0]): + for j in range(M.shape[1]): + up = 0 if i == 0 else M[i - 1, j] + left = 0 if j == 0 else M[i, j - 1] + M[i, j] = max(up, left, M[i, j] if (i == 0 or j == 0) else M[i, j] + M[i - 1, j - 1]) + return M.max() + + +def Accuracy_LCS(label, pred): + global BATCH_SIZE + global SEQ_LENGTH + hit = 0. + total = 0. + for i in range(BATCH_SIZE): + l = remove_blank(label[i]) + p = [] + for k in range(SEQ_LENGTH): + p.append(np.argmax(pred[k * BATCH_SIZE + i])) + p = ctc_label(p) + hit += LCS(p, l) * 1.0 / len(l) + total += 1.0 + return hit / total + + +def asum_stat(x): + """returns |x|/size(x), async execution.""" + # npx = x.asnumpy() + # print(npx) + return x + return mx.ndarray.norm(x) / np.sqrt(x.size) + + +if __name__ == '__main__': + num_hidden = 100 + num_lstm_layer = 2 + + num_epoch = 100 + learning_rate = 0.01 + momentum = 0.9 + num_label = 4 + + contexts = [mx.context.gpu(0)] + + + def sym_gen(seq_len): + return lstm_unroll(num_lstm_layer, seq_len, + num_hidden=num_hidden, + num_label=num_label) + + + init_c = [('l%d_init_c' % l, (BATCH_SIZE, num_hidden)) for l in range(num_lstm_layer)] + init_h = [('l%d_init_h' % l, (BATCH_SIZE, num_hidden)) for l in range(num_lstm_layer)] + init_states = init_c + init_h + + data_train = OCRIter(20000, BATCH_SIZE, num_label, init_states) + data_val = OCRIter(1000, BATCH_SIZE, num_label, init_states) + + symbol = sym_gen(SEQ_LENGTH) + + import logging + + head = '%(asctime)-15s %(message)s' + logging.basicConfig(level=logging.DEBUG, format=head) + + print('begin fit') + + module = mx.mod.Module(symbol, data_names=['data', 'l0_init_c', 'l0_init_h', 'l1_init_c', 'l1_init_h'], + label_names=['label'], + context=contexts) + + module.fit(train_data=data_train, + eval_data=data_val, + eval_metric=mx.metric.np(Accuracy, allow_extra_outputs=True), + optimizer='sgd', + optimizer_params={'learning_rate': learning_rate, + 'momentum': momentum, + 'wd': 0.00001, + }, + initializer=mx.init.Xavier(factor_type="in", magnitude=2.34), + num_epoch=num_epoch, + batch_end_callback=mx.callback.Speedometer(BATCH_SIZE, 50), + epoch_end_callback=mx.callback.do_checkpoint("ocr"), + ) diff --git a/example/ctc/ocr_predict.py b/example/ctc/ocr_predict.py new file mode 100644 index 000000000000..3096a664a20f --- /dev/null +++ b/example/ctc/ocr_predict.py @@ -0,0 +1,106 @@ +#!/usr/bin/env python2.7 + +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +# coding=utf-8 +from __future__ import print_function +import sys, os +curr_path = os.path.dirname(os.path.abspath(os.path.expanduser(__file__))) +sys.path.append("../../amalgamation/python/") +sys.path.append("../../python/") + +from mxnet_predict import Predictor +import mxnet as mx + +import numpy as np +import cv2 +import os + +class lstm_ocr_model(object): + # Keep Zero index for blank. (CTC request it) + CONST_CHAR='0123456789' + def __init__(self, path_of_json, path_of_params): + super(lstm_ocr_model, self).__init__() + self.path_of_json = path_of_json + self.path_of_params = path_of_params + self.predictor = None + self.__init_ocr() + + def __init_ocr(self): + num_label = 4 # Set your max length of label, add one more for blank + batch_size = 1 + + num_hidden = 100 + num_lstm_layer = 2 + init_c = [('l%d_init_c'%l, (batch_size, num_hidden)) for l in range(num_lstm_layer)] + init_h = [('l%d_init_h'%l, (batch_size, num_hidden)) for l in range(num_lstm_layer)] + init_states = init_c + init_h + + init_state_arrays = np.zeros((batch_size, num_hidden), dtype="float32") + self.init_state_dict={} + for x in init_states: + self.init_state_dict[x[0]] = init_state_arrays + + all_shapes = [('data', (batch_size, 80 * 30))] + init_states + [('label', (batch_size, num_label))] + all_shapes_dict = {} + for _shape in all_shapes: + all_shapes_dict[_shape[0]] = _shape[1] + self.predictor = Predictor(open(self.path_of_json).read(), + open(self.path_of_params).read(), + all_shapes_dict) + + def forward_ocr(self, img): + img = cv2.resize(img, (80, 30)) + img = img.transpose(1, 0) + img = img.reshape((80 * 30)) + img = np.multiply(img, 1/255.0) + self.predictor.forward(data=img, **self.init_state_dict) + prob = self.predictor.get_output(0) + label_list = [] + for p in prob: + max_index = np.argsort(p)[::-1][0] + label_list.append(max_index) + return self.__get_string(label_list) + + def __get_string(self, label_list): + # Do CTC label rule + # CTC cannot emit a repeated symbol on consecutive timesteps + ret = [] + label_list2 = [0] + list(label_list) + for i in range(len(label_list)): + c1 = label_list2[i] + c2 = label_list2[i+1] + if c2 == 0 or c2 == c1: + continue + ret.append(c2) + # change to ascii + s = '' + for l in ret: + if l > 0 and l < (len(lstm_ocr_model.CONST_CHAR)+1): + c = lstm_ocr_model.CONST_CHAR[l-1] + else: + c = '' + s += c + return s + +if __name__ == '__main__': + _lstm_ocr_model = lstm_ocr_model('ocr-symbol.json', 'ocr-0010.params') + img = cv2.imread('sample.jpg', 0) + _str = _lstm_ocr_model.forward_ocr(img) + print('Result: ', _str) + diff --git a/example/dec/dec.py b/example/dec/dec.py index d8a45149d2e8..ac6545abb1de 100644 --- a/example/dec/dec.py +++ b/example/dec/dec.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # pylint: skip-file from __future__ import print_function import sys @@ -154,4 +171,4 @@ def mnist_exp(xpu): if __name__ == '__main__': logging.basicConfig(level=logging.INFO) mnist_exp(mx.gpu(0)) - + diff --git a/example/dsd/README.md b/example/dsd/README.md new file mode 100644 index 000000000000..0ce5cc5d1f0f --- /dev/null +++ b/example/dsd/README.md @@ -0,0 +1,30 @@ +DSD Training +============ +This folder contains an optimizer class that implements DSD training coupled with SGD. The training +procedure is described in the paper *DSD: Dense-Sparse-Dense Training for Deep Neural Networks*, +available at https://arxiv.org/pdf/1607.04381.pdf + +The optimizer class is flexible in the way it prunes weights. The user can define the following: +- The percentage sparsity they want or the thresholding value for the pruning +- The epochs at which they want a particular level of pruning + +Note that giving the sparsity level induces that level of sparsity in every layer of the neural +network. It layer-wise pruning, and not global pruning (which would require loooking at all the +weights of the neural network at the same time). However, global pruning can be done if the +threshold value is known to the user (by doing some preprocessing), and is passed to the optimizer. + +## Example + +To test out the sparsity feature on a MLP, run the following script: + + python mlp.py --pruning_switch_epoch 4,7,10 --bias_sparsity 0,30,50 --weight_sparsity 0,50,70 + +This will train a MLP with 0% sparsity uptil epoch 4, with 30% bias and 50% weight sparsity uptil +epoch 7, 50% bias and 70% weight sparsity uptil epoch 10. + +To test out the thresholding feature on a MLP, run the following script: + + python mlp.py --pruning_switch_epoch 4,6 --bias_threshold 0,0.01 --weight_threshold 0,0.05 + +This will train a MLP with thresholding at 0 uptil epoch 4, with bias thresholding at 0.01 and +weight thresholding at 0.05 uptil epoch 6. diff --git a/example/dsd/mlp.py b/example/dsd/mlp.py new file mode 100644 index 000000000000..767e5924b294 --- /dev/null +++ b/example/dsd/mlp.py @@ -0,0 +1,142 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +import mxnet as mx +import os +import logging +import argparse +from math import ceil +import sparse_sgd + +# symbol net +def get_symbol(): + data = mx.symbol.Variable('data') + fc1 = mx.symbol.FullyConnected(data, name='fc1', num_hidden=128) + act1 = mx.symbol.Activation(fc1, name='relu1', act_type="relu") + fc2 = mx.symbol.FullyConnected(act1, name='fc2', num_hidden=64) + act2 = mx.symbol.Activation(fc2, name='relu2', act_type="relu") + fc3 = mx.symbol.FullyConnected(act2, name='fc3', num_hidden=10) + softmax = mx.symbol.SoftmaxOutput(fc3, name='sm') + + return softmax + +# download ubyte version of mnist and untar +def download_data(): + if not os.path.isdir("data/"): + os.system("mkdir data/") + if (not os.path.exists('data/train-images-idx3-ubyte')) or \ + (not os.path.exists('data/train-labels-idx1-ubyte')) or \ + (not os.path.exists('data/t10k-images-idx3-ubyte')) or \ + (not os.path.exists('data/t10k-labels-idx1-ubyte')): + os.system("wget -q http://data.mxnet.io/mxnet/data/mnist.zip -P data/") + os.chdir("./data") + os.system("unzip -u mnist.zip") + os.chdir("..") + +# get data iterators +def get_iters(batch_size): + train = mx.io.MNISTIter( + image="data/train-images-idx3-ubyte", + label="data/train-labels-idx1-ubyte", + data_shape=(784,), + label_name='sm_label', + batch_size=batch_size, + shuffle=True, + flat=True, + silent=False, + seed=10) + val = mx.io.MNISTIter( + image="data/t10k-images-idx3-ubyte", + label="data/t10k-labels-idx1-ubyte", + data_shape=(784,), + label_name='sm_label', + batch_size=batch_size, + shuffle=True, + flat=True, + silent=False) + + return (train, val) + +def test_mlp(args): + # get parameters + prefix = './mlp' + batch_size = 100 + pruning_switch_epoch = [int(i) for i in args.pruning_switch_epoch.split(',')] + num_epoch = pruning_switch_epoch[-1] + batches_per_epoch = ceil(60000.0/batch_size) + weight_sparsity = args.weight_sparsity + bias_sparsity = args.bias_sparsity + weight_threshold = args.weight_threshold + bias_threshold = args.bias_threshold + if args.weight_sparsity: + weight_sparsity = [float(i) for i in args.weight_sparsity.split(',')] + bias_sparsity = [float(i) for i in args.bias_sparsity.split(',')] + else: + weight_threshold = [float(i) for i in args.weight_threshold.split(',')] + bias_threshold = [float(i) for i in args.bias_threshold.split(',')] + + # get symbols and iterators + sym = get_symbol() + download_data() + (train, val) = get_iters(batch_size) + + # fit model + model = mx.mod.Module( + sym, + context=[mx.cpu(i) for i in range(2)], + data_names=['data'], + label_names=['sm_label']) + optimizer_params = { + 'learning_rate' : 0.1, + 'wd' : 0.004, + 'momentum' : 0.9, + 'pruning_switch_epoch' : pruning_switch_epoch, + 'batches_per_epoch' : batches_per_epoch, + 'weight_sparsity' : weight_sparsity, + 'bias_sparsity' : bias_sparsity, + 'weight_threshold' : weight_threshold, + 'bias_threshold' : bias_threshold} + logging.info('Start training...') + model.fit(train, + eval_data=val, + eval_metric='acc', + epoch_end_callback=mx.callback.do_checkpoint(prefix), + num_epoch=num_epoch, + optimizer='sparsesgd', + optimizer_params=optimizer_params) + logging.info('Finish traning...') + + # remove files + for i in range(num_epoch): + os.remove('%s-%04d.params' % (prefix, i + 1)) + os.remove('%s-symbol.json' % prefix) + + +if __name__ == "__main__": + + # print logging by default + logging.basicConfig(level=logging.DEBUG) + + parser = argparse.ArgumentParser(description="sparse training") + parser.add_argument('--pruning_switch_epoch', type=str) + parser.add_argument('--weight_sparsity', type=str, default=None) + parser.add_argument('--bias_sparsity', type=str, default=None) + parser.add_argument('--weight_threshold', type=str, default=None) + parser.add_argument('--bias_threshold', type=str, default=None) + args = parser.parse_args() + + test_mlp(args) diff --git a/example/dsd/sparse_sgd.py b/example/dsd/sparse_sgd.py new file mode 100644 index 000000000000..b21e9b9b89fc --- /dev/null +++ b/example/dsd/sparse_sgd.py @@ -0,0 +1,187 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +from mxnet.ndarray import NDArray, topk, abs as NDabs +from mxnet.optimizer import SGD, register +import logging + +log = 'Sparsity Update:\t' + +@register +class SparseSGD(SGD): + """The SGD optimizer with weight pruning. + + This class implements the optimizer described in the paper *DSD: Dense-Sparse-Dense Training for + Deep Neural Networks*, available at https://arxiv.org/pdf/1607.04381.pdf + + The optimizer updates the weights the same way as done in SGD, but does the following + preprocessing:: + + if threshold given, all weights below the threshold in absolute value are pruned, + mask = abs(weight) >= threshold + if sparsity level given, the smallest (sparsity)% weights in absolute value are pruned + (or the largest (100-sparsity)% weights in absolute value are used) + mask = topk(abs(weight), ret_typ='mask', k=weight.size*(100-sparsity)/100) + + => mask[i,j] = {0 if weight[i,j] is pruned, 1 otherwise} (for a matrix representation) + + weight = weight * mask + grad = grad * mask + state = state * mask + + This optimizer accepts the following parameters in addition to those accepted + by :class:`.SGD`. + + Parameters + ---------- + pruning_switch_epoch : list of ints, optional + The epochs at which there is a change in sparsity level (should be in ascending order). + + weight_sparsity : list of floats, optional + The sparsity on the weights required on each iteration of sparse training. + + bias_sparsity : list of floats, optional + The sparsity on the biases required on each iteration of sparse training. + + weight_threshold : list of floats, optional + The absolute value threshold on the weights required on each iteration of sparse training. + + bias_threshold : list of floats, optional + The absolute value threshold on the biases required on each iteration of sparse training. + + batches_per_epoch : int, optional + The number of batches in each epoch. + (The ceiling integer value of number_of_examples / batch_size) + """ + def __init__(self, pruning_switch_epoch, batches_per_epoch, + weight_sparsity=None, bias_sparsity=None, + weight_threshold=None, bias_threshold=None, **kwargs): + super(SparseSGD, self).__init__(**kwargs) + + self.masks = [] + self.masks_updated = False + self.epoch = 0 + self.pruning_switch_epoch = pruning_switch_epoch + self.batches_per_epoch = batches_per_epoch + + # get weight and bias sparsity percentages + self.weight_sparsity = weight_sparsity + self.bias_sparsity = bias_sparsity + if weight_sparsity is not None: + assert len(weight_sparsity) == len(bias_sparsity), \ + 'weight_sparsity and bias_sparsity should have same length' + assert len(weight_sparsity) == len(pruning_switch_epoch), \ + 'pruning_switch_epoch and weight_sparsity should have same length' + + # get weight and bias sparsity thresholds + self.weight_threshold = weight_threshold + self.bias_threshold = bias_threshold + if weight_threshold is not None: + assert len(weight_threshold) == len(bias_threshold), \ + 'weight_threshold and bias_threshold should have same length' + assert len(weight_threshold) == len(pruning_switch_epoch), \ + 'pruning_switch_epoch and weight_sparsity_threshold should have same length' + + # either percentages or thresholds must be given + assert weight_sparsity is not None or weight_threshold is not None,\ + 'weight_sparsity or weight_sparsity_threshold should be given' + + def update_masks(self, index, weight): + """Updates the masks for sparse training. + + Parameters + ---------- + index : int + The index for weight. + weight : NDArray + The weight matrix. + + Returns + ------- + boolean + If the masks were changed + """ + # determine number of updates without actually updating the count + if index not in self._index_update_count: + num_update = self.begin_num_update + else: + num_update = self._index_update_count[index] + num_update += 1 + num_update = max(num_update, self.num_update) + + # calculate epoch + epoch = int((num_update - 1) / self.batches_per_epoch) + 1 + + # determine if masks need to be updated, and get corresponding parameters + if index == 0: + self.masks_updated = True + if self.epoch != epoch: + self.epoch = epoch + if epoch == 1: + self.masks_updated = False + if self.weight_sparsity is not None: + logging.info(log + 'bias-sparsity={}, weight-sparsity={}'.format(self.bias_sparsity[0], self.weight_sparsity[0])) + else: + logging.info(log + 'bias-threshold={}, weight-threshold={}'.format(self.bias_threshold[0], self.weight_threshold[0])) + if self.pruning_switch_epoch[0] + 1 == epoch: + self.masks_updated = False + self.pruning_switch_epoch.pop(0) + if self.weight_sparsity is not None: + self.weight_sparsity.pop(0) + self.bias_sparsity.pop(0) + logging.info(log + 'bias-sparsity={}, weight-sparsity={}'.format(self.bias_sparsity[0], self.weight_sparsity[0])) + else: + self.weight_threshold.pop(0) + self.bias_threshold.pop(0) + logging.info(log + 'bias-threshold={}, weight-threshold={}'.format(self.bias_threshold[0], self.weight_threshold[0])) + + # update masks if needed + if not self.masks_updated: + # initialize masks + if epoch == 1: + self.masks.append(None) + # if percentages are given + if self.weight_sparsity is not None: + if len(weight.shape) == 1: + sparsity = self.bias_sparsity[0] + else: + sparsity = self.weight_sparsity[0] + number_unpruned = int((100.0 - sparsity) * weight.size / 100.0) + self.masks[index] = topk(NDabs(weight), axis=None, ret_typ='mask', + k=number_unpruned) + # if thresholds are given + else: + if len(weight.shape) == 1: + threshold = self.bias_threshold[0] + else: + threshold = self.weight_threshold[0] + self.masks[index] = NDabs(weight) >= threshold + + return not self.masks_updated + + def update(self, index, weight, grad, state): + assert(isinstance(weight, NDArray)) + assert(isinstance(grad, NDArray)) + + # preprocessing for pruning + if self.update_masks(index, weight): + weight[:] = weight * self.masks[index] + grad[:] = grad * self.masks[index] + if state is not None: + state[:] = state * self.masks[index] + + super(SparseSGD, self).update(index, weight, grad, state) diff --git a/example/fcn-xs/data.py b/example/fcn-xs/data.py index 9de0d8d31c69..685b6f7da8f4 100644 --- a/example/fcn-xs/data.py +++ b/example/fcn-xs/data.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # pylint: skip-file """ file iterator for pasval voc 2012""" import mxnet as mx diff --git a/example/fcn-xs/fcn_xs.py b/example/fcn-xs/fcn_xs.py index 85961d92c694..53244a1759c3 100644 --- a/example/fcn-xs/fcn_xs.py +++ b/example/fcn-xs/fcn_xs.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # pylint: skip-file import sys, os import argparse diff --git a/example/fcn-xs/image_segmentaion.py b/example/fcn-xs/image_segmentaion.py index 6d619c198c0b..ddd850fe4e9d 100644 --- a/example/fcn-xs/image_segmentaion.py +++ b/example/fcn-xs/image_segmentaion.py @@ -1,58 +1,75 @@ -# pylint: skip-file -import numpy as np -import mxnet as mx -from PIL import Image - -def getpallete(num_cls): - # this function is to get the colormap for visualizing the segmentation mask - n = num_cls - pallete = [0]*(n*3) - for j in xrange(0,n): - lab = j - pallete[j*3+0] = 0 - pallete[j*3+1] = 0 - pallete[j*3+2] = 0 - i = 0 - while (lab > 0): - pallete[j*3+0] |= (((lab >> 0) & 1) << (7-i)) - pallete[j*3+1] |= (((lab >> 1) & 1) << (7-i)) - pallete[j*3+2] |= (((lab >> 2) & 1) << (7-i)) - i = i + 1 - lab >>= 3 - return pallete - -pallete = getpallete(256) -img = "./person_bicycle.jpg" -seg = img.replace("jpg", "png") -model_previx = "FCN8s_VGG16" -epoch = 19 -ctx = mx.gpu(0) - -def get_data(img_path): - """get the (1, 3, h, w) np.array data for the img_path""" - mean = np.array([123.68, 116.779, 103.939]) # (R,G,B) - img = Image.open(img_path) - img = np.array(img, dtype=np.float32) - reshaped_mean = mean.reshape(1, 1, 3) - img = img - reshaped_mean - img = np.swapaxes(img, 0, 2) - img = np.swapaxes(img, 1, 2) - img = np.expand_dims(img, axis=0) - return img - -def main(): - fcnxs, fcnxs_args, fcnxs_auxs = mx.model.load_checkpoint(model_previx, epoch) - fcnxs_args["data"] = mx.nd.array(get_data(img), ctx) - data_shape = fcnxs_args["data"].shape - label_shape = (1, data_shape[2]*data_shape[3]) - fcnxs_args["softmax_label"] = mx.nd.empty(label_shape, ctx) - exector = fcnxs.bind(ctx, fcnxs_args ,args_grad=None, grad_req="null", aux_states=fcnxs_args) - exector.forward(is_train=False) - output = exector.outputs[0] - out_img = np.uint8(np.squeeze(output.asnumpy().argmax(axis=1))) - out_img = Image.fromarray(out_img) - out_img.putpalette(pallete) - out_img.save(seg) - -if __name__ == "__main__": - main() +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +# pylint: skip-file +import numpy as np +import mxnet as mx +from PIL import Image + +def getpallete(num_cls): + # this function is to get the colormap for visualizing the segmentation mask + n = num_cls + pallete = [0]*(n*3) + for j in xrange(0,n): + lab = j + pallete[j*3+0] = 0 + pallete[j*3+1] = 0 + pallete[j*3+2] = 0 + i = 0 + while (lab > 0): + pallete[j*3+0] |= (((lab >> 0) & 1) << (7-i)) + pallete[j*3+1] |= (((lab >> 1) & 1) << (7-i)) + pallete[j*3+2] |= (((lab >> 2) & 1) << (7-i)) + i = i + 1 + lab >>= 3 + return pallete + +pallete = getpallete(256) +img = "./person_bicycle.jpg" +seg = img.replace("jpg", "png") +model_previx = "FCN8s_VGG16" +epoch = 19 +ctx = mx.gpu(0) + +def get_data(img_path): + """get the (1, 3, h, w) np.array data for the img_path""" + mean = np.array([123.68, 116.779, 103.939]) # (R,G,B) + img = Image.open(img_path) + img = np.array(img, dtype=np.float32) + reshaped_mean = mean.reshape(1, 1, 3) + img = img - reshaped_mean + img = np.swapaxes(img, 0, 2) + img = np.swapaxes(img, 1, 2) + img = np.expand_dims(img, axis=0) + return img + +def main(): + fcnxs, fcnxs_args, fcnxs_auxs = mx.model.load_checkpoint(model_previx, epoch) + fcnxs_args["data"] = mx.nd.array(get_data(img), ctx) + data_shape = fcnxs_args["data"].shape + label_shape = (1, data_shape[2]*data_shape[3]) + fcnxs_args["softmax_label"] = mx.nd.empty(label_shape, ctx) + exector = fcnxs.bind(ctx, fcnxs_args ,args_grad=None, grad_req="null", aux_states=fcnxs_args) + exector.forward(is_train=False) + output = exector.outputs[0] + out_img = np.uint8(np.squeeze(output.asnumpy().argmax(axis=1))) + out_img = Image.fromarray(out_img) + out_img.putpalette(pallete) + out_img.save(seg) + +if __name__ == "__main__": + main() diff --git a/example/fcn-xs/init_fcnxs.py b/example/fcn-xs/init_fcnxs.py index c90a45bb4358..ede46b80d56c 100644 --- a/example/fcn-xs/init_fcnxs.py +++ b/example/fcn-xs/init_fcnxs.py @@ -1,89 +1,106 @@ -# pylint: skip-file -import mxnet as mx -import numpy as np -import sys -import logging - -logger = logging.getLogger() -logger.setLevel(logging.INFO) - -# make a bilinear interpolation kernel, return a numpy.ndarray -def upsample_filt(size): - factor = (size + 1) // 2 - if size % 2 == 1: - center = factor - 1.0 - else: - center = factor - 0.5 - og = np.ogrid[:size, :size] - return (1 - abs(og[0] - center) / factor) * \ - (1 - abs(og[1] - center) / factor) - -def init_from_vgg16(ctx, fcnxs_symbol, vgg16fc_args, vgg16fc_auxs): - fcnxs_args = vgg16fc_args.copy() - fcnxs_auxs = vgg16fc_auxs.copy() - for k,v in fcnxs_args.items(): - if(v.context != ctx): - fcnxs_args[k] = mx.nd.zeros(v.shape, ctx) - v.copyto(fcnxs_args[k]) - for k,v in fcnxs_auxs.items(): - if(v.context != ctx): - fcnxs_auxs[k] = mx.nd.zeros(v.shape, ctx) - v.copyto(fcnxs_auxs[k]) - data_shape=(1,3,500,500) - arg_names = fcnxs_symbol.list_arguments() - arg_shapes, _, _ = fcnxs_symbol.infer_shape(data=data_shape) - rest_params = dict([(x[0], mx.nd.zeros(x[1], ctx)) for x in zip(arg_names, arg_shapes) - if x[0] in ['score_weight', 'score_bias', 'score_pool4_weight', 'score_pool4_bias', \ - 'score_pool3_weight', 'score_pool3_bias']]) - fcnxs_args.update(rest_params) - deconv_params = dict([(x[0], x[1]) for x in zip(arg_names, arg_shapes) - if x[0] in ["bigscore_weight", 'score2_weight', 'score4_weight']]) - for k, v in deconv_params.items(): - filt = upsample_filt(v[3]) - initw = np.zeros(v) - initw[range(v[0]), range(v[1]), :, :] = filt # becareful here is the slice assing - fcnxs_args[k] = mx.nd.array(initw, ctx) - return fcnxs_args, fcnxs_auxs - -def init_from_fcnxs(ctx, fcnxs_symbol, fcnxs_args_from, fcnxs_auxs_from): - """ use zero initialization for better convergence, because it tends to oputut 0, - and the label 0 stands for background, which may occupy most size of one image. - """ - fcnxs_args = fcnxs_args_from.copy() - fcnxs_auxs = fcnxs_auxs_from.copy() - for k,v in fcnxs_args.items(): - if(v.context != ctx): - fcnxs_args[k] = mx.nd.zeros(v.shape, ctx) - v.copyto(fcnxs_args[k]) - for k,v in fcnxs_auxs.items(): - if(v.context != ctx): - fcnxs_auxs[k] = mx.nd.zeros(v.shape, ctx) - v.copyto(fcnxs_auxs[k]) - data_shape=(1,3,500,500) - arg_names = fcnxs_symbol.list_arguments() - arg_shapes, _, _ = fcnxs_symbol.infer_shape(data=data_shape) - rest_params = {} - deconv_params = {} - # this is fcn8s init from fcn16s - if 'score_pool3_weight' in arg_names: - rest_params = dict([(x[0], mx.nd.zeros(x[1], ctx)) for x in zip(arg_names, arg_shapes) - if x[0] in ['score_pool3_bias', 'score_pool3_weight']]) - deconv_params = dict([(x[0], x[1]) for x in zip(arg_names, arg_shapes) if x[0] \ - in ["bigscore_weight", 'score4_weight']]) - # this is fcn16s init from fcn32s - elif 'score_pool4_weight' in arg_names: - rest_params = dict([(x[0], mx.nd.zeros(x[1], ctx)) for x in zip(arg_names, arg_shapes) - if x[0] in ['score_pool4_weight', 'score_pool4_bias']]) - deconv_params = dict([(x[0], x[1]) for x in zip(arg_names, arg_shapes) if x[0] \ - in ["bigscore_weight", 'score2_weight']]) - # this is fcn32s init - else: - logging.error("you are init the fcn32s model, so you should use init_from_vgg16()") - sys.exit() - fcnxs_args.update(rest_params) - for k, v in deconv_params.items(): - filt = upsample_filt(v[3]) - initw = np.zeros(v) - initw[range(v[0]), range(v[1]), :, :] = filt # becareful here is the slice assing - fcnxs_args[k] = mx.nd.array(initw, ctx) - return fcnxs_args, fcnxs_auxs +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +# pylint: skip-file +import mxnet as mx +import numpy as np +import sys +import logging + +logger = logging.getLogger() +logger.setLevel(logging.INFO) + +# make a bilinear interpolation kernel, return a numpy.ndarray +def upsample_filt(size): + factor = (size + 1) // 2 + if size % 2 == 1: + center = factor - 1.0 + else: + center = factor - 0.5 + og = np.ogrid[:size, :size] + return (1 - abs(og[0] - center) / factor) * \ + (1 - abs(og[1] - center) / factor) + +def init_from_vgg16(ctx, fcnxs_symbol, vgg16fc_args, vgg16fc_auxs): + fcnxs_args = vgg16fc_args.copy() + fcnxs_auxs = vgg16fc_auxs.copy() + for k,v in fcnxs_args.items(): + if(v.context != ctx): + fcnxs_args[k] = mx.nd.zeros(v.shape, ctx) + v.copyto(fcnxs_args[k]) + for k,v in fcnxs_auxs.items(): + if(v.context != ctx): + fcnxs_auxs[k] = mx.nd.zeros(v.shape, ctx) + v.copyto(fcnxs_auxs[k]) + data_shape=(1,3,500,500) + arg_names = fcnxs_symbol.list_arguments() + arg_shapes, _, _ = fcnxs_symbol.infer_shape(data=data_shape) + rest_params = dict([(x[0], mx.nd.zeros(x[1], ctx)) for x in zip(arg_names, arg_shapes) + if x[0] in ['score_weight', 'score_bias', 'score_pool4_weight', 'score_pool4_bias', \ + 'score_pool3_weight', 'score_pool3_bias']]) + fcnxs_args.update(rest_params) + deconv_params = dict([(x[0], x[1]) for x in zip(arg_names, arg_shapes) + if x[0] in ["bigscore_weight", 'score2_weight', 'score4_weight']]) + for k, v in deconv_params.items(): + filt = upsample_filt(v[3]) + initw = np.zeros(v) + initw[range(v[0]), range(v[1]), :, :] = filt # becareful here is the slice assing + fcnxs_args[k] = mx.nd.array(initw, ctx) + return fcnxs_args, fcnxs_auxs + +def init_from_fcnxs(ctx, fcnxs_symbol, fcnxs_args_from, fcnxs_auxs_from): + """ use zero initialization for better convergence, because it tends to oputut 0, + and the label 0 stands for background, which may occupy most size of one image. + """ + fcnxs_args = fcnxs_args_from.copy() + fcnxs_auxs = fcnxs_auxs_from.copy() + for k,v in fcnxs_args.items(): + if(v.context != ctx): + fcnxs_args[k] = mx.nd.zeros(v.shape, ctx) + v.copyto(fcnxs_args[k]) + for k,v in fcnxs_auxs.items(): + if(v.context != ctx): + fcnxs_auxs[k] = mx.nd.zeros(v.shape, ctx) + v.copyto(fcnxs_auxs[k]) + data_shape=(1,3,500,500) + arg_names = fcnxs_symbol.list_arguments() + arg_shapes, _, _ = fcnxs_symbol.infer_shape(data=data_shape) + rest_params = {} + deconv_params = {} + # this is fcn8s init from fcn16s + if 'score_pool3_weight' in arg_names: + rest_params = dict([(x[0], mx.nd.zeros(x[1], ctx)) for x in zip(arg_names, arg_shapes) + if x[0] in ['score_pool3_bias', 'score_pool3_weight']]) + deconv_params = dict([(x[0], x[1]) for x in zip(arg_names, arg_shapes) if x[0] \ + in ["bigscore_weight", 'score4_weight']]) + # this is fcn16s init from fcn32s + elif 'score_pool4_weight' in arg_names: + rest_params = dict([(x[0], mx.nd.zeros(x[1], ctx)) for x in zip(arg_names, arg_shapes) + if x[0] in ['score_pool4_weight', 'score_pool4_bias']]) + deconv_params = dict([(x[0], x[1]) for x in zip(arg_names, arg_shapes) if x[0] \ + in ["bigscore_weight", 'score2_weight']]) + # this is fcn32s init + else: + logging.error("you are init the fcn32s model, so you should use init_from_vgg16()") + sys.exit() + fcnxs_args.update(rest_params) + for k, v in deconv_params.items(): + filt = upsample_filt(v[3]) + initw = np.zeros(v) + initw[range(v[0]), range(v[1]), :, :] = filt # becareful here is the slice assing + fcnxs_args[k] = mx.nd.array(initw, ctx) + return fcnxs_args, fcnxs_auxs diff --git a/example/fcn-xs/run_fcnxs.sh b/example/fcn-xs/run_fcnxs.sh index 926f3f840415..df9a880b8396 100755 --- a/example/fcn-xs/run_fcnxs.sh +++ b/example/fcn-xs/run_fcnxs.sh @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # train fcn-32s model python -u fcn_xs.py --model=fcn32s --prefix=VGG_FC_ILSVRC_16_layers \ --epoch=74 --init-type=vgg16 diff --git a/example/fcn-xs/solver.py b/example/fcn-xs/solver.py index dd78e73b9b84..cf7298b83c8c 100644 --- a/example/fcn-xs/solver.py +++ b/example/fcn-xs/solver.py @@ -1,126 +1,143 @@ -# pylint: skip-file -import numpy as np -import mxnet as mx -import time -import logging -from collections import namedtuple -from mxnet import optimizer as opt -from mxnet.optimizer import get_updater -from mxnet import metric - -# Parameter to pass to batch_end_callback -BatchEndParam = namedtuple('BatchEndParams', ['epoch', 'nbatch', 'eval_metric']) -class Solver(object): - def __init__(self, symbol, ctx=None, - begin_epoch=0, num_epoch=None, - arg_params=None, aux_params=None, - optimizer='sgd', **kwargs): - self.symbol = symbol - if ctx is None: - ctx = mx.cpu() - self.ctx = ctx - self.begin_epoch = begin_epoch - self.num_epoch = num_epoch - self.arg_params = arg_params - self.aux_params = aux_params - self.optimizer = optimizer - self.kwargs = kwargs.copy() - - def fit(self, train_data, eval_data=None, - eval_metric='acc', - grad_req='write', - epoch_end_callback=None, - batch_end_callback=None, - kvstore='local', - logger=None): - if logger is None: - logger = logging - logging.info('Start training with %s', str(self.ctx)) - arg_shapes, out_shapes, aux_shapes = self.symbol.infer_shape(data=train_data.provide_data[0][1]) - arg_names = self.symbol.list_arguments() - if grad_req != 'null': - self.grad_params = {} - for name, shape in zip(arg_names, arg_shapes): - if not (name.endswith('data') or name.endswith('label')): - self.grad_params[name] = mx.nd.zeros(shape, self.ctx) - else: - self.grad_params = None - aux_names = self.symbol.list_auxiliary_states() - self.aux_params = {k : nd.zeros(s) for k, s in zip(aux_names, aux_shapes)} - data_name = train_data.data_name - label_name = train_data.label_name - input_names = [data_name, label_name] - self.optimizer = opt.create(self.optimizer, rescale_grad=(1.0/train_data.get_batch_size()), **(self.kwargs)) - self.updater = get_updater(self.optimizer) - eval_metric = metric.create(eval_metric) - # begin training - for epoch in range(self.begin_epoch, self.num_epoch): - nbatch = 0 - train_data.reset() - eval_metric.reset() - for data in train_data: - nbatch += 1 - label_shape = data[label_name].shape - self.arg_params[data_name] = mx.nd.array(data[data_name], self.ctx) - self.arg_params[label_name] = mx.nd.array(data[label_name].reshape(label_shape[0], \ - label_shape[1]*label_shape[2]), self.ctx) - output_names = self.symbol.list_outputs() - self.exector = self.symbol.bind(self.ctx, self.arg_params, - args_grad=self.grad_params, - grad_req=grad_req, - aux_states=self.aux_params) - assert len(self.symbol.list_arguments()) == len(self.exector.grad_arrays) - update_dict = {name: nd for name, nd in zip(self.symbol.list_arguments(), \ - self.exector.grad_arrays) if nd is not None} - output_dict = {} - output_buff = {} - for key, arr in zip(self.symbol.list_outputs(), self.exector.outputs): - output_dict[key] = arr - output_buff[key] = mx.nd.empty(arr.shape, ctx=mx.cpu()) - self.exector.forward(is_train=True) - for key in output_dict: - output_dict[key].copyto(output_buff[key]) - self.exector.backward() - for key, arr in update_dict.items(): - if key != "bigscore_weight": - self.updater(key, arr, self.arg_params[key]) - pred_shape = self.exector.outputs[0].shape - label = mx.nd.array(data[label_name].reshape(label_shape[0], label_shape[1]*label_shape[2])) - pred = mx.nd.array(output_buff["softmax_output"].asnumpy().reshape(pred_shape[0], \ - pred_shape[1], pred_shape[2]*pred_shape[3])) - eval_metric.update([label], [pred]) - self.exector.outputs[0].wait_to_read() - batch_end_params = BatchEndParam(epoch=epoch, nbatch=nbatch, eval_metric=eval_metric) - batch_end_callback(batch_end_params) - if epoch_end_callback is not None: - epoch_end_callback(epoch, self.symbol, self.arg_params, self.aux_params) - name, value = eval_metric.get() - logger.info(" --->Epoch[%d] Train-%s=%f", epoch, name, value) - # evaluation - if eval_data: - logger.info(" in eval process...") - nbatch = 0 - eval_data.reset() - eval_metric.reset() - for data in eval_data: - nbatch += 1 - label_shape = data[label_name].shape - self.arg_params[data_name] = mx.nd.array(data[data_name], self.ctx) - self.arg_params[label_name] = mx.nd.array(data[label_name].reshape(label_shape[0], \ - label_shape[1]*label_shape[2]), self.ctx) - exector = self.symbol.bind(self.ctx, self.arg_params, - args_grad=self.grad_params, - grad_req=grad_req, - aux_states=self.aux_params) - cpu_output_array = mx.nd.zeros(exector.outputs[0].shape) - exector.forward(is_train=False) - exector.outputs[0].copyto(cpu_output_array) - pred_shape = cpu_output_array.shape - label = mx.nd.array(data[label_name].reshape(label_shape[0], \ - label_shape[1]*label_shape[2])) - pred = mx.nd.array(cpu_output_array.asnumpy().reshape(pred_shape[0], \ - pred_shape[1], pred_shape[2]*pred_shape[3])) - eval_metric.update([label], [pred]) - exector.outputs[0].wait_to_read() - name, value = eval_metric.get() - logger.info('batch[%d] Validation-%s=%f', nbatch, name, value) +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +# pylint: skip-file +import numpy as np +import mxnet as mx +import time +import logging +from collections import namedtuple +from mxnet import optimizer as opt +from mxnet.optimizer import get_updater +from mxnet import metric + +# Parameter to pass to batch_end_callback +BatchEndParam = namedtuple('BatchEndParams', ['epoch', 'nbatch', 'eval_metric']) +class Solver(object): + def __init__(self, symbol, ctx=None, + begin_epoch=0, num_epoch=None, + arg_params=None, aux_params=None, + optimizer='sgd', **kwargs): + self.symbol = symbol + if ctx is None: + ctx = mx.cpu() + self.ctx = ctx + self.begin_epoch = begin_epoch + self.num_epoch = num_epoch + self.arg_params = arg_params + self.aux_params = aux_params + self.optimizer = optimizer + self.kwargs = kwargs.copy() + + def fit(self, train_data, eval_data=None, + eval_metric='acc', + grad_req='write', + epoch_end_callback=None, + batch_end_callback=None, + kvstore='local', + logger=None): + if logger is None: + logger = logging + logging.info('Start training with %s', str(self.ctx)) + arg_shapes, out_shapes, aux_shapes = self.symbol.infer_shape(data=train_data.provide_data[0][1]) + arg_names = self.symbol.list_arguments() + if grad_req != 'null': + self.grad_params = {} + for name, shape in zip(arg_names, arg_shapes): + if not (name.endswith('data') or name.endswith('label')): + self.grad_params[name] = mx.nd.zeros(shape, self.ctx) + else: + self.grad_params = None + aux_names = self.symbol.list_auxiliary_states() + self.aux_params = {k : nd.zeros(s) for k, s in zip(aux_names, aux_shapes)} + data_name = train_data.data_name + label_name = train_data.label_name + input_names = [data_name, label_name] + self.optimizer = opt.create(self.optimizer, rescale_grad=(1.0/train_data.get_batch_size()), **(self.kwargs)) + self.updater = get_updater(self.optimizer) + eval_metric = metric.create(eval_metric) + # begin training + for epoch in range(self.begin_epoch, self.num_epoch): + nbatch = 0 + train_data.reset() + eval_metric.reset() + for data in train_data: + nbatch += 1 + label_shape = data[label_name].shape + self.arg_params[data_name] = mx.nd.array(data[data_name], self.ctx) + self.arg_params[label_name] = mx.nd.array(data[label_name].reshape(label_shape[0], \ + label_shape[1]*label_shape[2]), self.ctx) + output_names = self.symbol.list_outputs() + self.exector = self.symbol.bind(self.ctx, self.arg_params, + args_grad=self.grad_params, + grad_req=grad_req, + aux_states=self.aux_params) + assert len(self.symbol.list_arguments()) == len(self.exector.grad_arrays) + update_dict = {name: nd for name, nd in zip(self.symbol.list_arguments(), \ + self.exector.grad_arrays) if nd is not None} + output_dict = {} + output_buff = {} + for key, arr in zip(self.symbol.list_outputs(), self.exector.outputs): + output_dict[key] = arr + output_buff[key] = mx.nd.empty(arr.shape, ctx=mx.cpu()) + self.exector.forward(is_train=True) + for key in output_dict: + output_dict[key].copyto(output_buff[key]) + self.exector.backward() + for key, arr in update_dict.items(): + if key != "bigscore_weight": + self.updater(key, arr, self.arg_params[key]) + pred_shape = self.exector.outputs[0].shape + label = mx.nd.array(data[label_name].reshape(label_shape[0], label_shape[1]*label_shape[2])) + pred = mx.nd.array(output_buff["softmax_output"].asnumpy().reshape(pred_shape[0], \ + pred_shape[1], pred_shape[2]*pred_shape[3])) + eval_metric.update([label], [pred]) + self.exector.outputs[0].wait_to_read() + batch_end_params = BatchEndParam(epoch=epoch, nbatch=nbatch, eval_metric=eval_metric) + batch_end_callback(batch_end_params) + if epoch_end_callback is not None: + epoch_end_callback(epoch, self.symbol, self.arg_params, self.aux_params) + name, value = eval_metric.get() + logger.info(" --->Epoch[%d] Train-%s=%f", epoch, name, value) + # evaluation + if eval_data: + logger.info(" in eval process...") + nbatch = 0 + eval_data.reset() + eval_metric.reset() + for data in eval_data: + nbatch += 1 + label_shape = data[label_name].shape + self.arg_params[data_name] = mx.nd.array(data[data_name], self.ctx) + self.arg_params[label_name] = mx.nd.array(data[label_name].reshape(label_shape[0], \ + label_shape[1]*label_shape[2]), self.ctx) + exector = self.symbol.bind(self.ctx, self.arg_params, + args_grad=self.grad_params, + grad_req=grad_req, + aux_states=self.aux_params) + cpu_output_array = mx.nd.zeros(exector.outputs[0].shape) + exector.forward(is_train=False) + exector.outputs[0].copyto(cpu_output_array) + pred_shape = cpu_output_array.shape + label = mx.nd.array(data[label_name].reshape(label_shape[0], \ + label_shape[1]*label_shape[2])) + pred = mx.nd.array(cpu_output_array.asnumpy().reshape(pred_shape[0], \ + pred_shape[1], pred_shape[2]*pred_shape[3])) + eval_metric.update([label], [pred]) + exector.outputs[0].wait_to_read() + name, value = eval_metric.get() + logger.info('batch[%d] Validation-%s=%f', nbatch, name, value) diff --git a/example/fcn-xs/symbol_fcnxs.py b/example/fcn-xs/symbol_fcnxs.py index a9c4f3b712ec..56888fc94822 100644 --- a/example/fcn-xs/symbol_fcnxs.py +++ b/example/fcn-xs/symbol_fcnxs.py @@ -1,189 +1,206 @@ -# pylint: skip-file -import mxnet as mx - -def filter_map(kernel=1, stride=1, pad=0): - return (stride, (kernel-stride)/2-pad) - -def compose_fp(fp_first, fp_second): - return (fp_first[0]*fp_second[0], fp_first[0]*fp_second[1]+fp_first[1]) - -def compose_fp_list(fp_list): - fp_out = (1.0, 0.0) - for fp in fp_list: - fp_out = compose_fp(fp_out, fp) - return fp_out - -def inv_fp(fp_in): - return (1.0/fp_in[0], -1.0*fp_in[1]/fp_in[0]) - -def offset(): - conv1_1_fp = filter_map(kernel=3, pad=100) - conv1_2_fp = conv2_1_fp = conv2_2_fp = conv3_1_fp = conv3_2_fp = conv3_3_fp \ - = conv4_1_fp = conv4_2_fp = conv4_3_fp = conv5_1_fp = conv5_2_fp \ - = conv5_3_fp = filter_map(kernel=3, pad=1) - pool1_fp = pool2_fp = pool3_fp = pool4_fp = pool5_fp = filter_map(kernel=2, stride=2) - fc6_fp = filter_map(kernel=7) - fc7_fp = score_fp = score_pool4_fp = score_pool3_fp = filter_map() - # for fcn-32s - fcn32s_upscore_fp = inv_fp(filter_map(kernel=64, stride=32)) - fcn32s_upscore_list = [conv1_1_fp, conv1_2_fp, pool1_fp, conv2_1_fp, conv2_2_fp, - pool2_fp, conv3_1_fp, conv3_2_fp, conv3_3_fp, pool3_fp, - conv4_1_fp, conv4_2_fp, conv4_3_fp, pool4_fp, conv5_1_fp, - conv5_2_fp, conv5_3_fp, pool5_fp, fc6_fp, fc7_fp, score_fp, - fcn32s_upscore_fp] - crop = {} - crop["fcn32s_upscore"] = (-int(round(compose_fp_list(fcn32s_upscore_list)[1])), - -int(round(compose_fp_list(fcn32s_upscore_list)[1]))) - # for fcn-16s - score2_fp = inv_fp(filter_map(kernel=4, stride=2)) - fcn16s_upscore_fp = inv_fp(filter_map(kernel=32, stride=16)) - score_pool4c_fp_list = [inv_fp(score2_fp), inv_fp(score_fp), inv_fp(fc7_fp), inv_fp(fc6_fp), - inv_fp(pool5_fp), inv_fp(conv5_3_fp), inv_fp(conv5_2_fp), - inv_fp(conv5_1_fp), score_pool4_fp] - crop["score_pool4c"] = (-int(round(compose_fp_list(score_pool4c_fp_list)[1])), - -int(round(compose_fp_list(score_pool4c_fp_list)[1]))) - fcn16s_upscore_list = [conv1_1_fp, conv1_2_fp, pool1_fp, conv2_1_fp, conv2_2_fp, - pool2_fp, conv3_1_fp, conv3_2_fp, conv3_3_fp, pool3_fp, - conv4_1_fp, conv4_2_fp, conv4_3_fp, pool4_fp, score_pool4_fp, - inv_fp((1, -crop["score_pool4c"][0])), fcn16s_upscore_fp] - crop["fcn16s_upscore"] = (-int(round(compose_fp_list(fcn16s_upscore_list)[1])), - -int(round(compose_fp_list(fcn16s_upscore_list)[1]))) - # for fcn-8s - score4_fp = inv_fp(filter_map(kernel=4, stride=2)) - fcn8s_upscore_fp = inv_fp(filter_map(kernel=16, stride=8)) - score_pool3c_fp_list = [inv_fp(score4_fp), (1, -crop["score_pool4c"][0]), inv_fp(score_pool4_fp), - inv_fp(pool4_fp), inv_fp(conv4_3_fp), inv_fp(conv4_2_fp), - inv_fp(conv4_1_fp), score_pool3_fp, score_pool3_fp] - crop["score_pool3c"] = (-int(round(compose_fp_list(score_pool3c_fp_list)[1])), - -int(round(compose_fp_list(score_pool3c_fp_list)[1]))) - fcn8s_upscore_list = [conv1_1_fp, conv1_2_fp, pool1_fp, conv2_1_fp, conv2_2_fp, pool2_fp, - conv3_1_fp, conv3_2_fp, conv3_3_fp, pool3_fp, score_pool3_fp, - inv_fp((1, -crop["score_pool3c"][0])), fcn8s_upscore_fp] - crop["fcn8s_upscore"] = (-int(round(compose_fp_list(fcn8s_upscore_list)[1])), - -int(round(compose_fp_list(fcn8s_upscore_list)[1]))) - return crop - -def vgg16_pool3(input, workspace_default=1024): - # group 1 - conv1_1 = mx.symbol.Convolution(data=input, kernel=(3, 3), pad=(100, 100), num_filter=64, - workspace=workspace_default, name="conv1_1") - relu1_1 = mx.symbol.Activation(data=conv1_1, act_type="relu", name="relu1_1") - conv1_2 = mx.symbol.Convolution(data=relu1_1, kernel=(3, 3), pad=(1, 1), num_filter=64, - workspace=workspace_default, name="conv1_2") - relu1_2 = mx.symbol.Activation(data=conv1_2, act_type="relu", name="relu1_2") - pool1 = mx.symbol.Pooling(data=relu1_2, pool_type="max", kernel=(2, 2), stride=(2,2), name="pool1") - # group 2 - conv2_1 = mx.symbol.Convolution(data=pool1, kernel=(3, 3), pad=(1, 1), num_filter=128, - workspace=workspace_default, name="conv2_1") - relu2_1 = mx.symbol.Activation(data=conv2_1, act_type="relu", name="relu2_1") - conv2_2 = mx.symbol.Convolution(data=relu2_1, kernel=(3, 3), pad=(1, 1), num_filter=128, - workspace=workspace_default, name="conv2_2") - relu2_2 = mx.symbol.Activation(data=conv2_2, act_type="relu", name="relu2_2") - pool2 = mx.symbol.Pooling(data=relu2_2, pool_type="max", kernel=(2, 2), stride=(2,2), name="pool2") - # group 3 - conv3_1 = mx.symbol.Convolution(data=pool2, kernel=(3, 3), pad=(1, 1), num_filter=256, - workspace=workspace_default, name="conv3_1") - relu3_1 = mx.symbol.Activation(data=conv3_1, act_type="relu", name="relu3_1") - conv3_2 = mx.symbol.Convolution(data=relu3_1, kernel=(3, 3), pad=(1, 1), num_filter=256, - workspace=workspace_default, name="conv3_2") - relu3_2 = mx.symbol.Activation(data=conv3_2, act_type="relu", name="relu3_2") - conv3_3 = mx.symbol.Convolution(data=relu3_2, kernel=(3, 3), pad=(1, 1), num_filter=256, - workspace=workspace_default, name="conv3_3") - relu3_3 = mx.symbol.Activation(data=conv3_3, act_type="relu", name="relu3_3") - pool3 = mx.symbol.Pooling(data=relu3_3, pool_type="max", kernel=(2, 2), stride=(2,2), name="pool3") - return pool3 - -def vgg16_pool4(input, workspace_default=1024): - # group 4 - conv4_1 = mx.symbol.Convolution(data=input, kernel=(3, 3), pad=(1, 1), num_filter=512, - workspace=workspace_default, name="conv4_1") - relu4_1 = mx.symbol.Activation(data=conv4_1, act_type="relu", name="relu4_1") - conv4_2 = mx.symbol.Convolution(data=relu4_1, kernel=(3, 3), pad=(1, 1), num_filter=512, - workspace=workspace_default, name="conv4_2") - relu4_2 = mx.symbol.Activation(data=conv4_2, act_type="relu", name="relu4_2") - conv4_3 = mx.symbol.Convolution(data=relu4_2, kernel=(3, 3), pad=(1, 1), num_filter=512, - workspace=workspace_default, name="conv4_3") - relu4_3 = mx.symbol.Activation(data=conv4_3, act_type="relu", name="relu4_3") - pool4 = mx.symbol.Pooling(data=relu4_3, pool_type="max", kernel=(2, 2), stride=(2,2), name="pool4") - return pool4 - -def vgg16_score(input, numclass, workspace_default=1024): - # group 5 - conv5_1 = mx.symbol.Convolution(data=input, kernel=(3, 3), pad=(1, 1), num_filter=512, - workspace=workspace_default, name="conv5_1") - relu5_1 = mx.symbol.Activation(data=conv5_1, act_type="relu", name="relu5_1") - conv5_2 = mx.symbol.Convolution(data=relu5_1, kernel=(3, 3), pad=(1, 1), num_filter=512, - workspace=workspace_default, name="conv5_2") - relu5_2 = mx.symbol.Activation(data=conv5_2, act_type="relu", name="relu5_2") - conv5_3 = mx.symbol.Convolution(data=relu5_2, kernel=(3, 3), pad=(1, 1), num_filter=512, - workspace=workspace_default, name="conv5_3") - relu5_3 = mx.symbol.Activation(data=conv5_3, act_type="relu", name="relu5_3") - pool5 = mx.symbol.Pooling(data=relu5_3, pool_type="max", kernel=(2, 2), stride=(2,2), name="pool5") - # group 6 - fc6 = mx.symbol.Convolution(data=pool5, kernel=(7, 7), num_filter=4096, - workspace=workspace_default, name="fc6") - relu6 = mx.symbol.Activation(data=fc6, act_type="relu", name="relu6") - drop6 = mx.symbol.Dropout(data=relu6, p=0.5, name="drop6") - # group 7 - fc7 = mx.symbol.Convolution(data=drop6, kernel=(1, 1), num_filter=4096, - workspace=workspace_default, name="fc7") - relu7 = mx.symbol.Activation(data=fc7, act_type="relu", name="relu7") - drop7 = mx.symbol.Dropout(data=relu7, p=0.5, name="drop7") - # group 8 - score = mx.symbol.Convolution(data=drop7, kernel=(1, 1), num_filter=numclass, - workspace=workspace_default, name="score") - return score - -def fcnxs_score(input, crop, offset, kernel=(64,64), stride=(32,32), numclass=21, workspace_default=1024): - # score out - bigscore = mx.symbol.Deconvolution(data=input, kernel=kernel, stride=stride, adj=(stride[0]-1, stride[1]-1), - num_filter=numclass, workspace=workspace_default, name="bigscore") - upscore = mx.symbol.Crop(*[bigscore, crop], offset=offset, name="upscore") - # upscore = mx.symbol.Crop(*[input, crop], offset=offset, name="upscore") - softmax = mx.symbol.SoftmaxOutput(data=upscore, multi_output=True, use_ignore=True, ignore_label=255, name="softmax") - return softmax - -def get_fcn32s_symbol(numclass=21, workspace_default=1024): - data = mx.symbol.Variable(name="data") - pool3 = vgg16_pool3(data, workspace_default) - pool4 = vgg16_pool4(pool3, workspace_default) - score = vgg16_score(pool4, numclass, workspace_default) - softmax = fcnxs_score(score, data, offset()["fcn32s_upscore"], (64,64), (32,32), numclass, workspace_default) - return softmax - -def get_fcn16s_symbol(numclass=21, workspace_default=1024): - data = mx.symbol.Variable(name="data") - pool3 = vgg16_pool3(data, workspace_default) - pool4 = vgg16_pool4(pool3, workspace_default) - score = vgg16_score(pool4, numclass, workspace_default) - # score 2X - score2 = mx.symbol.Deconvolution(data=score, kernel=(4, 4), stride=(2, 2), num_filter=numclass, - adj=(1, 1), workspace=workspace_default, name="score2") # 2X - score_pool4 = mx.symbol.Convolution(data=pool4, kernel=(1, 1), num_filter=numclass, - workspace=workspace_default, name="score_pool4") - score_pool4c = mx.symbol.Crop(*[score_pool4, score2], offset=offset()["score_pool4c"], name="score_pool4c") - score_fused = score2 + score_pool4c - softmax = fcnxs_score(score_fused, data, offset()["fcn16s_upscore"], (32, 32), (16, 16), numclass, workspace_default) - return softmax - -def get_fcn8s_symbol(numclass=21, workspace_default=1024): - data = mx.symbol.Variable(name="data") - pool3 = vgg16_pool3(data, workspace_default) - pool4 = vgg16_pool4(pool3, workspace_default) - score = vgg16_score(pool4, numclass, workspace_default) - # score 2X - score2 = mx.symbol.Deconvolution(data=score, kernel=(4, 4), stride=(2, 2),num_filter=numclass, - adj=(1, 1), workspace=workspace_default, name="score2") # 2X - score_pool4 = mx.symbol.Convolution(data=pool4, kernel=(1, 1), num_filter=numclass, - workspace=workspace_default, name="score_pool4") - score_pool4c = mx.symbol.Crop(*[score_pool4, score2], offset=offset()["score_pool4c"], name="score_pool4c") - score_fused = score2 + score_pool4c - # score 4X - score4 = mx.symbol.Deconvolution(data=score_fused, kernel=(4, 4), stride=(2, 2),num_filter=numclass, - adj=(1, 1), workspace=workspace_default, name="score4") # 4X - score_pool3 = mx.symbol.Convolution(data=pool3, kernel=(1, 1), num_filter=numclass, - workspace=workspace_default, name="score_pool3") - score_pool3c = mx.symbol.Crop(*[score_pool3, score4], offset=offset()["score_pool3c"], name="score_pool3c") - score_final = score4 + score_pool3c - softmax = fcnxs_score(score_final, data, offset()["fcn8s_upscore"], (16, 16), (8, 8), numclass, workspace_default) - return softmax +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +# pylint: skip-file +import mxnet as mx + +def filter_map(kernel=1, stride=1, pad=0): + return (stride, (kernel-stride)/2-pad) + +def compose_fp(fp_first, fp_second): + return (fp_first[0]*fp_second[0], fp_first[0]*fp_second[1]+fp_first[1]) + +def compose_fp_list(fp_list): + fp_out = (1.0, 0.0) + for fp in fp_list: + fp_out = compose_fp(fp_out, fp) + return fp_out + +def inv_fp(fp_in): + return (1.0/fp_in[0], -1.0*fp_in[1]/fp_in[0]) + +def offset(): + conv1_1_fp = filter_map(kernel=3, pad=100) + conv1_2_fp = conv2_1_fp = conv2_2_fp = conv3_1_fp = conv3_2_fp = conv3_3_fp \ + = conv4_1_fp = conv4_2_fp = conv4_3_fp = conv5_1_fp = conv5_2_fp \ + = conv5_3_fp = filter_map(kernel=3, pad=1) + pool1_fp = pool2_fp = pool3_fp = pool4_fp = pool5_fp = filter_map(kernel=2, stride=2) + fc6_fp = filter_map(kernel=7) + fc7_fp = score_fp = score_pool4_fp = score_pool3_fp = filter_map() + # for fcn-32s + fcn32s_upscore_fp = inv_fp(filter_map(kernel=64, stride=32)) + fcn32s_upscore_list = [conv1_1_fp, conv1_2_fp, pool1_fp, conv2_1_fp, conv2_2_fp, + pool2_fp, conv3_1_fp, conv3_2_fp, conv3_3_fp, pool3_fp, + conv4_1_fp, conv4_2_fp, conv4_3_fp, pool4_fp, conv5_1_fp, + conv5_2_fp, conv5_3_fp, pool5_fp, fc6_fp, fc7_fp, score_fp, + fcn32s_upscore_fp] + crop = {} + crop["fcn32s_upscore"] = (-int(round(compose_fp_list(fcn32s_upscore_list)[1])), + -int(round(compose_fp_list(fcn32s_upscore_list)[1]))) + # for fcn-16s + score2_fp = inv_fp(filter_map(kernel=4, stride=2)) + fcn16s_upscore_fp = inv_fp(filter_map(kernel=32, stride=16)) + score_pool4c_fp_list = [inv_fp(score2_fp), inv_fp(score_fp), inv_fp(fc7_fp), inv_fp(fc6_fp), + inv_fp(pool5_fp), inv_fp(conv5_3_fp), inv_fp(conv5_2_fp), + inv_fp(conv5_1_fp), score_pool4_fp] + crop["score_pool4c"] = (-int(round(compose_fp_list(score_pool4c_fp_list)[1])), + -int(round(compose_fp_list(score_pool4c_fp_list)[1]))) + fcn16s_upscore_list = [conv1_1_fp, conv1_2_fp, pool1_fp, conv2_1_fp, conv2_2_fp, + pool2_fp, conv3_1_fp, conv3_2_fp, conv3_3_fp, pool3_fp, + conv4_1_fp, conv4_2_fp, conv4_3_fp, pool4_fp, score_pool4_fp, + inv_fp((1, -crop["score_pool4c"][0])), fcn16s_upscore_fp] + crop["fcn16s_upscore"] = (-int(round(compose_fp_list(fcn16s_upscore_list)[1])), + -int(round(compose_fp_list(fcn16s_upscore_list)[1]))) + # for fcn-8s + score4_fp = inv_fp(filter_map(kernel=4, stride=2)) + fcn8s_upscore_fp = inv_fp(filter_map(kernel=16, stride=8)) + score_pool3c_fp_list = [inv_fp(score4_fp), (1, -crop["score_pool4c"][0]), inv_fp(score_pool4_fp), + inv_fp(pool4_fp), inv_fp(conv4_3_fp), inv_fp(conv4_2_fp), + inv_fp(conv4_1_fp), score_pool3_fp, score_pool3_fp] + crop["score_pool3c"] = (-int(round(compose_fp_list(score_pool3c_fp_list)[1])), + -int(round(compose_fp_list(score_pool3c_fp_list)[1]))) + fcn8s_upscore_list = [conv1_1_fp, conv1_2_fp, pool1_fp, conv2_1_fp, conv2_2_fp, pool2_fp, + conv3_1_fp, conv3_2_fp, conv3_3_fp, pool3_fp, score_pool3_fp, + inv_fp((1, -crop["score_pool3c"][0])), fcn8s_upscore_fp] + crop["fcn8s_upscore"] = (-int(round(compose_fp_list(fcn8s_upscore_list)[1])), + -int(round(compose_fp_list(fcn8s_upscore_list)[1]))) + return crop + +def vgg16_pool3(input, workspace_default=1024): + # group 1 + conv1_1 = mx.symbol.Convolution(data=input, kernel=(3, 3), pad=(100, 100), num_filter=64, + workspace=workspace_default, name="conv1_1") + relu1_1 = mx.symbol.Activation(data=conv1_1, act_type="relu", name="relu1_1") + conv1_2 = mx.symbol.Convolution(data=relu1_1, kernel=(3, 3), pad=(1, 1), num_filter=64, + workspace=workspace_default, name="conv1_2") + relu1_2 = mx.symbol.Activation(data=conv1_2, act_type="relu", name="relu1_2") + pool1 = mx.symbol.Pooling(data=relu1_2, pool_type="max", kernel=(2, 2), stride=(2,2), name="pool1") + # group 2 + conv2_1 = mx.symbol.Convolution(data=pool1, kernel=(3, 3), pad=(1, 1), num_filter=128, + workspace=workspace_default, name="conv2_1") + relu2_1 = mx.symbol.Activation(data=conv2_1, act_type="relu", name="relu2_1") + conv2_2 = mx.symbol.Convolution(data=relu2_1, kernel=(3, 3), pad=(1, 1), num_filter=128, + workspace=workspace_default, name="conv2_2") + relu2_2 = mx.symbol.Activation(data=conv2_2, act_type="relu", name="relu2_2") + pool2 = mx.symbol.Pooling(data=relu2_2, pool_type="max", kernel=(2, 2), stride=(2,2), name="pool2") + # group 3 + conv3_1 = mx.symbol.Convolution(data=pool2, kernel=(3, 3), pad=(1, 1), num_filter=256, + workspace=workspace_default, name="conv3_1") + relu3_1 = mx.symbol.Activation(data=conv3_1, act_type="relu", name="relu3_1") + conv3_2 = mx.symbol.Convolution(data=relu3_1, kernel=(3, 3), pad=(1, 1), num_filter=256, + workspace=workspace_default, name="conv3_2") + relu3_2 = mx.symbol.Activation(data=conv3_2, act_type="relu", name="relu3_2") + conv3_3 = mx.symbol.Convolution(data=relu3_2, kernel=(3, 3), pad=(1, 1), num_filter=256, + workspace=workspace_default, name="conv3_3") + relu3_3 = mx.symbol.Activation(data=conv3_3, act_type="relu", name="relu3_3") + pool3 = mx.symbol.Pooling(data=relu3_3, pool_type="max", kernel=(2, 2), stride=(2,2), name="pool3") + return pool3 + +def vgg16_pool4(input, workspace_default=1024): + # group 4 + conv4_1 = mx.symbol.Convolution(data=input, kernel=(3, 3), pad=(1, 1), num_filter=512, + workspace=workspace_default, name="conv4_1") + relu4_1 = mx.symbol.Activation(data=conv4_1, act_type="relu", name="relu4_1") + conv4_2 = mx.symbol.Convolution(data=relu4_1, kernel=(3, 3), pad=(1, 1), num_filter=512, + workspace=workspace_default, name="conv4_2") + relu4_2 = mx.symbol.Activation(data=conv4_2, act_type="relu", name="relu4_2") + conv4_3 = mx.symbol.Convolution(data=relu4_2, kernel=(3, 3), pad=(1, 1), num_filter=512, + workspace=workspace_default, name="conv4_3") + relu4_3 = mx.symbol.Activation(data=conv4_3, act_type="relu", name="relu4_3") + pool4 = mx.symbol.Pooling(data=relu4_3, pool_type="max", kernel=(2, 2), stride=(2,2), name="pool4") + return pool4 + +def vgg16_score(input, numclass, workspace_default=1024): + # group 5 + conv5_1 = mx.symbol.Convolution(data=input, kernel=(3, 3), pad=(1, 1), num_filter=512, + workspace=workspace_default, name="conv5_1") + relu5_1 = mx.symbol.Activation(data=conv5_1, act_type="relu", name="relu5_1") + conv5_2 = mx.symbol.Convolution(data=relu5_1, kernel=(3, 3), pad=(1, 1), num_filter=512, + workspace=workspace_default, name="conv5_2") + relu5_2 = mx.symbol.Activation(data=conv5_2, act_type="relu", name="relu5_2") + conv5_3 = mx.symbol.Convolution(data=relu5_2, kernel=(3, 3), pad=(1, 1), num_filter=512, + workspace=workspace_default, name="conv5_3") + relu5_3 = mx.symbol.Activation(data=conv5_3, act_type="relu", name="relu5_3") + pool5 = mx.symbol.Pooling(data=relu5_3, pool_type="max", kernel=(2, 2), stride=(2,2), name="pool5") + # group 6 + fc6 = mx.symbol.Convolution(data=pool5, kernel=(7, 7), num_filter=4096, + workspace=workspace_default, name="fc6") + relu6 = mx.symbol.Activation(data=fc6, act_type="relu", name="relu6") + drop6 = mx.symbol.Dropout(data=relu6, p=0.5, name="drop6") + # group 7 + fc7 = mx.symbol.Convolution(data=drop6, kernel=(1, 1), num_filter=4096, + workspace=workspace_default, name="fc7") + relu7 = mx.symbol.Activation(data=fc7, act_type="relu", name="relu7") + drop7 = mx.symbol.Dropout(data=relu7, p=0.5, name="drop7") + # group 8 + score = mx.symbol.Convolution(data=drop7, kernel=(1, 1), num_filter=numclass, + workspace=workspace_default, name="score") + return score + +def fcnxs_score(input, crop, offset, kernel=(64,64), stride=(32,32), numclass=21, workspace_default=1024): + # score out + bigscore = mx.symbol.Deconvolution(data=input, kernel=kernel, stride=stride, adj=(stride[0]-1, stride[1]-1), + num_filter=numclass, workspace=workspace_default, name="bigscore") + upscore = mx.symbol.Crop(*[bigscore, crop], offset=offset, name="upscore") + # upscore = mx.symbol.Crop(*[input, crop], offset=offset, name="upscore") + softmax = mx.symbol.SoftmaxOutput(data=upscore, multi_output=True, use_ignore=True, ignore_label=255, name="softmax") + return softmax + +def get_fcn32s_symbol(numclass=21, workspace_default=1024): + data = mx.symbol.Variable(name="data") + pool3 = vgg16_pool3(data, workspace_default) + pool4 = vgg16_pool4(pool3, workspace_default) + score = vgg16_score(pool4, numclass, workspace_default) + softmax = fcnxs_score(score, data, offset()["fcn32s_upscore"], (64,64), (32,32), numclass, workspace_default) + return softmax + +def get_fcn16s_symbol(numclass=21, workspace_default=1024): + data = mx.symbol.Variable(name="data") + pool3 = vgg16_pool3(data, workspace_default) + pool4 = vgg16_pool4(pool3, workspace_default) + score = vgg16_score(pool4, numclass, workspace_default) + # score 2X + score2 = mx.symbol.Deconvolution(data=score, kernel=(4, 4), stride=(2, 2), num_filter=numclass, + adj=(1, 1), workspace=workspace_default, name="score2") # 2X + score_pool4 = mx.symbol.Convolution(data=pool4, kernel=(1, 1), num_filter=numclass, + workspace=workspace_default, name="score_pool4") + score_pool4c = mx.symbol.Crop(*[score_pool4, score2], offset=offset()["score_pool4c"], name="score_pool4c") + score_fused = score2 + score_pool4c + softmax = fcnxs_score(score_fused, data, offset()["fcn16s_upscore"], (32, 32), (16, 16), numclass, workspace_default) + return softmax + +def get_fcn8s_symbol(numclass=21, workspace_default=1024): + data = mx.symbol.Variable(name="data") + pool3 = vgg16_pool3(data, workspace_default) + pool4 = vgg16_pool4(pool3, workspace_default) + score = vgg16_score(pool4, numclass, workspace_default) + # score 2X + score2 = mx.symbol.Deconvolution(data=score, kernel=(4, 4), stride=(2, 2),num_filter=numclass, + adj=(1, 1), workspace=workspace_default, name="score2") # 2X + score_pool4 = mx.symbol.Convolution(data=pool4, kernel=(1, 1), num_filter=numclass, + workspace=workspace_default, name="score_pool4") + score_pool4c = mx.symbol.Crop(*[score_pool4, score2], offset=offset()["score_pool4c"], name="score_pool4c") + score_fused = score2 + score_pool4c + # score 4X + score4 = mx.symbol.Deconvolution(data=score_fused, kernel=(4, 4), stride=(2, 2),num_filter=numclass, + adj=(1, 1), workspace=workspace_default, name="score4") # 4X + score_pool3 = mx.symbol.Convolution(data=pool3, kernel=(1, 1), num_filter=numclass, + workspace=workspace_default, name="score_pool3") + score_pool3c = mx.symbol.Crop(*[score_pool3, score4], offset=offset()["score_pool3c"], name="score_pool3c") + score_final = score4 + score_pool3c + softmax = fcnxs_score(score_final, data, offset()["fcn8s_upscore"], (16, 16), (8, 8), numclass, workspace_default) + return softmax diff --git a/example/gan/CGAN_mnist_R/CGAN_mnist_setup.R b/example/gan/CGAN_mnist_R/CGAN_mnist_setup.R new file mode 100644 index 000000000000..f3ebf0fe1718 --- /dev/null +++ b/example/gan/CGAN_mnist_R/CGAN_mnist_setup.R @@ -0,0 +1,104 @@ +require("imager") +require("dplyr") +require("readr") +require("mxnet") + +source("iterators.R") + +###################################################### +### Data import and preperation +### First download MNIST train data at Kaggle: +### https://www.kaggle.com/c/digit-recognizer/data +###################################################### +train <- read_csv('data/train.csv') +train<- data.matrix(train) + +train_data <- train[,-1] +train_data <- t(train_data/255*2-1) +train_label <- as.integer(train[,1]) + +dim(train_data) <- c(28, 28, 1, ncol(train_data)) + +################################################## +#### Model parameters +################################################## +random_dim<- 96 +gen_features<- 96 +dis_features<- 32 +image_depth = 1 +fix_gamma<- T +no_bias<- T +eps<- 1e-5 + 1e-12 +batch_size<- 64 + + +################################################## +#### Generator Symbol +################################################## +data = mx.symbol.Variable('data') + +gen_rand<- mx.symbol.normal(loc=0, scale=1, shape=c(1, 1, random_dim, batch_size), name="gen_rand") +gen_concat<- mx.symbol.Concat(data = list(data, gen_rand), num.args = 2, name="gen_concat") + +g1 = mx.symbol.Deconvolution(gen_concat, name='g1', kernel=c(4,4), num_filter=gen_features*4, no_bias=T) +gbn1 = mx.symbol.BatchNorm(g1, name='gbn1', fix_gamma=fix_gamma, eps=eps) +gact1 = mx.symbol.Activation(gbn1, name='gact1', act_type='relu') + +g2 = mx.symbol.Deconvolution(gact1, name='g2', kernel=c(3,3), stride=c(2,2), pad=c(1,1), num_filter=gen_features*2, no_bias=no_bias) +gbn2 = mx.symbol.BatchNorm(g2, name='gbn2', fix_gamma=fix_gamma, eps=eps) +gact2 = mx.symbol.Activation(gbn2, name='gact2', act_type='relu') + +g3 = mx.symbol.Deconvolution(gact2, name='g3', kernel=c(4,4), stride=c(2,2), pad=c(1,1), num_filter=gen_features, no_bias=no_bias) +gbn3 = mx.symbol.BatchNorm(g3, name='gbn3', fix_gamma=fix_gamma, eps=eps) +gact3 = mx.symbol.Activation(gbn3, name='gact3', act_type='relu') + +g4 = mx.symbol.Deconvolution(gact3, name='g4', kernel=c(4,4), stride=c(2,2), pad=c(1,1), num_filter=image_depth, no_bias=no_bias) +G_sym = mx.symbol.Activation(g4, name='G_sym', act_type='tanh') + + +################################################## +#### Discriminator Symbol +################################################## +data = mx.symbol.Variable('data') +dis_digit = mx.symbol.Variable('digit') +label = mx.symbol.Variable('label') + +dis_digit<- mx.symbol.Reshape(data=dis_digit, shape=c(1,1,10,batch_size), name="digit_reshape") +dis_digit<- mx.symbol.broadcast_to(data=dis_digit, shape=c(28,28,10, batch_size), name="digit_broadcast") + +data_concat <- mx.symbol.Concat(list(data, dis_digit), num.args = 2, dim = 1, name='dflat_concat') + +d1 = mx.symbol.Convolution(data=data_concat, name='d1', kernel=c(3,3), stride=c(1,1), pad=c(0,0), num_filter=24, no_bias=no_bias) +dbn1 = mx.symbol.BatchNorm(d1, name='dbn1', fix_gamma=fix_gamma, eps=eps) +dact1 = mx.symbol.LeakyReLU(dbn1, name='dact1', act_type='elu', slope=0.25) +pool1 <- mx.symbol.Pooling(data=dact1, name="pool1", pool_type="max", kernel=c(2,2), stride=c(2,2), pad=c(0,0)) + +d2 = mx.symbol.Convolution(pool1, name='d2', kernel=c(3,3), stride=c(2,2), pad=c(0,0), num_filter=32, no_bias=no_bias) +dbn2 = mx.symbol.BatchNorm(d2, name='dbn2', fix_gamma=fix_gamma, eps=eps) +dact2 = mx.symbol.LeakyReLU(dbn2, name='dact2', act_type='elu', slope=0.25) + +d3 = mx.symbol.Convolution(dact2, name='d3', kernel=c(3,3), stride=c(1,1), pad=c(0,0), num_filter=64, no_bias=no_bias) +dbn3 = mx.symbol.BatchNorm(d3, name='dbn3', fix_gamma=fix_gamma, eps=eps) +dact3 = mx.symbol.LeakyReLU(dbn3, name='dact3', act_type='elu', slope=0.25) + +d4 = mx.symbol.Convolution(dact2, name='d3', kernel=c(4,4), stride=c(1,1), pad=c(0,0), num_filter=64, no_bias=no_bias) +dbn4 = mx.symbol.BatchNorm(d4, name='dbn4', fix_gamma=fix_gamma, eps=eps) +dact4 = mx.symbol.LeakyReLU(dbn4, name='dact4', act_type='elu', slope=0.25) + +# pool4 <- mx.symbol.Pooling(data=dact3, name="pool4", pool_type="avg", kernel=c(4,4), stride=c(1,1), pad=c(0,0)) + +dflat = mx.symbol.Flatten(dact4, name="dflat") + +dfc <- mx.symbol.FullyConnected(data=dflat, name="dfc", num_hidden=1, no_bias=F) +D_sym = mx.symbol.LogisticRegressionOutput(data=dfc, label=label, name='D_sym') + + +######################## +### Graph +######################## +input_shape_G<- c(1, 1, 10, batch_size) +input_shape_D<- c(28, 28, 1, batch_size) + +graph.viz(G_sym, type = "graph", direction = "LR") +graph.viz(D_sym, type = "graph", direction = "LR") + diff --git a/example/gan/CGAN_mnist_R/CGAN_train.R b/example/gan/CGAN_mnist_R/CGAN_train.R new file mode 100644 index 000000000000..6778d6b9c2b1 --- /dev/null +++ b/example/gan/CGAN_mnist_R/CGAN_train.R @@ -0,0 +1,182 @@ +##################################################### +### Training module for GAN +##################################################### + +devices<- mx.cpu() + +data_shape_G<- c(1, 1, 10, batch_size) +data_shape_D<- c(28, 28, 1, batch_size) +digit_shape_D<- c(10, batch_size) + +mx.metric.binacc <- mx.metric.custom("binacc", function(label, pred) { + res <- mean(label==round(pred)) + return(res) +}) + +mx.metric.logloss <- mx.metric.custom("logloss", function(label, pred) { + res <- mean(label*log(pred)+(1-label)*log(1-pred)) + return(res) +}) + +############################################## +### Define iterators +iter_G<- G_iterator(batch_size = batch_size) +iter_D<- D_iterator(batch_size = batch_size) + +exec_G<- mx.simple.bind(symbol = G_sym, data=data_shape_G, ctx = devices, grad.req = "write") +exec_D<- mx.simple.bind(symbol = D_sym, data=data_shape_D, digit=digit_shape_D, ctx = devices, grad.req = "write") + +### initialize parameters - To Do - personalise each layer +initializer<- mx.init.Xavier(rnd_type = "gaussian", factor_type = "avg", magnitude = 3) + +arg_param_ini_G<- mx.init.create(initializer = initializer, shape.array = mx.symbol.infer.shape(G_sym, data=data_shape_G)$arg.shapes, ctx = mx.cpu()) +aux_param_ini_G<- mx.init.create(initializer = initializer, shape.array = mx.symbol.infer.shape(G_sym, data=data_shape_G)$aux.shapes, ctx = mx.cpu()) + +arg_param_ini_D<- mx.init.create(initializer = initializer, shape.array = mx.symbol.infer.shape(D_sym, data=data_shape_D, digit=digit_shape_D)$arg.shapes, ctx = mx.cpu()) +aux_param_ini_D<- mx.init.create(initializer = initializer, shape.array = mx.symbol.infer.shape(D_sym, data=data_shape_D, digit=digit_shape_D)$aux.shapes, ctx = mx.cpu()) + +mx.exec.update.arg.arrays(exec_G, arg_param_ini_G, match.name=TRUE) +mx.exec.update.aux.arrays(exec_G, aux_param_ini_G, match.name=TRUE) + +mx.exec.update.arg.arrays(exec_D, arg_param_ini_D, match.name=TRUE) +mx.exec.update.aux.arrays(exec_D, aux_param_ini_D, match.name=TRUE) + +input_names_G <- mxnet:::mx.model.check.arguments(G_sym) +input_names_D <- mxnet:::mx.model.check.arguments(D_sym) + + +################################################### +#initialize optimizers +optimizer_G<-mx.opt.create(name = "adadelta", + rho=0.92, + epsilon = 1e-6, + wd=0, + rescale.grad=1/batch_size, + clip_gradient=1) + +updater_G<- mx.opt.get.updater(optimizer = optimizer_G, weights = exec_G$ref.arg.arrays) + +optimizer_D<-mx.opt.create(name = "adadelta", + rho=0.92, + epsilon = 1e-6, + wd=0, + rescale.grad=1/batch_size, + clip_gradient=1) +updater_D<- mx.opt.get.updater(optimizer = optimizer_D, weights = exec_D$ref.arg.arrays) + +#################################### +#initialize metric +metric_G<- mx.metric.binacc +metric_G_value<- metric_G$init() + +metric_D<- mx.metric.binacc +metric_D_value<- metric_D$init() + +iteration<- 1 +iter_G$reset() +iter_D$reset() + + +for (iteration in 1:2400) { + + iter_G$iter.next() + iter_D$iter.next() + + ### Random input to Generator to produce fake sample + G_values <- iter_G$value() + G_data <- G_values[input_names_G] + mx.exec.update.arg.arrays(exec_G, arg.arrays = G_data, match.name=TRUE) + mx.exec.forward(exec_G, is.train=T) + + ### Feed Discriminator with Concatenated Generator images and real images + ### Random input to Generator + D_data_fake <- exec_G$ref.outputs$G_sym_output + D_digit_fake <- G_values$data %>% mx.nd.Reshape(shape=c(-1, batch_size)) + + D_values <- iter_D$value() + D_data_real <- D_values$data + D_digit_real <- D_values$digit + + ### Train loop on fake + mx.exec.update.arg.arrays(exec_D, arg.arrays = list(data=D_data_fake, digit=D_digit_fake, label=mx.nd.array(rep(0, batch_size))), match.name=TRUE) + mx.exec.forward(exec_D, is.train=T) + mx.exec.backward(exec_D) + update_args_D<- updater_D(weight = exec_D$ref.arg.arrays, grad = exec_D$ref.grad.arrays) + mx.exec.update.arg.arrays(exec_D, update_args_D, skip.null=TRUE) + + metric_D_value <- metric_D$update(label = mx.nd.array(rep(0, batch_size)), exec_D$ref.outputs[["D_sym_output"]], metric_D_value) + + ### Train loop on real + mx.exec.update.arg.arrays(exec_D, arg.arrays = list(data=D_data_real, digit=D_digit_real, label=mx.nd.array(rep(1, batch_size))), match.name=TRUE) + mx.exec.forward(exec_D, is.train=T) + mx.exec.backward(exec_D) + update_args_D<- updater_D(weight = exec_D$ref.arg.arrays, grad = exec_D$ref.grad.arrays) + mx.exec.update.arg.arrays(exec_D, update_args_D, skip.null=TRUE) + + metric_D_value <- metric_D$update(mx.nd.array(rep(1, batch_size)), exec_D$ref.outputs[["D_sym_output"]], metric_D_value) + + ### Update Generator weights - use a seperate executor for writing data gradients + exec_D_back<- mxnet:::mx.symbol.bind(symbol = D_sym, arg.arrays = exec_D$arg.arrays, aux.arrays = exec_D$aux.arrays, grad.reqs = rep("write", length(exec_D$arg.arrays)), ctx = devices) + mx.exec.update.arg.arrays(exec_D_back, arg.arrays = list(data=D_data_fake, digit=D_digit_fake, label=mx.nd.array(rep(1, batch_size))), match.name=TRUE) + mx.exec.forward(exec_D_back, is.train=T) + mx.exec.backward(exec_D_back) + D_grads<- exec_D_back$ref.grad.arrays$data + mx.exec.backward(exec_G, out_grads=D_grads) + + update_args_G<- updater_G(weight = exec_G$ref.arg.arrays, grad = exec_G$ref.grad.arrays) + mx.exec.update.arg.arrays(exec_G, update_args_G, skip.null=TRUE) + + ### Update metrics + #metric_G_value <- metric_G$update(values[[label_name]], exec_G$ref.outputs[[output_name]], metric_G_value) + + if (iteration %% 25==0){ + D_metric_result <- metric_D$get(metric_D_value) + cat(paste0("[", iteration, "] ", D_metric_result$name, ": ", D_metric_result$value, "\n")) + } + + if (iteration==1 | iteration %% 100==0){ + + metric_D_value<- metric_D$init() + + par(mfrow=c(3,3), mar=c(0.1,0.1,0.1,0.1)) + for (i in 1:9) { + img <- as.array(exec_G$ref.outputs$G_sym_output)[,,,i] + plot(as.cimg(img), axes=F) + } + + print(as.numeric(as.array(G_values$digit))) + print(as.numeric(as.array(D_values$label))) + + } +} + +mx.symbol.save(D_sym, filename = "models/D_sym_model_v1.json") +mx.nd.save(exec_D$arg.arrays, filename = "models/D_aux_params_v1.params") +mx.nd.save(exec_D$aux.arrays, filename = "models/D_aux_params_v1.params") + +mx.symbol.save(G_sym, filename = "models/G_sym_model_v1.json") +mx.nd.save(exec_G$arg.arrays, filename = "models/G_arg_params_v1.params") +mx.nd.save(exec_G$aux.arrays, filename = "models/G_aux_params_v1.params") + + +### Inference +G_sym<- mx.symbol.load("models/G_sym_model_v1.json") +G_arg_params<- mx.nd.load("models/G_arg_params_v1.params") +G_aux_params<- mx.nd.load("models/G_aux_params_v1.params") + +digit<- mx.nd.array(rep(9, times=batch_size)) +data<- mx.nd.one.hot(indices = digit, depth = 10) +data<- mx.nd.reshape(data = data, shape = c(1,1,-1, batch_size)) + +exec_G<- mx.simple.bind(symbol = G_sym, data=data_shape_G, ctx = devices, grad.req = "null") +mx.exec.update.arg.arrays(exec_G, G_arg_params, match.name=TRUE) +mx.exec.update.arg.arrays(exec_G, list(data=data), match.name=TRUE) +mx.exec.update.aux.arrays(exec_G, G_aux_params, match.name=TRUE) + +mx.exec.forward(exec_G, is.train=F) + +par(mfrow=c(3,3), mar=c(0.1,0.1,0.1,0.1)) +for (i in 1:9) { + img <- as.array(exec_G$ref.outputs$G_sym_output)[,,,i] + plot(as.cimg(img), axes=F) +} diff --git a/example/gan/CGAN_mnist_R/iterators.R b/example/gan/CGAN_mnist_R/iterators.R new file mode 100644 index 000000000000..fa113c554b75 --- /dev/null +++ b/example/gan/CGAN_mnist_R/iterators.R @@ -0,0 +1,62 @@ + +G_iterator<- function(batch_size){ + + batch<- 0 + batch_per_epoch<-5 + + reset<- function(){ + batch<<- 0 + } + + iter.next<- function(){ + batch<<- batch+1 + if (batch>batch_per_epoch) { + return(FALSE) + } else { + return(TRUE) + } + } + + value<- function(){ + set.seed(123+batch) + digit<- mx.nd.array(sample(0:9, size = batch_size, replace = T)) + data<- mx.nd.one.hot(indices = digit, depth = 10) + data<- mx.nd.reshape(data = data, shape = c(1,1,-1, batch_size)) + return(list(data=data, digit=digit)) + } + + return(list(reset=reset, iter.next=iter.next, value=value, batch_size=batch_size, batch=batch)) +} + +D_iterator<- function(batch_size){ + + batch<- 0 + batch_per_epoch<-5 + + reset<- function(){ + batch<<- 0 + } + + iter.next<- function(){ + batch<<- batch+1 + if (batch>batch_per_epoch) { + return(FALSE) + } else { + return(TRUE) + } + } + + value<- function(){ + set.seed(123+batch) + idx<- sample(length(train_label), size = batch_size, replace = T) + data<- train_data[,,,idx, drop=F] + label<- mx.nd.array(train_label[idx]) + digit<- mx.nd.one.hot(indices = label, depth = 10) + + return(list(data=mx.nd.array(data), digit=digit, label=label)) + } + + return(list(reset=reset, iter.next=iter.next, value=value, batch_size=batch_size, batch=batch)) +} + + diff --git a/example/gan/dcgan.py b/example/gan/dcgan.py index 5faff9aa5d9f..981f4a4778e3 100644 --- a/example/gan/dcgan.py +++ b/example/gan/dcgan.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + from __future__ import print_function import mxnet as mx import numpy as np diff --git a/example/gluon/actor_critic.py b/example/gluon/actor_critic.py new file mode 100644 index 000000000000..6d4474b4f239 --- /dev/null +++ b/example/gluon/actor_critic.py @@ -0,0 +1,120 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +from __future__ import print_function + +import argparse +import gym +from itertools import count +import numpy as np + +import mxnet as mx +import mxnet.ndarray as F +from mxnet import gluon +from mxnet.gluon import nn +from mxnet import autograd + + +parser = argparse.ArgumentParser(description='MXNet actor-critic example') +parser.add_argument('--gamma', type=float, default=0.99, metavar='G', + help='discount factor (default: 0.99)') +parser.add_argument('--seed', type=int, default=543, metavar='N', + help='random seed (default: 1)') +parser.add_argument('--render', action='store_true', + help='render the environment') +parser.add_argument('--log-interval', type=int, default=10, metavar='N', + help='interval between training status logs (default: 10)') +args = parser.parse_args() + + +env = gym.make('CartPole-v0') +env.seed(args.seed) + + +class Policy(gluon.Block): + def __init__(self, **kwargs): + super(Policy, self).__init__(**kwargs) + with self.name_scope(): + self.dense = nn.Dense(16, in_units=4, activation='relu') + self.action_pred = nn.Dense(2, in_units=16) + self.value_pred = nn.Dense(1, in_units=16) + + def forward(self, x): + x = self.dense(x) + probs = self.action_pred(x) + values = self.value_pred(x) + return F.softmax(probs), values + +net = Policy() +net.initialize(mx.init.Uniform(0.02)) +trainer = gluon.Trainer(net.collect_params(), 'adam', {'learning_rate': 3e-2}) +loss = gluon.loss.L1Loss() + +running_reward = 10 +for epoch in count(1): + state = env.reset() + rewards = [] + values = [] + heads = [] + actions = [] + with autograd.record(): + # Sample a sequence of actions + for t in range(10000): + state = mx.nd.array(np.expand_dims(state, 0)) + prob, value = net(state) + action, logp = mx.nd.sample_multinomial(prob, get_prob=True) + state, reward, done, _ = env.step(action.asnumpy()[0]) + if args.render: + env.render() + rewards.append(reward) + values.append(value) + actions.append(action.asnumpy()[0]) + heads.append(logp) + if done: + break + + # reverse accumulate and normalize rewards + running_reward = running_reward * 0.99 + t * 0.01 + R = 0 + for i in range(len(rewards)-1, -1, -1): + R = rewards[i] + args.gamma * R + rewards[i] = R + rewards = np.array(rewards) + rewards -= rewards.mean() + rewards /= rewards.std() + np.finfo(rewards.dtype).eps + + # compute loss and gradient + L = sum([loss(value, mx.nd.array([r])) for r, value in zip(rewards, values)]) + final_nodes = [L] + for logp, r, v in zip(heads, rewards, values): + reward = r - v.asnumpy()[0,0] + # Here we differentiate the stochastic graph, corresponds to the + # first term of equation (6) in https://arxiv.org/pdf/1506.05254.pdf + # Optimizer minimizes the loss but we want to maximizing the reward, + # so use we use -reward here. + final_nodes.append(logp*(-reward)) + autograd.backward(final_nodes) + + trainer.step(t) + + if epoch % args.log_interval == 0: + print('Episode {}\tLast length: {:5d}\tAverage length: {:.2f}'.format( + epoch, t, running_reward)) + if running_reward > 200: + print("Solved! Running reward is now {} and " + "the last episode runs to {} time steps!".format(running_reward, t)) + break diff --git a/example/gluon/data.py b/example/gluon/data.py new file mode 100644 index 000000000000..c5ddd0af302b --- /dev/null +++ b/example/gluon/data.py @@ -0,0 +1,145 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +# pylint: skip-file +""" data iterator for mnist """ +import os +import random +import sys +# code to automatically download dataset +curr_path = os.path.dirname(os.path.abspath(os.path.expanduser(__file__))) +sys.path.append(os.path.join(curr_path, "../../tests/python/common")) +import get_data +import mxnet as mx + +def mnist_iterator(batch_size, input_shape): + """return train and val iterators for mnist""" + # download data + get_data.GetMNIST_ubyte() + flat = False if len(input_shape) == 3 else True + + train_dataiter = mx.io.MNISTIter( + image="data/train-images-idx3-ubyte", + label="data/train-labels-idx1-ubyte", + input_shape=input_shape, + batch_size=batch_size, + shuffle=True, + flat=flat) + + val_dataiter = mx.io.MNISTIter( + image="data/t10k-images-idx3-ubyte", + label="data/t10k-labels-idx1-ubyte", + input_shape=input_shape, + batch_size=batch_size, + flat=flat) + + return (train_dataiter, val_dataiter) + + +def cifar10_iterator(batch_size, data_shape, resize=-1): + get_data.GetCifar10() + + train = mx.io.ImageRecordIter( + path_imgrec = "data/cifar/train.rec", + # mean_img = "data/cifar/mean.bin", + resize = resize, + data_shape = data_shape, + batch_size = batch_size, + rand_crop = True, + rand_mirror = True) + + val = mx.io.ImageRecordIter( + path_imgrec = "data/cifar/test.rec", + # mean_img = "data/cifar/mean.bin", + resize = resize, + rand_crop = False, + rand_mirror = False, + data_shape = data_shape, + batch_size = batch_size) + + return train, val + +class DummyIter(mx.io.DataIter): + def __init__(self, batch_size, data_shape, batches = 5): + super(DummyIter, self).__init__(batch_size) + self.data_shape = (batch_size,) + data_shape + self.label_shape = (batch_size,) + self.provide_data = [('data', self.data_shape)] + self.provide_label = [('softmax_label', self.label_shape)] + self.batch = mx.io.DataBatch(data=[mx.nd.zeros(self.data_shape)], + label=[mx.nd.zeros(self.label_shape)]) + self._batches = 0 + self.batches = batches + + def next(self): + if self._batches < self.batches: + self._batches += 1 + return self.batch + else: + self._batches = 0 + raise StopIteration + +def dummy_iterator(batch_size, data_shape): + return DummyIter(batch_size, data_shape), DummyIter(batch_size, data_shape) + +class ImagePairIter(mx.io.DataIter): + def __init__(self, path, data_shape, label_shape, batch_size=64, flag=0, input_aug=None, target_aug=None): + super(ImagePairIter, self).__init__(batch_size) + self.data_shape = (batch_size,) + data_shape + self.label_shape = (batch_size,) + label_shape + self.input_aug = input_aug + self.target_aug = target_aug + self.provide_data = [('data', self.data_shape)] + self.provide_label = [('label', self.label_shape)] + is_image_file = lambda fn: any(fn.endswith(ext) for ext in [".png", ".jpg", ".jpeg"]) + self.filenames = [os.path.join(path, x) for x in os.listdir(path) if is_image_file(x)] + self.count = 0 + self.flag = flag + random.shuffle(self.filenames) + + def next(self): + from PIL import Image + if self.count + self.batch_size <= len(self.filenames): + data = [] + label = [] + for i in range(self.batch_size): + fn = self.filenames[self.count] + self.count += 1 + image = Image.open(fn).convert('YCbCr').split()[0] + if image.size[0] > image.size[1]: + image = image.transpose(Image.TRANSPOSE) + image = mx.nd.expand_dims(mx.nd.array(image), axis=2) + target = image.copy() + for aug in self.input_aug: + image = aug(image)[0] + for aug in self.target_aug: + target = aug(target)[0] + data.append(image) + label.append(target) + + data = mx.nd.concat(*[mx.nd.expand_dims(d, axis=0) for d in data], dim=0) + label = mx.nd.concat(*[mx.nd.expand_dims(d, axis=0) for d in label], dim=0) + data = [mx.nd.transpose(data, axes=(0, 3, 1, 2)).astype('float32')/255] + label = [mx.nd.transpose(label, axes=(0, 3, 1, 2)).astype('float32')/255] + + return mx.io.DataBatch(data=data, label=label) + else: + raise StopIteration + + def reset(self): + self.count = 0 + random.shuffle(self.filenames) diff --git a/example/gluon/dcgan.py b/example/gluon/dcgan.py new file mode 100644 index 000000000000..ed814df61e99 --- /dev/null +++ b/example/gluon/dcgan.py @@ -0,0 +1,236 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +import matplotlib as mpl +mpl.use('Agg') +from matplotlib import pyplot as plt + +import argparse +import mxnet as mx +from mxnet import gluon +from mxnet.gluon import nn +from mxnet import autograd +import numpy as np +import logging +from datetime import datetime +import os +import time + +def fill_buf(buf, i, img, shape): + n = buf.shape[0]//shape[1] + m = buf.shape[1]//shape[0] + + sx = (i%m)*shape[0] + sy = (i//m)*shape[1] + buf[sy:sy+shape[1], sx:sx+shape[0], :] = img + return None + +def visual(title, X, name): + assert len(X.shape) == 4 + X = X.transpose((0, 2, 3, 1)) + X = np.clip((X - np.min(X))*(255.0/(np.max(X) - np.min(X))), 0, 255).astype(np.uint8) + n = np.ceil(np.sqrt(X.shape[0])) + buff = np.zeros((int(n*X.shape[1]), int(n*X.shape[2]), int(X.shape[3])), dtype=np.uint8) + for i, img in enumerate(X): + fill_buf(buff, i, img, X.shape[1:3]) + buff = buff[:,:,::-1] + plt.imshow(buff) + plt.title(title) + plt.savefig(name) + + +parser = argparse.ArgumentParser() +parser.add_argument('--dataset', type=str, default='cifar10', help='dataset to use. options are cifar10 and imagenet.') +parser.add_argument('--batch-size', type=int, default=64, help='input batch size') +parser.add_argument('--nz', type=int, default=100, help='size of the latent z vector') +parser.add_argument('--ngf', type=int, default=64) +parser.add_argument('--ndf', type=int, default=64) +parser.add_argument('--nepoch', type=int, default=25, help='number of epochs to train for') +parser.add_argument('--lr', type=float, default=0.0002, help='learning rate, default=0.0002') +parser.add_argument('--beta1', type=float, default=0.5, help='beta1 for adam. default=0.5') +parser.add_argument('--cuda', action='store_true', help='enables cuda') +parser.add_argument('--ngpu', type=int, default=1, help='number of GPUs to use') +parser.add_argument('--netG', default='', help="path to netG (to continue training)") +parser.add_argument('--netD', default='', help="path to netD (to continue training)") +parser.add_argument('--outf', default='./results', help='folder to output images and model checkpoints') +parser.add_argument('--check-point', default=True, help="save results at each epoch or not") + +opt = parser.parse_args() +print(opt) + +logging.basicConfig(level=logging.DEBUG) +ngpu = int(opt.ngpu) +nz = int(opt.nz) +ngf = int(opt.ngf) +ndf = int(opt.ndf) +nc = 3 +if opt.cuda: + ctx = mx.gpu(0) +else: + ctx = mx.cpu() +check_point = bool(opt.check_point) +outf = opt.outf + +if not os.path.exists(outf): + os.makedirs(outf) + + +def transformer(data, label): + # resize to 64x64 + data = mx.image.imresize(data, 64, 64) + # transpose from (64, 64, 3) to (3, 64, 64) + data = mx.nd.transpose(data, (2,0,1)) + # normalize to [-1, 1] + data = data.astype(np.float32)/128 - 1 + # if image is greyscale, repeat 3 times to get RGB image. + if data.shape[0] == 1: + data = mx.nd.tile(data, (3, 1, 1)) + return data, label + +train_data = gluon.data.DataLoader( + gluon.data.vision.MNIST('./data', train=True, transform=transformer), + batch_size=opt.batch_size, shuffle=True, last_batch='discard') + +val_data = gluon.data.DataLoader( + gluon.data.vision.MNIST('./data', train=False, transform=transformer), + batch_size=opt.batch_size, shuffle=False) + + +# build the generator +netG = nn.Sequential() +with netG.name_scope(): + # input is Z, going into a convolution + netG.add(nn.Conv2DTranspose(ngf * 8, 4, 1, 0, use_bias=False)) + netG.add(nn.BatchNorm()) + netG.add(nn.Activation('relu')) + # state size. (ngf*8) x 4 x 4 + netG.add(nn.Conv2DTranspose(ngf * 4, 4, 2, 1, use_bias=False)) + netG.add(nn.BatchNorm()) + netG.add(nn.Activation('relu')) + # state size. (ngf*8) x 8 x 8 + netG.add(nn.Conv2DTranspose(ngf * 2, 4, 2, 1, use_bias=False)) + netG.add(nn.BatchNorm()) + netG.add(nn.Activation('relu')) + # state size. (ngf*8) x 16 x 16 + netG.add(nn.Conv2DTranspose(ngf, 4, 2, 1, use_bias=False)) + netG.add(nn.BatchNorm()) + netG.add(nn.Activation('relu')) + # state size. (ngf*8) x 32 x 32 + netG.add(nn.Conv2DTranspose(nc, 4, 2, 1, use_bias=False)) + netG.add(nn.Activation('tanh')) + # state size. (nc) x 64 x 64 + +# build the discriminator +netD = nn.Sequential() +with netD.name_scope(): + # input is (nc) x 64 x 64 + netD.add(nn.Conv2D(ndf, 4, 2, 1, use_bias=False)) + netD.add(nn.LeakyReLU(0.2)) + # state size. (ndf) x 32 x 32 + netD.add(nn.Conv2D(ndf * 2, 4, 2, 1, use_bias=False)) + netD.add(nn.BatchNorm()) + netD.add(nn.LeakyReLU(0.2)) + # state size. (ndf) x 16 x 16 + netD.add(nn.Conv2D(ndf * 4, 4, 2, 1, use_bias=False)) + netD.add(nn.BatchNorm()) + netD.add(nn.LeakyReLU(0.2)) + # state size. (ndf) x 8 x 8 + netD.add(nn.Conv2D(ndf * 8, 4, 2, 1, use_bias=False)) + netD.add(nn.BatchNorm()) + netD.add(nn.LeakyReLU(0.2)) + # state size. (ndf) x 4 x 4 + netD.add(nn.Conv2D(2, 4, 1, 0, use_bias=False)) + +# loss +loss = gluon.loss.SoftmaxCrossEntropyLoss() + +# initialize the generator and the discriminator +netG.initialize(mx.init.Normal(0.02), ctx=ctx) +netD.initialize(mx.init.Normal(0.02), ctx=ctx) + +# trainer for the generator and the discriminator +trainerG = gluon.Trainer(netG.collect_params(), 'adam', {'learning_rate': opt.lr, 'beta1': opt.beta1}) +trainerD = gluon.Trainer(netD.collect_params(), 'adam', {'learning_rate': opt.lr, 'beta1': opt.beta1}) + +# ============printing============== +real_label = mx.nd.ones((opt.batch_size,), ctx=ctx) +fake_label = mx.nd.zeros((opt.batch_size,), ctx=ctx) + +metric = mx.metric.Accuracy() +print('Training... ') +stamp = datetime.now().strftime('%Y_%m_%d-%H_%M') + +iter = 0 +for epoch in range(opt.nepoch): + tic = time.time() + btic = time.time() + for data, _ in train_data: + ############################ + # (1) Update D network: maximize log(D(x)) + log(1 - D(G(z))) + ########################### + # train with real_t + data = data.as_in_context(ctx) + noise = mx.nd.random_normal(0, 1, shape=(opt.batch_size, nz, 1, 1), ctx=ctx) + + with autograd.record(): + output = netD(data) + output = output.reshape((opt.batch_size, 2)) + errD_real = loss(output, real_label) + metric.update([real_label,], [output,]) + + fake = netG(noise) + output = netD(fake.detach()) + output = output.reshape((opt.batch_size, 2)) + errD_fake = loss(output, fake_label) + errD = errD_real + errD_fake + errD.backward() + metric.update([fake_label,], [output,]) + + trainerD.step(opt.batch_size) + + ############################ + # (2) Update G network: maximize log(D(G(z))) + ########################### + with autograd.record(): + output = netD(fake) + output = output.reshape((-1, 2)) + errG = loss(output, real_label) + errG.backward() + + trainerG.step(opt.batch_size) + + name, acc = metric.get() + # logging.info('speed: {} samples/s'.format(opt.batch_size / (time.time() - btic))) + logging.info('discriminator loss = %f, generator loss = %f, binary training acc = %f at iter %d epoch %d' %(mx.nd.mean(errD).asscalar(), mx.nd.mean(errG).asscalar(), acc, iter, epoch)) + if iter % 1 == 0: + visual('gout', fake.asnumpy(), name=os.path.join(outf,'fake_img_iter_%d.png' %iter)) + visual('data', data.asnumpy(), name=os.path.join(outf,'real_img_iter_%d.png' %iter)) + + iter = iter + 1 + btic = time.time() + + name, acc = metric.get() + metric.reset() + logging.info('\nbinary training acc at epoch %d: %s=%f' % (epoch, name, acc)) + logging.info('time: %f' % (time.time() - tic)) + + if check_point: + netG.save_params(os.path.join(outf,'generator_epoch_%d.params' %epoch)) + netD.save_params(os.path.join(outf,'discriminator_epoch_%d.params' % epoch)) + +netG.save_params(os.path.join(outf, 'generator.params')) +netD.save_params(os.path.join(outf, 'discriminator.params')) diff --git a/example/gluon/image_classification.py b/example/gluon/image_classification.py new file mode 100644 index 000000000000..3f84ff8602ed --- /dev/null +++ b/example/gluon/image_classification.py @@ -0,0 +1,169 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +from __future__ import division + +import argparse, time +import logging +logging.basicConfig(level=logging.INFO) + +import mxnet as mx +from mxnet import gluon +from mxnet.gluon import nn +from mxnet.gluon.model_zoo import vision as models +from mxnet import autograd as ag + +from data import * + +# CLI +parser = argparse.ArgumentParser(description='Train a model for image classification.') +parser.add_argument('--dataset', type=str, default='mnist', + help='dataset to use. options are mnist, cifar10, and dummy.') +parser.add_argument('--batch-size', type=int, default=32, + help='training batch size per device (CPU/GPU).') +parser.add_argument('--gpus', type=int, default=0, + help='number of gpus to use.') +parser.add_argument('--epochs', type=int, default=3, + help='number of training epochs.') +parser.add_argument('--lr', type=float, default=0.01, + help='learning rate. default is 0.01.') +parser.add_argument('--wd', type=float, default=0.0001, + help='weight decay rate. default is 0.0001.') +parser.add_argument('--seed', type=int, default=123, + help='random seed to use. Default=123.') +parser.add_argument('--benchmark', action='store_true', + help='whether to run benchmark.') +parser.add_argument('--mode', type=str, + help='mode in which to train the model. options are symbolic, imperative, hybrid') +parser.add_argument('--model', type=str, required=True, + help='type of model to use. see vision_model for options.') +parser.add_argument('--use_thumbnail', action='store_true', + help='use thumbnail or not in resnet. default is false.') +parser.add_argument('--batch-norm', action='store_true', + help='enable batch normalization or not in vgg. default is false.') +parser.add_argument('--use-pretrained', action='store_true', + help='enable using pretrained model from gluon.') +parser.add_argument('--log-interval', type=int, default=50, help='Number of batches to wait before logging.') +opt = parser.parse_args() + +print(opt) + +mx.random.seed(opt.seed) + +dataset_classes = {'mnist': 10, 'cifar10': 10, 'imagenet': 1000, 'dummy': 1000} + +batch_size, dataset, classes = opt.batch_size, opt.dataset, dataset_classes[opt.dataset] + +gpus = opt.gpus + +if opt.benchmark: + batch_size = 32 + dataset = 'dummy' + classes = 1000 + +batch_size *= max(1, gpus) +context = [mx.gpu(i) for i in range(gpus)] if gpus > 0 else [mx.cpu()] + +model_name = opt.model + +kwargs = {'ctx': context, 'pretrained': opt.use_pretrained, 'classes': classes} +if model_name.startswith('resnet'): + kwargs['thumbnail'] = opt.use_thumbnail +elif model_name.startswith('vgg'): + kwargs['batch_norm'] = opt.batch_norm + +net = models.get_model(opt.model, **kwargs) + +# get dataset iterators +if dataset == 'mnist': + train_data, val_data = mnist_iterator(batch_size, (1, 32, 32)) +elif dataset == 'cifar10': + train_data, val_data = cifar10_iterator(batch_size, (3, 32, 32)) +elif dataset == 'dummy': + if model_name == 'inceptionv3': + train_data, val_data = dummy_iterator(batch_size, (3, 299, 299)) + else: + train_data, val_data = dummy_iterator(batch_size, (3, 224, 224)) + +def test(ctx): + metric = mx.metric.Accuracy() + val_data.reset() + for batch in val_data: + data = gluon.utils.split_and_load(batch.data[0], ctx_list=ctx, batch_axis=0) + label = gluon.utils.split_and_load(batch.label[0], ctx_list=ctx, batch_axis=0) + outputs = [] + for x in data: + outputs.append(net(x)) + metric.update(label, outputs) + return metric.get() + + +def train(epochs, ctx): + if isinstance(ctx, mx.Context): + ctx = [ctx] + net.initialize(mx.init.Xavier(magnitude=2.24), ctx=ctx) + trainer = gluon.Trainer(net.collect_params(), 'sgd', {'learning_rate': opt.lr, 'wd': opt.wd}) + metric = mx.metric.Accuracy() + loss = gluon.loss.SoftmaxCrossEntropyLoss() + + for epoch in range(epochs): + tic = time.time() + train_data.reset() + metric.reset() + btic = time.time() + for i, batch in enumerate(train_data): + data = gluon.utils.split_and_load(batch.data[0], ctx_list=ctx, batch_axis=0) + label = gluon.utils.split_and_load(batch.label[0], ctx_list=ctx, batch_axis=0) + outputs = [] + Ls = [] + with ag.record(): + for x, y in zip(data, label): + z = net(x) + L = loss(z, y) + # store the loss and do backward after we have done forward + # on all GPUs for better speed on multiple GPUs. + Ls.append(L) + outputs.append(z) + for L in Ls: + L.backward() + trainer.step(batch.data[0].shape[0]) + metric.update(label, outputs) + if opt.log_interval and not (i+1)%opt.log_interval: + name, acc = metric.get() + logging.info('[Epoch %d Batch %d] speed: %f samples/s, training: %s=%f'%( + epoch, i, batch_size/(time.time()-btic), name, acc)) + btic = time.time() + + name, acc = metric.get() + logging.info('[Epoch %d] training: %s=%f'%(epoch, name, acc)) + logging.info('[Epoch %d] time cost: %f'%(epoch, time.time()-tic)) + name, val_acc = test(ctx) + logging.info('[Epoch %d] validation: %s=%f'%(epoch, name, val_acc)) + + net.save_params('image-classifier-%s-%d.params'%(opt.model, epochs)) + +if __name__ == '__main__': + if opt.mode == 'symbolic': + data = mx.sym.var('data') + out = net(data) + softmax = mx.sym.SoftmaxOutput(out, name='softmax') + mod = mx.mod.Module(softmax, context=[mx.gpu(i) for i in range(gpus)] if gpus > 0 else [mx.cpu()]) + mod.fit(train_data, num_epoch=opt.epochs, batch_end_callback = mx.callback.Speedometer(batch_size, 1)) + else: + if opt.mode == 'hybrid': + net.hybridize() + train(opt.epochs, context) diff --git a/example/gluon/lstm_crf.py b/example/gluon/lstm_crf.py new file mode 100644 index 000000000000..40c8c2be2784 --- /dev/null +++ b/example/gluon/lstm_crf.py @@ -0,0 +1,230 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +import mxnet as mx +from mxnet import autograd as ag, ndarray as nd, gluon +from mxnet.gluon import Block, nn, rnn +import mxnet.optimizer as optim +import sys + +# This example demonstrates how LSTM-CRF model can be implemented in Gluon to perform +# noun-phrase chunking as a sequence labeling task. + +mx.random.seed(1) + +# Helper functions to make the code more readable. +def to_scalar(x): + return int(x.asscalar()) + +def argmax(vec): + # return the argmax as a python int + idx = nd.argmax(vec, axis=1) + return to_scalar(idx) + +def prepare_sequence(seq, word2idx): + return nd.array([word2idx[w] for w in seq]) + +# Compute log sum exp is numerically more stable than multiplying probabilities +def log_sum_exp(vec): + max_score = nd.max(vec).asscalar() + return nd.log(nd.sum(nd.exp(vec - max_score))) + max_score + +# Model +class BiLSTM_CRF(Block): + def __init__(self, vocab_size, tag2idx, embedding_dim, hidden_dim): + super(BiLSTM_CRF, self).__init__() + with self.name_scope(): + self.embedding_dim = embedding_dim + self.hidden_dim = hidden_dim + self.vocab_size = vocab_size + self.tag2idx = tag2idx + self.tagset_size = len(tag2idx) + + self.word_embeds = nn.Embedding(vocab_size, embedding_dim) + self.lstm = rnn.LSTM(hidden_dim // 2, num_layers=1, bidirectional=True) + + # Maps the output of the LSTM into tag space. + self.hidden2tag = nn.Dense(self.tagset_size) + + # Matrix of transition parameters. Entry i,j is the score of + # transitioning *to* i *from* j. + self.transitions = nd.random_normal(shape=(self.tagset_size, self.tagset_size)) + + self.hidden = self.init_hidden() + + def init_hidden(self): + return [nd.random_normal(shape=(2, 1, self.hidden_dim // 2)), + nd.random_normal(shape=(2, 1, self.hidden_dim // 2))] + + def _forward_alg(self, feats): + # Do the forward algorithm to compute the partition function + alphas = [[-10000.] * self.tagset_size] + alphas[0][self.tag2idx[START_TAG]] = 0. + alphas = nd.array(alphas) + + # Iterate through the sentence + for feat in feats: + alphas_t = [] # The forward variables at this timestep + for next_tag in range(self.tagset_size): + # broadcast the emission score: it is the same regardless of + # the previous tag + emit_score = feat[next_tag].reshape((1, -1)) + # the ith entry of trans_score is the score of transitioning to + # next_tag from i + trans_score = self.transitions[next_tag].reshape((1, -1)) + # The ith entry of next_tag_var is the value for the + # edge (i -> next_tag) before we do log-sum-exp + next_tag_var = alphas + trans_score + emit_score + # The forward variable for this tag is log-sum-exp of all the + # scores. + alphas_t.append(log_sum_exp(next_tag_var)) + alphas = nd.concat(*alphas_t, dim=0).reshape((1, -1)) + terminal_var = alphas + self.transitions[self.tag2idx[STOP_TAG]] + alpha = log_sum_exp(terminal_var) + return alpha + + def _get_lstm_features(self, sentence): + self.hidden = self.init_hidden() + length = sentence.shape[0] + embeds = self.word_embeds(sentence).reshape((length, 1, -1)) + lstm_out, self.hidden = self.lstm(embeds, self.hidden) + lstm_out = lstm_out.reshape((length, self.hidden_dim)) + lstm_feats = self.hidden2tag(lstm_out) + return nd.split(lstm_feats, num_outputs=length, axis=0, squeeze_axis=True) + + def _score_sentence(self, feats, tags): + # Gives the score of a provided tag sequence + score = nd.array([0]) + tags = nd.concat(nd.array([self.tag2idx[START_TAG]]), *tags, dim=0) + for i, feat in enumerate(feats): + score = score + \ + self.transitions[to_scalar(tags[i+1]), to_scalar(tags[i])] + feat[to_scalar(tags[i+1])] + score = score + self.transitions[self.tag2idx[STOP_TAG], + to_scalar(tags[int(tags.shape[0]-1)])] + return score + + def _viterbi_decode(self, feats): + backpointers = [] + + # Initialize the viterbi variables in log space + vvars = nd.full((1, self.tagset_size), -10000.) + vvars[0, self.tag2idx[START_TAG]] = 0 + + for feat in feats: + bptrs_t = [] # holds the backpointers for this step + viterbivars_t = [] # holds the viterbi variables for this step + + for next_tag in range(self.tagset_size): + # next_tag_var[i] holds the viterbi variable for tag i at the + # previous step, plus the score of transitioning + # from tag i to next_tag. + # We don't include the emission scores here because the max + # does not depend on them (we add them in below) + next_tag_var = vvars + self.transitions[next_tag] + best_tag_id = argmax(next_tag_var) + bptrs_t.append(best_tag_id) + viterbivars_t.append(next_tag_var[0, best_tag_id]) + # Now add in the emission scores, and assign vvars to the set + # of viterbi variables we just computed + vvars = (nd.concat(*viterbivars_t, dim=0) + feat).reshape((1, -1)) + backpointers.append(bptrs_t) + + # Transition to STOP_TAG + terminal_var = vvars + self.transitions[self.tag2idx[STOP_TAG]] + best_tag_id = argmax(terminal_var) + path_score = terminal_var[0, best_tag_id] + + # Follow the back pointers to decode the best path. + best_path = [best_tag_id] + for bptrs_t in reversed(backpointers): + best_tag_id = bptrs_t[best_tag_id] + best_path.append(best_tag_id) + # Pop off the start tag (we dont want to return that to the caller) + start = best_path.pop() + assert start == self.tag2idx[START_TAG] # Sanity check + best_path.reverse() + return path_score, best_path + + def neg_log_likelihood(self, sentence, tags): + feats = self._get_lstm_features(sentence) + forward_score = self._forward_alg(feats) + gold_score = self._score_sentence(feats, tags) + return forward_score - gold_score + + def forward(self, sentence): # dont confuse this with _forward_alg above. + # Get the emission scores from the BiLSTM + lstm_feats = self._get_lstm_features(sentence) + + # Find the best path, given the features. + score, tag_seq = self._viterbi_decode(lstm_feats) + return score, tag_seq + +# Run training +START_TAG = "" +STOP_TAG = "" +EMBEDDING_DIM = 5 +HIDDEN_DIM = 4 + +# Make up some training data +training_data = [( + "the wall street journal reported today that apple corporation made money".split(), + "B I I I O O O B I O O".split() +), ( + "georgia tech is a university in georgia".split(), + "B I O O O O B".split() +)] + +word2idx = {} +for sentence, tags in training_data: + for word in sentence: + if word not in word2idx: + word2idx[word] = len(word2idx) + +tag2idx = {"B": 0, "I": 1, "O": 2, START_TAG: 3, STOP_TAG: 4} + +model = BiLSTM_CRF(len(word2idx), tag2idx, EMBEDDING_DIM, HIDDEN_DIM) +model.collect_params().initialize(mx.init.Xavier(magnitude=2.24), ctx=mx.cpu()) +optimizer = gluon.Trainer(model.collect_params(), 'sgd', {'learning_rate': 0.01, 'wd': 1e-4}) + +# Check predictions before training +precheck_sent = prepare_sequence(training_data[0][0], word2idx) +precheck_tags = nd.array([tag2idx[t] for t in training_data[0][1]]) +print(model(precheck_sent)) + +# Make sure prepare_sequence from earlier in the LSTM section is loaded +for epoch in range(300): # again, normally you would NOT do 300 epochs, it is toy data + for sentence, tags in training_data: + # Step 1. Get our inputs ready for the network, that is, + # turn them into Variables of word indices. + # Remember to use autograd to record the calculation. + with ag.record(): + sentence_in = prepare_sequence(sentence, word2idx) + targets = nd.array([tag2idx[t] for t in tags]) + + # Step 2. Run our forward pass. + neg_log_likelihood = model.neg_log_likelihood(sentence_in, targets) + + # Step 3. Compute the loss, gradients, and update the parameters by + # calling optimizer.step() + neg_log_likelihood.backward() + optimizer.step(1) + +# Check predictions after training +precheck_sent = prepare_sequence(training_data[0][0], word2idx) +print(model(precheck_sent)) + +# Acknowledgement: this example is adopted from pytorch nlp tutorials. diff --git a/example/gluon/mnist.py b/example/gluon/mnist.py new file mode 100644 index 000000000000..198d7ca5ab2a --- /dev/null +++ b/example/gluon/mnist.py @@ -0,0 +1,128 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +# pylint: skip-file +from __future__ import print_function + +import argparse +import logging +logging.basicConfig(level=logging.DEBUG) + +import numpy as np +import mxnet as mx +from mxnet import gluon, autograd +from mxnet.gluon import nn + +# Parse CLI arguments + +parser = argparse.ArgumentParser(description='MXNet Gluon MNIST Example') +parser.add_argument('--batch-size', type=int, default=100, + help='batch size for training and testing (default: 100)') +parser.add_argument('--epochs', type=int, default=10, + help='number of epochs to train (default: 10)') +parser.add_argument('--lr', type=float, default=0.1, + help='learning rate (default: 0.1)') +parser.add_argument('--momentum', type=float, default=0.9, + help='SGD momentum (default: 0.9)') +parser.add_argument('--cuda', action='store_true', default=False, + help='Train on GPU with CUDA') +parser.add_argument('--log-interval', type=int, default=100, metavar='N', + help='how many batches to wait before logging training status') +opt = parser.parse_args() + + +# define network + +net = nn.Sequential() +with net.name_scope(): + net.add(nn.Dense(128, activation='relu')) + net.add(nn.Dense(64, activation='relu')) + net.add(nn.Dense(10)) + +# data + +def transformer(data, label): + data = data.reshape((-1,)).astype(np.float32)/255 + return data, label + +train_data = gluon.data.DataLoader( + gluon.data.vision.MNIST('./data', train=True, transform=transformer), + batch_size=opt.batch_size, shuffle=True, last_batch='discard') + +val_data = gluon.data.DataLoader( + gluon.data.vision.MNIST('./data', train=False, transform=transformer), + batch_size=opt.batch_size, shuffle=False) + +# train + +def test(ctx): + metric = mx.metric.Accuracy() + for data, label in val_data: + data = data.as_in_context(ctx) + label = label.as_in_context(ctx) + output = net(data) + metric.update([label], [output]) + + return metric.get() + + +def train(epochs, ctx): + # Collect all parameters from net and its children, then initialize them. + net.initialize(mx.init.Xavier(magnitude=2.24), ctx=ctx) + # Trainer is for updating parameters with gradient. + trainer = gluon.Trainer(net.collect_params(), 'sgd', + {'learning_rate': opt.lr, 'momentum': opt.momentum}) + metric = mx.metric.Accuracy() + loss = gluon.loss.SoftmaxCrossEntropyLoss() + + for epoch in range(epochs): + # reset data iterator and metric at begining of epoch. + metric.reset() + for i, (data, label) in enumerate(train_data): + # Copy data to ctx if necessary + data = data.as_in_context(ctx) + label = label.as_in_context(ctx) + # Start recording computation graph with record() section. + # Recorded graphs can then be differentiated with backward. + with autograd.record(): + output = net(data) + L = loss(output, label) + L.backward() + # take a gradient step with batch_size equal to data.shape[0] + trainer.step(data.shape[0]) + # update metric at last. + metric.update([label], [output]) + + if i % opt.log_interval == 0 and i > 0: + name, acc = metric.get() + print('[Epoch %d Batch %d] Training: %s=%f'%(epoch, i, name, acc)) + + name, acc = metric.get() + print('[Epoch %d] Training: %s=%f'%(epoch, name, acc)) + + name, val_acc = test(ctx) + print('[Epoch %d] Validation: %s=%f'%(epoch, name, val_acc)) + + net.save_params('mnist.params') + + +if __name__ == '__main__': + if opt.cuda: + ctx = mx.gpu(0) + else: + ctx = mx.cpu() + train(opt.epochs, ctx) diff --git a/example/gluon/super_resolution.py b/example/gluon/super_resolution.py new file mode 100644 index 000000000000..acc5ffa01d4a --- /dev/null +++ b/example/gluon/super_resolution.py @@ -0,0 +1,194 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +from __future__ import print_function +import argparse, tarfile +import math +import os +import numpy as np + +import mxnet as mx +import mxnet.ndarray as F +from mxnet import gluon +from mxnet.gluon import nn +from mxnet import autograd as ag +from mxnet.test_utils import download +from mxnet.image import CenterCropAug, ResizeAug +from mxnet.io import PrefetchingIter + +from data import ImagePairIter + + +# CLI +parser = argparse.ArgumentParser(description='Super-resolution using an efficient sub-pixel convolution neural network.') +parser.add_argument('--upscale_factor', type=int, default=3, help="super resolution upscale factor. default is 3.") +parser.add_argument('--batch_size', type=int, default=4, help='training batch size, per device. default is 4.') +parser.add_argument('--test_batch_size', type=int, default=100, help='test batch size') +parser.add_argument('--epochs', type=int, default=30, help='number of training epochs') +parser.add_argument('--lr', type=float, default=0.001, help='learning Rate. default is 0.001.') +parser.add_argument('--use-gpu', action='store_true', help='whether to use GPU.') +parser.add_argument('--seed', type=int, default=123, help='random seed to use. Default=123') +parser.add_argument('--resolve_img', type=str, help='input image to use') +opt = parser.parse_args() + +print(opt) + +upscale_factor = opt.upscale_factor +batch_size, test_batch_size = opt.batch_size, opt.test_batch_size +color_flag = 0 + +# get data +dataset_path = "dataset" +dataset_url = "http://www2.eecs.berkeley.edu/Research/Projects/CS/vision/bsds/BSDS300-images.tgz" +def get_dataset(prefetch=False): + image_path = os.path.join(dataset_path, "BSDS300/images") + + if not os.path.exists(image_path): + os.makedirs(dataset_path) + file_name = download(dataset_url) + with tarfile.open(file_name) as tar: + for item in tar: + tar.extract(item, dataset_path) + os.remove(file_name) + + crop_size = 256 + crop_size -= crop_size % upscale_factor + input_crop_size = crop_size // upscale_factor + + input_transform = [CenterCropAug((crop_size, crop_size)), ResizeAug(input_crop_size)] + target_transform = [CenterCropAug((crop_size, crop_size))] + + iters = (ImagePairIter(os.path.join(image_path, "train"), + (input_crop_size, input_crop_size), + (crop_size, crop_size), + batch_size, color_flag, input_transform, target_transform), + ImagePairIter(os.path.join(image_path, "test"), + (input_crop_size, input_crop_size), + (crop_size, crop_size), + test_batch_size, color_flag, + input_transform, target_transform)) + + return [PrefetchingIter(i) for i in iters] if prefetch else iters + +train_data, val_data = get_dataset() + +mx.random.seed(opt.seed) +ctx = [mx.gpu(0)] if opt.use_gpu else [mx.cpu()] + + +# define model +def _rearrange(raw, F, upscale_factor): + # (N, C * r^2, H, W) -> (N, C, r^2, H, W) + splitted = F.reshape(raw, shape=(0, -4, -1, upscale_factor**2, 0, 0)) + # (N, C, r^2, H, W) -> (N, C, r, r, H, W) + unflatten = F.reshape(splitted, shape=(0, 0, -4, upscale_factor, upscale_factor, 0, 0)) + # (N, C, r, r, H, W) -> (N, C, H, r, W, r) + swapped = F.transpose(unflatten, axes=(0, 1, 4, 2, 5, 3)) + # (N, C, H, r, W, r) -> (N, C, H*r, W*r) + return F.reshape(swapped, shape=(0, 0, -3, -3)) + + +class SuperResolutionNet(gluon.Block): + def __init__(self, upscale_factor): + super(SuperResolutionNet, self).__init__() + with self.name_scope(): + self.conv1 = nn.Conv2D(64, (5, 5), strides=(1, 1), padding=(2, 2)) + self.conv2 = nn.Conv2D(64, (3, 3), strides=(1, 1), padding=(1, 1)) + self.conv3 = nn.Conv2D(32, (3, 3), strides=(1, 1), padding=(1, 1)) + self.conv4 = nn.Conv2D(upscale_factor ** 2, (3, 3), strides=(1, 1), padding=(1, 1)) + self.upscale_factor = upscale_factor + + def forward(self, x): + x = F.Activation(self.conv1(x), act_type='relu') + x = F.Activation(self.conv2(x), act_type='relu') + x = F.Activation(self.conv3(x), act_type='relu') + return _rearrange(self.conv4(x), F, self.upscale_factor) + +net = SuperResolutionNet(upscale_factor) +metric = mx.metric.MSE() + +def test(ctx): + val_data.reset() + avg_psnr = 0 + batches = 0 + for batch in val_data: + batches += 1 + data = gluon.utils.split_and_load(batch.data[0], ctx_list=ctx, batch_axis=0) + label = gluon.utils.split_and_load(batch.label[0], ctx_list=ctx, batch_axis=0) + outputs = [] + for x in data: + outputs.append(net(x)) + metric.update(label, outputs) + avg_psnr += 10 * math.log10(1/metric.get()[1]) + metric.reset() + avg_psnr /= batches + print('validation avg psnr: %f'%avg_psnr) + + +def train(epoch, ctx): + if isinstance(ctx, mx.Context): + ctx = [ctx] + net.initialize(mx.init.Orthogonal(), ctx=ctx) + # re-initialize conv4's weight to be Orthogonal + net.conv4.collect_params().initialize(mx.init.Orthogonal(scale=1), ctx=ctx) + trainer = gluon.Trainer(net.collect_params(), 'adam', {'learning_rate': opt.lr}) + loss = gluon.loss.L2Loss() + + for i in range(epoch): + train_data.reset() + for batch in train_data: + data = gluon.utils.split_and_load(batch.data[0], ctx_list=ctx, batch_axis=0) + label = gluon.utils.split_and_load(batch.label[0], ctx_list=ctx, batch_axis=0) + outputs = [] + with ag.record(): + for x, y in zip(data, label): + z = net(x) + L = loss(z, y) + L.backward() + outputs.append(z) + trainer.step(batch.data[0].shape[0]) + metric.update(label, outputs) + + name, acc = metric.get() + metric.reset() + print('training mse at epoch %d: %s=%f'%(i, name, acc)) + test(ctx) + + net.save_params('superres.params') + +def resolve(ctx): + from PIL import Image + if isinstance(ctx, list): + ctx = [ctx[0]] + net.load_params('superres.params', ctx=ctx) + img = Image.open(opt.resolve_img).convert('YCbCr') + y, cb, cr = img.split() + data = mx.nd.expand_dims(mx.nd.expand_dims(mx.nd.array(y), axis=0), axis=0) + out_img_y = mx.nd.reshape(net(data), shape=(-3, -2)).asnumpy() + out_img_y = out_img_y.clip(0, 255) + out_img_y = Image.fromarray(np.uint8(out_img_y[0]), mode='L') + + out_img_cb = cb.resize(out_img_y.size, Image.BICUBIC) + out_img_cr = cr.resize(out_img_y.size, Image.BICUBIC) + out_img = Image.merge('YCbCr', [out_img_y, out_img_cb, out_img_cr]).convert('RGB') + + out_img.save('resolved.png') + +if opt.resolve_img: + resolve(ctx) +else: + train(opt.epochs, ctx) diff --git a/example/gluon/tree_lstm/LICENSE b/example/gluon/tree_lstm/LICENSE new file mode 100644 index 000000000000..441cb8a1d7de --- /dev/null +++ b/example/gluon/tree_lstm/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 Riddhiman Dasgupta, Sheng Zha + +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/example/gluon/tree_lstm/dataset.py b/example/gluon/tree_lstm/dataset.py new file mode 100644 index 000000000000..4a836ddb0eaf --- /dev/null +++ b/example/gluon/tree_lstm/dataset.py @@ -0,0 +1,227 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +import os +import logging +logging.basicConfig(level=logging.INFO) +import numpy as np +import random +from tqdm import tqdm + +import mxnet as mx + +class Vocab(object): + # constants for special tokens: padding, unknown, and beginning/end of sentence. + PAD = 0 + UNK = 1 + BOS = 2 + EOS = 3 + PAD_WORD = '' + UNK_WORD = '' + BOS_WORD = '' + EOS_WORD = '' + def __init__(self, filepaths=[], embedpath=None, include_unseen=False, lower=False): + self.idx2tok = [] + self.tok2idx = {} + self.lower = lower + self.include_unseen = include_unseen + + self.add(Vocab.PAD_WORD) + self.add(Vocab.UNK_WORD) + self.add(Vocab.BOS_WORD) + self.add(Vocab.EOS_WORD) + + self.embed = None + + for filename in filepaths: + logging.info('loading %s'%filename) + with open(filename, 'r') as f: + self.load_file(f) + if embedpath is not None: + logging.info('loading %s'%embedpath) + with open(embedpath, 'r') as f: + self.load_embedding(f, reset=set([Vocab.PAD_WORD, Vocab.UNK_WORD, Vocab.BOS_WORD, + Vocab.EOS_WORD])) + + @property + def size(self): + return len(self.idx2tok) + + def get_index(self, key): + return self.tok2idx.get(key.lower() if self.lower else key, + Vocab.UNK) + + def get_token(self, idx): + if idx < self.size: + return self.idx2tok[idx] + else: + return Vocab.UNK_WORD + + def add(self, token): + token = token.lower() if self.lower else token + if token in self.tok2idx: + idx = self.tok2idx[token] + else: + idx = len(self.idx2tok) + self.idx2tok.append(token) + self.tok2idx[token] = idx + return idx + + def to_indices(self, tokens, add_bos=False, add_eos=False): + vec = [BOS] if add_bos else [] + vec += [self.get_index(token) for token in tokens] + if add_eos: + vec.append(EOS) + return vec + + def to_tokens(self, indices, stop): + tokens = [] + for i in indices: + tokens += [self.get_token(i)] + if i == stop: + break + return tokens + + def load_file(self, f): + for line in f: + tokens = line.rstrip('\n').split() + for token in tokens: + self.add(token) + + def load_embedding(self, f, reset=[]): + vectors = {} + for line in tqdm(f.readlines(), desc='Loading embeddings'): + tokens = line.rstrip('\n').split(' ') + word = tokens[0].lower() if self.lower else tokens[0] + if self.include_unseen: + self.add(word) + if word in self.tok2idx: + vectors[word] = [float(x) for x in tokens[1:]] + dim = len(vectors.values()[0]) + def to_vector(tok): + if tok in vectors and tok not in reset: + return vectors[tok] + elif tok not in vectors: + return np.random.normal(-0.05, 0.05, size=dim) + else: + return [0.0]*dim + self.embed = mx.nd.array([vectors[tok] if tok in vectors and tok not in reset + else [0.0]*dim for tok in self.idx2tok]) + +class Tree(object): + def __init__(self, idx): + self.children = [] + self.idx = idx + + def __repr__(self): + if self.children: + return '{0}: {1}'.format(self.idx, str(self.children)) + else: + return str(self.idx) + +# Dataset class for SICK dataset +class SICKDataIter(object): + def __init__(self, path, vocab, num_classes, shuffle=True): + super(SICKDataIter, self).__init__() + self.vocab = vocab + self.num_classes = num_classes + self.l_sentences = self.read_sentences(os.path.join(path,'a.toks')) + self.r_sentences = self.read_sentences(os.path.join(path,'b.toks')) + self.l_trees = self.read_trees(os.path.join(path,'a.parents')) + self.r_trees = self.read_trees(os.path.join(path,'b.parents')) + self.labels = self.read_labels(os.path.join(path,'sim.txt')) + self.size = len(self.labels) + self.shuffle = shuffle + self.reset() + + def reset(self): + if self.shuffle: + mask = range(self.size) + random.shuffle(mask) + self.l_sentences = [self.l_sentences[i] for i in mask] + self.r_sentences = [self.r_sentences[i] for i in mask] + self.l_trees = [self.l_trees[i] for i in mask] + self.r_trees = [self.r_trees[i] for i in mask] + self.labels = [self.labels[i] for i in mask] + self.index = 0 + + def next(self): + out = self[self.index] + self.index += 1 + return out + + def set_context(self, context): + self.l_sentences = [a.as_in_context(context) for a in self.l_sentences] + self.r_sentences = [a.as_in_context(context) for a in self.r_sentences] + + def __len__(self): + return self.size + + def __getitem__(self, index): + l_tree = self.l_trees[index] + r_tree = self.r_trees[index] + l_sent = self.l_sentences[index] + r_sent = self.r_sentences[index] + label = self.labels[index] + return (l_tree,l_sent,r_tree,r_sent,label) + + def read_sentence(self, line): + indices = self.vocab.to_indices(line.split()) + return mx.nd.array(indices) + + def read_sentences(self, filename): + with open(filename,'r') as f: + sentences = [self.read_sentence(line) for line in f.readlines()] + return sentences + + def read_tree(self, line): + parents = [int(x) for x in line.split()] + nodes = {} + root = None + for i in range(1,len(parents)+1): + if i-1 not in nodes and parents[i-1]!=-1: + idx = i + prev = None + while True: + parent = parents[idx-1] + if parent == -1: + break + tree = Tree(idx) + if prev is not None: + tree.children.append(prev) + nodes[idx-1] = tree + tree.idx = idx-1 + if parent-1 in nodes: + nodes[parent-1].children.append(tree) + break + elif parent==0: + root = tree + break + else: + prev = tree + idx = parent + return root + + def read_trees(self, filename): + with open(filename,'r') as f: + trees = [self.read_tree(line) for line in tqdm(f.readlines(), 'Parsing trees')] + return trees + + def read_labels(self, filename): + with open(filename,'r') as f: + labels = [float(x) for x in f.readlines()] + return labels diff --git a/example/gluon/tree_lstm/fetch_and_preprocess.sh b/example/gluon/tree_lstm/fetch_and_preprocess.sh new file mode 100755 index 000000000000..f372392830d0 --- /dev/null +++ b/example/gluon/tree_lstm/fetch_and_preprocess.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +set -e +python2.7 scripts/download.py + +CLASSPATH="lib:lib/stanford-parser/stanford-parser.jar:lib/stanford-parser/stanford-parser-3.5.1-models.jar" +javac -cp $CLASSPATH lib/*.java +python2.7 scripts/preprocess-sick.py diff --git a/example/gluon/tree_lstm/lib/CollapseUnaryTransformer.java b/example/gluon/tree_lstm/lib/CollapseUnaryTransformer.java new file mode 100644 index 000000000000..a0ff1936cb88 --- /dev/null +++ b/example/gluon/tree_lstm/lib/CollapseUnaryTransformer.java @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +import java.util.List; + +import edu.stanford.nlp.ling.Label; +import edu.stanford.nlp.trees.Tree; +import edu.stanford.nlp.trees.TreeTransformer; +import edu.stanford.nlp.util.Generics; + +/** + * This transformer collapses chains of unary nodes so that the top + * node is the only node left. The Sentiment model does not handle + * unary nodes, so this simplifies them to make a binary tree consist + * entirely of binary nodes and preterminals. A new tree with new + * nodes and labels is returned; the original tree is unchanged. + * + * @author John Bauer + */ +public class CollapseUnaryTransformer implements TreeTransformer { + public Tree transformTree(Tree tree) { + if (tree.isPreTerminal() || tree.isLeaf()) { + return tree.deepCopy(); + } + + Label label = tree.label().labelFactory().newLabel(tree.label()); + Tree[] children = tree.children(); + while (children.length == 1 && !children[0].isLeaf()) { + children = children[0].children(); + } + List processedChildren = Generics.newArrayList(); + for (Tree child : children) { + processedChildren.add(transformTree(child)); + } + return tree.treeFactory().newTreeNode(label, processedChildren); + } +} diff --git a/example/gluon/tree_lstm/lib/ConstituencyParse.java b/example/gluon/tree_lstm/lib/ConstituencyParse.java new file mode 100644 index 000000000000..346138c6a06d --- /dev/null +++ b/example/gluon/tree_lstm/lib/ConstituencyParse.java @@ -0,0 +1,253 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +import edu.stanford.nlp.process.WordTokenFactory; +import edu.stanford.nlp.ling.HasWord; +import edu.stanford.nlp.ling.Word; +import edu.stanford.nlp.ling.CoreLabel; +import edu.stanford.nlp.process.PTBTokenizer; +import edu.stanford.nlp.util.StringUtils; +import edu.stanford.nlp.parser.lexparser.LexicalizedParser; +import edu.stanford.nlp.parser.lexparser.TreeBinarizer; +import edu.stanford.nlp.trees.GrammaticalStructure; +import edu.stanford.nlp.trees.GrammaticalStructureFactory; +import edu.stanford.nlp.trees.PennTreebankLanguagePack; +import edu.stanford.nlp.trees.Tree; +import edu.stanford.nlp.trees.Trees; +import edu.stanford.nlp.trees.TreebankLanguagePack; +import edu.stanford.nlp.trees.TypedDependency; + +import java.io.BufferedWriter; +import java.io.FileWriter; +import java.io.StringReader; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.HashMap; +import java.util.Properties; +import java.util.Scanner; + +public class ConstituencyParse { + + private boolean tokenize; + private BufferedWriter tokWriter, parentWriter; + private LexicalizedParser parser; + private TreeBinarizer binarizer; + private CollapseUnaryTransformer transformer; + private GrammaticalStructureFactory gsf; + + private static final String PCFG_PATH = "edu/stanford/nlp/models/lexparser/englishPCFG.ser.gz"; + + public ConstituencyParse(String tokPath, String parentPath, boolean tokenize) throws IOException { + this.tokenize = tokenize; + if (tokPath != null) { + tokWriter = new BufferedWriter(new FileWriter(tokPath)); + } + parentWriter = new BufferedWriter(new FileWriter(parentPath)); + parser = LexicalizedParser.loadModel(PCFG_PATH); + binarizer = TreeBinarizer.simpleTreeBinarizer( + parser.getTLPParams().headFinder(), parser.treebankLanguagePack()); + transformer = new CollapseUnaryTransformer(); + + // set up to produce dependency representations from constituency trees + TreebankLanguagePack tlp = new PennTreebankLanguagePack(); + gsf = tlp.grammaticalStructureFactory(); + } + + public List sentenceToTokens(String line) { + List tokens = new ArrayList<>(); + if (tokenize) { + PTBTokenizer tokenizer = new PTBTokenizer(new StringReader(line), new WordTokenFactory(), ""); + for (Word label; tokenizer.hasNext(); ) { + tokens.add(tokenizer.next()); + } + } else { + for (String word : line.split(" ")) { + tokens.add(new Word(word)); + } + } + + return tokens; + } + + public Tree parse(List tokens) { + Tree tree = parser.apply(tokens); + return tree; + } + + public int[] constTreeParents(Tree tree) { + Tree binarized = binarizer.transformTree(tree); + Tree collapsedUnary = transformer.transformTree(binarized); + Trees.convertToCoreLabels(collapsedUnary); + collapsedUnary.indexSpans(); + List leaves = collapsedUnary.getLeaves(); + int size = collapsedUnary.size() - leaves.size(); + int[] parents = new int[size]; + HashMap index = new HashMap(); + + int idx = leaves.size(); + int leafIdx = 0; + for (Tree leaf : leaves) { + Tree cur = leaf.parent(collapsedUnary); // go to preterminal + int curIdx = leafIdx++; + boolean done = false; + while (!done) { + Tree parent = cur.parent(collapsedUnary); + if (parent == null) { + parents[curIdx] = 0; + break; + } + + int parentIdx; + int parentNumber = parent.nodeNumber(collapsedUnary); + if (!index.containsKey(parentNumber)) { + parentIdx = idx++; + index.put(parentNumber, parentIdx); + } else { + parentIdx = index.get(parentNumber); + done = true; + } + + parents[curIdx] = parentIdx + 1; + cur = parent; + curIdx = parentIdx; + } + } + + return parents; + } + + // convert constituency parse to a dependency representation and return the + // parent pointer representation of the tree + public int[] depTreeParents(Tree tree, List tokens) { + GrammaticalStructure gs = gsf.newGrammaticalStructure(tree); + Collection tdl = gs.typedDependencies(); + int len = tokens.size(); + int[] parents = new int[len]; + for (int i = 0; i < len; i++) { + // if a node has a parent of -1 at the end of parsing, then the node + // has no parent. + parents[i] = -1; + } + + for (TypedDependency td : tdl) { + // let root have index 0 + int child = td.dep().index(); + int parent = td.gov().index(); + parents[child - 1] = parent; + } + + return parents; + } + + public void printTokens(List tokens) throws IOException { + int len = tokens.size(); + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < len - 1; i++) { + if (tokenize) { + sb.append(PTBTokenizer.ptbToken2Text(tokens.get(i).word())); + } else { + sb.append(tokens.get(i).word()); + } + sb.append(' '); + } + + if (tokenize) { + sb.append(PTBTokenizer.ptbToken2Text(tokens.get(len - 1).word())); + } else { + sb.append(tokens.get(len - 1).word()); + } + + sb.append('\n'); + tokWriter.write(sb.toString()); + } + + public void printParents(int[] parents) throws IOException { + StringBuilder sb = new StringBuilder(); + int size = parents.length; + for (int i = 0; i < size - 1; i++) { + sb.append(parents[i]); + sb.append(' '); + } + sb.append(parents[size - 1]); + sb.append('\n'); + parentWriter.write(sb.toString()); + } + + public void close() throws IOException { + if (tokWriter != null) tokWriter.close(); + parentWriter.close(); + } + + public static void main(String[] args) throws Exception { + Properties props = StringUtils.argsToProperties(args); + if (!props.containsKey("parentpath")) { + System.err.println( + "usage: java ConstituencyParse -deps - -tokenize - -tokpath -parentpath "); + System.exit(1); + } + + // whether to tokenize input sentences + boolean tokenize = false; + if (props.containsKey("tokenize")) { + tokenize = true; + } + + // whether to produce dependency trees from the constituency parse + boolean deps = false; + if (props.containsKey("deps")) { + deps = true; + } + + String tokPath = props.containsKey("tokpath") ? props.getProperty("tokpath") : null; + String parentPath = props.getProperty("parentpath"); + ConstituencyParse processor = new ConstituencyParse(tokPath, parentPath, tokenize); + + Scanner stdin = new Scanner(System.in); + int count = 0; + long start = System.currentTimeMillis(); + while (stdin.hasNextLine()) { + String line = stdin.nextLine(); + List tokens = processor.sentenceToTokens(line); + Tree parse = processor.parse(tokens); + + // produce parent pointer representation + int[] parents = deps ? processor.depTreeParents(parse, tokens) + : processor.constTreeParents(parse); + + // print + if (tokPath != null) { + processor.printTokens(tokens); + } + processor.printParents(parents); + + count++; + if (count % 1000 == 0) { + double elapsed = (System.currentTimeMillis() - start) / 1000.0; + System.err.printf("Parsed %d lines (%.2fs)\n", count, elapsed); + } + } + + long totalTimeMillis = System.currentTimeMillis() - start; + System.err.printf("Done: %d lines in %.2fs (%.1fms per line)\n", + count, totalTimeMillis / 1000.0, totalTimeMillis / (double) count); + processor.close(); + } +} diff --git a/example/gluon/tree_lstm/lib/DependencyParse.java b/example/gluon/tree_lstm/lib/DependencyParse.java new file mode 100644 index 000000000000..445cab805cc9 --- /dev/null +++ b/example/gluon/tree_lstm/lib/DependencyParse.java @@ -0,0 +1,159 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +import edu.stanford.nlp.process.WordTokenFactory; +import edu.stanford.nlp.ling.HasWord; +import edu.stanford.nlp.ling.Word; +import edu.stanford.nlp.ling.TaggedWord; +import edu.stanford.nlp.parser.nndep.DependencyParser; +import edu.stanford.nlp.process.PTBTokenizer; +import edu.stanford.nlp.trees.TypedDependency; +import edu.stanford.nlp.util.StringUtils; +import edu.stanford.nlp.tagger.maxent.MaxentTagger; + +import java.io.BufferedWriter; +import java.io.FileWriter; +import java.io.StringReader; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Properties; +import java.util.Scanner; + +public class DependencyParse { + + public static final String TAGGER_MODEL = "stanford-tagger/models/english-left3words-distsim.tagger"; + public static final String PARSER_MODEL = "edu/stanford/nlp/models/parser/nndep/english_SD.gz"; + + public static void main(String[] args) throws Exception { + Properties props = StringUtils.argsToProperties(args); + if (!props.containsKey("tokpath") || + !props.containsKey("parentpath") || + !props.containsKey("relpath")) { + System.err.println( + "usage: java DependencyParse -tokenize - -tokpath -parentpath -relpath "); + System.exit(1); + } + + boolean tokenize = false; + if (props.containsKey("tokenize")) { + tokenize = true; + } + + String tokPath = props.getProperty("tokpath"); + String parentPath = props.getProperty("parentpath"); + String relPath = props.getProperty("relpath"); + + BufferedWriter tokWriter = new BufferedWriter(new FileWriter(tokPath)); + BufferedWriter parentWriter = new BufferedWriter(new FileWriter(parentPath)); + BufferedWriter relWriter = new BufferedWriter(new FileWriter(relPath)); + + MaxentTagger tagger = new MaxentTagger(TAGGER_MODEL); + DependencyParser parser = DependencyParser.loadFromModelFile(PARSER_MODEL); + Scanner stdin = new Scanner(System.in); + int count = 0; + long start = System.currentTimeMillis(); + while (stdin.hasNextLine()) { + String line = stdin.nextLine(); + List tokens = new ArrayList<>(); + if (tokenize) { + PTBTokenizer tokenizer = new PTBTokenizer( + new StringReader(line), new WordTokenFactory(), ""); + for (Word label; tokenizer.hasNext(); ) { + tokens.add(tokenizer.next()); + } + } else { + for (String word : line.split(" ")) { + tokens.add(new Word(word)); + } + } + + List tagged = tagger.tagSentence(tokens); + + int len = tagged.size(); + Collection tdl = parser.predict(tagged).typedDependencies(); + int[] parents = new int[len]; + for (int i = 0; i < len; i++) { + // if a node has a parent of -1 at the end of parsing, then the node + // has no parent. + parents[i] = -1; + } + + String[] relns = new String[len]; + for (TypedDependency td : tdl) { + // let root have index 0 + int child = td.dep().index(); + int parent = td.gov().index(); + relns[child - 1] = td.reln().toString(); + parents[child - 1] = parent; + } + + // print tokens + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < len - 1; i++) { + if (tokenize) { + sb.append(PTBTokenizer.ptbToken2Text(tokens.get(i).word())); + } else { + sb.append(tokens.get(i).word()); + } + sb.append(' '); + } + if (tokenize) { + sb.append(PTBTokenizer.ptbToken2Text(tokens.get(len - 1).word())); + } else { + sb.append(tokens.get(len - 1).word()); + } + sb.append('\n'); + tokWriter.write(sb.toString()); + + // print parent pointers + sb = new StringBuilder(); + for (int i = 0; i < len - 1; i++) { + sb.append(parents[i]); + sb.append(' '); + } + sb.append(parents[len - 1]); + sb.append('\n'); + parentWriter.write(sb.toString()); + + // print relations + sb = new StringBuilder(); + for (int i = 0; i < len - 1; i++) { + sb.append(relns[i]); + sb.append(' '); + } + sb.append(relns[len - 1]); + sb.append('\n'); + relWriter.write(sb.toString()); + + count++; + if (count % 1000 == 0) { + double elapsed = (System.currentTimeMillis() - start) / 1000.0; + System.err.printf("Parsed %d lines (%.2fs)\n", count, elapsed); + } + } + + long totalTimeMillis = System.currentTimeMillis() - start; + System.err.printf("Done: %d lines in %.2fs (%.1fms per line)\n", + count, totalTimeMillis / 1000.0, totalTimeMillis / (double) count); + tokWriter.close(); + parentWriter.close(); + relWriter.close(); + } +} diff --git a/example/gluon/tree_lstm/main.py b/example/gluon/tree_lstm/main.py new file mode 100644 index 000000000000..f04a69f2671f --- /dev/null +++ b/example/gluon/tree_lstm/main.py @@ -0,0 +1,188 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +# This example is inspired by https://github.com/dasguptar/treelstm.pytorch +import argparse, cPickle, math, os, random +import logging +logging.basicConfig(level=logging.INFO) +import numpy as np +from tqdm import tqdm + +import mxnet as mx +from mxnet import gluon +from mxnet.gluon import nn +from mxnet import autograd as ag + +from tree_lstm import SimilarityTreeLSTM +from dataset import Vocab, SICKDataIter + +parser = argparse.ArgumentParser(description='TreeLSTM for Sentence Similarity on Dependency Trees') +parser.add_argument('--data', default='data/sick/', + help='path to raw dataset. required when preprocessed dataset is not available.') +parser.add_argument('--word_embed', default='data/glove/glove.840B.300d.txt', + help='directory with word embeddings. required when preprocessed dataset is not available.') +parser.add_argument('--batch_size', type=int, default=25, + help='training batch size per device (CPU/GPU).') +parser.add_argument('--epochs', default=50, type=int, + help='number of total epochs to run') +parser.add_argument('--lr', default=0.02, type=float, + help='initial learning rate') +parser.add_argument('--wd', default=0.0001, type=float, + help='weight decay factor') +parser.add_argument('--optimizer', default='adagrad', + help='optimizer (default: adagrad)') +parser.add_argument('--seed', default=123, type=int, + help='random seed (default: 123)') +parser.add_argument('--use-gpu', action='store_true', + help='whether to use GPU.') + +opt = parser.parse_args() + +logging.info(opt) + +context = [mx.gpu(0) if opt.use_gpu else mx.cpu()] + +rnn_hidden_size, sim_hidden_size, num_classes = 150, 50, 5 +optimizer = opt.optimizer.lower() + +mx.random.seed(opt.seed) +np.random.seed(opt.seed) +random.seed(opt.seed) + +batch_size = opt.batch_size + +# read dataset +if os.path.exists('dataset.cPickle'): + with open('dataset.cPickle', 'rb') as f: + train_iter, dev_iter, test_iter, vocab = cPickle.load(f) +else: + root_dir = opt.data + segments = ['train', 'dev', 'test'] + token_files = [os.path.join(root_dir, seg, '%s.toks'%tok) + for tok in ['a', 'b'] + for seg in segments] + + vocab = Vocab(filepaths=token_files, embedpath=opt.word_embed) + + train_iter, dev_iter, test_iter = [SICKDataIter(os.path.join(root_dir, segment), vocab, num_classes) + for segment in segments] + with open('dataset.cPickle', 'wb') as f: + cPickle.dump([train_iter, dev_iter, test_iter, vocab], f) + +logging.info('==> SICK vocabulary size : %d ' % vocab.size) +logging.info('==> Size of train data : %d ' % len(train_iter)) +logging.info('==> Size of dev data : %d ' % len(dev_iter)) +logging.info('==> Size of test data : %d ' % len(test_iter)) + +# get network +net = SimilarityTreeLSTM(sim_hidden_size, rnn_hidden_size, vocab.size, vocab.embed.shape[1], num_classes) + +# use pearson correlation and mean-square error for evaluation +metric = mx.metric.create(['pearsonr', 'mse']) + +def to_target(x): + target = np.zeros((1, num_classes)) + ceil = int(math.ceil(x)) + floor = int(math.floor(x)) + if ceil==floor: + target[0][floor-1] = 1 + else: + target[0][floor-1] = ceil - x + target[0][ceil-1] = x - floor + return mx.nd.array(target) + +def to_score(x): + levels = mx.nd.arange(1, 6, ctx=x.context) + return [mx.nd.sum(levels*mx.nd.exp(x), axis=1).reshape((-1,1))] + +# when evaluating in validation mode, check and see if pearson-r is improved +# if so, checkpoint and run evaluation on test dataset +def test(ctx, data_iter, best, mode='validation', num_iter=-1): + data_iter.reset() + batches = len(data_iter) + data_iter.set_context(ctx[0]) + preds = [] + labels = [mx.nd.array(data_iter.labels, ctx=ctx[0]).reshape((-1,1))] + for _ in tqdm(range(batches), desc='Testing in {} mode'.format(mode)): + l_tree, l_sent, r_tree, r_sent, label = data_iter.next() + z = net(mx.nd, l_sent, r_sent, l_tree, r_tree) + preds.append(z) + + preds = to_score(mx.nd.concat(*preds, dim=0)) + metric.update(preds, labels) + names, values = metric.get() + metric.reset() + for name, acc in zip(names, values): + logging.info(mode+' acc: %s=%f'%(name, acc)) + if name == 'pearsonr': + test_r = acc + if mode == 'validation' and num_iter >= 0: + if test_r >= best: + best = test_r + logging.info('New optimum found: {}. Checkpointing.'.format(best)) + net.collect_params().save('childsum_tree_lstm_{}.params'.format(num_iter)) + test(ctx, test_iter, -1, 'test') + return best + + +def train(epoch, ctx, train_data, dev_data): + + # initialization with context + if isinstance(ctx, mx.Context): + ctx = [ctx] + net.collect_params().initialize(mx.init.Xavier(magnitude=2.24), ctx=ctx[0]) + net.embed.weight.set_data(vocab.embed.as_in_context(ctx[0])) + train_data.set_context(ctx[0]) + dev_data.set_context(ctx[0]) + + # set up trainer for optimizing the network. + trainer = gluon.Trainer(net.collect_params(), optimizer, {'learning_rate': opt.lr, 'wd': opt.wd}) + + best_r = -1 + Loss = gluon.loss.KLDivLoss() + for i in range(epoch): + train_data.reset() + num_batches = len(train_data) + # collect predictions and labels for evaluation metrics + preds = [] + labels = [mx.nd.array(train_data.labels, ctx=ctx[0]).reshape((-1,1))] + for j in tqdm(range(num_batches), desc='Training epoch {}'.format(i)): + # get next batch + l_tree, l_sent, r_tree, r_sent, label = train_data.next() + # use autograd to record the forward calculation + with ag.record(): + # forward calculation. the output is log probability + z = net(mx.nd, l_sent, r_sent, l_tree, r_tree) + # calculate loss + loss = Loss(z, to_target(label).as_in_context(ctx[0])) + # backward calculation for gradients. + loss.backward() + preds.append(z) + # update weight after every batch_size samples + if (j+1) % batch_size == 0: + trainer.step(batch_size) + + # translate log-probability to scores, and evaluate + preds = to_score(mx.nd.concat(*preds, dim=0)) + metric.update(preds, labels) + names, values = metric.get() + metric.reset() + for name, acc in zip(names, values): + logging.info('training acc at epoch %d: %s=%f'%(i, name, acc)) + best_r = test(ctx, dev_data, best_r, num_iter=i) + +train(opt.epochs, context, train_iter, dev_iter) diff --git a/example/gluon/tree_lstm/scripts/download.py b/example/gluon/tree_lstm/scripts/download.py new file mode 100644 index 000000000000..7ea930370175 --- /dev/null +++ b/example/gluon/tree_lstm/scripts/download.py @@ -0,0 +1,107 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +""" +Downloads the following: +- Stanford parser +- Stanford POS tagger +- Glove vectors +- SICK dataset (semantic relatedness task) +""" + +from __future__ import print_function +import urllib2 +import sys +import os +import shutil +import zipfile +import gzip +from mxnet.test_utils import download + +def unzip(filepath): + print("Extracting: " + filepath) + dirpath = os.path.dirname(filepath) + with zipfile.ZipFile(filepath) as zf: + zf.extractall(dirpath) + os.remove(filepath) + +def download_tagger(dirpath): + tagger_dir = 'stanford-tagger' + if os.path.exists(os.path.join(dirpath, tagger_dir)): + print('Found Stanford POS Tagger - skip') + return + url = 'http://nlp.stanford.edu/software/stanford-postagger-2015-01-29.zip' + filepath = download(url, dirname=dirpath) + zip_dir = '' + with zipfile.ZipFile(filepath) as zf: + zip_dir = zf.namelist()[0] + zf.extractall(dirpath) + os.remove(filepath) + os.rename(os.path.join(dirpath, zip_dir), os.path.join(dirpath, tagger_dir)) + +def download_parser(dirpath): + parser_dir = 'stanford-parser' + if os.path.exists(os.path.join(dirpath, parser_dir)): + print('Found Stanford Parser - skip') + return + url = 'http://nlp.stanford.edu/software/stanford-parser-full-2015-01-29.zip' + filepath = download(url, dirname=dirpath) + zip_dir = '' + with zipfile.ZipFile(filepath) as zf: + zip_dir = zf.namelist()[0] + zf.extractall(dirpath) + os.remove(filepath) + os.rename(os.path.join(dirpath, zip_dir), os.path.join(dirpath, parser_dir)) + +def download_wordvecs(dirpath): + if os.path.exists(dirpath): + print('Found Glove vectors - skip') + return + else: + os.makedirs(dirpath) + url = 'http://www-nlp.stanford.edu/data/glove.840B.300d.zip' + unzip(download(url, dirname=dirpath)) + +def download_sick(dirpath): + if os.path.exists(dirpath): + print('Found SICK dataset - skip') + return + else: + os.makedirs(dirpath) + train_url = 'http://alt.qcri.org/semeval2014/task1/data/uploads/sick_train.zip' + trial_url = 'http://alt.qcri.org/semeval2014/task1/data/uploads/sick_trial.zip' + test_url = 'http://alt.qcri.org/semeval2014/task1/data/uploads/sick_test_annotated.zip' + unzip(download(train_url, dirname=dirpath)) + unzip(download(trial_url, dirname=dirpath)) + unzip(download(test_url, dirname=dirpath)) + +if __name__ == '__main__': + base_dir = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) + + # data + data_dir = os.path.join(base_dir, 'data') + wordvec_dir = os.path.join(data_dir, 'glove') + sick_dir = os.path.join(data_dir, 'sick') + + # libraries + lib_dir = os.path.join(base_dir, 'lib') + + # download dependencies + download_tagger(lib_dir) + download_parser(lib_dir) + download_wordvecs(wordvec_dir) + download_sick(sick_dir) diff --git a/example/gluon/tree_lstm/scripts/preprocess-sick.py b/example/gluon/tree_lstm/scripts/preprocess-sick.py new file mode 100644 index 000000000000..abbcc5fac844 --- /dev/null +++ b/example/gluon/tree_lstm/scripts/preprocess-sick.py @@ -0,0 +1,122 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +""" +Preprocessing script for SICK data. + +""" + +import os +import glob + +def make_dirs(dirs): + for d in dirs: + if not os.path.exists(d): + os.makedirs(d) + +def dependency_parse(filepath, cp='', tokenize=True): + print('\nDependency parsing ' + filepath) + dirpath = os.path.dirname(filepath) + filepre = os.path.splitext(os.path.basename(filepath))[0] + tokpath = os.path.join(dirpath, filepre + '.toks') + parentpath = os.path.join(dirpath, filepre + '.parents') + relpath = os.path.join(dirpath, filepre + '.rels') + tokenize_flag = '-tokenize - ' if tokenize else '' + cmd = ('java -cp %s DependencyParse -tokpath %s -parentpath %s -relpath %s %s < %s' + % (cp, tokpath, parentpath, relpath, tokenize_flag, filepath)) + os.system(cmd) + +def constituency_parse(filepath, cp='', tokenize=True): + dirpath = os.path.dirname(filepath) + filepre = os.path.splitext(os.path.basename(filepath))[0] + tokpath = os.path.join(dirpath, filepre + '.toks') + parentpath = os.path.join(dirpath, filepre + '.cparents') + tokenize_flag = '-tokenize - ' if tokenize else '' + cmd = ('java -cp %s ConstituencyParse -tokpath %s -parentpath %s %s < %s' + % (cp, tokpath, parentpath, tokenize_flag, filepath)) + os.system(cmd) + +def build_vocab(filepaths, dst_path, lowercase=True): + vocab = set() + for filepath in filepaths: + with open(filepath) as f: + for line in f: + if lowercase: + line = line.lower() + vocab |= set(line.split()) + with open(dst_path, 'w') as f: + for w in sorted(vocab): + f.write(w + '\n') + +def split(filepath, dst_dir): + with open(filepath) as datafile, \ + open(os.path.join(dst_dir, 'a.txt'), 'w') as afile, \ + open(os.path.join(dst_dir, 'b.txt'), 'w') as bfile, \ + open(os.path.join(dst_dir, 'id.txt'), 'w') as idfile, \ + open(os.path.join(dst_dir, 'sim.txt'), 'w') as simfile: + datafile.readline() + for line in datafile: + i, a, b, sim, ent = line.strip().split('\t') + idfile.write(i + '\n') + afile.write(a + '\n') + bfile.write(b + '\n') + simfile.write(sim + '\n') + +def parse(dirpath, cp=''): + dependency_parse(os.path.join(dirpath, 'a.txt'), cp=cp, tokenize=True) + dependency_parse(os.path.join(dirpath, 'b.txt'), cp=cp, tokenize=True) + constituency_parse(os.path.join(dirpath, 'a.txt'), cp=cp, tokenize=True) + constituency_parse(os.path.join(dirpath, 'b.txt'), cp=cp, tokenize=True) + +if __name__ == '__main__': + print('=' * 80) + print('Preprocessing SICK dataset') + print('=' * 80) + + base_dir = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) + data_dir = os.path.join(base_dir, 'data') + sick_dir = os.path.join(data_dir, 'sick') + lib_dir = os.path.join(base_dir, 'lib') + train_dir = os.path.join(sick_dir, 'train') + dev_dir = os.path.join(sick_dir, 'dev') + test_dir = os.path.join(sick_dir, 'test') + make_dirs([train_dir, dev_dir, test_dir]) + + # java classpath for calling Stanford parser + classpath = ':'.join([ + lib_dir, + os.path.join(lib_dir, 'stanford-parser/stanford-parser.jar'), + os.path.join(lib_dir, 'stanford-parser/stanford-parser-3.5.1-models.jar')]) + + # split into separate files + split(os.path.join(sick_dir, 'SICK_train.txt'), train_dir) + split(os.path.join(sick_dir, 'SICK_trial.txt'), dev_dir) + split(os.path.join(sick_dir, 'SICK_test_annotated.txt'), test_dir) + + # parse sentences + parse(train_dir, cp=classpath) + parse(dev_dir, cp=classpath) + parse(test_dir, cp=classpath) + + # get vocabulary + build_vocab( + glob.glob(os.path.join(sick_dir, '*/*.toks')), + os.path.join(sick_dir, 'vocab.txt')) + build_vocab( + glob.glob(os.path.join(sick_dir, '*/*.toks')), + os.path.join(sick_dir, 'vocab-cased.txt'), + lowercase=False) diff --git a/example/gluon/tree_lstm/tree_lstm.py b/example/gluon/tree_lstm/tree_lstm.py new file mode 100644 index 000000000000..e96fe26bf9b6 --- /dev/null +++ b/example/gluon/tree_lstm/tree_lstm.py @@ -0,0 +1,154 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +import mxnet as mx +from mxnet.gluon import Block, nn +from mxnet.gluon.parameter import Parameter + +class ChildSumLSTMCell(Block): + def __init__(self, hidden_size, + i2h_weight_initializer=None, + hs2h_weight_initializer=None, + hc2h_weight_initializer=None, + i2h_bias_initializer='zeros', + hs2h_bias_initializer='zeros', + hc2h_bias_initializer='zeros', + input_size=0, prefix=None, params=None): + super(ChildSumLSTMCell, self).__init__(prefix=prefix, params=params) + with self.name_scope(): + self._hidden_size = hidden_size + self._input_size = input_size + self.i2h_weight = self.params.get('i2h_weight', shape=(4*hidden_size, input_size), + init=i2h_weight_initializer) + self.hs2h_weight = self.params.get('hs2h_weight', shape=(3*hidden_size, hidden_size), + init=hs2h_weight_initializer) + self.hc2h_weight = self.params.get('hc2h_weight', shape=(hidden_size, hidden_size), + init=hc2h_weight_initializer) + self.i2h_bias = self.params.get('i2h_bias', shape=(4*hidden_size,), + init=i2h_bias_initializer) + self.hs2h_bias = self.params.get('hs2h_bias', shape=(3*hidden_size,), + init=hs2h_bias_initializer) + self.hc2h_bias = self.params.get('hc2h_bias', shape=(hidden_size,), + init=hc2h_bias_initializer) + + def _alias(self): + return 'childsum_lstm' + + def forward(self, F, inputs, tree): + children_outputs = [self.forward(F, inputs, child) + for child in tree.children] + if children_outputs: + _, children_states = zip(*children_outputs) # unzip + else: + children_states = None + + with inputs.context as ctx: + return self.node_forward(F, F.expand_dims(inputs[tree.idx], axis=0), children_states, + self.i2h_weight.data(ctx), + self.hs2h_weight.data(ctx), + self.hc2h_weight.data(ctx), + self.i2h_bias.data(ctx), + self.hs2h_bias.data(ctx), + self.hc2h_bias.data(ctx)) + + def node_forward(self, F, inputs, children_states, + i2h_weight, hs2h_weight, hc2h_weight, + i2h_bias, hs2h_bias, hc2h_bias): + name = '{0}{1}_'.format(self.prefix, self._alias) + # notation: N for batch size, C for hidden state dimensions, K for number of children. + + # FC for i, f, u, o gates (N, 4*C), from input to hidden + i2h = F.FullyConnected(data=inputs, weight=i2h_weight, bias=i2h_bias, + num_hidden=self._hidden_size*4, + name='%si2h'%name) + i2h_slices = F.split(i2h, num_outputs=4, name='%siuo_slice'%name) # (N, C)*4 + i2h_iuo = F.concat(*[i2h_slices[i] for i in [0, 2, 3]], dim=1) # (N, C*3) + if children_states: + # sum of children states + hs = F.add_n(*[state[0] for state in children_states], name='%shs'%name) # (N, C) + # concatenation of children hidden states + hc = F.concat(*[F.expand_dims(state[0], axis=1) for state in children_states], dim=1, + name='%shc') # (N, K, C) + # concatenation of children cell states + cs = F.concat(*[F.expand_dims(state[1], axis=1) for state in children_states], dim=1, + name='%scs') # (N, K, C) + + # calculate activation for forget gate. addition in f_act is done with broadcast + i2h_f_slice = i2h_slices[1] + f_act = i2h_f_slice + hc2h_bias + F.dot(hc, hc2h_weight) # (N, K, C) + forget_gates = F.Activation(f_act, act_type='sigmoid', name='%sf'%name) # (N, K, C) + else: + # for leaf nodes, summation of children hidden states are zeros. + hs = F.zeros_like(i2h_slices[0]) + + # FC for i, u, o gates, from summation of children states to hidden state + hs2h_iuo = F.FullyConnected(data=hs, weight=hs2h_weight, bias=hs2h_bias, + num_hidden=self._hidden_size*3, + name='%shs2h'%name) + i2h_iuo = i2h_iuo + hs2h_iuo + + iuo_act_slices = F.SliceChannel(i2h_iuo, num_outputs=3, + name='%sslice'%name) # (N, C)*3 + i_act, u_act, o_act = iuo_act_slices[0], iuo_act_slices[1], iuo_act_slices[2] # (N, C) each + + # calculate gate outputs + in_gate = F.Activation(i_act, act_type='sigmoid', name='%si'%name) + in_transform = F.Activation(u_act, act_type='tanh', name='%sc'%name) + out_gate = F.Activation(o_act, act_type='sigmoid', name='%so'%name) + + # calculate cell state and hidden state + next_c = in_gate * in_transform + if children_states: + next_c = F._internal._plus(F.sum(forget_gates * cs, axis=1), next_c, + name='%sstate'%name) + next_h = F._internal._mul(out_gate, F.Activation(next_c, act_type='tanh'), + name='%sout'%name) + + return next_h, [next_h, next_c] + +# module for distance-angle similarity +class Similarity(nn.Block): + def __init__(self, sim_hidden_size, rnn_hidden_size, num_classes): + super(Similarity, self).__init__() + with self.name_scope(): + self.wh = nn.Dense(sim_hidden_size, in_units=2*rnn_hidden_size, prefix='sim_embed_') + self.wp = nn.Dense(num_classes, in_units=sim_hidden_size, prefix='sim_out_') + + def forward(self, F, lvec, rvec): + # lvec and rvec will be tree_lstm cell states at roots + mult_dist = F.broadcast_mul(lvec, rvec) + abs_dist = F.abs(F.add(lvec,-rvec)) + vec_dist = F.concat(*[mult_dist, abs_dist],dim=1) + out = F.log_softmax(self.wp(F.sigmoid(self.wh(vec_dist)))) + return out + +# putting the whole model together +class SimilarityTreeLSTM(nn.Block): + def __init__(self, sim_hidden_size, rnn_hidden_size, embed_in_size, embed_dim, num_classes): + super(SimilarityTreeLSTM, self).__init__() + with self.name_scope(): + self.embed = nn.Embedding(embed_in_size, embed_dim, prefix='word_embed_') + self.childsumtreelstm = ChildSumLSTMCell(rnn_hidden_size, input_size=embed_dim) + self.similarity = Similarity(sim_hidden_size, rnn_hidden_size, num_classes) + + def forward(self, F, l_inputs, r_inputs, l_tree, r_tree): + l_inputs = self.embed(l_inputs) + r_inputs = self.embed(r_inputs) + lstate = self.childsumtreelstm(F, l_inputs, l_tree)[1][1] + rstate = self.childsumtreelstm(F, r_inputs, r_tree)[1][1] + output = self.similarity(F, lstate, rstate) + return output diff --git a/example/gluon/word_language_model/README.md b/example/gluon/word_language_model/README.md new file mode 100644 index 000000000000..f200c164a78a --- /dev/null +++ b/example/gluon/word_language_model/README.md @@ -0,0 +1,49 @@ +# Word-level language modeling RNN + +This example trains a multi-layer RNN (Elman, GRU, or LSTM) on Penn Treebank (PTB) language modeling benchmark. + +The model obtains the state-of-the-art result on PTB using LSTM, getting a test perplexity of ~72. + +The following techniques have been adopted for SOTA results: +- [LSTM for LM](https://arxiv.org/pdf/1409.2329.pdf) +- [Weight tying](https://arxiv.org/abs/1608.05859) between word vectors and softmax output embeddings + +## Data + +The PTB data is the processed version from [(Mikolov et al, 2010)](http://www.fit.vutbr.cz/research/groups/speech/publi/2010/mikolov_interspeech2010_IS100722.pdf): + +```bash +python data.py +``` + +## Usage + +Example runs and the results: + +``` +python train.py --cuda --tied --nhid 650 --emsize 650 --dropout 0.5 # Test ppl of 75.3 +python train.py --cuda --tied --nhid 1500 --emsize 1500 --dropout 0.65 # Test ppl of 72.0 +``` + +
    + +`python train.py --help` gives the following arguments: +``` +Optional arguments: + -h, --help show this help message and exit + --data DATA location of the data corpus + --model MODEL type of recurrent net (rnn_tanh, rnn_relu, lstm, gru) + --emsize EMSIZE size of word embeddings + --nhid NHID number of hidden units per layer + --nlayers NLAYERS number of layers + --lr LR initial learning rate + --clip CLIP gradient clipping + --epochs EPOCHS upper epoch limit + --batch_size N batch size + --bptt BPTT sequence length + --dropout DROPOUT dropout applied to layers (0 = no dropout) + --tied tie the word embedding and softmax weights + --cuda Whether to use gpu + --log-interval N report interval + --save SAVE path to save the final model +``` diff --git a/example/gluon/word_language_model/data.py b/example/gluon/word_language_model/data.py new file mode 100644 index 000000000000..913963ec20cb --- /dev/null +++ b/example/gluon/word_language_model/data.py @@ -0,0 +1,66 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +import os +import numpy as np +import mxnet as mx + +class Dictionary(object): + def __init__(self): + self.word2idx = {} + self.idx2word = [] + + def add_word(self, word): + if word not in self.word2idx: + self.idx2word.append(word) + self.word2idx[word] = len(self.idx2word) - 1 + return self.word2idx[word] + + def __len__(self): + return len(self.idx2word) + + +class Corpus(object): + def __init__(self, path): + self.dictionary = Dictionary() + self.train = self.tokenize(path + 'train.txt') + self.valid = self.tokenize(path + 'valid.txt') + self.test = self.tokenize(path + 'test.txt') + + def tokenize(self, path): + """Tokenizes a text file.""" + assert os.path.exists(path) + # Add words to the dictionary + with open(path, 'r') as f: + tokens = 0 + for line in f: + words = line.split() + [''] + tokens += len(words) + for word in words: + self.dictionary.add_word(word) + + # Tokenize file content + with open(path, 'r') as f: + ids = np.zeros((tokens,), dtype='int32') + token = 0 + for line in f: + words = line.split() + [''] + for word in words: + ids[token] = self.dictionary.word2idx[word] + token += 1 + + return mx.nd.array(ids, dtype='int32') diff --git a/example/gluon/word_language_model/get_ptb_data.sh b/example/gluon/word_language_model/get_ptb_data.sh new file mode 100755 index 000000000000..d2641cb32b81 --- /dev/null +++ b/example/gluon/word_language_model/get_ptb_data.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash + +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + + +RNN_DIR=$(cd `dirname $0`; pwd) +DATA_DIR="${RNN_DIR}/data/" + +if [[ ! -d "${DATA_DIR}" ]]; then + echo "${DATA_DIR} doesn't exist, will create one"; + mkdir -p ${DATA_DIR} +fi + +wget -P ${DATA_DIR} https://raw.githubusercontent.com/dmlc/web-data/master/mxnet/ptb/ptb.train.txt; +wget -P ${DATA_DIR} https://raw.githubusercontent.com/dmlc/web-data/master/mxnet/ptb/ptb.valid.txt; +wget -P ${DATA_DIR} https://raw.githubusercontent.com/dmlc/web-data/master/mxnet/ptb/ptb.test.txt; +wget -P ${DATA_DIR} https://raw.githubusercontent.com/dmlc/web-data/master/mxnet/tinyshakespeare/input.txt; diff --git a/example/gluon/word_language_model/model.py b/example/gluon/word_language_model/model.py new file mode 100644 index 000000000000..40e7926ef8d6 --- /dev/null +++ b/example/gluon/word_language_model/model.py @@ -0,0 +1,64 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +import mxnet as mx +from mxnet import gluon +from mxnet.gluon import nn, rnn + +class RNNModel(gluon.Block): + """A model with an encoder, recurrent layer, and a decoder.""" + + def __init__(self, mode, vocab_size, num_embed, num_hidden, + num_layers, dropout=0.5, tie_weights=False, **kwargs): + super(RNNModel, self).__init__(**kwargs) + with self.name_scope(): + self.drop = nn.Dropout(dropout) + self.encoder = nn.Embedding(vocab_size, num_embed, + weight_initializer=mx.init.Uniform(0.1)) + if mode == 'rnn_relu': + self.rnn = rnn.RNN(num_hidden, 'relu', num_layers, dropout=dropout, + input_size=num_embed) + elif mode == 'rnn_tanh': + self.rnn = rnn.RNN(num_hidden, num_layers, dropout=dropout, + input_size=num_embed) + elif mode == 'lstm': + self.rnn = rnn.LSTM(num_hidden, num_layers, dropout=dropout, + input_size=num_embed) + elif mode == 'gru': + self.rnn = rnn.GRU(num_hidden, num_layers, dropout=dropout, + input_size=num_embed) + else: + raise ValueError("Invalid mode %s. Options are rnn_relu, " + "rnn_tanh, lstm, and gru"%mode) + + if tie_weights: + self.decoder = nn.Dense(vocab_size, in_units=num_hidden, + params=self.encoder.params) + else: + self.decoder = nn.Dense(vocab_size, in_units=num_hidden) + + self.num_hidden = num_hidden + + def forward(self, inputs, hidden): + emb = self.drop(self.encoder(inputs)) + output, hidden = self.rnn(emb, hidden) + output = self.drop(output) + decoded = self.decoder(output.reshape((-1, self.num_hidden))) + return decoded, hidden + + def begin_state(self, *args, **kwargs): + return self.rnn.begin_state(*args, **kwargs) diff --git a/example/gluon/word_language_model/train.py b/example/gluon/word_language_model/train.py new file mode 100644 index 000000000000..0b504998bed2 --- /dev/null +++ b/example/gluon/word_language_model/train.py @@ -0,0 +1,178 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +import argparse +import time +import math +import mxnet as mx +from mxnet import gluon, autograd +import model +import data + +parser = argparse.ArgumentParser(description='MXNet Autograd PennTreeBank RNN/LSTM Language Model') +parser.add_argument('--data', type=str, default='./data/ptb.', + help='location of the data corpus') +parser.add_argument('--model', type=str, default='lstm', + help='type of recurrent net (rnn_tanh, rnn_relu, lstm, gru)') +parser.add_argument('--emsize', type=int, default=200, + help='size of word embeddings') +parser.add_argument('--nhid', type=int, default=200, + help='number of hidden units per layer') +parser.add_argument('--nlayers', type=int, default=2, + help='number of layers') +parser.add_argument('--lr', type=float, default=1.0, + help='initial learning rate') +parser.add_argument('--clip', type=float, default=0.2, + help='gradient clipping') +parser.add_argument('--epochs', type=int, default=40, + help='upper epoch limit') +parser.add_argument('--batch_size', type=int, default=32, metavar='N', + help='batch size') +parser.add_argument('--bptt', type=int, default=35, + help='sequence length') +parser.add_argument('--dropout', type=float, default=0.2, + help='dropout applied to layers (0 = no dropout)') +parser.add_argument('--tied', action='store_true', + help='tie the word embedding and softmax weights') +parser.add_argument('--cuda', action='store_true', + help='Whether to use gpu') +parser.add_argument('--log-interval', type=int, default=200, metavar='N', + help='report interval') +parser.add_argument('--save', type=str, default='model.params', + help='path to save the final model') +args = parser.parse_args() + + +############################################################################### +# Load data +############################################################################### + + +if args.cuda: + context = mx.gpu(0) +else: + context = mx.cpu(0) + +corpus = data.Corpus(args.data) + +def batchify(data, batch_size): + """Reshape data into (num_example, batch_size)""" + nbatch = data.shape[0] // batch_size + data = data[:nbatch * batch_size] + data = data.reshape((batch_size, nbatch)).T + return data + +train_data = batchify(corpus.train, args.batch_size).as_in_context(context) +val_data = batchify(corpus.valid, args.batch_size).as_in_context(context) +test_data = batchify(corpus.test, args.batch_size).as_in_context(context) + + +############################################################################### +# Build the model +############################################################################### + + +ntokens = len(corpus.dictionary) +model = model.RNNModel(args.model, ntokens, args.emsize, args.nhid, + args.nlayers, args.dropout, args.tied) +model.collect_params().initialize(mx.init.Xavier(), ctx=context) +trainer = gluon.Trainer(model.collect_params(), 'sgd', + {'learning_rate': args.lr, + 'momentum': 0, + 'wd': 0}) +loss = gluon.loss.SoftmaxCrossEntropyLoss() + +############################################################################### +# Training code +############################################################################### + +def get_batch(source, i): + seq_len = min(args.bptt, source.shape[0] - 1 - i) + data = source[i:i+seq_len] + target = source[i+1:i+1+seq_len] + return data, target.reshape((-1,)) + +def detach(hidden): + if isinstance(hidden, (tuple, list)): + hidden = [i.detach() for i in hidden] + else: + hidden = hidden.detach() + return hidden + +def eval(data_source): + total_L = 0.0 + ntotal = 0 + hidden = model.begin_state(func=mx.nd.zeros, batch_size=args.batch_size, ctx=context) + for i in range(0, data_source.shape[0] - 1, args.bptt): + data, target = get_batch(data_source, i) + output, hidden = model(data, hidden) + L = loss(output, target) + total_L += mx.nd.sum(L).asscalar() + ntotal += L.size + return total_L / ntotal + +def train(): + best_val = float("Inf") + for epoch in range(args.epochs): + total_L = 0.0 + start_time = time.time() + hidden = model.begin_state(func=mx.nd.zeros, batch_size=args.batch_size, ctx=context) + for ibatch, i in enumerate(range(0, train_data.shape[0] - 1, args.bptt)): + data, target = get_batch(train_data, i) + hidden = detach(hidden) + with autograd.record(): + output, hidden = model(data, hidden) + L = loss(output, target) + L.backward() + + grads = [i.grad(context) for i in model.collect_params().values()] + # Here gradient is for the whole batch. + # So we multiply max_norm by batch_size and bptt size to balance it. + gluon.utils.clip_global_norm(grads, args.clip * args.bptt * args.batch_size) + + trainer.step(args.batch_size) + total_L += mx.nd.sum(L).asscalar() + + if ibatch % args.log_interval == 0 and ibatch > 0: + cur_L = total_L / args.bptt / args.batch_size / args.log_interval + print('[Epoch %d Batch %d] loss %.2f, ppl %.2f'%( + epoch, ibatch, cur_L, math.exp(cur_L))) + total_L = 0.0 + + val_L = eval(val_data) + + print('[Epoch %d] time cost %.2fs, valid loss %.2f, valid ppl %.2f'%( + epoch, time.time()-start_time, val_L, math.exp(val_L))) + + if val_L < best_val: + best_val = val_L + test_L = eval(test_data) + model.collect_params().save(args.save) + print('test loss %.2f, test ppl %.2f'%(test_L, math.exp(test_L))) + else: + args.lr = args.lr*0.25 + trainer._init_optimizer('sgd', + {'learning_rate': args.lr, + 'momentum': 0, + 'wd': 0}) + model.collect_params().load(args.save, context) + +if __name__ == '__main__': + train() + model.collect_params().load(args.save, context) + test_L = eval(test_data) + print('Best test loss %.2f, test ppl %.2f'%(test_L, math.exp(test_L))) diff --git a/example/image-classification/README.md b/example/image-classification/README.md index 2c5f2d3a5409..3f514e2a391f 100644 --- a/example/image-classification/README.md +++ b/example/image-classification/README.md @@ -2,7 +2,7 @@ This fold contains examples for image classification. The goal of image classifcation is to identify the objects contained in images. The following -[example](http://mxnet.io/tutorials/python/predict_imagenet.html) shows +[example](http://mxnet.io/tutorials/python/predict_image.html) shows recognized object classes with corresponding probabilities using a pre-traind model. @@ -39,7 +39,7 @@ commonly used options are listed as following: | Argument | Comments | | ----------------------------- | ---------------------------------------- | -| `network` | The network to train, which is defined in [symbol/](https://github.com/dmlc/mxnet/tree/master/example/image-classification/symbol). Some networks may accept additional arguments, such as `--num-layers` is used to specify the number of layers in ResNet. | +| `network`                     | The network to train, which is defined in [symbol/](https://github.com/dmlc/mxnet/tree/master/example/image-classification/symbols). Some networks may accept additional arguments, such as `--num-layers` is used to specify the number of layers in ResNet. | | `data-train`, `data-val` | The data for training and validation. It can be either a filename or a directory. For the latter, all files in the directory will be used. But if `--benchmark 1` is used, then there two arguments will be ignored. | | `gpus` | The list of GPUs to use, such as `0` or `0,3,4,7`. If an empty string `''` is given, then we will use CPU. | | `batch-size` | The batch size for SGD training. It specifies the number of examples used for each SGD iteration. If we use *k* GPUs, then each GPU will compute *batch_size/k* examples in each time. | @@ -102,13 +102,13 @@ We provide multiple pre-trained models on various datasets. Use [common/modelzone.py](https://github.com/dmlc/mxnet/blob/master/example/image-classification/common/modelzoo.py) to download these models. These models can be used in any front-end language MXNet supports. For example, -[the tutorial](http://mxnet.io/tutorials/python/predict_imagenet.html) shows how +[the tutorial](http://mxnet.io/tutorials/python/predict_image.html) shows how to classify an image with jupyter notebook. ### ImageNet 1K It is first used by -[ImageNet challenge 2012](http://mxnet.io/tutorials/python/predict_imagenet.html), +[ImageNet challenge 2012](http://www.image-net.org/challenges/LSVRC/2012/), which contains about 1.2M images with 1000 classes. To test these models, one can use [data/imagenet1k-val.sh](https://github.com/dmlc/mxnet/blob/master/example/image-classification/data/imagenet1k-val.sh) diff --git a/example/image-classification/benchmark.py b/example/image-classification/benchmark.py index 5b040f3b09ff..3096fae07897 100644 --- a/example/image-classification/benchmark.py +++ b/example/image-classification/benchmark.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + from __future__ import print_function import logging import argparse diff --git a/example/image-classification/benchmark_score.py b/example/image-classification/benchmark_score.py index f54b6aebb750..aeacffa82b78 100644 --- a/example/image-classification/benchmark_score.py +++ b/example/image-classification/benchmark_score.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + """ Benchmark the scoring performance on various CNNs """ diff --git a/example/image-classification/common/data.py b/example/image-classification/common/data.py index e9bb4abc0814..eb694a45dc27 100755 --- a/example/image-classification/common/data.py +++ b/example/image-classification/common/data.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import mxnet as mx import random from mxnet.io import DataBatch, DataIter @@ -19,8 +36,6 @@ def add_data_args(parser): help='number of threads for data decoding') data.add_argument('--benchmark', type=int, default=0, help='if 1, then feed the network with synthetic data') - data.add_argument('--dtype', type=str, default='float32', - help='data type: float32 or float16') return data def add_data_aug_args(parser): @@ -65,8 +80,8 @@ def __init__(self, num_classes, data_shape, max_iter, dtype): self.dtype = dtype label = np.random.randint(0, num_classes, [self.batch_size,]) data = np.random.uniform(-1, 1, data_shape) - self.data = mx.nd.array(data, dtype=self.dtype) - self.label = mx.nd.array(label, dtype=self.dtype) + self.data = mx.nd.array(data, dtype=self.dtype, ctx=mx.Context('cpu_pinned', 0)) + self.label = mx.nd.array(label, dtype=self.dtype, ctx=mx.Context('cpu_pinned', 0)) def __iter__(self): return self @property @@ -93,13 +108,9 @@ def reset(self): def get_rec_iter(args, kv=None): image_shape = tuple([int(l) for l in args.image_shape.split(',')]) - dtype = np.float32; - if 'dtype' in args: - if args.dtype == 'float16': - dtype = np.float16 if 'benchmark' in args and args.benchmark: data_shape = (args.batch_size,) + image_shape - train = SyntheticDataIter(args.num_classes, data_shape, 50, dtype) + train = SyntheticDataIter(args.num_classes, data_shape, 500, np.float32) return (train, None) if kv: (rank, nworker) = (kv.rank, kv.num_workers) diff --git a/example/image-classification/common/find_mxnet.py b/example/image-classification/common/find_mxnet.py index a24444306721..2ce07130a361 100644 --- a/example/image-classification/common/find_mxnet.py +++ b/example/image-classification/common/find_mxnet.py @@ -1,5 +1,21 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import os, sys -os.environ["MXNET_CUDNN_AUTOTUNE_DEFAULT"] = "1" try: import mxnet as mx except ImportError: diff --git a/example/image-classification/common/fit.py b/example/image-classification/common/fit.py index 6d79385cb6ff..73235fc2e4ef 100755 --- a/example/image-classification/common/fit.py +++ b/example/image-classification/common/fit.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import mxnet as mx import logging import os @@ -84,6 +101,8 @@ def add_fit_args(parser): help='report the top-k accuracy. 0 means no report.') train.add_argument('--test-io', type=int, default=0, help='1 means test reading speed without training') + train.add_argument('--dtype', type=str, default='float32', + help='precision: float32 or float16') return train def fit(args, network, data_loader, **kwargs): @@ -146,7 +165,8 @@ def fit(args, network, data_loader, **kwargs): 'learning_rate': lr, 'momentum' : args.mom, 'wd' : args.wd, - 'lr_scheduler': lr_scheduler} + 'lr_scheduler': lr_scheduler, + 'multi_precision': True} monitor = mx.mon.Monitor(args.monitor, pattern=".*") if args.monitor > 0 else None diff --git a/example/image-classification/common/modelzoo.py b/example/image-classification/common/modelzoo.py index c2944cdc2827..1fe14ca4fcd2 100644 --- a/example/image-classification/common/modelzoo.py +++ b/example/image-classification/common/modelzoo.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import os from util import download_file diff --git a/example/image-classification/common/util.py b/example/image-classification/common/util.py index a25e2181be92..5f70411ab084 100644 --- a/example/image-classification/common/util.py +++ b/example/image-classification/common/util.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import subprocess import os import errno diff --git a/example/image-classification/data/caltech256.sh b/example/image-classification/data/caltech256.sh index 3befdac0c73a..3fc329a9b835 100755 --- a/example/image-classification/data/caltech256.sh +++ b/example/image-classification/data/caltech256.sh @@ -1,5 +1,23 @@ #!/bin/bash +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + + # This file download the caltech 256 dataset # (http://www.vision.caltech.edu/Image_Datasets/Caltech256/), and split it into # the train and val rec files. diff --git a/example/image-classification/data/imagenet1k-val.sh b/example/image-classification/data/imagenet1k-val.sh index 51f8130103fd..13cb551140f8 100755 --- a/example/image-classification/data/imagenet1k-val.sh +++ b/example/image-classification/data/imagenet1k-val.sh @@ -1,5 +1,23 @@ #!/bin/bash +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + + # This file download the imagnet-1k validation dataset and convert it into a rec # file. One need to provide the URL for the ILSVRC2012_img_val.tar, which can be # find at http://www.image-net.org/download-images diff --git a/example/image-classification/fine-tune.py b/example/image-classification/fine-tune.py index cfb43101542e..a5fb2434d958 100644 --- a/example/image-classification/fine-tune.py +++ b/example/image-classification/fine-tune.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import os import argparse import logging @@ -13,7 +30,7 @@ def get_fine_tune_model(symbol, arg_params, num_classes, layer_name): num_classes: the number of classes for the fine-tune datasets layer_name: the layer name before the last fully-connected layer """ - all_layers = sym.get_internals() + all_layers = symbol.get_internals() net = all_layers[layer_name+'_output'] net = mx.symbol.FullyConnected(data=net, num_hidden=num_classes, name='fc') net = mx.symbol.SoftmaxOutput(data=net, name='softmax') diff --git a/example/image-classification/predict-cpp/CMakeLists.txt b/example/image-classification/predict-cpp/CMakeLists.txt index 646f907f8f6d..59c98d8ee568 100644 --- a/example/image-classification/predict-cpp/CMakeLists.txt +++ b/example/image-classification/predict-cpp/CMakeLists.txt @@ -3,12 +3,31 @@ if(USE_OPENCV) if(NOT OpenCV_FOUND) # if not OpenCV 3.x, then imgcodecs are not found find_package(OpenCV REQUIRED COMPONENTS core highgui imgproc) endif() + + if(NOT MSVC) + set(UNITTEST_STATIC_LINK ON) + endif() + add_executable(image-classification-predict image-classification-predict.cc) include_directories(SYSTEM ${OpenCV_INCLUDE_DIRS}) - target_link_libraries(image-classification-predict mxnet) + + if(UNITTEST_STATIC_LINK) + target_link_libraries(image-classification-predict + ${BEGIN_WHOLE_ARCHIVE} mxnet_static ${END_WHOLE_ARCHIVE} + dmlc + ${mxnet_LINKER_LIBS} + ) + else() + target_link_libraries(image-classification-predict + dmlc + ${nnvm_LINKER_LIBS} + ${mxnet_LINKER_LIBS} + mxnet + ) + endif() target_link_libraries(image-classification-predict ${OpenCV_LIBS}) if(UNIX) - target_link_libraries(image-classification-predict rt) + target_link_libraries(image-classification-predict rt) endif() list(APPEND mxnet_LINKER_LIBS ${OpenCV_LIBS}) endif() diff --git a/example/image-classification/predict-cpp/image-classification-predict.cc b/example/image-classification/predict-cpp/image-classification-predict.cc index a8652c4f0815..fb74ed9d7170 100644 --- a/example/image-classification/predict-cpp/image-classification-predict.cc +++ b/example/image-classification/predict-cpp/image-classification-predict.cc @@ -1,3 +1,22 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! * Copyright (c) 2015 by Xiao Liu, pertusa, caprice-j * \file image_classification-predict.cpp @@ -196,7 +215,7 @@ int main(int argc, char* argv[]) { const mx_uint input_shape_indptr[2] = { 0, 4 }; const mx_uint input_shape_data[4] = { 1, static_cast(channels), - static_cast(height), + static_cast(height), static_cast(width)}; PredictorHandle pred_hnd = 0; diff --git a/example/image-classification/score.py b/example/image-classification/score.py index d26ddddf9b83..f40e649f1f42 100644 --- a/example/image-classification/score.py +++ b/example/image-classification/score.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import argparse from common import modelzoo, find_mxnet import mxnet as mx diff --git a/example/image-classification/symbol_alexnet.R b/example/image-classification/symbol_alexnet.R index ec768c9adb14..b6698288cd42 100644 --- a/example/image-classification/symbol_alexnet.R +++ b/example/image-classification/symbol_alexnet.R @@ -5,13 +5,13 @@ get_symbol <- function(num_classes = 1000) { # stage 1 conv1 <- mx.symbol.Convolution(data = input_data, kernel = c(11, 11), stride = c(4, 4), num_filter = 96) relu1 <- mx.symbol.Activation(data = conv1, act_type = "relu") - pool1 <- mx.symbol.Pooling(data = relu1, pool_type = "max", kernel = c(3, 3), stride = c(2, 2)) - lrn1 <- mx.symbol.LRN(data = pool1, alpha = 0.0001, beta = 0.75, knorm = 1, nsize = 5) + lrn1 <- mx.symbol.LRN(data = relu1, alpha = 0.0001, beta = 0.75, knorm = 2, nsize = 5) + pool1 <- mx.symbol.Pooling(data = lrn1, kernel = c(3, 3), stride = c(2, 2), pool_type = "max") # stage 2 conv2 <- mx.symbol.Convolution(data = lrn1, kernel = c(5, 5), pad = c(2, 2), num_filter = 256) relu2 <- mx.symbol.Activation(data = conv2, act_type = "relu") - pool2 <- mx.symbol.Pooling(data = relu2, kernel = c(3, 3), stride = c(2, 2), pool_type = "max") - lrn2 <- mx.symbol.LRN(data = pool2, alpha = 0.0001, beta = 0.75, knorm = 1, nsize = 5) + lrn2 <- mx.symbol.LRN(data = relu2, alpha = 0.0001, beta = 0.75, knorm = 2, nsize = 5) + pool2 <- mx.symbol.Pooling(data = lrn2, kernel = c(3, 3), stride = c(2, 2), pool_type = "max") # stage 3 conv3 <- mx.symbol.Convolution(data = lrn2, kernel = c(3, 3), pad = c(1, 1), num_filter = 384) relu3 <- mx.symbol.Activation(data = conv3, act_type = "relu") diff --git a/example/image-classification/symbol_unet.R b/example/image-classification/symbol_unet.R deleted file mode 100644 index e15b48a4a005..000000000000 --- a/example/image-classification/symbol_unet.R +++ /dev/null @@ -1,81 +0,0 @@ -library(mxnet) - -convolution_module <- function(net, kernel_size, pad_size, - filter_count, stride = c(1, 1), work_space = 2048, - batch_norm = TRUE, down_pool = FALSE, up_pool = FALSE, - act_type = "relu", convolution = TRUE) { - if (up_pool) { - net = mx.symbol.Deconvolution(net, kernel = c(2, 2), pad = c(0, 0), - stride = c(2, 2), num_filter = filter_count, workspace = work_space) - net = mx.symbol.BatchNorm(net) - if (act_type != "") { - net = mx.symbol.Activation(net, act_type = act_type) - } - } - if (convolution) { - conv = mx.symbol.Convolution(data = net, kernel = kernel_size, stride = stride, - pad = pad_size, num_filter = filter_count, workspace = work_space) - net = conv - } - - if (batch_norm) { - net = mx.symbol.BatchNorm(net) - } - - if (act_type != "") { - net = mx.symbol.Activation(net, act_type = act_type) - } - - if (down_pool) { - pool = mx.symbol.Pooling(net, pool_type = "max", kernel = c(2, 2), stride = c(2, 2)) - net = pool - } - return(net) -} - -get_symbol <- function(num_classes = 10) { - data = mx.symbol.Variable('data') - kernel_size = c(3, 3) - pad_size = c(1, 1) - filter_count = 32 - pool1 = convolution_module(data, kernel_size, pad_size, filter_count = filter_count, down_pool = TRUE) - net = pool1 - pool2 = convolution_module(net, kernel_size, pad_size, filter_count = filter_count * 2, down_pool = TRUE) - net = pool2 - pool3 = convolution_module(net, kernel_size, pad_size, filter_count = filter_count * 4, down_pool = TRUE) - net = pool3 - pool4 = convolution_module(net, - kernel_size, - pad_size, - filter_count = filter_count * 4, - down_pool = TRUE) - net = pool4 - net = mx.symbol.Dropout(net) - pool5 = convolution_module(net, kernel_size, pad_size, filter_count = filter_count * 8, down_pool = TRUE) - net = pool5 - net = convolution_module(net, kernel_size, pad_size, filter_count = filter_count * 4, up_pool = TRUE) - net = convolution_module(net, kernel_size, pad_size, filter_count = filter_count * 4, up_pool = TRUE) - - # dirty "CROP" to wanted size... I was on old MxNet branch so used conv instead of crop for cropping - net = convolution_module(net, c(4, 4), c(0, 0), filter_count = filter_count * 4) - - net = mx.symbol.Concat(c(pool3, net), num.args = 2) - net = mx.symbol.Dropout(net) - net = convolution_module(net, kernel_size, pad_size, filter_count = filter_count * 4) - net = convolution_module(net, kernel_size, pad_size, filter_count = filter_count * 4, up_pool = TRUE) - - net = mx.symbol.Concat(c(pool2, net), num.args = 2) - net = mx.symbol.Dropout(net) - net = convolution_module(net, kernel_size, pad_size, filter_count = filter_count * 4) - net = convolution_module(net, kernel_size, pad_size, - filter_count = filter_count * 4, up_pool = TRUE) - convolution_module(net, kernel_size, pad_size, filter_count = filter_count * 4) - net = mx.symbol.Concat(c(pool1, net), num.args = 2) - net = mx.symbol.Dropout(net) - net = convolution_module(net, kernel_size, pad_size, filter_count = filter_count * 2) - net = convolution_module(net, kernel_size, pad_size, filter_count = filter_count * 2, up_pool = TRUE) - net = mx.symbol.Flatten(net) - net = mx.symbol.FullyConnected(data = net, num_hidden = num_classes) - net = mx.symbol.SoftmaxOutput(data = net, name = 'softmax') - return(net) -} \ No newline at end of file diff --git a/example/image-classification/symbols/alexnet.py b/example/image-classification/symbols/alexnet.py index 4931c269352b..f945b9f87cd9 100755 --- a/example/image-classification/symbols/alexnet.py +++ b/example/image-classification/symbols/alexnet.py @@ -1,46 +1,68 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + """ Reference: Krizhevsky, Alex, Ilya Sutskever, and Geoffrey E. Hinton. "Imagenet classification with deep convolutional neural networks." Advances in neural information processing systems. 2012. """ import mxnet as mx +import numpy as np -def get_symbol(num_classes, **kwargs): - input_data = mx.symbol.Variable(name="data") +def get_symbol(num_classes, dtype='float32', **kwargs): + input_data = mx.sym.Variable(name="data") + if dtype == 'float16': + input_data = mx.sym.Cast(data=input_data, dtype=np.float16) # stage 1 - conv1 = mx.symbol.Convolution(name='conv1', + conv1 = mx.sym.Convolution(name='conv1', data=input_data, kernel=(11, 11), stride=(4, 4), num_filter=96) - relu1 = mx.symbol.Activation(data=conv1, act_type="relu") - lrn1 = mx.symbol.LRN(data=relu1, alpha=0.0001, beta=0.75, knorm=2, nsize=5) - pool1 = mx.symbol.Pooling( + relu1 = mx.sym.Activation(data=conv1, act_type="relu") + lrn1 = mx.sym.LRN(data=relu1, alpha=0.0001, beta=0.75, knorm=2, nsize=5) + pool1 = mx.sym.Pooling( data=lrn1, pool_type="max", kernel=(3, 3), stride=(2,2)) # stage 2 - conv2 = mx.symbol.Convolution(name='conv2', + conv2 = mx.sym.Convolution(name='conv2', data=pool1, kernel=(5, 5), pad=(2, 2), num_filter=256) - relu2 = mx.symbol.Activation(data=conv2, act_type="relu") - lrn2 = mx.symbol.LRN(data=relu2, alpha=0.0001, beta=0.75, knorm=2, nsize=5) - pool2 = mx.symbol.Pooling(data=lrn2, kernel=(3, 3), stride=(2, 2), pool_type="max") + relu2 = mx.sym.Activation(data=conv2, act_type="relu") + lrn2 = mx.sym.LRN(data=relu2, alpha=0.0001, beta=0.75, knorm=2, nsize=5) + pool2 = mx.sym.Pooling(data=lrn2, kernel=(3, 3), stride=(2, 2), pool_type="max") # stage 3 - conv3 = mx.symbol.Convolution(name='conv3', + conv3 = mx.sym.Convolution(name='conv3', data=pool2, kernel=(3, 3), pad=(1, 1), num_filter=384) - relu3 = mx.symbol.Activation(data=conv3, act_type="relu") - conv4 = mx.symbol.Convolution(name='conv4', + relu3 = mx.sym.Activation(data=conv3, act_type="relu") + conv4 = mx.sym.Convolution(name='conv4', data=relu3, kernel=(3, 3), pad=(1, 1), num_filter=384) - relu4 = mx.symbol.Activation(data=conv4, act_type="relu") - conv5 = mx.symbol.Convolution(name='conv5', + relu4 = mx.sym.Activation(data=conv4, act_type="relu") + conv5 = mx.sym.Convolution(name='conv5', data=relu4, kernel=(3, 3), pad=(1, 1), num_filter=256) - relu5 = mx.symbol.Activation(data=conv5, act_type="relu") - pool3 = mx.symbol.Pooling(data=relu5, kernel=(3, 3), stride=(2, 2), pool_type="max") + relu5 = mx.sym.Activation(data=conv5, act_type="relu") + pool3 = mx.sym.Pooling(data=relu5, kernel=(3, 3), stride=(2, 2), pool_type="max") # stage 4 - flatten = mx.symbol.Flatten(data=pool3) - fc1 = mx.symbol.FullyConnected(name='fc1', data=flatten, num_hidden=4096) - relu6 = mx.symbol.Activation(data=fc1, act_type="relu") - dropout1 = mx.symbol.Dropout(data=relu6, p=0.5) + flatten = mx.sym.Flatten(data=pool3) + fc1 = mx.sym.FullyConnected(name='fc1', data=flatten, num_hidden=4096) + relu6 = mx.sym.Activation(data=fc1, act_type="relu") + dropout1 = mx.sym.Dropout(data=relu6, p=0.5) # stage 5 - fc2 = mx.symbol.FullyConnected(name='fc2', data=dropout1, num_hidden=4096) - relu7 = mx.symbol.Activation(data=fc2, act_type="relu") - dropout2 = mx.symbol.Dropout(data=relu7, p=0.5) + fc2 = mx.sym.FullyConnected(name='fc2', data=dropout1, num_hidden=4096) + relu7 = mx.sym.Activation(data=fc2, act_type="relu") + dropout2 = mx.sym.Dropout(data=relu7, p=0.5) # stage 6 - fc3 = mx.symbol.FullyConnected(name='fc3', data=dropout2, num_hidden=num_classes) - softmax = mx.symbol.SoftmaxOutput(data=fc3, name='softmax') + fc3 = mx.sym.FullyConnected(name='fc3', data=dropout2, num_hidden=num_classes) + if dtype == 'float16': + fc3 = mx.sym.Cast(data=fc3, dtype=np.float32) + softmax = mx.sym.SoftmaxOutput(data=fc3, name='softmax') return softmax diff --git a/example/image-classification/symbols/alexnet_fp16.py b/example/image-classification/symbols/alexnet_fp16.py deleted file mode 100755 index 94440812618f..000000000000 --- a/example/image-classification/symbols/alexnet_fp16.py +++ /dev/null @@ -1,85 +0,0 @@ -""" -Reference: - -Krizhevsky, Alex, Ilya Sutskever, and Geoffrey E. Hinton. "Imagenet classification with deep convolutional neural networks." Advances in neural information processing systems. 2012. -""" -import mxnet as mx -import numpy as np - -def get_symbol(num_classes, **kwargs): - input_data = mx.symbol.Variable(name="data") - input_data = mx.symbol.Cast(data=input_data, dtype=np.float16) - # stage 1 - weight = mx.symbol.Variable(name='conv1_weight', dtype=np.float32) - bias = mx.symbol.Variable(name='conv1_bias', dtype=np.float32) - weight = mx.symbol.Cast(data=weight, dtype=np.float16) - bias = mx.symbol.Cast(data=bias, dtype=np.float16) - conv1 = mx.symbol.Convolution(name='conv1', - data=input_data, weight=weight, bias=bias, kernel=(11, 11), stride=(4, 4), num_filter=96) - relu1 = mx.symbol.Activation(data=conv1, act_type="relu") - lrn1 = mx.symbol.LRN(data=relu1, alpha=0.0001, beta=0.75, knorm=2, nsize=5) - pool1 = mx.symbol.Pooling( - data=lrn1, pool_type="max", kernel=(3, 3), stride=(2,2)) - # stage 2 - weight = mx.symbol.Variable(name='conv2_weight', dtype=np.float32) - bias = mx.symbol.Variable(name='conv2_bias', dtype=np.float32) - weight = mx.symbol.Cast(data=weight, dtype=np.float16) - bias = mx.symbol.Cast(data=bias, dtype=np.float16) - conv2 = mx.symbol.Convolution(name='conv2', - data=pool1, weight=weight, bias=bias, kernel=(5, 5), pad=(2, 2), num_filter=256) - relu2 = mx.symbol.Activation(data=conv2, act_type="relu") - lrn2 = mx.symbol.LRN(data=relu2, alpha=0.0001, beta=0.75, knorm=2, nsize=5) - pool2 = mx.symbol.Pooling(data=lrn2, kernel=(3, 3), stride=(2, 2), pool_type="max") - # stage 3 - weight = mx.symbol.Variable(name='conv3_weight', dtype=np.float32) - bias = mx.symbol.Variable(name='conv3_bias', dtype=np.float32) - weight = mx.symbol.Cast(data=weight, dtype=np.float16) - bias = mx.symbol.Cast(data=bias, dtype=np.float16) - conv3 = mx.symbol.Convolution(name='conv3', - data=pool2, weight=weight, bias=bias, kernel=(3, 3), pad=(1, 1), num_filter=384) - relu3 = mx.symbol.Activation(data=conv3, act_type="relu") - weight = mx.symbol.Variable(name='conv4_weight', dtype=np.float32) - bias = mx.symbol.Variable(name='conv4_bias', dtype=np.float32) - weight = mx.symbol.Cast(data=weight, dtype=np.float16) - bias = mx.symbol.Cast(data=bias, dtype=np.float16) - conv4 = mx.symbol.Convolution(name='conv4', - data=relu3, weight=weight, bias=bias, kernel=(3, 3), pad=(1, 1), num_filter=384) - relu4 = mx.symbol.Activation(data=conv4, act_type="relu") - weight = mx.symbol.Variable(name='conv5_weight', dtype=np.float32) - bias = mx.symbol.Variable(name='conv5_bias', dtype=np.float32) - weight = mx.symbol.Cast(data=weight, dtype=np.float16) - bias = mx.symbol.Cast(data=bias, dtype=np.float16) - conv5 = mx.symbol.Convolution(name='conv5', - data=relu4, weight=weight, bias=bias, kernel=(3, 3), pad=(1, 1), num_filter=256) - relu5 = mx.symbol.Activation(data=conv5, act_type="relu") - pool3 = mx.symbol.Pooling(data=relu5, kernel=(3, 3), stride=(2, 2), pool_type="max") - # stage 4 - flatten = mx.symbol.Flatten(data=pool3) - weight = mx.symbol.Variable(name='fc1_weight', dtype=np.float32) - bias = mx.symbol.Variable(name='fc1_bias', dtype=np.float32) - weight = mx.symbol.Cast(data=weight, dtype=np.float16) - bias = mx.symbol.Cast(data=bias, dtype=np.float16) - fc1 = mx.symbol.FullyConnected(name='fc1', data=flatten, weight=weight, bias=bias, - num_hidden=4096) - relu6 = mx.symbol.Activation(data=fc1, act_type="relu") - dropout1 = mx.symbol.Dropout(data=relu6, p=0.5) - # stage 5 - weight = mx.symbol.Variable(name='fc2_weight', dtype=np.float32) - bias = mx.symbol.Variable(name='fc2_bias', dtype=np.float32) - weight = mx.symbol.Cast(data=weight, dtype=np.float16) - bias = mx.symbol.Cast(data=bias, dtype=np.float16) - fc2 = mx.symbol.FullyConnected(name='fc2', data=dropout1, weight=weight, bias=bias, - num_hidden=4096) - relu7 = mx.symbol.Activation(data=fc2, act_type="relu") - dropout2 = mx.symbol.Dropout(data=relu7, p=0.5) - # stage 6 - weight = mx.symbol.Variable(name='fc3_weight', dtype=np.float32) - bias = mx.symbol.Variable(name='fc3_bias', dtype=np.float32) - weight = mx.symbol.Cast(data=weight, dtype=np.float16) - bias = mx.symbol.Cast(data=bias, dtype=np.float16) - fc3 = mx.symbol.FullyConnected(name='fc3', data=dropout2, weight=weight, bias=bias, - num_hidden=num_classes) - label = mx.symbol.Variable(name='softmax_label') - label = mx.symbol.Cast(data=label, dtype=np.float16) - softmax = mx.symbol.SoftmaxOutput(data=fc3, name='softmax', label=label) - return softmax diff --git a/example/image-classification/symbols/googlenet.py b/example/image-classification/symbols/googlenet.py index cc8c7adc6540..05f33da5d884 100644 --- a/example/image-classification/symbols/googlenet.py +++ b/example/image-classification/symbols/googlenet.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + """References: Szegedy, Christian, Wei Liu, Yangqing Jia, Pierre Sermanet, Scott Reed, Dragomir diff --git a/example/image-classification/symbols/inception-bn.py b/example/image-classification/symbols/inception-bn.py index 7dae9ad053e0..84934a5f72aa 100644 --- a/example/image-classification/symbols/inception-bn.py +++ b/example/image-classification/symbols/inception-bn.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + """ Inception + BN, suitable for images with around 224 x 224 diff --git a/example/image-classification/symbols/inception-resnet-v2.py b/example/image-classification/symbols/inception-resnet-v2.py index b2b0c6023ac1..5f313351eab2 100644 --- a/example/image-classification/symbols/inception-resnet-v2.py +++ b/example/image-classification/symbols/inception-resnet-v2.py @@ -1,9 +1,26 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + """ -Contains the definition of the Inception Resnet V2 architecture. -As described in http://arxiv.org/abs/1602.07261. -Inception-v4, Inception-ResNet and the Impact of Residual Connections -on Learning -Christian Szegedy, Sergey Ioffe, Vincent Vanhoucke, Alex Alemi +Contains the definition of the Inception Resnet V2 architecture. +As described in http://arxiv.org/abs/1602.07261. +Inception-v4, Inception-ResNet and the Impact of Residual Connections +on Learning +Christian Szegedy, Sergey Ioffe, Vincent Vanhoucke, Alex Alemi """ import mxnet as mx diff --git a/example/image-classification/symbols/inception-v3.py b/example/image-classification/symbols/inception-v3.py index 1c38ae6d57c9..5108579ffd3a 100644 --- a/example/image-classification/symbols/inception-v3.py +++ b/example/image-classification/symbols/inception-v3.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + """ Inception V3, suitable for images with around 299 x 299 @@ -6,6 +23,7 @@ Szegedy, Christian, et al. "Rethinking the Inception Architecture for Computer Vision." arXiv preprint arXiv:1512.00567 (2015). """ import mxnet as mx +import numpy as np def Conv(data, num_filter, kernel=(1, 1), stride=(1, 1), pad=(0, 0), name=None, suffix=''): conv = mx.sym.Convolution(data=data, num_filter=num_filter, kernel=kernel, stride=stride, pad=pad, no_bias=True, name='%s%s_conv2d' %(name, suffix)) @@ -41,7 +59,7 @@ def Inception7B(data, tower_d3x3 = Conv(data, num_d3x3_red, name=('%s_tower' % name), suffix='_conv') tower_d3x3 = Conv(tower_d3x3, num_d3x3_1, kernel=(3, 3), pad=(1, 1), stride=(1, 1), name=('%s_tower' % name), suffix='_conv_1') tower_d3x3 = Conv(tower_d3x3, num_d3x3_2, kernel=(3, 3), pad=(0, 0), stride=(2, 2), name=('%s_tower' % name), suffix='_conv_2') - pooling = mx.symbol.Pooling(data=data, kernel=(3, 3), stride=(2, 2), pad=(0,0), pool_type="max", name=('max_pool_%s_pool' % name)) + pooling = mx.sym.Pooling(data=data, kernel=(3, 3), stride=(2, 2), pad=(0,0), pool_type="max", name=('max_pool_%s_pool' % name)) concat = mx.sym.Concat(*[tower_3x3, tower_d3x3, pooling], name='ch_concat_%s_chconcat' % name) return concat @@ -104,8 +122,13 @@ def Inception7E(data, # In[49]: -def get_symbol(num_classes=1000, **kwargs): - data = mx.symbol.Variable(name="data") +def get_symbol(num_classes=1000, dtype='float32', **kwargs): + data = mx.sym.Variable(name="data") + if dtype == 'float32': + data = mx.sym.identity(data=data, name='id') + else: + if dtype == 'float16': + data = mx.sym.Cast(data=data, dtype=np.float16) # stage 1 conv = Conv(data, 32, kernel=(3, 3), stride=(2, 2), name="conv") conv_1 = Conv(conv, 32, kernel=(3, 3), name="conv_1") @@ -163,6 +186,8 @@ def get_symbol(num_classes=1000, **kwargs): # pool pool = mx.sym.Pooling(data=in5b, kernel=(8, 8), stride=(1, 1), pool_type="avg", name="global_pool") flatten = mx.sym.Flatten(data=pool, name="flatten") - fc1 = mx.symbol.FullyConnected(data=flatten, num_hidden=num_classes, name='fc1') - softmax = mx.symbol.SoftmaxOutput(data=fc1, name='softmax') + fc1 = mx.sym.FullyConnected(data=flatten, num_hidden=num_classes, name='fc1') + if dtype == 'float16': + fc1 = mx.sym.Cast(data=fc1, dtype=np.float32) + softmax = mx.sym.SoftmaxOutput(data=fc1, name='softmax') return softmax diff --git a/example/image-classification/symbols/inception-v4.py b/example/image-classification/symbols/inception-v4.py new file mode 100644 index 000000000000..2b4fe6fbb0c7 --- /dev/null +++ b/example/image-classification/symbols/inception-v4.py @@ -0,0 +1,215 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +# -*- coding:utf-8 -*- +__author__ = 'zhangshuai' +modified_date = '16/7/5' +__modify__ = 'anchengwu' +modified_date = '17/2/22' + +''' +Inception v4 , suittable for image with around 299 x 299 + +Reference: + Inception-v4, Inception-ResNet and the Impact of Residual Connections on Learning + Christian Szegedy, Sergey Ioffe, Vincent Vanhoucke + arXiv.1602.07261 +''' +import mxnet as mx +import numpy as np + +def Conv(data, num_filter, kernel=(1, 1), stride=(1, 1), pad=(0, 0), name=None, suffix=''): + conv = mx.sym.Convolution(data=data, num_filter=num_filter, kernel=kernel, stride=stride, pad=pad, no_bias=True, name='%s%s_conv2d' %(name, suffix)) + bn = mx.sym.BatchNorm(data=conv, name='%s%s_batchnorm' %(name, suffix), fix_gamma=True) + act = mx.sym.Activation(data=bn, act_type='relu', name='%s%s_relu' %(name, suffix)) + + return act + + +def Inception_stem(data, name= None): + c = Conv(data, 32, kernel=(3, 3), stride=(2, 2), name='%s_conv1_3*3' %name) + c = Conv(c, 32, kernel=(3, 3), name='%s_conv2_3*3' %name) + c = Conv(c, 64, kernel=(3, 3), pad=(1, 1), name='%s_conv3_3*3' %name) + + p1 = mx.sym.Pooling(c, kernel=(3, 3), stride=(2, 2), pool_type='max', name='%s_maxpool_1' %name) + c2 = Conv(c, 96, kernel=(3, 3), stride=(2, 2), name='%s_conv4_3*3' %name) + concat = mx.sym.Concat(*[p1, c2], name='%s_concat_1' %name) + + c1 = Conv(concat, 64, kernel=(1, 1), pad=(0, 0), name='%s_conv5_1*1' %name) + c1 = Conv(c1, 96, kernel=(3, 3), name='%s_conv6_3*3' %name) + + c2 = Conv(concat, 64, kernel=(1, 1), pad=(0, 0), name='%s_conv7_1*1' %name) + c2 = Conv(c2, 64, kernel=(7, 1), pad=(3, 0), name='%s_conv8_7*1' %name) + c2 = Conv(c2, 64, kernel=(1, 7), pad=(0, 3), name='%s_conv9_1*7' %name) + c2 = Conv(c2, 96, kernel=(3, 3), pad=(0, 0), name='%s_conv10_3*3' %name) + + concat = mx.sym.Concat(*[c1, c2], name='%s_concat_2' %name) + + c1 = Conv(concat, 192, kernel=(3, 3), stride=(2, 2), name='%s_conv11_3*3' %name) + p1 = mx.sym.Pooling(concat, kernel=(3, 3), stride=(2, 2), pool_type='max', name='%s_maxpool_2' %name) + + concat = mx.sym.Concat(*[c1, p1], name='%s_concat_3' %name) + + return concat + + +def InceptionA(input, name=None): + p1 = mx.sym.Pooling(input, kernel=(3, 3), pad=(1, 1), pool_type='avg', name='%s_avgpool_1' %name) + c1 = Conv(p1, 96, kernel=(1, 1), pad=(0, 0), name='%s_conv1_1*1' %name) + + c2 = Conv(input, 96, kernel=(1, 1), pad=(0, 0), name='%s_conv2_1*1' %name) + + c3 = Conv(input, 64, kernel=(1, 1), pad=(0, 0), name='%s_conv3_1*1' %name) + c3 = Conv(c3, 96, kernel=(3, 3), pad=(1, 1), name='%s_conv4_3*3' %name) + + c4 = Conv(input, 64, kernel=(1, 1), pad=(0, 0), name='%s_conv5_1*1' % name) + c4 = Conv(c4, 96, kernel=(3, 3), pad=(1, 1), name='%s_conv6_3*3' % name) + c4 = Conv(c4, 96, kernel=(3, 3), pad=(1, 1), name='%s_conv7_3*3' %name) + + concat = mx.sym.Concat(*[c1, c2, c3, c4], name='%s_concat_1' %name) + + return concat + + +def ReductionA(input, name=None): + p1 = mx.sym.Pooling(input, kernel=(3, 3), stride=(2, 2), pool_type='max', name='%s_maxpool_1' %name) + + c2 = Conv(input, 384, kernel=(3, 3), stride=(2, 2), name='%s_conv1_3*3' %name) + + c3 = Conv(input, 192, kernel=(1, 1), pad=(0, 0), name='%s_conv2_1*1' %name) + c3 = Conv(c3, 224, kernel=(3, 3), pad=(1, 1), name='%s_conv3_3*3' %name) + c3 = Conv(c3, 256, kernel=(3, 3), stride=(2, 2), pad=(0, 0), name='%s_conv4_3*3' %name) + + concat = mx.sym.Concat(*[p1, c2, c3], name='%s_concat_1' %name) + + return concat + +def InceptionB(input, name=None): + p1 = mx.sym.Pooling(input, kernel=(3, 3), pad=(1, 1), pool_type='avg', name='%s_avgpool_1' %name) + c1 = Conv(p1, 128, kernel=(1, 1), pad=(0, 0), name='%s_conv1_1*1' %name) + + c2 = Conv(input, 384, kernel=(1, 1), pad=(0, 0), name='%s_conv2_1*1' %name) + + c3 = Conv(input, 192, kernel=(1, 1), pad=(0, 0), name='%s_conv3_1*1' %name) + c3 = Conv(c3, 224, kernel=(1, 7), pad=(0, 3), name='%s_conv4_1*7' %name) + #paper wrong + c3 = Conv(c3, 256, kernel=(7, 1), pad=(3, 0), name='%s_conv5_1*7' %name) + + c4 = Conv(input, 192, kernel=(1, 1), pad=(0, 0), name='%s_conv6_1*1' %name) + c4 = Conv(c4, 192, kernel=(1, 7), pad=(0, 3), name='%s_conv7_1*7' %name) + c4 = Conv(c4, 224, kernel=(7, 1), pad=(3, 0), name='%s_conv8_7*1' %name) + c4 = Conv(c4, 224, kernel=(1, 7), pad=(0, 3), name='%s_conv9_1*7' %name) + c4 = Conv(c4, 256, kernel=(7, 1), pad=(3, 0), name='%s_conv10_7*1' %name) + + concat = mx.sym.Concat(*[c1, c2, c3, c4], name='%s_concat_1' %name) + + return concat + +def ReductionB(input,name=None): + p1 = mx.sym.Pooling(input, kernel=(3, 3), stride=(2, 2), pool_type='max', name='%s_maxpool_1' %name) + + c2 = Conv(input, 192, kernel=(1, 1), pad=(0, 0), name='%s_conv1_1*1' %name) + c2 = Conv(c2, 192, kernel=(3, 3), stride=(2, 2), name='%s_conv2_3*3' %name) + + c3 = Conv(input, 256, kernel=(1, 1), pad=(0, 0), name='%s_conv3_1*1' %name) + c3 = Conv(c3, 256, kernel=(1, 7), pad=(0, 3), name='%s_conv4_1*7' %name) + c3 = Conv(c3, 320, kernel=(7, 1), pad=(3, 0), name='%s_conv5_7*1' %name) + c3 = Conv(c3, 320, kernel=(3, 3), stride=(2, 2), name='%s_conv6_3*3' %name) + + concat = mx.sym.Concat(*[p1, c2, c3], name='%s_concat_1' %name) + + return concat + + +def InceptionC(input, name=None): + p1 = mx.sym.Pooling(input, kernel=(3, 3), pad=(1, 1), pool_type='avg', name='%s_avgpool_1' %name) + c1 = Conv(p1, 256, kernel=(1, 1), pad=(0, 0), name='%s_conv1_1*1' %name) + + c2 = Conv(input, 256, kernel=(1, 1), pad=(0, 0), name='%s_conv2_1*1' %name) + + c3 = Conv(input, 384, kernel=(1, 1), pad=(0, 0), name='%s_conv3_1*1' %name) + c3_1 = Conv(c3, 256, kernel=(1, 3), pad=(0, 1), name='%s_conv4_3*1' %name) + c3_2 = Conv(c3, 256, kernel=(3, 1), pad=(1, 0), name='%s_conv5_1*3' %name) + + c4 = Conv(input, 384, kernel=(1, 1), pad=(0, 0), name='%s_conv6_1*1' %name) + c4 = Conv(c4, 448, kernel=(1, 3), pad=(0, 1), name='%s_conv7_1*3' %name) + c4 = Conv(c4, 512, kernel=(3, 1), pad=(1, 0), name='%s_conv8_3*1' %name) + c4_1 = Conv(c4, 256, kernel=(3, 1), pad=(1, 0), name='%s_conv9_1*3' %name) + c4_2 = Conv(c4, 256, kernel=(1, 3), pad=(0, 1), name='%s_conv10_3*1' %name) + + concat = mx.sym.Concat(*[c1, c2, c3_1, c3_2, c4_1, c4_2], name='%s_concat' %name) + + return concat + + +def get_symbol(num_classes=1000, dtype='float32', **kwargs): + data = mx.sym.Variable(name="data") + if dtype == 'float32': + data = mx.sym.identity(data=data, name='id') + else: + if dtype == 'float16': + data = mx.sym.Cast(data=data, dtype=np.float16) + x = Inception_stem(data, name='in_stem') + + #4 * InceptionA + # x = InceptionA(x, name='in1A') + # x = InceptionA(x, name='in2A') + # x = InceptionA(x, name='in3A') + # x = InceptionA(x, name='in4A') + + for i in range(4): + x = InceptionA(x, name='in%dA' %(i+1)) + + #Reduction A + x = ReductionA(x, name='re1A') + + #7 * InceptionB + # x = InceptionB(x, name='in1B') + # x = InceptionB(x, name='in2B') + # x = InceptionB(x, name='in3B') + # x = InceptionB(x, name='in4B') + # x = InceptionB(x, name='in5B') + # x = InceptionB(x, name='in6B') + # x = InceptionB(x, name='in7B') + + for i in range(7): + x = InceptionB(x, name='in%dB' %(i+1)) + + #ReductionB + x = ReductionB(x, name='re1B') + + #3 * InceptionC + # x = InceptionC(x, name='in1C') + # x = InceptionC(x, name='in2C') + # x = InceptionC(x, name='in3C') + + for i in range(3): + x = InceptionC(x, name='in%dC' %(i+1)) + + #Average Pooling + x = mx.sym.Pooling(x, kernel=(8, 8), pad=(1, 1), pool_type='avg', name='global_avgpool') + + #Dropout + x = mx.sym.Dropout(x, p=0.2) + + flatten = mx.sym.Flatten(x, name='flatten') + fc1 = mx.sym.FullyConnected(flatten, num_hidden=num_classes, name='fc1') + if dtype == 'float16': + fc1 = mx.sym.Cast(data=fc1, dtype=np.float32) + softmax = mx.sym.SoftmaxOutput(fc1, name='softmax') + + return softmax diff --git a/example/image-classification/symbols/lenet.py b/example/image-classification/symbols/lenet.py index f6cfd6893afe..f2cc106f60ac 100644 --- a/example/image-classification/symbols/lenet.py +++ b/example/image-classification/symbols/lenet.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + """ LeCun, Yann, Leon Bottou, Yoshua Bengio, and Patrick Haffner. Gradient-based learning applied to document recognition. @@ -5,9 +22,25 @@ """ import mxnet as mx +def get_loc(data, attr={'lr_mult':'0.01'}): + """ + the localisation network in lenet-stn, it will increase acc about more than 1%, + when num-epoch >=15 + """ + loc = mx.symbol.Convolution(data=data, num_filter=30, kernel=(5, 5), stride=(2,2)) + loc = mx.symbol.Activation(data = loc, act_type='relu') + loc = mx.symbol.Pooling(data=loc, kernel=(2, 2), stride=(2, 2), pool_type='max') + loc = mx.symbol.Convolution(data=loc, num_filter=60, kernel=(3, 3), stride=(1,1), pad=(1, 1)) + loc = mx.symbol.Activation(data = loc, act_type='relu') + loc = mx.symbol.Pooling(data=loc, global_pool=True, kernel=(2, 2), pool_type='avg') + loc = mx.symbol.Flatten(data=loc) + loc = mx.symbol.FullyConnected(data=loc, num_hidden=6, name="stn_loc", attr=attr) + return loc + + def get_symbol(num_classes=10, add_stn=False, **kwargs): data = mx.symbol.Variable('data') - if(add_stn): + if add_stn: data = mx.sym.SpatialTransformer(data=data, loc=get_loc(data), target_shape = (28,28), transform_type="affine", sampler_type="bilinear") # first conv diff --git a/example/image-classification/symbols/mlp.py b/example/image-classification/symbols/mlp.py index cc569bc10917..4b190b29db9e 100644 --- a/example/image-classification/symbols/mlp.py +++ b/example/image-classification/symbols/mlp.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + """ a simple multilayer perceptron """ diff --git a/example/image-classification/symbols/mobilenet.py b/example/image-classification/symbols/mobilenet.py new file mode 100644 index 000000000000..42b963626164 --- /dev/null +++ b/example/image-classification/symbols/mobilenet.py @@ -0,0 +1,62 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +import mxnet as mx + +def Conv(data, num_filter=1, kernel=(1, 1), stride=(1, 1), pad=(0, 0), num_group=1, name=None, suffix=''): + conv = mx.sym.Convolution(data=data, num_filter=num_filter, kernel=kernel, num_group=num_group, stride=stride, pad=pad, no_bias=True, name='%s%s_conv2d' %(name, suffix)) + bn = mx.sym.BatchNorm(data=conv, name='%s%s_batchnorm' %(name, suffix), fix_gamma=True) + act = mx.sym.Activation(data=bn, act_type='relu', name='%s%s_relu' %(name, suffix)) + return act + +def get_symbol(num_classes, **kwargs): + data = mx.symbol.Variable(name="data") # 224 + conv_1 = Conv(data, num_filter=32, kernel=(3, 3), pad=(1, 1), stride=(2, 2), name="conv_1") # 224/112 + conv_2_dw = Conv(conv_1, num_group=32, num_filter=32, kernel=(3, 3), pad=(1, 1), stride=(1, 1), name="conv_2_dw") # 112/112 + conv_2 = Conv(conv_2_dw, num_filter=64, kernel=(1, 1), pad=(0, 0), stride=(1, 1), name="conv_2") # 112/112 + conv_3_dw = Conv(conv_2, num_group=64, num_filter=64, kernel=(3, 3), pad=(1, 1), stride=(2, 2), name="conv_3_dw") # 112/56 + conv_3 = Conv(conv_3_dw, num_filter=128, kernel=(1, 1), pad=(0, 0), stride=(1, 1), name="conv_3") # 56/56 + conv_4_dw = Conv(conv_3, num_group=128, num_filter=128, kernel=(3, 3), pad=(1, 1), stride=(1, 1), name="conv_4_dw") # 56/56 + conv_4 = Conv(conv_4_dw, num_filter=128, kernel=(1, 1), pad=(0, 0), stride=(1, 1), name="conv_4") # 56/56 + conv_5_dw = Conv(conv_4, num_group=128, num_filter=128, kernel=(3, 3), pad=(1, 1), stride=(2, 2), name="conv_5_dw") # 56/28 + conv_5 = Conv(conv_5_dw, num_filter=256, kernel=(1, 1), pad=(0, 0), stride=(1, 1), name="conv_5") # 28/28 + conv_6_dw = Conv(conv_5, num_group=256, num_filter=256, kernel=(3, 3), pad=(1, 1), stride=(1, 1), name="conv_6_dw") # 28/28 + conv_6 = Conv(conv_6_dw, num_filter=256, kernel=(1, 1), pad=(0, 0), stride=(1, 1), name="conv_6") # 28/28 + conv_7_dw = Conv(conv_6, num_group=256, num_filter=256, kernel=(3, 3), pad=(1, 1), stride=(2, 2), name="conv_7_dw") # 28/14 + conv_7 = Conv(conv_7_dw, num_filter=512, kernel=(1, 1), pad=(0, 0), stride=(1, 1), name="conv_7") # 14/14 + + conv_8_dw = Conv(conv_7, num_group=512, num_filter=512, kernel=(3, 3), pad=(1, 1), stride=(1, 1), name="conv_8_dw") # 14/14 + conv_8 = Conv(conv_8_dw, num_filter=512, kernel=(1, 1), pad=(0, 0), stride=(1, 1), name="conv_8") # 14/14 + conv_9_dw = Conv(conv_8, num_group=512, num_filter=512, kernel=(3, 3), pad=(1, 1), stride=(1, 1), name="conv_9_dw") # 14/14 + conv_9 = Conv(conv_9_dw, num_filter=512, kernel=(1, 1), pad=(0, 0), stride=(1, 1), name="conv_9") # 14/14 + conv_10_dw = Conv(conv_9, num_group=512, num_filter=512, kernel=(3, 3), pad=(1, 1), stride=(1, 1), name="conv_10_dw") # 14/14 + conv_10 = Conv(conv_10_dw, num_filter=512, kernel=(1, 1), pad=(0, 0), stride=(1, 1), name="conv_10") # 14/14 + conv_11_dw = Conv(conv_10, num_group=512, num_filter=512, kernel=(3, 3), pad=(1, 1), stride=(1, 1), name="conv_11_dw") # 14/14 + conv_11 = Conv(conv_11_dw, num_filter=512, kernel=(1, 1), pad=(0, 0), stride=(1, 1), name="conv_11") # 14/14 + conv_12_dw = Conv(conv_11, num_group=512, num_filter=512, kernel=(3, 3), pad=(1, 1), stride=(1, 1), name="conv_12_dw") # 14/14 + conv_12 = Conv(conv_12_dw, num_filter=512, kernel=(1, 1), pad=(0, 0), stride=(1, 1), name="conv_12") # 14/14 + + conv_13_dw = Conv(conv_12, num_group=512, num_filter=512, kernel=(3, 3), pad=(1, 1), stride=(2, 2), name="conv_13_dw") # 14/7 + conv_13 = Conv(conv_13_dw, num_filter=1024, kernel=(1, 1), pad=(0, 0), stride=(1, 1), name="conv_13") # 7/7 + conv_14_dw = Conv(conv_13, num_group=1024, num_filter=1024, kernel=(3, 3), pad=(1, 1), stride=(1, 1), name="conv_14_dw") # 7/7 + conv_14 = Conv(conv_14_dw, num_filter=1024, kernel=(1, 1), pad=(0, 0), stride=(1, 1), name="conv_14") # 7/7 + + pool = mx.sym.Pooling(data=conv_14, kernel=(7, 7), stride=(1, 1), pool_type="avg", name="global_pool") + flatten = mx.sym.Flatten(data=pool, name="flatten") + fc = mx.symbol.FullyConnected(data=flatten, num_hidden=num_classes, name='fc') + softmax = mx.symbol.SoftmaxOutput(data=fc, name='softmax') + return softmax diff --git a/example/image-classification/symbols/resnet-v1-fp16.py b/example/image-classification/symbols/resnet-v1.py similarity index 67% rename from example/image-classification/symbols/resnet-v1-fp16.py rename to example/image-classification/symbols/resnet-v1.py index 36aa51c5bd8a..e5752f775447 100755 --- a/example/image-classification/symbols/resnet-v1-fp16.py +++ b/example/image-classification/symbols/resnet-v1.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + ''' Adapted from https://github.com/tornadomeet/ResNet/blob/master/symbol_resnet.py (Original author Wei Wu) by Antti-Pekka Hynninen @@ -19,9 +36,9 @@ def residual_unit(data, num_filter, stride, dim_match, name, bottle_neck=True, b Number of output channels bnf : int Bottle neck channels factor with regard to num_filter - stride : tupe + stride : tuple Stride used in convolution - dim_match : Boolen + dim_match : Boolean True means channel number between input and output is the same, otherwise means differ name : str Base name of the operators @@ -29,61 +46,47 @@ def residual_unit(data, num_filter, stride, dim_match, name, bottle_neck=True, b Workspace used in convolution operator """ if bottle_neck: - weight = mx.symbol.Variable(name=name + '_conv1_weight', dtype=np.float32) - weight = mx.symbol.Cast(data=weight, dtype=np.float16) - conv1 = mx.sym.Convolution(data=data, weight=weight, num_filter=int(num_filter*0.25), kernel=(1,1), stride=stride, pad=(0,0), + conv1 = mx.sym.Convolution(data=data, num_filter=int(num_filter*0.25), kernel=(1,1), stride=stride, pad=(0,0), no_bias=True, workspace=workspace, name=name + '_conv1') bn1 = mx.sym.BatchNorm(data=conv1, fix_gamma=False, eps=2e-5, momentum=bn_mom, name=name + '_bn1') act1 = mx.sym.Activation(data=bn1, act_type='relu', name=name + '_relu1') - weight = mx.symbol.Variable(name=name + '_conv2_weight', dtype=np.float32) - weight = mx.symbol.Cast(data=weight, dtype=np.float16) - conv2 = mx.sym.Convolution(data=act1, weight=weight, num_filter=int(num_filter*0.25), kernel=(3,3), stride=(1,1), pad=(1,1), + conv2 = mx.sym.Convolution(data=act1, num_filter=int(num_filter*0.25), kernel=(3,3), stride=(1,1), pad=(1,1), no_bias=True, workspace=workspace, name=name + '_conv2') bn2 = mx.sym.BatchNorm(data=conv2, fix_gamma=False, eps=2e-5, momentum=bn_mom, name=name + '_bn2') act2 = mx.sym.Activation(data=bn2, act_type='relu', name=name + '_relu2') - weight = mx.symbol.Variable(name=name + '_conv3_weight', dtype=np.float32) - weight = mx.symbol.Cast(data=weight, dtype=np.float16) - conv3 = mx.sym.Convolution(data=act2, weight=weight, num_filter=num_filter, kernel=(1,1), stride=(1,1), pad=(0,0), no_bias=True, + conv3 = mx.sym.Convolution(data=act2, num_filter=num_filter, kernel=(1,1), stride=(1,1), pad=(0,0), no_bias=True, workspace=workspace, name=name + '_conv3') bn3 = mx.sym.BatchNorm(data=conv3, fix_gamma=False, eps=2e-5, momentum=bn_mom, name=name + '_bn3') if dim_match: shortcut = data else: - weight = mx.symbol.Variable(name=name + '_conv1sc_weight', dtype=np.float32) - weight = mx.symbol.Cast(data=weight, dtype=np.float16) - conv1sc = mx.sym.Convolution(data=data, weight=weight, num_filter=num_filter, kernel=(1,1), stride=stride, no_bias=True, + conv1sc = mx.sym.Convolution(data=data, num_filter=num_filter, kernel=(1,1), stride=stride, no_bias=True, workspace=workspace, name=name+'_conv1sc') shortcut = mx.sym.BatchNorm(data=conv1sc, fix_gamma=False, eps=2e-5, momentum=bn_mom, name=name + '_sc') if memonger: shortcut._set_attr(mirror_stage='True') return mx.sym.Activation(data=bn3 + shortcut, act_type='relu', name=name + '_relu3') else: - weight = mx.symbol.Variable(name=name + '_conv1_weight', dtype=np.float32) - weight = mx.symbol.Cast(data=weight, dtype=np.float16) - conv1 = mx.sym.Convolution(data=data, weight=weight, num_filter=num_filter, kernel=(3,3), stride=stride, pad=(1,1), + conv1 = mx.sym.Convolution(data=data, num_filter=num_filter, kernel=(3,3), stride=stride, pad=(1,1), no_bias=True, workspace=workspace, name=name + '_conv1') bn1 = mx.sym.BatchNorm(data=conv1, fix_gamma=False, momentum=bn_mom, eps=2e-5, name=name + '_bn1') act1 = mx.sym.Activation(data=bn1, act_type='relu', name=name + '_relu1') - weight = mx.symbol.Variable(name=name + '_conv2_weight', dtype=np.float32) - weight = mx.symbol.Cast(data=weight, dtype=np.float16) - conv2 = mx.sym.Convolution(data=act1, weight=weight, num_filter=num_filter, kernel=(3,3), stride=(1,1), pad=(1,1), + conv2 = mx.sym.Convolution(data=act1, num_filter=num_filter, kernel=(3,3), stride=(1,1), pad=(1,1), no_bias=True, workspace=workspace, name=name + '_conv2') bn2 = mx.sym.BatchNorm(data=conv2, fix_gamma=False, momentum=bn_mom, eps=2e-5, name=name + '_bn2') if dim_match: shortcut = data else: - weight = mx.symbol.Variable(name=name + '_conv1sc_weight', dtype=np.float32) - weight = mx.symbol.Cast(data=weight, dtype=np.float16) - conv1sc = mx.sym.Convolution(data=data, weight=weight, num_filter=num_filter, kernel=(1,1), stride=stride, no_bias=True, + conv1sc = mx.sym.Convolution(data=data, num_filter=num_filter, kernel=(1,1), stride=stride, no_bias=True, workspace=workspace, name=name+'_conv1sc') shortcut = mx.sym.BatchNorm(data=conv1sc, fix_gamma=False, momentum=bn_mom, eps=2e-5, name=name + '_sc') if memonger: shortcut._set_attr(mirror_stage='True') return mx.sym.Activation(data=bn2 + shortcut, act_type='relu', name=name + '_relu3') -def resnet(units, num_stages, filter_list, num_classes, image_shape, bottle_neck=True, bn_mom=0.9, workspace=256, memonger=False): +def resnet(units, num_stages, filter_list, num_classes, image_shape, bottle_neck=True, bn_mom=0.9, workspace=256, dtype='float32', memonger=False): """Return ResNet symbol of Parameters ---------- @@ -99,25 +102,29 @@ def resnet(units, num_stages, filter_list, num_classes, image_shape, bottle_neck Dataset type, only cifar10 and imagenet supports workspace : int Workspace used in convolution operator + dtype : str + Precision (float32 or float16) """ num_unit = len(units) assert(num_unit == num_stages) data = mx.sym.Variable(name='data') - data = mx.symbol.Cast(data=data, dtype=np.float16) + if dtype == 'float32': + data = mx.sym.identity(data=data, name='id') + else: + if dtype == 'float16': + data = mx.sym.Cast(data=data, dtype=np.float16) (nchannel, height, width) = image_shape - weight = mx.symbol.Variable(name='conv0_weight', dtype=np.float32) - weight = mx.symbol.Cast(data=weight, dtype=np.float16) if height <= 32: # such as cifar10 - body = mx.sym.Convolution(data=data, weight=weight, num_filter=filter_list[0], kernel=(3, 3), stride=(1,1), pad=(1, 1), + body = mx.sym.Convolution(data=data, num_filter=filter_list[0], kernel=(3, 3), stride=(1,1), pad=(1, 1), no_bias=True, name="conv0", workspace=workspace) # Is this BatchNorm supposed to be here? body = mx.sym.BatchNorm(data=body, fix_gamma=False, eps=2e-5, momentum=bn_mom, name='bn0') else: # often expected to be 224 such as imagenet - body = mx.sym.Convolution(data=data, weight=weight, num_filter=filter_list[0], kernel=(7, 7), stride=(2,2), pad=(3, 3), + body = mx.sym.Convolution(data=data, num_filter=filter_list[0], kernel=(7, 7), stride=(2,2), pad=(3, 3), no_bias=True, name="conv0", workspace=workspace) body = mx.sym.BatchNorm(data=body, fix_gamma=False, eps=2e-5, momentum=bn_mom, name='bn0') body = mx.sym.Activation(data=body, act_type='relu', name='relu0') - body = mx.symbol.Pooling(data=body, kernel=(3, 3), stride=(2,2), pad=(1,1), pool_type='max') + body = mx.sym.Pooling(data=body, kernel=(3, 3), stride=(2,2), pad=(1,1), pool_type='max') for i in range(num_stages): body = residual_unit(body, filter_list[i+1], (1 if i==0 else 2, 1 if i==0 else 2), False, @@ -129,17 +136,14 @@ def resnet(units, num_stages, filter_list, num_classes, image_shape, bottle_neck # bn1 = mx.sym.BatchNorm(data=body, fix_gamma=False, eps=2e-5, momentum=bn_mom, name='bn1') # relu1 = mx.sym.Activation(data=bn1, act_type='relu', name='relu1') # Although kernel is not used here when global_pool=True, we should put one - pool1 = mx.symbol.Pooling(data=body, global_pool=True, kernel=(7, 7), pool_type='avg', name='pool1') - flat = mx.symbol.Flatten(data=pool1) - weight = mx.symbol.Variable(name='fc1_weight', dtype=np.float32) - bias = mx.symbol.Variable(name='fc1_bias', dtype=np.float32) - weight = mx.symbol.Cast(data=weight, dtype=np.float16) - bias = mx.symbol.Cast(data=bias, dtype=np.float16) - fc1 = mx.symbol.FullyConnected(data=flat, weight=weight, bias=bias, num_hidden=num_classes, name='fc1') - fc1 = mx.symbol.Cast(data=fc1, dtype=np.float32) - return mx.symbol.SoftmaxOutput(data=fc1, name='softmax') + pool1 = mx.sym.Pooling(data=body, global_pool=True, kernel=(7, 7), pool_type='avg', name='pool1') + flat = mx.sym.Flatten(data=pool1) + fc1 = mx.sym.FullyConnected(data=flat, num_hidden=num_classes, name='fc1') + if dtype == 'float16': + fc1 = mx.sym.Cast(data=fc1, dtype=np.float32) + return mx.sym.SoftmaxOutput(data=fc1, name='softmax') -def get_symbol(num_classes, num_layers, image_shape, conv_workspace=256, **kwargs): +def get_symbol(num_classes, num_layers, image_shape, conv_workspace=256, dtype='float32', **kwargs): """ Adapted from https://github.com/tornadomeet/ResNet/blob/master/symbol_resnet.py (Original author Wei Wu) by Antti-Pekka Hynninen @@ -159,7 +163,7 @@ def get_symbol(num_classes, num_layers, image_shape, conv_workspace=256, **kwarg filter_list = [16, 16, 32, 64] bottle_neck = False else: - raise ValueError("no experiments done on num_layers {}, you can do it youself".format(num_layers)) + raise ValueError("no experiments done on num_layers {}, you can do it yourself".format(num_layers)) units = per_unit * num_stages else: if num_layers >= 50: @@ -184,7 +188,7 @@ def get_symbol(num_classes, num_layers, image_shape, conv_workspace=256, **kwarg elif num_layers == 269: units = [3, 30, 48, 8] else: - raise ValueError("no experiments done on num_layers {}, you can do it youself".format(num_layers)) + raise ValueError("no experiments done on num_layers {}, you can do it yourself".format(num_layers)) return resnet(units = units, num_stages = num_stages, @@ -192,4 +196,5 @@ def get_symbol(num_classes, num_layers, image_shape, conv_workspace=256, **kwarg num_classes = num_classes, image_shape = image_shape, bottle_neck = bottle_neck, - workspace = conv_workspace) + workspace = conv_workspace, + dtype = dtype) diff --git a/example/image-classification/symbols/resnet.py b/example/image-classification/symbols/resnet.py index 9fb24cd10c6f..be498602f0b7 100644 --- a/example/image-classification/symbols/resnet.py +++ b/example/image-classification/symbols/resnet.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + ''' Adapted from https://github.com/tornadomeet/ResNet/blob/master/symbol_resnet.py Original author Wei Wu @@ -7,6 +24,7 @@ Kaiming He, Xiangyu Zhang, Shaoqing Ren, Jian Sun. "Identity Mappings in Deep Residual Networks" ''' import mxnet as mx +import numpy as np def residual_unit(data, num_filter, stride, dim_match, name, bottle_neck=True, bn_mom=0.9, workspace=256, memonger=False): """Return ResNet Unit symbol for building ResNet @@ -18,9 +36,9 @@ def residual_unit(data, num_filter, stride, dim_match, name, bottle_neck=True, b Number of output channels bnf : int Bottle neck channels factor with regard to num_filter - stride : tupe + stride : tuple Stride used in convolution - dim_match : Boolen + dim_match : Boolean True means channel number between input and output is the same, otherwise means differ name : str Base name of the operators @@ -67,7 +85,7 @@ def residual_unit(data, num_filter, stride, dim_match, name, bottle_neck=True, b shortcut._set_attr(mirror_stage='True') return conv2 + shortcut -def resnet(units, num_stages, filter_list, num_classes, image_shape, bottle_neck=True, bn_mom=0.9, workspace=256, memonger=False): +def resnet(units, num_stages, filter_list, num_classes, image_shape, bottle_neck=True, bn_mom=0.9, workspace=256, dtype='float32', memonger=False): """Return ResNet symbol of Parameters ---------- @@ -83,10 +101,17 @@ def resnet(units, num_stages, filter_list, num_classes, image_shape, bottle_neck Dataset type, only cifar10 and imagenet supports workspace : int Workspace used in convolution operator + dtype : str + Precision (float32 or float16) """ num_unit = len(units) assert(num_unit == num_stages) data = mx.sym.Variable(name='data') + if dtype == 'float32': + data = mx.sym.identity(data=data, name='id') + else: + if dtype == 'float16': + data = mx.sym.Cast(data=data, dtype=np.float16) data = mx.sym.BatchNorm(data=data, fix_gamma=True, eps=2e-5, momentum=bn_mom, name='bn_data') (nchannel, height, width) = image_shape if height <= 32: # such as cifar10 @@ -97,7 +122,7 @@ def resnet(units, num_stages, filter_list, num_classes, image_shape, bottle_neck no_bias=True, name="conv0", workspace=workspace) body = mx.sym.BatchNorm(data=body, fix_gamma=False, eps=2e-5, momentum=bn_mom, name='bn0') body = mx.sym.Activation(data=body, act_type='relu', name='relu0') - body = mx.symbol.Pooling(data=body, kernel=(3, 3), stride=(2,2), pad=(1,1), pool_type='max') + body = mx.sym.Pooling(data=body, kernel=(3, 3), stride=(2,2), pad=(1,1), pool_type='max') for i in range(num_stages): body = residual_unit(body, filter_list[i+1], (1 if i==0 else 2, 1 if i==0 else 2), False, @@ -109,12 +134,14 @@ def resnet(units, num_stages, filter_list, num_classes, image_shape, bottle_neck bn1 = mx.sym.BatchNorm(data=body, fix_gamma=False, eps=2e-5, momentum=bn_mom, name='bn1') relu1 = mx.sym.Activation(data=bn1, act_type='relu', name='relu1') # Although kernel is not used here when global_pool=True, we should put one - pool1 = mx.symbol.Pooling(data=relu1, global_pool=True, kernel=(7, 7), pool_type='avg', name='pool1') - flat = mx.symbol.Flatten(data=pool1) - fc1 = mx.symbol.FullyConnected(data=flat, num_hidden=num_classes, name='fc1') - return mx.symbol.SoftmaxOutput(data=fc1, name='softmax') + pool1 = mx.sym.Pooling(data=relu1, global_pool=True, kernel=(7, 7), pool_type='avg', name='pool1') + flat = mx.sym.Flatten(data=pool1) + fc1 = mx.sym.FullyConnected(data=flat, num_hidden=num_classes, name='fc1') + if dtype == 'float16': + fc1 = mx.sym.Cast(data=fc1, dtype=np.float32) + return mx.sym.SoftmaxOutput(data=fc1, name='softmax') -def get_symbol(num_classes, num_layers, image_shape, conv_workspace=256, **kwargs): +def get_symbol(num_classes, num_layers, image_shape, conv_workspace=256, dtype='float32', **kwargs): """ Adapted from https://github.com/tornadomeet/ResNet/blob/master/train_resnet.py Original author Wei Wu @@ -132,7 +159,7 @@ def get_symbol(num_classes, num_layers, image_shape, conv_workspace=256, **kwarg filter_list = [16, 16, 32, 64] bottle_neck = False else: - raise ValueError("no experiments done on num_layers {}, you can do it youself".format(num_layers)) + raise ValueError("no experiments done on num_layers {}, you can do it yourself".format(num_layers)) units = per_unit * num_stages else: if num_layers >= 50: @@ -157,7 +184,7 @@ def get_symbol(num_classes, num_layers, image_shape, conv_workspace=256, **kwarg elif num_layers == 269: units = [3, 30, 48, 8] else: - raise ValueError("no experiments done on num_layers {}, you can do it youself".format(num_layers)) + raise ValueError("no experiments done on num_layers {}, you can do it yourself".format(num_layers)) return resnet(units = units, num_stages = num_stages, @@ -165,4 +192,5 @@ def get_symbol(num_classes, num_layers, image_shape, conv_workspace=256, **kwarg num_classes = num_classes, image_shape = image_shape, bottle_neck = bottle_neck, - workspace = conv_workspace) + workspace = conv_workspace, + dtype = dtype) diff --git a/example/image-classification/symbols/resnext.py b/example/image-classification/symbols/resnext.py index c4341339f689..59749430c76c 100644 --- a/example/image-classification/symbols/resnext.py +++ b/example/image-classification/symbols/resnext.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + ''' Adapted from https://github.com/tornadomeet/ResNet/blob/master/symbol_resnet.py Original author Wei Wu @@ -6,6 +23,7 @@ Saining Xie, Ross Girshick, Piotr Dollar, Zhuowen Tu, Kaiming He. "Aggregated Residual Transformations for Deep Neural Network" ''' import mxnet as mx +import numpy as np def residual_unit(data, num_filter, stride, dim_match, name, bottle_neck=True, num_group=32, bn_mom=0.9, workspace=256, memonger=False): """Return ResNet Unit symbol for building ResNet @@ -17,9 +35,9 @@ def residual_unit(data, num_filter, stride, dim_match, name, bottle_neck=True, n Number of output channels bnf : int Bottle neck channels factor with regard to num_filter - stride : tupe + stride : tuple Stride used in convolution - dim_match : Boolen + dim_match : Boolean True means channel number between input and output is the same, otherwise means differ name : str Base name of the operators @@ -28,19 +46,19 @@ def residual_unit(data, num_filter, stride, dim_match, name, bottle_neck=True, n """ if bottle_neck: # the same as https://github.com/facebook/fb.resnet.torch#notes, a bit difference with origin paper - + conv1 = mx.sym.Convolution(data=data, num_filter=int(num_filter*0.5), kernel=(1,1), stride=(1,1), pad=(0,0), no_bias=True, workspace=workspace, name=name + '_conv1') bn1 = mx.sym.BatchNorm(data=conv1, fix_gamma=False, eps=2e-5, momentum=bn_mom, name=name + '_bn1') act1 = mx.sym.Activation(data=bn1, act_type='relu', name=name + '_relu1') - + conv2 = mx.sym.Convolution(data=act1, num_filter=int(num_filter*0.5), num_group=num_group, kernel=(3,3), stride=stride, pad=(1,1), no_bias=True, workspace=workspace, name=name + '_conv2') bn2 = mx.sym.BatchNorm(data=conv2, fix_gamma=False, eps=2e-5, momentum=bn_mom, name=name + '_bn2') act2 = mx.sym.Activation(data=bn2, act_type='relu', name=name + '_relu2') - + conv3 = mx.sym.Convolution(data=act2, num_filter=num_filter, kernel=(1,1), stride=(1,1), pad=(0,0), no_bias=True, workspace=workspace, name=name + '_conv3') bn3 = mx.sym.BatchNorm(data=conv3, fix_gamma=False, eps=2e-5, momentum=bn_mom, name=name + '_bn3') @@ -57,13 +75,13 @@ def residual_unit(data, num_filter, stride, dim_match, name, bottle_neck=True, n eltwise = bn3 + shortcut return mx.sym.Activation(data=eltwise, act_type='relu', name=name + '_relu') else: - + conv1 = mx.sym.Convolution(data=data, num_filter=num_filter, kernel=(3,3), stride=stride, pad=(1,1), no_bias=True, workspace=workspace, name=name + '_conv1') bn1 = mx.sym.BatchNorm(data=conv1, fix_gamma=False, momentum=bn_mom, eps=2e-5, name=name + '_bn1') act1 = mx.sym.Activation(data=bn1, act_type='relu', name=name + '_relu1') - + conv2 = mx.sym.Convolution(data=act1, num_filter=num_filter, kernel=(3,3), stride=(1,1), pad=(1,1), no_bias=True, workspace=workspace, name=name + '_conv2') bn2 = mx.sym.BatchNorm(data=conv2, fix_gamma=False, momentum=bn_mom, eps=2e-5, name=name + '_bn2') @@ -80,7 +98,7 @@ def residual_unit(data, num_filter, stride, dim_match, name, bottle_neck=True, n eltwise = bn2 + shortcut return mx.sym.Activation(data=eltwise, act_type='relu', name=name + '_relu') -def resnext(units, num_stages, filter_list, num_classes, num_group, image_shape, bottle_neck=True, bn_mom=0.9, workspace=256, memonger=False): +def resnext(units, num_stages, filter_list, num_classes, num_group, image_shape, bottle_neck=True, bn_mom=0.9, workspace=256, dtype='float32', memonger=False): """Return ResNeXt symbol of Parameters ---------- @@ -98,10 +116,17 @@ def resnext(units, num_stages, filter_list, num_classes, num_group, image_shape, Dataset type, only cifar10 and imagenet supports workspace : int Workspace used in convolution operator + dtype : str + Precision (float32 or float16) """ num_unit = len(units) assert(num_unit == num_stages) data = mx.sym.Variable(name='data') + if dtype == 'float32': + data = mx.sym.identity(data=data, name='id') + else: + if dtype == 'float16': + data = mx.sym.Cast(data=data, dtype=np.float16) data = mx.sym.BatchNorm(data=data, fix_gamma=True, eps=2e-5, momentum=bn_mom, name='bn_data') (nchannel, height, width) = image_shape if height <= 32: # such as cifar10 @@ -112,22 +137,24 @@ def resnext(units, num_stages, filter_list, num_classes, num_group, image_shape, no_bias=True, name="conv0", workspace=workspace) body = mx.sym.BatchNorm(data=body, fix_gamma=False, eps=2e-5, momentum=bn_mom, name='bn0') body = mx.sym.Activation(data=body, act_type='relu', name='relu0') - body = mx.symbol.Pooling(data=body, kernel=(3, 3), stride=(2,2), pad=(1,1), pool_type='max') + body = mx.sym.Pooling(data=body, kernel=(3, 3), stride=(2,2), pad=(1,1), pool_type='max') for i in range(num_stages): body = residual_unit(body, filter_list[i+1], (1 if i==0 else 2, 1 if i==0 else 2), False, - name='stage%d_unit%d' % (i + 1, 1), bottle_neck=bottle_neck, num_group=num_group, + name='stage%d_unit%d' % (i + 1, 1), bottle_neck=bottle_neck, num_group=num_group, bn_mom=bn_mom, workspace=workspace, memonger=memonger) for j in range(units[i]-1): body = residual_unit(body, filter_list[i+1], (1,1), True, name='stage%d_unit%d' % (i + 1, j + 2), bottle_neck=bottle_neck, num_group=num_group, bn_mom=bn_mom, workspace=workspace, memonger=memonger) - - pool1 = mx.symbol.Pooling(data=body, global_pool=True, kernel=(7, 7), pool_type='avg', name='pool1') - flat = mx.symbol.Flatten(data=pool1) - fc1 = mx.symbol.FullyConnected(data=flat, num_hidden=num_classes, name='fc1') - return mx.symbol.SoftmaxOutput(data=fc1, name='softmax') -def get_symbol(num_classes, num_layers, image_shape, num_group=32, conv_workspace=256, **kwargs): + pool1 = mx.sym.Pooling(data=body, global_pool=True, kernel=(7, 7), pool_type='avg', name='pool1') + flat = mx.sym.Flatten(data=pool1) + fc1 = mx.sym.FullyConnected(data=flat, num_hidden=num_classes, name='fc1') + if dtype == 'float16': + fc1 = mx.sym.Cast(data=fc1, dtype=np.float32) + return mx.sym.SoftmaxOutput(data=fc1, name='softmax') + +def get_symbol(num_classes, num_layers, image_shape, num_group=32, conv_workspace=256, dtype='float32', **kwargs): """ Adapted from https://github.com/tornadomeet/ResNet/blob/master/train_resnet.py Original author Wei Wu @@ -145,7 +172,7 @@ def get_symbol(num_classes, num_layers, image_shape, num_group=32, conv_workspac filter_list = [16, 16, 32, 64] bottle_neck = False else: - raise ValueError("no experiments done on num_layers {}, you can do it youself".format(num_layers)) + raise ValueError("no experiments done on num_layers {}, you can do it yourself".format(num_layers)) units = per_unit * num_stages else: if num_layers >= 50: @@ -170,13 +197,14 @@ def get_symbol(num_classes, num_layers, image_shape, num_group=32, conv_workspac elif num_layers == 269: units = [3, 30, 48, 8] else: - raise ValueError("no experiments done on num_layers {}, you can do it youself".format(num_layers)) + raise ValueError("no experiments done on num_layers {}, you can do it yourself".format(num_layers)) return resnext(units = units, num_stages = num_stages, filter_list = filter_list, num_classes = num_classes, - num_group = num_group, + num_group = num_group, image_shape = image_shape, bottle_neck = bottle_neck, - workspace = conv_workspace) + workspace = conv_workspace, + dtype = dtype) diff --git a/example/image-classification/symbols/vgg.py b/example/image-classification/symbols/vgg.py index a7ec3860784f..ca1013621863 100644 --- a/example/image-classification/symbols/vgg.py +++ b/example/image-classification/symbols/vgg.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + """References: Simonyan, Karen, and Andrew Zisserman. "Very deep convolutional networks for @@ -6,7 +23,7 @@ import mxnet as mx def get_symbol(num_classes, **kwargs): - ## define alexnet + ## define VGG11 data = mx.symbol.Variable(name="data") # group 1 conv1_1 = mx.symbol.Convolution(data=data, kernel=(3, 3), pad=(1, 1), num_filter=64, name="conv1_1") @@ -43,7 +60,7 @@ def get_symbol(num_classes, **kwargs): relu5_1 = mx.symbol.Activation(data=conv5_1, act_type="relu", name="relu5_1") conv5_2 = mx.symbol.Convolution( data=relu5_1, kernel=(3, 3), pad=(1, 1), num_filter=512, name="conv5_2") - relu5_2 = mx.symbol.Activation(data=conv5_2, act_type="relu", name="conv1_2") + relu5_2 = mx.symbol.Activation(data=conv5_2, act_type="relu", name="relu5_2") pool5 = mx.symbol.Pooling( data=relu5_2, pool_type="max", kernel=(2, 2), stride=(2,2), name="pool5") # group 6 diff --git a/example/image-classification/test_score.py b/example/image-classification/test_score.py index 19a1d3072664..0789c9270fff 100644 --- a/example/image-classification/test_score.py +++ b/example/image-classification/test_score.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + """ test pretrained models """ diff --git a/example/image-classification/train_cifar10.py b/example/image-classification/train_cifar10.py index 0186233d6ea2..7eb56ebce36d 100644 --- a/example/image-classification/train_cifar10.py +++ b/example/image-classification/train_cifar10.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import os import argparse import logging diff --git a/example/image-classification/train_imagenet.py b/example/image-classification/train_imagenet.py index 710398b3cb57..5760a9af3782 100644 --- a/example/image-classification/train_imagenet.py +++ b/example/image-classification/train_imagenet.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import os import argparse import logging @@ -28,6 +45,7 @@ # train num_epochs = 80, lr_step_epochs = '30,60', + dtype = 'float32' ) args = parser.parse_args() diff --git a/example/image-classification/train_mnist.py b/example/image-classification/train_mnist.py index 61162e637520..2bc4289318d9 100644 --- a/example/image-classification/train_mnist.py +++ b/example/image-classification/train_mnist.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + """ Train mnist, see more explanation at http://mxnet.io/tutorials/python/mnist.html """ @@ -53,6 +70,9 @@ def get_mnist_iter(args, kv): help='the number of classes') parser.add_argument('--num-examples', type=int, default=60000, help='the number of training examples') + + parser.add_argument('--add_stn', action="store_true", default=False, help='Add Spatial Transformer Network Layer (lenet only)') + fit.add_fit_args(parser) parser.set_defaults( # network @@ -63,7 +83,7 @@ def get_mnist_iter(args, kv): disp_batches = 100, num_epochs = 20, lr = .05, - lr_step_epochs = '10', + lr_step_epochs = '10' ) args = parser.parse_args() diff --git a/example/kaggle-ndsb1/gen_img_list.py b/example/kaggle-ndsb1/gen_img_list.py index 2da5d7097d96..adfc4fe09d68 100644 --- a/example/kaggle-ndsb1/gen_img_list.py +++ b/example/kaggle-ndsb1/gen_img_list.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + from __future__ import print_function import csv import os @@ -26,7 +43,7 @@ fo_name=os.path.join(args.out_folder+args.out_file) fo = csv.writer(open(fo_name, "w"), delimiter='\t', lineterminator='\n') - + if args.train: tr_fo_name=os.path.join(args.out_folder+"tr.lst") va_fo_name=os.path.join(args.out_folder+"va.lst") @@ -58,7 +75,7 @@ #write for item in img_lst: fo.writerow(item) - + ## If training, split into train and validation lists (tr.lst and va.lst) diff --git a/example/kaggle-ndsb1/predict_dsb.py b/example/kaggle-ndsb1/predict_dsb.py index 483243a430a7..2be2eccb2c88 100644 --- a/example/kaggle-ndsb1/predict_dsb.py +++ b/example/kaggle-ndsb1/predict_dsb.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + from __future__ import print_function import find_mxnet import submission_dsb diff --git a/example/kaggle-ndsb1/submission_dsb.py b/example/kaggle-ndsb1/submission_dsb.py index a2644f8d2a74..2695c1abb7ce 100644 --- a/example/kaggle-ndsb1/submission_dsb.py +++ b/example/kaggle-ndsb1/submission_dsb.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + from __future__ import print_function import pandas as pd import os @@ -14,9 +31,9 @@ def gen_sub(predictions,test_lst_path="test.lst",submission_path="submission.csv ## check sampleSubmission.csv from kaggle website to view submission format header = "acantharia_protist_big_center,acantharia_protist_halo,acantharia_protist,amphipods,appendicularian_fritillaridae,appendicularian_s_shape,appendicularian_slight_curve,appendicularian_straight,artifacts_edge,artifacts,chaetognath_non_sagitta,chaetognath_other,chaetognath_sagitta,chordate_type1,copepod_calanoid_eggs,copepod_calanoid_eucalanus,copepod_calanoid_flatheads,copepod_calanoid_frillyAntennae,copepod_calanoid_large_side_antennatucked,copepod_calanoid_large,copepod_calanoid_octomoms,copepod_calanoid_small_longantennae,copepod_calanoid,copepod_cyclopoid_copilia,copepod_cyclopoid_oithona_eggs,copepod_cyclopoid_oithona,copepod_other,crustacean_other,ctenophore_cestid,ctenophore_cydippid_no_tentacles,ctenophore_cydippid_tentacles,ctenophore_lobate,decapods,detritus_blob,detritus_filamentous,detritus_other,diatom_chain_string,diatom_chain_tube,echinoderm_larva_pluteus_brittlestar,echinoderm_larva_pluteus_early,echinoderm_larva_pluteus_typeC,echinoderm_larva_pluteus_urchin,echinoderm_larva_seastar_bipinnaria,echinoderm_larva_seastar_brachiolaria,echinoderm_seacucumber_auricularia_larva,echinopluteus,ephyra,euphausiids_young,euphausiids,fecal_pellet,fish_larvae_deep_body,fish_larvae_leptocephali,fish_larvae_medium_body,fish_larvae_myctophids,fish_larvae_thin_body,fish_larvae_very_thin_body,heteropod,hydromedusae_aglaura,hydromedusae_bell_and_tentacles,hydromedusae_h15,hydromedusae_haliscera_small_sideview,hydromedusae_haliscera,hydromedusae_liriope,hydromedusae_narco_dark,hydromedusae_narco_young,hydromedusae_narcomedusae,hydromedusae_other,hydromedusae_partial_dark,hydromedusae_shapeA_sideview_small,hydromedusae_shapeA,hydromedusae_shapeB,hydromedusae_sideview_big,hydromedusae_solmaris,hydromedusae_solmundella,hydromedusae_typeD_bell_and_tentacles,hydromedusae_typeD,hydromedusae_typeE,hydromedusae_typeF,invertebrate_larvae_other_A,invertebrate_larvae_other_B,jellies_tentacles,polychaete,protist_dark_center,protist_fuzzy_olive,protist_noctiluca,protist_other,protist_star,pteropod_butterfly,pteropod_theco_dev_seq,pteropod_triangle,radiolarian_chain,radiolarian_colony,shrimp_caridean,shrimp_sergestidae,shrimp_zoea,shrimp-like_other,siphonophore_calycophoran_abylidae,siphonophore_calycophoran_rocketship_adult,siphonophore_calycophoran_rocketship_young,siphonophore_calycophoran_sphaeronectes_stem,siphonophore_calycophoran_sphaeronectes_young,siphonophore_calycophoran_sphaeronectes,siphonophore_other_parts,siphonophore_partial,siphonophore_physonect_young,siphonophore_physonect,stomatopod,tornaria_acorn_worm_larvae,trichodesmium_bowtie,trichodesmium_multiple,trichodesmium_puff,trichodesmium_tuft,trochophore_larvae,tunicate_doliolid_nurse,tunicate_doliolid,tunicate_partial,tunicate_salp_chains,tunicate_salp,unknown_blobs_and_smudges,unknown_sticks,unknown_unclassified".split(',') - + # read first line to know the number of columns and column to use - img_lst = pd.read_csv(test_lst_path,sep="/",header=None, nrows=1) + img_lst = pd.read_csv(test_lst_path,sep="/",header=None, nrows=1) columns = img_lst.columns.tolist() # get the columns cols_to_use = columns[len(columns)-1] # drop the last one cols_to_use= map(int, str(cols_to_use)) ## convert scalar to list @@ -28,15 +45,15 @@ def gen_sub(predictions,test_lst_path="test.lst",submission_path="submission.csv df = pd.DataFrame(predictions,columns = header, index=img_lst) df.index.name = 'image' - + print("Saving csv to %s" % submission_path) df.to_csv(submission_path) - + print("Compress with gzip") os.system("gzip -f %s" % submission_path) - + print(" stored in %s.gz" % submission_path) - + diff --git a/example/kaggle-ndsb1/symbol_dsb.py b/example/kaggle-ndsb1/symbol_dsb.py index 43898a106c00..0a4db8f19ad5 100644 --- a/example/kaggle-ndsb1/symbol_dsb.py +++ b/example/kaggle-ndsb1/symbol_dsb.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import find_mxnet import mxnet as mx diff --git a/example/kaggle-ndsb1/train_dsb.py b/example/kaggle-ndsb1/train_dsb.py index 19beb022c8fa..5cec0f6d4fd4 100644 --- a/example/kaggle-ndsb1/train_dsb.py +++ b/example/kaggle-ndsb1/train_dsb.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import find_mxnet import mxnet as mx import logging @@ -23,7 +40,7 @@ help='clip min/max gradient to prevent extreme value') parser.add_argument('--num-epochs', type=int, default=100, help='the number of training epochs') -parser.add_argument('--load-epoch', type=int, +parser.add_argument('--load-epoch', type=int, help="load the model on an epoch using the model-prefix") parser.add_argument('--batch-size', type=int, default=64, help='the batch size') @@ -35,7 +52,7 @@ help='the number of training examples') parser.add_argument('--num-classes', type=int, default=121, help='the number of classes') -parser.add_argument('--log-file', type=str, +parser.add_argument('--log-file', type=str, help='the name of log file') parser.add_argument('--log-dir', type=str, default="/tmp/", help='directory of the log file') @@ -63,7 +80,7 @@ def get_iterator(args, kv): rand_crop = True, rand_mirror = True, ) - + # validate data iterator val = mx.io.ImageRecordIter( path_imgrec = args.data_dir + "va.rec", diff --git a/example/kaggle-ndsb1/training_curves.py b/example/kaggle-ndsb1/training_curves.py index e4ffd94081da..67f25f0042f6 100644 --- a/example/kaggle-ndsb1/training_curves.py +++ b/example/kaggle-ndsb1/training_curves.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + ## based on https://github.com/dmlc/mxnet/issues/1302 ## Parses the model fit log file and generates a train/val vs epoch plot import matplotlib.pyplot as plt diff --git a/example/kaggle-ndsb2/Preprocessing.py b/example/kaggle-ndsb2/Preprocessing.py index 64d15e036d6d..29b4ba009a9a 100644 --- a/example/kaggle-ndsb2/Preprocessing.py +++ b/example/kaggle-ndsb2/Preprocessing.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + """Preprocessing script. This script walks over the directories and dump the frames into a csv file diff --git a/example/kaggle-ndsb2/Train.py b/example/kaggle-ndsb2/Train.py index 22aa3ed721e1..51e308a2e21c 100644 --- a/example/kaggle-ndsb2/Train.py +++ b/example/kaggle-ndsb2/Train.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + """Training script, this is converted from a ipython notebook """ diff --git a/example/memcost/inception_memcost.py b/example/memcost/inception_memcost.py index 45e85901714b..c539e73b3c24 100644 --- a/example/memcost/inception_memcost.py +++ b/example/memcost/inception_memcost.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # pylint: skip-file import sys sys.path.append('../../python/') diff --git a/example/model-parallel-lstm/get_ptb_data.sh b/example/model-parallel-lstm/get_ptb_data.sh index 1ec009aa2f99..d2641cb32b81 100755 --- a/example/model-parallel-lstm/get_ptb_data.sh +++ b/example/model-parallel-lstm/get_ptb_data.sh @@ -1,5 +1,23 @@ #!/usr/bin/env bash +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + + RNN_DIR=$(cd `dirname $0`; pwd) DATA_DIR="${RNN_DIR}/data/" diff --git a/example/model-parallel-lstm/lstm.py b/example/model-parallel-lstm/lstm.py index 795eb6ef3bc1..c24017ff0d9c 100644 --- a/example/model-parallel-lstm/lstm.py +++ b/example/model-parallel-lstm/lstm.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # pylint:skip-file import sys sys.path.insert(0, "../../python") @@ -152,7 +169,7 @@ def setup_rnn_model(default_ctx, models = {} buckets.reverse() for bucket_key in buckets: - # bind max_len first + # bind max_len first rnn_sym = lstm_unroll(num_lstm_layer=num_lstm_layer, num_hidden=num_hidden, seq_len=seq_len, @@ -190,7 +207,7 @@ def setup_rnn_model(default_ctx, args_grad[name] = mx.nd.zeros(shape, ctx) if not name.startswith("t"): print("%s group=%s, ctx=%s" % (name, group, str(ctx))) - + #bind with shared executor rnn_exec = None if max_len == bucket_key: @@ -220,7 +237,7 @@ def setup_rnn_model(default_ctx, h=arg_dict["l%d_init_h" % i]) for i in range(num_lstm_layer)] seq_data = [rnn_exec.arg_dict["t%d_data" % i] for i in range(seq_len)] - # we don't need to store the last state + # we don't need to store the last state last_states = None if concat_decode: @@ -235,7 +252,7 @@ def setup_rnn_model(default_ctx, seq_data=seq_data, seq_labels=seq_labels, seq_outputs=seq_outputs, param_blocks=param_blocks) models[bucket_key] = model - buckets.reverse() + buckets.reverse() return models @@ -256,7 +273,7 @@ def set_rnn_inputs(m, X, begin): def set_rnn_inputs_from_batch(m, batch, batch_seq_length, batch_size): X = batch.data for seqidx in range(batch_seq_length): - idx = seqidx + idx = seqidx next_idx = (seqidx + 1) % batch_seq_length x = X[idx, :] y = X[next_idx, :] @@ -295,20 +312,20 @@ def train_lstm(model, X_train_batch, X_val_batch, nbatch = 0 train_nll = 0 tic = time.time() - for data_batch in X_train_batch: + for data_batch in X_train_batch: batch_seq_length = data_batch.bucket_key m = model[batch_seq_length] # reset init state for state in m.init_states: state.c[:] = 0.0 state.h[:] = 0.0 - + head_grad = [] if use_loss: ctx = m.seq_outputs[0].context head_grad = [mx.nd.ones((1,), ctx) for x in m.seq_outputs] - set_rnn_inputs_from_batch(m, data_batch, batch_seq_length, batch_size) + set_rnn_inputs_from_batch(m, data_batch, batch_seq_length, batch_size) m.rnn_exec.forward(is_train=True) # probability of each label class, used to evaluate nll @@ -390,7 +407,7 @@ def train_lstm(model, X_train_batch, X_val_batch, else: val_nll += sum([x.asscalar() for x in seq_loss]) / batch_size nbatch += batch_size - + perp = np.exp(val_nll / nbatch) print("Iter [%d] Val: NLL=%.3f, Perp=%.3f" % ( iteration, val_nll / nbatch, np.exp(val_nll / nbatch))) @@ -401,7 +418,7 @@ def train_lstm(model, X_train_batch, X_val_batch, X_val_batch.reset() X_train_batch.reset() -# is this function being used? +# is this function being used? def setup_rnn_sample_model(ctx, params, num_lstm_layer, diff --git a/example/model-parallel-lstm/lstm_ptb.py b/example/model-parallel-lstm/lstm_ptb.py index 20ce89653fd8..0141338329e4 100644 --- a/example/model-parallel-lstm/lstm_ptb.py +++ b/example/model-parallel-lstm/lstm_ptb.py @@ -1,10 +1,27 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # pylint:skip-file import lstm import sys sys.path.insert(0, "../../python") import mxnet as mx import numpy as np -# reuse the bucket_io library +# reuse the bucket_io library sys.path.insert(0, "../rnn") from bucket_io import BucketSentenceIter, default_build_vocab diff --git a/example/module/lstm_bucketing.py b/example/module/lstm_bucketing.py index dc00ef55aa7d..ecc7e7be0bca 100644 --- a/example/module/lstm_bucketing.py +++ b/example/module/lstm_bucketing.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # pylint: disable=C0111,too-many-arguments,too-many-instance-attributes,too-many-locals,redefined-outer-name,fixme # pylint: disable=superfluous-parens, no-member, invalid-name import sys diff --git a/example/module/mnist_mlp.py b/example/module/mnist_mlp.py index 6d9d6bff4cb5..d2737dc12af7 100644 --- a/example/module/mnist_mlp.py +++ b/example/module/mnist_mlp.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # pylint: skip-file import os, sys sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))) diff --git a/example/module/python_loss.py b/example/module/python_loss.py index d139789fdf33..9680ac6cb091 100644 --- a/example/module/python_loss.py +++ b/example/module/python_loss.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # pylint: skip-file import numpy as np import mxnet as mx diff --git a/example/module/sequential_module.py b/example/module/sequential_module.py index 4659457c5113..48e1046a2067 100644 --- a/example/module/sequential_module.py +++ b/example/module/sequential_module.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # pylint: skip-file import os, sys sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))) diff --git a/example/module/train_cifar10.py b/example/module/train_cifar10.py index ec3be57edb66..a96e8d92846b 100644 --- a/example/module/train_cifar10.py +++ b/example/module/train_cifar10.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + """Train CIFAR-10 classifier in MXNet. Demonstrates using the Module class. """ @@ -132,7 +149,7 @@ def do_train(args, callback_args=None): (train, val) = get_iterator(args, kv) if args.gpus is None or args.gpus == '': - devs = mx.cpu() + devs = mx.cpu() elif type(args.gpus) == str: devs = [mx.gpu(int(i)) for i in args.gpus.split(',')] else: diff --git a/example/multi-task/data.py b/example/multi-task/data.py index d39821f52145..0ca8e1fd6653 100644 --- a/example/multi-task/data.py +++ b/example/multi-task/data.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # pylint: skip-file """ data iterator for mnist """ import sys diff --git a/example/multi-task/example_multi_task.py b/example/multi-task/example_multi_task.py index 8ee396f0daf4..853d435bbf0b 100644 --- a/example/multi-task/example_multi_task.py +++ b/example/multi-task/example_multi_task.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # pylint: skip-file import sys sys.path.insert(0, "../../python/") diff --git a/example/nce-loss/get_text8.sh b/example/nce-loss/get_text8.sh index ccd4a08e69bb..e1390eb6fe90 100755 --- a/example/nce-loss/get_text8.sh +++ b/example/nce-loss/get_text8.sh @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + mkdir -p ./data/ cd ./data/ wget http://mattmahoney.net/dc/text8.zip diff --git a/example/nce-loss/lstm_word.py b/example/nce-loss/lstm_word.py index 3b39207b58a3..23729917d939 100644 --- a/example/nce-loss/lstm_word.py +++ b/example/nce-loss/lstm_word.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # pylint:skip-file from __future__ import print_function import logging @@ -55,7 +72,7 @@ def get_net(vocab_size, seq_len, num_label, num_lstm_layer, num_hidden): state = LSTMState(c=mx.sym.Variable("l%d_init_c" % i), h=mx.sym.Variable("l%d_init_h" % i)) last_states.append(state) - + data = mx.sym.Variable('data') label = mx.sym.Variable('label') label_weight = mx.sym.Variable('label_weight') @@ -76,7 +93,7 @@ def get_net(vocab_size, seq_len, num_label, num_lstm_layer, num_hidden): probs = [] for seqidx in range(seq_len): hidden = datavec[seqidx] - + for i in range(num_lstm_layer): next_state = lstm(num_hidden, indata = hidden, prev_state = last_states[i], @@ -84,7 +101,7 @@ def get_net(vocab_size, seq_len, num_label, num_lstm_layer, num_hidden): seqidx = seqidx, layeridx = i) hidden = next_state.h last_states[i] = next_state - + probs.append(nce_loss(data = hidden, label = labelvec[seqidx], label_weight = labelweightvec[seqidx], @@ -149,7 +166,7 @@ def __init__(self, name, batch_size, seq_len, num_label, init_states): self.provide_data = [('data', (batch_size, seq_len))] + init_states self.provide_label = [('label', (self.batch_size, seq_len, num_label)), ('label_weight', (self.batch_size, seq_len, num_label))] - + def sample_ne(self): return self.negative[random.randint(0, len(self.negative) - 1)] @@ -203,7 +220,7 @@ def reset(self): data_train = DataIter("./data/text8", batch_size, seq_len, num_label, init_states) - + network = get_net(data_train.vocab_size, seq_len, num_label, num_lstm_layer, num_hidden) options, args = parser.parse_args() devs = mx.cpu() @@ -216,7 +233,7 @@ def reset(self): momentum = 0.9, wd = 0.0000, initializer=mx.init.Xavier(factor_type="in", magnitude=2.34)) - + metric = NceLSTMAuc() model.fit(X = data_train, eval_metric = metric, diff --git a/example/nce-loss/nce.py b/example/nce-loss/nce.py index abe4135ef367..7f57dfdb751d 100644 --- a/example/nce-loss/nce.py +++ b/example/nce-loss/nce.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # pylint:skip-file import sys sys.path.insert(0, "../../python") diff --git a/example/nce-loss/toy_nce.py b/example/nce-loss/toy_nce.py index 9770be093fbe..39da7c779031 100644 --- a/example/nce-loss/toy_nce.py +++ b/example/nce-loss/toy_nce.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # pylint:skip-file import logging import sys, random, time @@ -19,7 +36,7 @@ def get_net(vocab_size, num_label): embed_weight = embed_weight, vocab_size = vocab_size, num_hidden = 100, - num_label = num_label) + num_label = num_label) return ret class SimpleBatch(object): @@ -91,10 +108,10 @@ def reset(self): vocab_size = 10000 feature_size = 100 num_label = 6 - + data_train = DataIter(100000, batch_size, vocab_size, num_label, feature_size) data_test = DataIter(1000, batch_size, vocab_size, num_label, feature_size) - + network = get_net(vocab_size, num_label) devs = [mx.cpu()] model = mx.model.FeedForward(ctx = devs, @@ -104,7 +121,7 @@ def reset(self): momentum = 0.9, wd = 0.00001, initializer=mx.init.Xavier(factor_type="in", magnitude=2.34)) - + metric = NceAccuracy() model.fit(X = data_train, eval_data = data_test, eval_metric = metric, diff --git a/example/nce-loss/toy_softmax.py b/example/nce-loss/toy_softmax.py index 66f9cdc0e113..ff6ff4327c8e 100644 --- a/example/nce-loss/toy_softmax.py +++ b/example/nce-loss/toy_softmax.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # pylint:skip-file import logging import sys, random, time @@ -83,7 +100,7 @@ def reset(self): data_train = DataIter(100000, batch_size, vocab_size, num_label, feature_size) data_test = DataIter(1000, batch_size, vocab_size, num_label, feature_size) - + network = get_net(vocab_size) devs = mx.cpu() model = mx.model.FeedForward(ctx = devs, @@ -93,7 +110,7 @@ def reset(self): momentum = 0.9, wd = 0.0000, initializer=mx.init.Xavier(factor_type="in", magnitude=2.34)) - + model.fit(X = data_train, eval_data = data_test, batch_end_callback = mx.callback.Speedometer(batch_size, 50),) diff --git a/example/nce-loss/wordvec.py b/example/nce-loss/wordvec.py index 24b78305210d..887d586ff342 100644 --- a/example/nce-loss/wordvec.py +++ b/example/nce-loss/wordvec.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # pylint:skip-file from __future__ import print_function import logging @@ -30,7 +47,7 @@ def get_net(vocab_size, num_input, num_label): embed_weight = embed_weight, vocab_size = vocab_size, num_hidden = 100, - num_label = num_label) + num_label = num_label) def load_data(name): buf = open(name).read() @@ -82,7 +99,7 @@ def __init__(self, name, batch_size, num_label): self.provide_data = [('data', (batch_size, num_label - 1))] self.provide_label = [('label', (self.batch_size, num_label)), ('label_weight', (self.batch_size, num_label))] - + def sample_ne(self): return self.negative[random.randint(0, len(self.negative) - 1)] @@ -126,11 +143,11 @@ def reset(self): help = "use gpu") batch_size = 256 num_label = 5 - + data_train = DataIter("./data/text8", batch_size, num_label) - + network = get_net(data_train.vocab_size, num_label - 1, num_label) - + options, args = parser.parse_args() devs = mx.cpu() if options.gpu == True: @@ -143,7 +160,7 @@ def reset(self): wd = 0.0000, initializer=mx.init.Xavier(factor_type="in", magnitude=2.34)) - + metric = NceAuc() model.fit(X = data_train, eval_metric = metric, diff --git a/example/nce-loss/wordvec_subwords.py b/example/nce-loss/wordvec_subwords.py index 049dc9d6ffcb..c8d46a1aeb3a 100644 --- a/example/nce-loss/wordvec_subwords.py +++ b/example/nce-loss/wordvec_subwords.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # pylint:skip-file import logging import sys, random, time, math diff --git a/example/neural-style/download.sh b/example/neural-style/download.sh index a58640aa8142..d5303a72c5a6 100755 --- a/example/neural-style/download.sh +++ b/example/neural-style/download.sh @@ -1,5 +1,23 @@ #!/bin/bash +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + + mkdir -p model cd model wget https://github.com/dmlc/web-data/raw/master/mxnet/neural-style/model/vgg19.params diff --git a/example/neural-style/end_to_end/basic.py b/example/neural-style/end_to_end/basic.py index ed9d3f601554..1763e884b984 100644 --- a/example/neural-style/end_to_end/basic.py +++ b/example/neural-style/end_to_end/basic.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import sys sys.path.insert(0, "../../mxnet/python/") diff --git a/example/neural-style/end_to_end/boost_inference.py b/example/neural-style/end_to_end/boost_inference.py index 72427bedc7a6..0ec8308f3054 100644 --- a/example/neural-style/end_to_end/boost_inference.py +++ b/example/neural-style/end_to_end/boost_inference.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import sys sys.path.insert(0, "../mxnet/python") diff --git a/example/neural-style/end_to_end/boost_train.py b/example/neural-style/end_to_end/boost_train.py index 9100cc1875a2..fa525e7e52c0 100644 --- a/example/neural-style/end_to_end/boost_train.py +++ b/example/neural-style/end_to_end/boost_train.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import sys sys.path.insert(0, "../../mxnet/python") diff --git a/example/neural-style/end_to_end/data_processing.py b/example/neural-style/end_to_end/data_processing.py index 80f1bcd5cfcd..1c1ab493edec 100644 --- a/example/neural-style/end_to_end/data_processing.py +++ b/example/neural-style/end_to_end/data_processing.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import numpy as np from skimage import io, transform from skimage.restoration import denoise_tv_chambolle diff --git a/example/neural-style/end_to_end/gen_v3.py b/example/neural-style/end_to_end/gen_v3.py index dbc83b1ea004..7962e68da2fd 100644 --- a/example/neural-style/end_to_end/gen_v3.py +++ b/example/neural-style/end_to_end/gen_v3.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # coding: utf-8 diff --git a/example/neural-style/end_to_end/gen_v4.py b/example/neural-style/end_to_end/gen_v4.py index 379e904b9690..fb4e6d1e1647 100644 --- a/example/neural-style/end_to_end/gen_v4.py +++ b/example/neural-style/end_to_end/gen_v4.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # coding: utf-8 diff --git a/example/neural-style/end_to_end/model_vgg19.py b/example/neural-style/end_to_end/model_vgg19.py index 6e287b55b2fa..0d369ae08f58 100644 --- a/example/neural-style/end_to_end/model_vgg19.py +++ b/example/neural-style/end_to_end/model_vgg19.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import mxnet as mx import os, sys from collections import namedtuple diff --git a/example/neural-style/find_mxnet.py b/example/neural-style/find_mxnet.py index 2e3970ddd85d..b919a2a78715 100644 --- a/example/neural-style/find_mxnet.py +++ b/example/neural-style/find_mxnet.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + try: import mxnet as mx except ImportError: diff --git a/example/neural-style/model_vgg19.py b/example/neural-style/model_vgg19.py index 3344a274e827..aa83bc362e5c 100644 --- a/example/neural-style/model_vgg19.py +++ b/example/neural-style/model_vgg19.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import find_mxnet import mxnet as mx import os, sys diff --git a/example/neural-style/nstyle.py b/example/neural-style/nstyle.py index 3eec33d4cbf1..e3bc8bcc5684 100644 --- a/example/neural-style/nstyle.py +++ b/example/neural-style/nstyle.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import find_mxnet import mxnet as mx import numpy as np @@ -196,7 +213,7 @@ def train_nstyle(args, callback=None): img = mx.nd.zeros(content_np.shape, ctx=dev) img[:] = mx.rnd.uniform(-0.1, 0.1, img.shape) - lr = mx.lr_scheduler.FactorScheduler(step=args.lr_sched_delay, + lr = mx.lr_scheduler.FactorScheduler(step=args.lr_sched_delay, factor=args.lr_sched_factor) optimizer = mx.optimizer.NAG( diff --git a/example/numpy-ops/custom_softmax.py b/example/numpy-ops/custom_softmax.py index cbd9a027d7a6..162215f3b0d1 100644 --- a/example/numpy-ops/custom_softmax.py +++ b/example/numpy-ops/custom_softmax.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # pylint: skip-file import os from data import mnist_iterator @@ -23,7 +40,7 @@ def backward(self, req, out_grad, in_data, out_data, in_grad, aux): class SoftmaxProp(mx.operator.CustomOpProp): def __init__(self): super(SoftmaxProp, self).__init__(need_top_grad=False) - + def list_arguments(self): return ['data', 'label'] diff --git a/example/numpy-ops/data.py b/example/numpy-ops/data.py index d39821f52145..0ca8e1fd6653 100644 --- a/example/numpy-ops/data.py +++ b/example/numpy-ops/data.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # pylint: skip-file """ data iterator for mnist """ import sys diff --git a/example/numpy-ops/ndarray_softmax.py b/example/numpy-ops/ndarray_softmax.py index 5c3176833b63..aa8555e5ad3e 100644 --- a/example/numpy-ops/ndarray_softmax.py +++ b/example/numpy-ops/ndarray_softmax.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # pylint: skip-file from data import mnist_iterator import mxnet as mx @@ -10,7 +27,7 @@ def __init__(self): super(NDArraySoftmax, self).__init__(False) self.fwd_kernel = None self.bwd_kernel = None - + def list_arguments(self): return ['data', 'label'] diff --git a/example/numpy-ops/numpy_softmax.py b/example/numpy-ops/numpy_softmax.py index 3f9f6c8be849..f90783b494b4 100644 --- a/example/numpy-ops/numpy_softmax.py +++ b/example/numpy-ops/numpy_softmax.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # pylint: skip-file from data import mnist_iterator import mxnet as mx @@ -8,7 +25,7 @@ class NumpySoftmax(mx.operator.NumpyOp): def __init__(self): super(NumpySoftmax, self).__init__(False) - + def list_arguments(self): return ['data', 'label'] diff --git a/example/numpy-ops/weighted_logistic_regression.py b/example/numpy-ops/weighted_logistic_regression.py index 7094b3aca969..26b5fb2fda84 100644 --- a/example/numpy-ops/weighted_logistic_regression.py +++ b/example/numpy-ops/weighted_logistic_regression.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import os import numpy as np import mxnet as mx diff --git a/example/profiler/profiler_executor.py b/example/profiler/profiler_executor.py index e70574d41cb5..26e3e1ba2acd 100644 --- a/example/profiler/profiler_executor.py +++ b/example/profiler/profiler_executor.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import mxnet as mx import argparse import os, sys diff --git a/example/profiler/profiler_imageiter.py b/example/profiler/profiler_imageiter.py index af4c5d11aee8..e16b9b7de45f 100644 --- a/example/profiler/profiler_imageiter.py +++ b/example/profiler/profiler_imageiter.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import os # uncomment to set the number of worker threads. # os.environ["MXNET_CPU_WORKER_NTHREADS"] = "4" @@ -8,7 +25,7 @@ def run_imageiter(path_rec, n, batch_size = 32): - + data = mx.img.ImageIter(batch_size=batch_size, data_shape=(3, 224, 224), path_imgrec=path_rec, @@ -26,4 +43,4 @@ def run_imageiter(path_rec, n, batch_size = 32): mx.profiler.profiler_set_config(mode='all', filename='profile_imageiter.json') mx.profiler.profiler_set_state('run') run_imageiter('test.rec', 20) # See http://mxnet.io/tutorials/python/image_io.html for how to create .rec files. - mx.profiler.profiler_set_state('stop') \ No newline at end of file + mx.profiler.profiler_set_state('stop') diff --git a/example/profiler/profiler_matmul.py b/example/profiler/profiler_matmul.py index baa962307461..1b1cf74f4187 100644 --- a/example/profiler/profiler_matmul.py +++ b/example/profiler/profiler_matmul.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + from __future__ import print_function import mxnet as mx import argparse diff --git a/example/profiler/profiler_ndarray.py b/example/profiler/profiler_ndarray.py index bb4d658275c0..67ea87b1ed62 100644 --- a/example/profiler/profiler_ndarray.py +++ b/example/profiler/profiler_ndarray.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import os import mxnet as mx import numpy as np diff --git a/example/python-howto/README.md b/example/python-howto/README.md index 5007e858be4c..2499c2ab078c 100644 --- a/example/python-howto/README.md +++ b/example/python-howto/README.md @@ -15,7 +15,7 @@ Python Howto Examples * run python under gdb: ```gdb --args python debug_conv.py``` * in gdb set break point on particular line of the code and run execution: - ``` +``` (gdb) break src/operator/convolution-inl.h:120 (gdb) run Breakpoint 1, mxnet::op::ConvolutionOp::Forward (this=0x12219d0, ctx=..., in_data=std::vector of length 3, capacity 4 = {...}, req=std::vector of length 1, capacity 1 = {...}, out_data=std::vector of length 1, capacity 1 = {...}, @@ -32,4 +32,4 @@ Breakpoint 1, mxnet::op::ConvolutionOp::Forward (this=0x122 123 in_data[conv::kWeight].get_with_shape(wmat_shape, s); 124 Tensor out = out_data[conv::kOut].get(s); 125 #if defined(__CUDACC__) - ``` \ No newline at end of file +``` diff --git a/example/python-howto/data.py b/example/python-howto/data.py index d39821f52145..0ca8e1fd6653 100644 --- a/example/python-howto/data.py +++ b/example/python-howto/data.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # pylint: skip-file """ data iterator for mnist """ import sys diff --git a/example/python-howto/data_iter.py b/example/python-howto/data_iter.py index 34e9a4181cd1..81c8988a8e51 100644 --- a/example/python-howto/data_iter.py +++ b/example/python-howto/data_iter.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + """Create a Cifar data iterator. This example shows how to create a iterator reading from recordio, diff --git a/example/python-howto/debug_conv.py b/example/python-howto/debug_conv.py index 3c38d20b89fa..9de421d8e88e 100644 --- a/example/python-howto/debug_conv.py +++ b/example/python-howto/debug_conv.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import mxnet as mx data_shape = (1,3,5,5) @@ -19,4 +36,4 @@ def __init__(self, data): input_data = mx.nd.ones(data_shape) mod.forward(data_batch=SimpleData([input_data])) res = mod.get_outputs()[0].asnumpy() -print(res) \ No newline at end of file +print(res) diff --git a/example/python-howto/monitor_weights.py b/example/python-howto/monitor_weights.py index 8dcca1fba70b..c54e64954535 100644 --- a/example/python-howto/monitor_weights.py +++ b/example/python-howto/monitor_weights.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # pylint: skip-file from data import mnist_iterator import mxnet as mx @@ -27,6 +44,6 @@ def norm_stat(d): return mx.nd.norm(d)/np.sqrt(d.size) mon = mx.mon.Monitor(100, norm_stat) -model.fit(X=train, eval_data=val, monitor=mon, +model.fit(X=train, eval_data=val, monitor=mon, batch_end_callback = mx.callback.Speedometer(100, 100)) diff --git a/example/python-howto/multiple_outputs.py b/example/python-howto/multiple_outputs.py index 97ce469d58a2..43b4538d1d79 100644 --- a/example/python-howto/multiple_outputs.py +++ b/example/python-howto/multiple_outputs.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + """Create a Multiple output configuration. This example shows how to create a multiple output configuration. diff --git a/example/rcnn/README.md b/example/rcnn/README.md index 43cd054cb876..282a1aebe9a9 100644 --- a/example/rcnn/README.md +++ b/example/rcnn/README.md @@ -1,5 +1,7 @@ # Faster R-CNN in MXNet with distributed implementation and data parallelization +![example detections](https://cloud.githubusercontent.com/assets/13162287/22101032/92085dc0-de6c-11e6-9228-67e72606ddbc.png) + ## Why? There exist good implementations of Faster R-CNN yet they lack support for recent ConvNet architectures. The aim of reproducing it from scratch is to fully utilize @@ -43,9 +45,8 @@ MXNet engines and parallelization for object detection. | Faster R-CNN end-to-end | VGG16 | COCO train | COCO val | 21.2 | 22.8 | | Faster R-CNN end-to-end | ResNet-101 | COCO train | COCO val | 27.2 | 26.1 | -All reference results are from original publications. -All VOC experiments are conducted in MXNet-v0.9.1-nnvm. MXNet-v0.8 have similar results. -All COCO experiments are conducted in MXNet-v0.8. +The above experiments were conducted at [mx-rcnn](https://github.com/precedenceguo/mx-rcnn/tree/6a1ab0eec5035a10a1efb5fc8c9d6c54e101b4d0) +using [a MXNet fork, based on MXNet 0.9.1 nnvm pre-release](https://github.com/precedenceguo/mxnet/tree/simple). ## I'm Feeling Lucky * Prepare: `bash script/additional_deps.sh` @@ -56,9 +57,8 @@ All COCO experiments are conducted in MXNet-v0.8. ## Getting started See if `bash script/additional_deps.sh` will do the following for you. * Suppose `HOME` represents where this file is located. All commands, unless stated otherwise, should be started from `HOME`. - Executing scripts in `script` must also be from `HOME`. * Install python package `cython easydict matplotlib scikit-image`. -* Install MXNet Python Interface. Open `python` type `import mxnet` to confirm. +* Install MXNet version v0.9.5 or higher and MXNet Python Interface. Open `python` type `import mxnet` to confirm. * Run `make` in `HOME`. Command line arguments have the same meaning as in mxnet/example/image-classification. @@ -82,7 +82,7 @@ Refer to `script/vgg_voc07.sh` and other experiments for examples. ### Prepare Training Data See `bash script/get_voc.sh` and `bash script/get_coco.sh` will do the following for you. -* Make a folder `data` in `HOME`. `data` folder will be used to place the training data folder `VOCdevkit` and `coco`. +* Make a folder `data` in `HOME`. `data` folder will be used to place the training data folder `VOCdevkit` and `coco`. * Download and extract [Pascal VOC data](http://host.robots.ox.ac.uk/pascal/VOC/), place the `VOCdevkit` folder in `HOME/data`. * Download and extract [coco dataset](http://mscoco.org/dataset/), place all images to `coco/images` and annotation jsons to `data/annotations`. @@ -94,6 +94,7 @@ See `bash script/get_voc.sh` and `bash script/get_coco.sh` will do the following ### Prepare Pretrained Models See if `bash script/get_pretrained_model.sh` will do this for you. If not, * Make a folder `model` in `HOME`. `model` folder will be used to place model checkpoints along the training process. + It is recommended to set `model` as a symbolic link to somewhere else in hard disk. * Download VGG16 pretrained model `vgg16-0000.params` from [MXNet model gallery](https://github.com/dmlc/mxnet-model-gallery/blob/master/imagenet-1k-vgg.md) to `model` folder. * Download ResNet pretrained model `resnet-101-0000.params` from [ResNet](https://github.com/tornadomeet/ResNet) to `model` folder. @@ -174,7 +175,7 @@ History of this implementation is: * Faster R-CNN with end-to-end training and module testing (v4) * Faster R-CNN with accelerated training and resnet (v5) -mxnet/example/rcnn was v1, v2 and v3.5. +mxnet/example/rcnn was v1, v2, v3.5 and now v5. ## References 1. Tianqi Chen, Mu Li, Yutian Li, Min Lin, Naiyan Wang, Minjie Wang, Tianjun Xiao, Bing Xu, Chiyuan Zhang, and Zheng Zhang. MXNet: A Flexible and Efficient Machine Learning Library for Heterogeneous Distributed Systems. In Neural Information Processing Systems, Workshop on Machine Learning Systems, 2015 @@ -186,3 +187,4 @@ mxnet/example/rcnn was v1, v2 and v3.5. 7. Karen Simonyan, and Andrew Zisserman. "Very deep convolutional networks for large-scale image recognition." arXiv preprint arXiv:1409.1556 (2014). 8. Kaiming He, Xiangyu Zhang, Shaoqing Ren, Jian Sun. "Deep Residual Learning for Image Recognition". In Computer Vision and Pattern Recognition, IEEE Conference on, 2016. 9. Tsung-Yi Lin, Michael Maire, Serge Belongie, James Hays, Pietro Perona, Deva Ramanan, Piotr DollĂ¡r, and C. Lawrence Zitnick. "Microsoft COCO: Common Objects in Context" In European Conference on Computer Vision, pp. 740-755. Springer International Publishing, 2014. + diff --git a/example/rcnn/demo.py b/example/rcnn/demo.py index 9c01b48fd1bd..b59403379ddd 100644 --- a/example/rcnn/demo.py +++ b/example/rcnn/demo.py @@ -1,9 +1,26 @@ -from __future__ import print_function +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import argparse import os import cv2 import mxnet as mx import numpy as np +from rcnn.logger import logger from rcnn.config import config from rcnn.symbol import get_vgg_test, get_vgg_rpn_test from rcnn.io.image import resize, transform @@ -104,17 +121,18 @@ def demo_net(predictor, image_name, vis=False): boxes_this_image = [[]] + [all_boxes[j] for j in range(1, len(CLASSES))] # print results - print('class ---- [[x1, x2, y1, y2, confidence]]') + logger.info('---class---') + logger.info('[[x1, x2, y1, y2, confidence]]') for ind, boxes in enumerate(boxes_this_image): if len(boxes) > 0: - print('---------', CLASSES[ind], '---------') - print(boxes) + logger.info('---%s---' % CLASSES[ind]) + logger.info('%s' % boxes) if vis: vis_all_detection(data_dict['data'].asnumpy(), boxes_this_image, CLASSES, im_scale) else: result_file = image_name.replace('.', '_result.') - print('results saved to %s' % result_file) + logger.info('results saved to %s' % result_file) im = draw_all_detection(data_dict['data'].asnumpy(), boxes_this_image, CLASSES, im_scale) cv2.imwrite(result_file, im) diff --git a/example/rcnn/rcnn/config.py b/example/rcnn/rcnn/config.py index 445c2439b91e..17738f054b33 100644 --- a/example/rcnn/rcnn/config.py +++ b/example/rcnn/rcnn/config.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import numpy as np from easydict import EasyDict as edict diff --git a/example/rcnn/rcnn/core/callback.py b/example/rcnn/rcnn/core/callback.py index 5d48b9fc75d1..bacff9665e91 100644 --- a/example/rcnn/rcnn/core/callback.py +++ b/example/rcnn/rcnn/core/callback.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import time import logging import mxnet as mx diff --git a/example/rcnn/rcnn/core/loader.py b/example/rcnn/rcnn/core/loader.py index 3f5cf3c6c011..826ee20f080c 100644 --- a/example/rcnn/rcnn/core/loader.py +++ b/example/rcnn/rcnn/core/loader.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import mxnet as mx import numpy as np from mxnet.executor_manager import _split_input_slice diff --git a/example/rcnn/rcnn/core/metric.py b/example/rcnn/rcnn/core/metric.py index 5808190e9d80..d33edb65beda 100644 --- a/example/rcnn/rcnn/core/metric.py +++ b/example/rcnn/rcnn/core/metric.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import mxnet as mx import numpy as np diff --git a/example/rcnn/rcnn/core/module.py b/example/rcnn/rcnn/core/module.py index c823cb926e8d..337f0f35852b 100644 --- a/example/rcnn/rcnn/core/module.py +++ b/example/rcnn/rcnn/core/module.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + """A `MutableModule` implement the `BaseModule` API, and allows input shape varying with training iterations. If shapes vary, executors will rebind, using shared arrays from the initial module binded with maximum shape. @@ -80,13 +97,13 @@ def get_params(self): return self._curr_module.get_params() def init_params(self, initializer=Uniform(0.01), arg_params=None, aux_params=None, - allow_missing=False, force_init=False): + allow_missing=False, force_init=False, allow_extra=False): if self.params_initialized and not force_init: return assert self.binded, 'call bind before initializing the parameters' self._curr_module.init_params(initializer=initializer, arg_params=arg_params, aux_params=aux_params, allow_missing=allow_missing, - force_init=force_init) + force_init=force_init, allow_extra=allow_extra) self.params_initialized = True def bind(self, data_shapes, label_shapes=None, for_training=True, diff --git a/example/rcnn/rcnn/core/tester.py b/example/rcnn/rcnn/core/tester.py index a99614b370b5..651b2a945e71 100644 --- a/example/rcnn/rcnn/core/tester.py +++ b/example/rcnn/rcnn/core/tester.py @@ -1,4 +1,20 @@ -from __future__ import print_function +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import cPickle import os import time @@ -6,6 +22,7 @@ import numpy as np from module import MutableModule +from rcnn.logger import logger from rcnn.config import config from rcnn.io import image from rcnn.processing.bbox_transform import bbox_pred, clip_boxes @@ -79,9 +96,9 @@ def generate_proposals(predictor, test_data, imdb, vis=False, thresh=0.): if vis: vis_all_detection(data_dict['data'].asnumpy(), [dets], ['obj'], scale) - print('generating %d/%d' % (i + 1, imdb.num_images), - 'proposal %d' % (dets.shape[0]), - 'data %.4fs net %.4fs' % (t1, t2)) + logger.info('generating %d/%d ' % (i + 1, imdb.num_images) + + 'proposal %d ' % (dets.shape[0]) + + 'data %.4fs net %.4fs' % (t1, t2)) i += 1 assert len(imdb_boxes) == imdb.num_images, 'calculations not complete' @@ -100,7 +117,7 @@ def generate_proposals(predictor, test_data, imdb, vis=False, thresh=0.): with open(full_rpn_file, 'wb') as f: cPickle.dump(original_boxes, f, cPickle.HIGHEST_PROTOCOL) - print('wrote rpn proposals to {}'.format(rpn_file)) + logger.info('wrote rpn proposals to %s' % rpn_file) return imdb_boxes @@ -189,7 +206,7 @@ def pred_eval(predictor, test_data, imdb, vis=False, thresh=1e-3): t3 = time.time() - t t = time.time() - print('testing {}/{} data {:.4f}s net {:.4f}s post {:.4f}s'.format(i, imdb.num_images, t1, t2, t3)) + logger.info('testing %d/%d data %.4fs net %.4fs post %.4fs' % (i, imdb.num_images, t1, t2, t3)) i += 1 det_file = os.path.join(imdb.cache_path, imdb.name + '_detections.pkl') diff --git a/example/rcnn/rcnn/cython/gpu_nms.hpp b/example/rcnn/rcnn/cython/gpu_nms.hpp index 68b6d42cd88b..93d1f90183bb 100644 --- a/example/rcnn/rcnn/cython/gpu_nms.hpp +++ b/example/rcnn/rcnn/cython/gpu_nms.hpp @@ -1,2 +1,21 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + void _nms(int* keep_out, int* num_out, const float* boxes_host, int boxes_num, int boxes_dim, float nms_overlap_thresh, int device_id); diff --git a/example/rcnn/rcnn/cython/nms_kernel.cu b/example/rcnn/rcnn/cython/nms_kernel.cu index 038a59012f60..047a5e0c6d9f 100644 --- a/example/rcnn/rcnn/cython/nms_kernel.cu +++ b/example/rcnn/rcnn/cython/nms_kernel.cu @@ -1,3 +1,22 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + // ------------------------------------------------------------------ // Faster R-CNN // Copyright (c) 2015 Microsoft diff --git a/example/rcnn/rcnn/cython/setup.py b/example/rcnn/rcnn/cython/setup.py index 330373dddb72..e50478b2d967 100644 --- a/example/rcnn/rcnn/cython/setup.py +++ b/example/rcnn/rcnn/cython/setup.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # -------------------------------------------------------- # Fast R-CNN # Copyright (c) 2015 Microsoft @@ -55,7 +72,13 @@ def locate_cuda(): raise EnvironmentError('The CUDA %s path could not be located in %s' % (k, v)) return cudaconfig -CUDA = locate_cuda() + + +# Test if cuda could be foun +try: + CUDA = locate_cuda() +except EnvironmentError: + CUDA = None # Obtain the numpy include directory. This logic works across numpy versions. @@ -123,25 +146,32 @@ def build_extensions(self): extra_compile_args={'gcc': ["-Wno-cpp", "-Wno-unused-function"]}, include_dirs = [numpy_include] ), - Extension('gpu_nms', - ['nms_kernel.cu', 'gpu_nms.pyx'], - library_dirs=[CUDA['lib64']], - libraries=['cudart'], - language='c++', - runtime_library_dirs=[CUDA['lib64']], - # this syntax is specific to this build system - # we're only going to use certain compiler args with nvcc and not with - # gcc the implementation of this trick is in customize_compiler() below - extra_compile_args={'gcc': ["-Wno-unused-function"], - 'nvcc': ['-arch=sm_35', - '--ptxas-options=-v', - '-c', - '--compiler-options', - "'-fPIC'"]}, - include_dirs = [numpy_include, CUDA['include']] - ), ] +if CUDA is not None: + ext_modules.append( + Extension('gpu_nms', + ['nms_kernel.cu', 'gpu_nms.pyx'], + library_dirs=[CUDA['lib64']], + libraries=['cudart'], + language='c++', + runtime_library_dirs=[CUDA['lib64']], + # this syntax is specific to this build system + # we're only going to use certain compiler args with nvcc and not with + # gcc the implementation of this trick is in customize_compiler() below + extra_compile_args={'gcc': ["-Wno-unused-function"], + 'nvcc': ['-arch=sm_35', + '--ptxas-options=-v', + '-c', + '--compiler-options', + "'-fPIC'"]}, + include_dirs = [numpy_include, CUDA['include']] + ) + ) +else: + print('Skipping GPU_NMS') + + setup( name='frcnn_cython', ext_modules=ext_modules, diff --git a/example/rcnn/rcnn/dataset/__init__.py b/example/rcnn/rcnn/dataset/__init__.py index 266f344ede75..1a706e9e0c15 100644 --- a/example/rcnn/rcnn/dataset/__init__.py +++ b/example/rcnn/rcnn/dataset/__init__.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + from imdb import IMDB from pascal_voc import PascalVOC from coco import coco diff --git a/example/rcnn/rcnn/dataset/coco.py b/example/rcnn/rcnn/dataset/coco.py index 8026071a90c3..9ca5a74cc461 100644 --- a/example/rcnn/rcnn/dataset/coco.py +++ b/example/rcnn/rcnn/dataset/coco.py @@ -1,10 +1,27 @@ -from __future__ import print_function +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import cPickle import cv2 import os import json import numpy as np +from ..logger import logger from imdb import IMDB # coco api @@ -38,7 +55,7 @@ def __init__(self, image_set, root_path, data_path): # load image file names self.image_set_index = self._load_image_set_index() self.num_images = len(self.image_set_index) - print('num_images', self.num_images) + logger.info('%s num_images %d' % (self.name, self.num_images)) # deal with data name view_map = {'minival2014': 'val2014', @@ -68,13 +85,13 @@ def gt_roidb(self): if os.path.exists(cache_file): with open(cache_file, 'rb') as fid: roidb = cPickle.load(fid) - print('{} gt roidb loaded from {}'.format(self.name, cache_file)) + logger.info('%s gt roidb loaded from %s' % (self.name, cache_file)) return roidb gt_roidb = [self._load_coco_annotation(index) for index in self.image_set_index] with open(cache_file, 'wb') as fid: cPickle.dump(gt_roidb, fid, cPickle.HIGHEST_PROTOCOL) - print('wrote gt roidb to {}'.format(cache_file)) + logger.info('%s wrote gt roidb to %s' % (self.name, cache_file)) return gt_roidb @@ -155,10 +172,10 @@ def _write_coco_results(self, detections, res_file): for cls_ind, cls in enumerate(self.classes): if cls == '__background__': continue - print('Collecting %s results (%d/%d)' % (cls, cls_ind, self.num_classes - 1)) + logger.info('collecting %s results (%d/%d)' % (cls, cls_ind, self.num_classes - 1)) coco_cat_id = self._class_to_coco_ind[cls] results.extend(self._coco_results_one_category(detections[cls_ind], coco_cat_id)) - print('Writing results json to %s' % res_file) + logger.info('writing results json to %s' % res_file) with open(res_file, 'w') as f: json.dump(results, f, sort_keys=True, indent=4) @@ -192,7 +209,7 @@ def _do_python_eval(self, res_file, res_folder): eval_file = os.path.join(res_folder, 'detections_%s_results.pkl' % self.image_set) with open(eval_file, 'wb') as f: cPickle.dump(coco_eval, f, cPickle.HIGHEST_PROTOCOL) - print('coco eval results saved to %s' % eval_file) + logger.info('eval results saved to %s' % eval_file) def _print_detection_metrics(self, coco_eval): IoU_lo_thresh = 0.5 @@ -214,15 +231,15 @@ def _get_thr_ind(coco_eval, thr): precision = \ coco_eval.eval['precision'][ind_lo:(ind_hi + 1), :, :, 0, 2] ap_default = np.mean(precision[precision > -1]) - print('~~~~ Mean and per-category AP @ IoU=%.2f,%.2f] ~~~~' % (IoU_lo_thresh, IoU_hi_thresh)) - print('%-15s %5.1f' % ('all', 100 * ap_default)) + logger.info('~~~~ Mean and per-category AP @ IoU=%.2f,%.2f] ~~~~' % (IoU_lo_thresh, IoU_hi_thresh)) + logger.info('%-15s %5.1f' % ('all', 100 * ap_default)) for cls_ind, cls in enumerate(self.classes): if cls == '__background__': continue # minus 1 because of __background__ precision = coco_eval.eval['precision'][ind_lo:(ind_hi + 1), :, cls_ind - 1, 0, 2] ap = np.mean(precision[precision > -1]) - print('%-15s %5.1f' % (cls, 100 * ap)) + logger.info('%-15s %5.1f' % (cls, 100 * ap)) - print('~~~~ Summary metrics ~~~~') + logger.info('~~~~ Summary metrics ~~~~') coco_eval.summarize() diff --git a/example/rcnn/rcnn/dataset/ds_utils.py b/example/rcnn/rcnn/dataset/ds_utils.py index 131644b726fc..e6f839b8fdb9 100644 --- a/example/rcnn/rcnn/dataset/ds_utils.py +++ b/example/rcnn/rcnn/dataset/ds_utils.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import numpy as np @@ -13,4 +30,4 @@ def filter_small_boxes(boxes, min_size): w = boxes[:, 2] - boxes[:, 0] h = boxes[:, 3] - boxes[:, 1] keep = np.where((w >= min_size) & (h > min_size))[0] - return keep \ No newline at end of file + return keep diff --git a/example/rcnn/rcnn/dataset/imdb.py b/example/rcnn/rcnn/dataset/imdb.py index 1ad18dbc29bc..b9038c5da0a0 100644 --- a/example/rcnn/rcnn/dataset/imdb.py +++ b/example/rcnn/rcnn/dataset/imdb.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + """ General image database An image database creates a list of relative image path called image_set_index and @@ -9,7 +26,7 @@ 'boxes', 'gt_classes', 'gt_overlaps', 'max_classes', 'max_overlaps', 'bbox_targets'] """ -from __future__ import print_function +from ..logger import logger import os import cPickle import numpy as np @@ -70,8 +87,8 @@ def load_rpn_data(self, full=False): rpn_file = os.path.join(self.root_path, 'rpn_data', self.name + '_full_rpn.pkl') else: rpn_file = os.path.join(self.root_path, 'rpn_data', self.name + '_rpn.pkl') - print('loading {}'.format(rpn_file)) - assert os.path.exists(rpn_file), 'rpn data not found at {}'.format(rpn_file) + assert os.path.exists(rpn_file), '%s rpn data not found at %s' % (self.name, rpn_file) + logger.info('%s loading rpn data from %s' % (self.name, rpn_file)) with open(rpn_file, 'rb') as f: box_list = cPickle.load(f) return box_list @@ -93,7 +110,7 @@ def rpn_roidb(self, gt_roidb, append_gt=False): :return: roidb of rpn """ if append_gt: - print('appending ground truth annotations') + logger.info('%s appending ground truth annotations' % self.name) rpn_roidb = self.load_rpn_roidb(gt_roidb) roidb = IMDB.merge_roidbs(gt_roidb, rpn_roidb) else: @@ -156,7 +173,7 @@ def append_flipped_images(self, roidb): :param roidb: [image_index]['boxes', 'gt_classes', 'gt_overlaps', 'flipped'] :return: roidb: [image_index]['boxes', 'gt_classes', 'gt_overlaps', 'flipped'] """ - print('append flipped images to roidb') + logger.info('%s append flipped images to roidb' % self.name) assert self.num_images == len(roidb) for i in range(self.num_images): roi_rec = roidb[i] @@ -211,8 +228,8 @@ def evaluate_recall(self, roidb, candidate_boxes=None, thresholds=None): area_counts.append(area_count) total_counts = float(sum(area_counts)) for area_name, area_count in zip(area_names[1:], area_counts): - print('percentage of', area_name, area_count / total_counts) - print('average number of proposal', total_counts / self.num_images) + logger.info('percentage of %s is %f' % (area_name, area_count / total_counts)) + logger.info('average number of proposal is %f' % (total_counts / self.num_images)) for area_name, area_range in zip(area_names, area_ranges): gt_overlaps = np.zeros(0) num_pos = 0 diff --git a/example/rcnn/rcnn/dataset/pascal_voc.py b/example/rcnn/rcnn/dataset/pascal_voc.py index 268399316162..091c4e8ea17b 100644 --- a/example/rcnn/rcnn/dataset/pascal_voc.py +++ b/example/rcnn/rcnn/dataset/pascal_voc.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + """ Pascal VOC database This class loads ground truth notations from standard Pascal VOC XML data formats @@ -6,12 +23,12 @@ criterion. """ -from __future__ import print_function import cPickle import cv2 import os import numpy as np +from ..logger import logger from imdb import IMDB from pascal_voc_eval import voc_eval from ds_utils import unique_boxes, filter_small_boxes @@ -42,7 +59,7 @@ def __init__(self, image_set, root_path, devkit_path): self.num_classes = len(self.classes) self.image_set_index = self.load_image_set_index() self.num_images = len(self.image_set_index) - print('num_images', self.num_images) + logger.info('%s num_images %d' % (self.name, self.num_images)) self.config = {'comp_id': 'comp4', 'use_diff': False, @@ -78,13 +95,13 @@ def gt_roidb(self): if os.path.exists(cache_file): with open(cache_file, 'rb') as fid: roidb = cPickle.load(fid) - print('{} gt roidb loaded from {}'.format(self.name, cache_file)) + logger.info('%s gt roidb loaded from %s' % (self.name, cache_file)) return roidb gt_roidb = [self.load_pascal_annotation(index) for index in self.image_set_index] with open(cache_file, 'wb') as fid: cPickle.dump(gt_roidb, fid, cPickle.HIGHEST_PROTOCOL) - print('wrote gt roidb to {}'.format(cache_file)) + logger.info('%s wrote gt roidb to %s' % (self.name, cache_file)) return gt_roidb @@ -168,18 +185,18 @@ def selective_search_roidb(self, gt_roidb, append_gt=False): if os.path.exists(cache_file): with open(cache_file, 'rb') as fid: roidb = cPickle.load(fid) - print('{} ss roidb loaded from {}'.format(self.name, cache_file)) + logger.info('%s ss roidb loaded from %s' % (self.name, cache_file)) return roidb if append_gt: - print('appending ground truth annotations') + logger.info('%s appending ground truth annotations' % self.name) ss_roidb = self.load_selective_search_roidb(gt_roidb) roidb = IMDB.merge_roidbs(gt_roidb, ss_roidb) else: roidb = self.load_selective_search_roidb(gt_roidb) with open(cache_file, 'wb') as fid: cPickle.dump(roidb, fid, cPickle.HIGHEST_PROTOCOL) - print('wrote ss roidb to {}'.format(cache_file)) + logger.info('%s wrote ss roidb to %s' % (self.name, cache_file)) return roidb @@ -224,7 +241,7 @@ def write_pascal_results(self, all_boxes): for cls_ind, cls in enumerate(self.classes): if cls == '__background__': continue - print('Writing {} VOC results file'.format(cls)) + logger.info('Writing %s VOC results file' % cls) filename = self.get_result_file_template().format(cls) with open(filename, 'wt') as f: for im_ind, index in enumerate(self.image_set_index): @@ -248,7 +265,7 @@ def do_python_eval(self): aps = [] # The PASCAL VOC metric changed in 2010 use_07_metric = True if int(self.year) < 2010 else False - print('VOC07 metric? ' + ('Y' if use_07_metric else 'No')) + logger.info('VOC07 metric? ' + ('Y' if use_07_metric else 'No')) for cls_ind, cls in enumerate(self.classes): if cls == '__background__': continue @@ -256,5 +273,5 @@ def do_python_eval(self): rec, prec, ap = voc_eval(filename, annopath, imageset_file, cls, annocache, ovthresh=0.5, use_07_metric=use_07_metric) aps += [ap] - print('AP for {} = {:.4f}'.format(cls, ap)) - print('Mean AP = {:.4f}'.format(np.mean(aps))) + logger.info('AP for {} = {:.4f}'.format(cls, ap)) + logger.info('Mean AP = {:.4f}'.format(np.mean(aps))) diff --git a/example/rcnn/rcnn/dataset/pascal_voc_eval.py b/example/rcnn/rcnn/dataset/pascal_voc_eval.py index 295b866bb697..e584ed750304 100644 --- a/example/rcnn/rcnn/dataset/pascal_voc_eval.py +++ b/example/rcnn/rcnn/dataset/pascal_voc_eval.py @@ -1,8 +1,25 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + """ given a pascal voc imdb, compute mAP """ -from __future__ import print_function +from ..logger import logger import numpy as np import os import cPickle @@ -86,8 +103,8 @@ def voc_eval(detpath, annopath, imageset_file, classname, annocache, ovthresh=0. for ind, image_filename in enumerate(image_filenames): recs[image_filename] = parse_voc_rec(annopath.format(image_filename)) if ind % 100 == 0: - print('reading annotations for {:d}/{:d}'.format(ind + 1, len(image_filenames))) - print('saving annotations cache to {:s}'.format(annocache)) + logger.info('reading annotations for %d/%d' % (ind + 1, len(image_filenames))) + logger.info('saving annotations cache to %s' % annocache) with open(annocache, 'wb') as f: cPickle.dump(recs, f, protocol=cPickle.HIGHEST_PROTOCOL) else: diff --git a/example/rcnn/rcnn/io/image.py b/example/rcnn/rcnn/io/image.py index 04254fd94c43..e468e4647b97 100644 --- a/example/rcnn/rcnn/io/image.py +++ b/example/rcnn/rcnn/io/image.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import numpy as np import cv2 import os @@ -70,8 +87,8 @@ def resize(im, target_size, max_size, stride=0): def transform(im, pixel_means): """ - transform into mxnet tensor - substract pixel size and transform to correct format + transform into mxnet tensor, + subtract pixel size and transform to correct format :param im: [height, width, channel] in BGR :param pixel_means: [B, G, R pixel means] :return: [batch, channel, height, width] diff --git a/example/rcnn/rcnn/io/rcnn.py b/example/rcnn/rcnn/io/rcnn.py index aad1a4617c0e..f9613d68bda5 100644 --- a/example/rcnn/rcnn/io/rcnn.py +++ b/example/rcnn/rcnn/io/rcnn.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + """ Fast R-CNN: data = @@ -146,12 +163,13 @@ def sample_rois(rois, fg_rois_per_image, rois_per_image, num_classes, # indexes selected keep_indexes = np.append(fg_indexes, bg_indexes) - + neg_idx = np.where(overlaps < config.TRAIN.FG_THRESH)[0] + neg_rois = rois[neg_idx] # pad more to ensure a fixed minibatch size while keep_indexes.shape[0] < rois_per_image: - gap = np.minimum(len(rois), rois_per_image - keep_indexes.shape[0]) - gap_indexes = npr.choice(range(len(rois)), size=gap, replace=False) - keep_indexes = np.append(keep_indexes, gap_indexes) + gap = np.minimum(len(neg_rois), rois_per_image - keep_indexes.shape[0]) + gap_indexes = npr.choice(range(len(neg_rois)), size=gap, replace=False) + keep_indexes = np.append(keep_indexes, neg_idx[gap_indexes]) # select labels labels = labels[keep_indexes] diff --git a/example/rcnn/rcnn/io/rpn.py b/example/rcnn/rcnn/io/rpn.py index c813e4ab06f6..20cd1ce4e744 100644 --- a/example/rcnn/rcnn/io/rpn.py +++ b/example/rcnn/rcnn/io/rpn.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + """ RPN: data = @@ -10,10 +27,11 @@ 'bbox_weight': [batch_size, num_anchors, feat_height, feat_width]} """ -from __future__ import print_function +import logging import numpy as np import numpy.random as npr +from ..logger import logger from ..config import config from .image import get_image, tensor_vstack from ..processing.generate_anchor import generate_anchors @@ -94,23 +112,19 @@ def _unmap(data, count, inds, fill=0): ret[inds, :] = data return ret - DEBUG = False im_info = im_info[0] scales = np.array(scales, dtype=np.float32) base_anchors = generate_anchors(base_size=feat_stride, ratios=list(ratios), scales=scales) num_anchors = base_anchors.shape[0] feat_height, feat_width = feat_shape[-2:] - if DEBUG: - print('anchors:') - print(base_anchors) - print('anchor shapes:') - print(np.hstack((base_anchors[:, 2::4] - base_anchors[:, 0::4], - base_anchors[:, 3::4] - base_anchors[:, 1::4]))) - print('im_info', im_info) - print('height', feat_height, 'width', feat_width) - print('gt_boxes shape', gt_boxes.shape) - print('gt_boxes', gt_boxes) + logger.debug('anchors: %s' % base_anchors) + logger.debug('anchor shapes: %s' % np.hstack((base_anchors[:, 2::4] - base_anchors[:, 0::4], + base_anchors[:, 3::4] - base_anchors[:, 1::4]))) + logger.debug('im_info %s' % im_info) + logger.debug('height %d width %d' % (feat_height, feat_width)) + logger.debug('gt_boxes shape %s' % np.array(gt_boxes.shape)) + logger.debug('gt_boxes %s' % gt_boxes) # 1. generate proposals from bbox deltas and shifted anchors shift_x = np.arange(0, feat_width) * feat_stride @@ -132,14 +146,12 @@ def _unmap(data, count, inds, fill=0): (all_anchors[:, 1] >= -allowed_border) & (all_anchors[:, 2] < im_info[1] + allowed_border) & (all_anchors[:, 3] < im_info[0] + allowed_border))[0] - if DEBUG: - print('total_anchors', total_anchors) - print('inds_inside', len(inds_inside)) + logger.debug('total_anchors %d' % total_anchors) + logger.debug('inds_inside %d' % len(inds_inside)) # keep only inside anchors anchors = all_anchors[inds_inside, :] - if DEBUG: - print('anchors shape', anchors.shape) + logger.debug('anchors shape %s' % np.array(anchors.shape)) # label: 1 is positive, 0 is negative, -1 is dont care labels = np.empty((len(inds_inside),), dtype=np.float32) @@ -176,7 +188,7 @@ def _unmap(data, count, inds, fill=0): fg_inds = np.where(labels == 1)[0] if len(fg_inds) > num_fg: disable_inds = npr.choice(fg_inds, size=(len(fg_inds) - num_fg), replace=False) - if DEBUG: + if logger.level == logging.INFO: disable_inds = fg_inds[:(len(fg_inds) - num_fg)] labels[disable_inds] = -1 @@ -185,7 +197,7 @@ def _unmap(data, count, inds, fill=0): bg_inds = np.where(labels == 0)[0] if len(bg_inds) > num_bg: disable_inds = npr.choice(bg_inds, size=(len(bg_inds) - num_bg), replace=False) - if DEBUG: + if logger.level == logging.INFO: disable_inds = bg_inds[:(len(bg_inds) - num_bg)] labels[disable_inds] = -1 @@ -196,29 +208,30 @@ def _unmap(data, count, inds, fill=0): bbox_weights = np.zeros((len(inds_inside), 4), dtype=np.float32) bbox_weights[labels == 1, :] = np.array(config.TRAIN.RPN_BBOX_WEIGHTS) - if DEBUG: + if logger.level == logging.DEBUG: _sums = bbox_targets[labels == 1, :].sum(axis=0) _squared_sums = (bbox_targets[labels == 1, :] ** 2).sum(axis=0) _counts = np.sum(labels == 1) means = _sums / (_counts + 1e-14) stds = np.sqrt(_squared_sums / _counts - means ** 2) - print('means', means) - print('stdevs', stds) + logger.debug('means %s' % means) + logger.debug('stdevs %s' % stds) # map up to original set of anchors labels = _unmap(labels, total_anchors, inds_inside, fill=-1) bbox_targets = _unmap(bbox_targets, total_anchors, inds_inside, fill=0) bbox_weights = _unmap(bbox_weights, total_anchors, inds_inside, fill=0) - if DEBUG: - print('rpn: max max_overlaps', np.max(max_overlaps)) - print('rpn: num_positives', np.sum(labels == 1)) - print('rpn: num_negatives', np.sum(labels == 0)) + if logger.level == logging.DEBUG: + if gt_boxes.size > 0: + logger.debug('rpn: max max_overlaps %f' % np.max(max_overlaps)) + logger.debug('rpn: num_positives %f' % np.sum(labels == 1)) + logger.debug('rpn: num_negatives %f' % np.sum(labels == 0)) _fg_sum = np.sum(labels == 1) _bg_sum = np.sum(labels == 0) _count = 1 - print('rpn: num_positive avg', _fg_sum / _count) - print('rpn: num_negative avg', _bg_sum / _count) + logger.debug('rpn: num_positive avg %f' % (_fg_sum / _count)) + logger.debug('rpn: num_negative avg %f' % (_bg_sum / _count)) labels = labels.reshape((1, feat_height, feat_width, A)).transpose(0, 3, 1, 2) labels = labels.reshape((1, A * feat_height * feat_width)) diff --git a/example/rcnn/rcnn/logger.py b/example/rcnn/rcnn/logger.py new file mode 100644 index 000000000000..e82201797942 --- /dev/null +++ b/example/rcnn/rcnn/logger.py @@ -0,0 +1,23 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +import logging + +# set up logger +logging.basicConfig() +logger = logging.getLogger() +logger.setLevel(logging.INFO) diff --git a/example/rcnn/rcnn/processing/bbox_regression.py b/example/rcnn/rcnn/processing/bbox_regression.py index 46969aa0ec5e..d5330f409875 100644 --- a/example/rcnn/rcnn/processing/bbox_regression.py +++ b/example/rcnn/rcnn/processing/bbox_regression.py @@ -1,10 +1,27 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + """ This file has functions about generating bounding box regression targets """ -from __future__ import print_function import numpy as np +from ..logger import logger from bbox_transform import bbox_overlaps, bbox_transform from rcnn.config import config @@ -22,12 +39,13 @@ def compute_bbox_regression_targets(rois, overlaps, labels): # Sanity check if len(rois) != len(overlaps): - print('bbox regression: this should not happen') + logger.warning('bbox regression: len(rois) != len(overlaps)') # Indices of ground-truth ROIs gt_inds = np.where(overlaps == 1)[0] if len(gt_inds) == 0: - print('something wrong : zero ground truth rois') + logger.warning('bbox regression: len(gt_inds) == 0') + # Indices of examples for which we try to make predictions ex_inds = np.where(overlaps >= config.TRAIN.BBOX_REGRESSION_THRESH)[0] @@ -52,7 +70,7 @@ def add_bbox_regression_targets(roidb): :param roidb: roidb to be processed. must have gone through imdb.prepare_roidb :return: means, std variances of targets """ - print('add bounding box regression targets') + logger.info('bbox regression: add bounding box regression targets') assert len(roidb) > 0 assert 'max_classes' in roidb[0] diff --git a/example/rcnn/rcnn/processing/bbox_transform.py b/example/rcnn/rcnn/processing/bbox_transform.py index 7a8667e14fe2..04fa81feda78 100644 --- a/example/rcnn/rcnn/processing/bbox_transform.py +++ b/example/rcnn/rcnn/processing/bbox_transform.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import numpy as np from ..cython.bbox import bbox_overlaps_cython diff --git a/example/rcnn/rcnn/processing/generate_anchor.py b/example/rcnn/rcnn/processing/generate_anchor.py index 8996a3aaab48..0e97d6ef2ba6 100644 --- a/example/rcnn/rcnn/processing/generate_anchor.py +++ b/example/rcnn/rcnn/processing/generate_anchor.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + """ Generate base anchors on index 0 """ diff --git a/example/rcnn/rcnn/processing/image_processing.py b/example/rcnn/rcnn/processing/image_processing.py deleted file mode 100644 index dafca3c15850..000000000000 --- a/example/rcnn/rcnn/processing/image_processing.py +++ /dev/null @@ -1,83 +0,0 @@ -import numpy as np -import cv2 - - -def resize(im, target_size, max_size): - """ - only resize input image to target size and return scale - :param im: BGR image input by opencv - :param target_size: one dimensional size (the short side) - :param max_size: one dimensional max size (the long side) - :return: - """ - im_shape = im.shape - im_size_min = np.min(im_shape[0:2]) - im_size_max = np.max(im_shape[0:2]) - im_scale = float(target_size) / float(im_size_min) - # prevent bigger axis from being more than max_size: - if np.round(im_scale * im_size_max) > max_size: - im_scale = float(max_size) / float(im_size_max) - im = cv2.resize(im, None, None, fx=im_scale, fy=im_scale, interpolation=cv2.INTER_LINEAR) - return im, im_scale - - -def transform(im, pixel_means, need_mean=False): - """ - transform into mxnet tensor - subtract pixel size and transform to correct format - :param im: [height, width, channel] in BGR - :param pixel_means: [[[R, G, B pixel means]]] - :return: [batch, channel, height, width] - """ - im = im.copy() - im[:, :, (0, 1, 2)] = im[:, :, (2, 1, 0)] - im = im.astype(float) - if need_mean: - im -= pixel_means - im_tensor = im[np.newaxis, :] - # put channel first - channel_swap = (0, 3, 1, 2) - im_tensor = im_tensor.transpose(channel_swap) - return im_tensor - - -def transform_inverse(im_tensor, pixel_means): - """ - transform from mxnet im_tensor to ordinary RGB image - im_tensor is limited to one image - :param im_tensor: [batch, channel, height, width] - :param pixel_means: [[[R, G, B pixel means]]] - :return: im [height, width, channel(RGB)] - """ - assert im_tensor.shape[0] == 1 - im_tensor = im_tensor.copy() - # put channel back - channel_swap = (0, 2, 3, 1) - im_tensor = im_tensor.transpose(channel_swap) - im = im_tensor[0] - assert im.shape[2] == 3 - im += pixel_means - im = im.astype(np.uint8) - return im - - -def tensor_vstack(tensor_list, pad=0): - """ - vertically stack tensors - :param tensor_list: list of tensor to be stacked vertically - :param pad: label to pad with - :return: tensor with max shape - """ - ndim = len(tensor_list[0].shape) - if ndim == 1: - return np.hstack(tensor_list) - dimensions = [0] - for dim in range(1, ndim): - dimensions.append(max([tensor.shape[dim] for tensor in tensor_list])) - for ind, tensor in enumerate(tensor_list): - pad_shape = [(0, 0)] - for dim in range(1, ndim): - pad_shape.append((0, dimensions[dim] - tensor.shape[dim])) - tensor_list[ind] = np.lib.pad(tensor, pad_shape, 'constant', constant_values=pad) - all_tensor = np.vstack(tensor_list) - return all_tensor diff --git a/example/rcnn/rcnn/processing/nms.py b/example/rcnn/rcnn/processing/nms.py index cab093c51152..eca8d58626d3 100644 --- a/example/rcnn/rcnn/processing/nms.py +++ b/example/rcnn/rcnn/processing/nms.py @@ -1,6 +1,26 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import numpy as np from ..cython.cpu_nms import cpu_nms -from ..cython.gpu_nms import gpu_nms +try: + from ..cython.gpu_nms import gpu_nms +except ImportError: + gpu_nms = None def py_nms_wrapper(thresh): @@ -18,7 +38,10 @@ def _nms(dets): def gpu_nms_wrapper(thresh, device_id): def _nms(dets): return gpu_nms(dets, thresh, device_id) - return _nms + if gpu_nms is not None: + return _nms + else: + return cpu_nms_wrapper(thresh) def nms(dets, thresh): diff --git a/example/rcnn/rcnn/processing/roidb.py b/example/rcnn/rcnn/processing/roidb.py deleted file mode 100644 index 8dddc27f60c9..000000000000 --- a/example/rcnn/rcnn/processing/roidb.py +++ /dev/null @@ -1,91 +0,0 @@ -""" -roidb -basic format [image_index]['boxes', 'gt_classes', 'gt_overlaps', 'flipped'] -extended ['image', 'max_classes', 'max_overlaps', 'bbox_targets'] -""" - -from __future__ import print_function -import cv2 -import numpy as np - -from bbox_regression import compute_bbox_regression_targets -from rcnn.config import config - - -def prepare_roidb(imdb, roidb): - """ - add image path, max_classes, max_overlaps to roidb - :param imdb: image database, provide path - :param roidb: roidb - :return: None - """ - print('prepare roidb') - for i in range(len(roidb)): # image_index - roidb[i]['image'] = imdb.image_path_from_index(imdb.image_set_index[i]) - if config.TRAIN.ASPECT_GROUPING: - size = cv2.imread(roidb[i]['image']).shape - roidb[i]['height'] = size[0] - roidb[i]['width'] = size[1] - gt_overlaps = roidb[i]['gt_overlaps'].toarray() - max_overlaps = gt_overlaps.max(axis=1) - max_classes = gt_overlaps.argmax(axis=1) - roidb[i]['max_overlaps'] = max_overlaps - roidb[i]['max_classes'] = max_classes - - # background roi => background class - zero_indexes = np.where(max_overlaps == 0)[0] - assert all(max_classes[zero_indexes] == 0) - # foreground roi => foreground class - nonzero_indexes = np.where(max_overlaps > 0)[0] - assert all(max_classes[nonzero_indexes] != 0) - - -def add_bbox_regression_targets(roidb): - """ - given roidb, add ['bbox_targets'] and normalize bounding box regression targets - :param roidb: roidb to be processed. must have gone through imdb.prepare_roidb - :return: means, std variances of targets - """ - print('add bounding box regression targets') - assert len(roidb) > 0 - assert 'max_classes' in roidb[0] - - num_images = len(roidb) - num_classes = roidb[0]['gt_overlaps'].shape[1] - for im_i in range(num_images): - rois = roidb[im_i]['boxes'] - max_overlaps = roidb[im_i]['max_overlaps'] - max_classes = roidb[im_i]['max_classes'] - roidb[im_i]['bbox_targets'] = compute_bbox_regression_targets(rois, max_overlaps, max_classes) - - if config.TRAIN.BBOX_NORMALIZATION_PRECOMPUTED: - # use fixed / precomputed means and stds instead of empirical values - means = np.tile(np.array(config.TRAIN.BBOX_MEANS), (num_classes, 1)) - stds = np.tile(np.array(config.TRAIN.BBOX_STDS), (num_classes, 1)) - else: - # compute mean, std values - class_counts = np.zeros((num_classes, 1)) + config.EPS - sums = np.zeros((num_classes, 4)) - squared_sums = np.zeros((num_classes, 4)) - for im_i in range(num_images): - targets = roidb[im_i]['bbox_targets'] - for cls in range(1, num_classes): - cls_indexes = np.where(targets[:, 0] == cls)[0] - if cls_indexes.size > 0: - class_counts[cls] += cls_indexes.size - sums[cls, :] += targets[cls_indexes, 1:].sum(axis=0) - squared_sums[cls, :] += (targets[cls_indexes, 1:] ** 2).sum(axis=0) - - means = sums / class_counts - # var(x) = E(x^2) - E(x)^2 - stds = np.sqrt(squared_sums / class_counts - means ** 2) - - # normalized targets - for im_i in range(num_images): - targets = roidb[im_i]['bbox_targets'] - for cls in range(1, num_classes): - cls_indexes = np.where(targets[:, 0] == cls)[0] - roidb[im_i]['bbox_targets'][cls_indexes, 1:] -= means[cls, :] - roidb[im_i]['bbox_targets'][cls_indexes, 1:] /= stds[cls, :] - - return means.ravel(), stds.ravel() diff --git a/example/rcnn/rcnn/pycocotools/UPSTREAM_REV b/example/rcnn/rcnn/pycocotools/UPSTREAM_REV index 706219b77d90..9613b145b237 100644 --- a/example/rcnn/rcnn/pycocotools/UPSTREAM_REV +++ b/example/rcnn/rcnn/pycocotools/UPSTREAM_REV @@ -1 +1 @@ -https://github.com/pdollar/coco/commit/3ac47c77ebd5a1ed4254a98b7fbf2ef4765a3574 +https://github.com/pdollar/coco/commit/336d2a27c91e3c0663d2dcf0b13574674d30f88e diff --git a/example/rcnn/rcnn/pycocotools/__init__.py b/example/rcnn/rcnn/pycocotools/__init__.py index 3f7d85bba884..2f4e0d430df9 100644 --- a/example/rcnn/rcnn/pycocotools/__init__.py +++ b/example/rcnn/rcnn/pycocotools/__init__.py @@ -1 +1,18 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + __author__ = 'tylin' diff --git a/example/rcnn/rcnn/pycocotools/_mask.pyx b/example/rcnn/rcnn/pycocotools/_mask.pyx index 4e9278af2a03..1c3e127a1c05 100644 --- a/example/rcnn/rcnn/pycocotools/_mask.pyx +++ b/example/rcnn/rcnn/pycocotools/_mask.pyx @@ -10,6 +10,9 @@ __author__ = 'tsungyi' +import sys +PYTHON_VERSION = sys.version_info[0] + # import both Python-level and C-level symbols of Numpy # the API uses Numpy to interface C and Python import numpy as np @@ -38,7 +41,7 @@ cdef extern from "maskApi.h": void rlesInit( RLE **R, siz n ) void rleEncode( RLE *R, const byte *M, siz h, siz w, siz n ) void rleDecode( const RLE *R, byte *mask, siz n ) - void rleMerge( const RLE *R, RLE *M, siz n, bint intersect ) + void rleMerge( const RLE *R, RLE *M, siz n, int intersect ) void rleArea( const RLE *R, siz n, uint *a ) void rleIou( RLE *dt, RLE *gt, siz m, siz n, byte *iscrowd, double *o ) void bbIou( BB dt, BB gt, siz m, siz n, byte *iscrowd, double *o ) @@ -119,7 +122,12 @@ def _frString(rleObjs): cdef bytes py_string cdef char* c_string for i, obj in enumerate(rleObjs): - py_string = str(obj['counts']) + if PYTHON_VERSION == 2: + py_string = str(obj['counts']).encode('utf8') + elif PYTHON_VERSION == 3: + py_string = str.encode(obj['counts']) if type(obj['counts']) == str else obj['counts'] + else: + raise Exception('Python version must be 2 or 3') c_string = py_string rleFrString( &Rs._R[i], c_string, obj['size'][0], obj['size'][1] ) return Rs @@ -138,10 +146,10 @@ def decode(rleObjs): cdef RLEs Rs = _frString(rleObjs) h, w, n = Rs._R[0].h, Rs._R[0].w, Rs._n masks = Masks(h, w, n) - rleDecode( Rs._R, masks._mask, n ); + rleDecode(Rs._R, masks._mask, n); return np.array(masks) -def merge(rleObjs, bint intersect=0): +def merge(rleObjs, intersect=0): cdef RLEs Rs = _frString(rleObjs) cdef RLEs R = RLEs(1) rleMerge(Rs._R, R._R, Rs._n, intersect) @@ -255,7 +263,7 @@ def frPoly( poly, siz h, siz w ): Rs = RLEs(n) for i, p in enumerate(poly): np_poly = np.array(p, dtype=np.double, order='F') - rleFrPoly( &Rs._R[i], np_poly.data, len(np_poly)/2, h, w ) + rleFrPoly( &Rs._R[i], np_poly.data, int(len(p)/2), h, w ) objs = _toString(Rs) return objs @@ -277,15 +285,24 @@ def frUncompressedRLE(ucRles, siz h, siz w): objs.append(_toString(Rs)[0]) return objs -def frPyObjects(pyobj, siz h, w): +def frPyObjects(pyobj, h, w): + # encode rle from a list of python objects if type(pyobj) == np.ndarray: - objs = frBbox(pyobj, h, w ) + objs = frBbox(pyobj, h, w) elif type(pyobj) == list and len(pyobj[0]) == 4: - objs = frBbox(pyobj, h, w ) + objs = frBbox(pyobj, h, w) elif type(pyobj) == list and len(pyobj[0]) > 4: - objs = frPoly(pyobj, h, w ) - elif type(pyobj) == list and type(pyobj[0]) == dict: + objs = frPoly(pyobj, h, w) + elif type(pyobj) == list and type(pyobj[0]) == dict \ + and 'counts' in pyobj[0] and 'size' in pyobj[0]: objs = frUncompressedRLE(pyobj, h, w) + # encode rle from single python object + elif type(pyobj) == list and len(pyobj) == 4: + objs = frBbox([pyobj], h, w)[0] + elif type(pyobj) == list and len(pyobj) > 4: + objs = frPoly([pyobj], h, w)[0] + elif type(pyobj) == dict and 'counts' in pyobj and 'size' in pyobj: + objs = frUncompressedRLE([pyobj], h, w)[0] else: raise Exception('input type is not supported.') return objs diff --git a/example/rcnn/rcnn/pycocotools/coco.py b/example/rcnn/rcnn/pycocotools/coco.py index 44158d21d5a4..5cc835a05633 100644 --- a/example/rcnn/rcnn/pycocotools/coco.py +++ b/example/rcnn/rcnn/pycocotools/coco.py @@ -1,5 +1,22 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + __author__ = 'tylin' -__version__ = '1.0.1' +__version__ = '2.0' # Interface for accessing the Microsoft COCO dataset. # Microsoft COCO is a large image dataset designed for object detection, @@ -27,7 +44,7 @@ # loadAnns - Load anns with the specified ids. # loadCats - Load cats with the specified ids. # loadImgs - Load imgs with the specified ids. -# segToMask - Convert polygon segmentation to binary mask. +# annToMask - Convert segmentation in an annotation to binary mask. # showAnns - Display the specified annotations. # loadRes - Load algorithm results and create API for accessing them. # download - Download COCO images from mscoco.org server. @@ -37,27 +54,30 @@ # See also COCO>decodeMask, # COCO>encodeMask, COCO>getAnnIds, COCO>getCatIds, # COCO>getImgIds, COCO>loadAnns, COCO>loadCats, -# COCO>loadImgs, COCO>segToMask, COCO>showAnns +# COCO>loadImgs, COCO>annToMask, COCO>showAnns # Microsoft COCO Toolbox. version 2.0 # Data, paper, and tutorials available at: http://mscoco.org/ # Code written by Piotr Dollar and Tsung-Yi Lin, 2014. # Licensed under the Simplified BSD License [see bsd.txt] -from __future__ import print_function import json -import datetime import time import matplotlib.pyplot as plt from matplotlib.collections import PatchCollection from matplotlib.patches import Polygon import numpy as np -from skimage.draw import polygon -import urllib import copy import itertools -import mask +from . import mask as maskUtils import os +from collections import defaultdict +import sys +PYTHON_VERSION = sys.version_info[0] +if PYTHON_VERSION == 2: + from urllib import urlretrieve +elif PYTHON_VERSION == 3: + from urllib.request import urlretrieve class COCO: def __init__(self, annotation_file=None): @@ -68,47 +88,38 @@ def __init__(self, annotation_file=None): :return: """ # load dataset - self.dataset = {} - self.anns = [] - self.imgToAnns = {} - self.catToImgs = {} - self.imgs = {} - self.cats = {} - if annotation_file is not None: + self.dataset,self.anns,self.cats,self.imgs = dict(),dict(),dict(),dict() + self.imgToAnns, self.catToImgs = defaultdict(list), defaultdict(list) + if not annotation_file == None: print('loading annotations into memory...') tic = time.time() dataset = json.load(open(annotation_file, 'r')) - print('Done (t=%0.2fs)'%(time.time()- tic)) + assert type(dataset)==dict, 'annotation file format {} not supported'.format(type(dataset)) + print('Done (t={:0.2f}s)'.format(time.time()- tic)) self.dataset = dataset self.createIndex() def createIndex(self): # create index print('creating index...') - anns = {} - imgToAnns = {} - catToImgs = {} - cats = {} - imgs = {} + anns, cats, imgs = {}, {}, {} + imgToAnns,catToImgs = defaultdict(list),defaultdict(list) if 'annotations' in self.dataset: - imgToAnns = {ann['image_id']: [] for ann in self.dataset['annotations']} - anns = {ann['id']: [] for ann in self.dataset['annotations']} for ann in self.dataset['annotations']: - imgToAnns[ann['image_id']] += [ann] + imgToAnns[ann['image_id']].append(ann) anns[ann['id']] = ann if 'images' in self.dataset: - imgs = {im['id']: {} for im in self.dataset['images']} for img in self.dataset['images']: imgs[img['id']] = img if 'categories' in self.dataset: - cats = {cat['id']: [] for cat in self.dataset['categories']} for cat in self.dataset['categories']: cats[cat['id']] = cat - catToImgs = {cat['id']: [] for cat in self.dataset['categories']} + + if 'annotations' in self.dataset and 'categories' in self.dataset: for ann in self.dataset['annotations']: - catToImgs[ann['category_id']] += [ann['image_id']] + catToImgs[ann['category_id']].append(ann['image_id']) print('index created!') @@ -125,7 +136,7 @@ def info(self): :return: """ for key, value in self.dataset['info'].items(): - print('%s: %s'%(key, value)) + print('{}: {}'.format(key, value)) def getAnnIds(self, imgIds=[], catIds=[], areaRng=[], iscrowd=None): """ @@ -143,14 +154,13 @@ def getAnnIds(self, imgIds=[], catIds=[], areaRng=[], iscrowd=None): anns = self.dataset['annotations'] else: if not len(imgIds) == 0: - # this can be changed by defaultdict lists = [self.imgToAnns[imgId] for imgId in imgIds if imgId in self.imgToAnns] anns = list(itertools.chain.from_iterable(lists)) else: anns = self.dataset['annotations'] anns = anns if len(catIds) == 0 else [ann for ann in anns if ann['category_id'] in catIds] anns = anns if len(areaRng) == 0 else [ann for ann in anns if ann['area'] > areaRng[0] and ann['area'] < areaRng[1]] - if iscrowd is not None: + if not iscrowd == None: ids = [ann['id'] for ann in anns if ann['iscrowd'] == iscrowd] else: ids = [ann['id'] for ann in anns] @@ -240,39 +250,57 @@ def showAnns(self, anns): """ if len(anns) == 0: return 0 - if 'segmentation' in anns[0]: + if 'segmentation' in anns[0] or 'keypoints' in anns[0]: datasetType = 'instances' elif 'caption' in anns[0]: datasetType = 'captions' + else: + raise Exception('datasetType not supported') if datasetType == 'instances': ax = plt.gca() + ax.set_autoscale_on(False) polygons = [] color = [] for ann in anns: - c = np.random.random((1, 3)).tolist()[0] - if type(ann['segmentation']) == list: - # polygon - for seg in ann['segmentation']: - poly = np.array(seg).reshape((len(seg)/2, 2)) - polygons.append(Polygon(poly, True,alpha=0.4)) - color.append(c) - else: - # mask - t = self.imgs[ann['image_id']] - if type(ann['segmentation']['counts']) == list: - rle = mask.frPyObjects([ann['segmentation']], t['height'], t['width']) + c = (np.random.random((1, 3))*0.6+0.4).tolist()[0] + if 'segmentation' in ann: + if type(ann['segmentation']) == list: + # polygon + for seg in ann['segmentation']: + poly = np.array(seg).reshape((int(len(seg)/2), 2)) + polygons.append(Polygon(poly)) + color.append(c) else: - rle = [ann['segmentation']] - m = mask.decode(rle) - img = np.ones( (m.shape[0], m.shape[1], 3) ) - if ann['iscrowd'] == 1: - color_mask = np.array([2.0,166.0,101.0])/255 - if ann['iscrowd'] == 0: - color_mask = np.random.random((1, 3)).tolist()[0] - for i in range(3): - img[:,:,i] = color_mask[i] - ax.imshow(np.dstack( (img, m*0.5) )) - p = PatchCollection(polygons, facecolors=color, edgecolors=(0,0,0,1), linewidths=3, alpha=0.4) + # mask + t = self.imgs[ann['image_id']] + if type(ann['segmentation']['counts']) == list: + rle = maskUtils.frPyObjects([ann['segmentation']], t['height'], t['width']) + else: + rle = [ann['segmentation']] + m = maskUtils.decode(rle) + img = np.ones( (m.shape[0], m.shape[1], 3) ) + if ann['iscrowd'] == 1: + color_mask = np.array([2.0,166.0,101.0])/255 + if ann['iscrowd'] == 0: + color_mask = np.random.random((1, 3)).tolist()[0] + for i in range(3): + img[:,:,i] = color_mask[i] + ax.imshow(np.dstack( (img, m*0.5) )) + if 'keypoints' in ann and type(ann['keypoints']) == list: + # turn skeleton into zero-based index + sks = np.array(self.loadCats(ann['category_id'])[0]['skeleton'])-1 + kp = np.array(ann['keypoints']) + x = kp[0::3] + y = kp[1::3] + v = kp[2::3] + for sk in sks: + if np.all(v[sk]>0): + plt.plot(x[sk],y[sk], linewidth=3, color=c) + plt.plot(x[v>0], y[v>0],'o',markersize=8, markerfacecolor=c, markeredgecolor='k',markeredgewidth=2) + plt.plot(x[v>1], y[v>1],'o',markersize=8, markerfacecolor=c, markeredgecolor=c, markeredgewidth=2) + p = PatchCollection(polygons, facecolor=color, linewidths=0, alpha=0.4) + ax.add_collection(p) + p = PatchCollection(polygons, facecolor='none', edgecolors=color, linewidths=2) ax.add_collection(p) elif datasetType == 'captions': for ann in anns: @@ -286,12 +314,15 @@ def loadRes(self, resFile): """ res = COCO() res.dataset['images'] = [img for img in self.dataset['images']] - # res.dataset['info'] = copy.deepcopy(self.dataset['info']) - # res.dataset['licenses'] = copy.deepcopy(self.dataset['licenses']) - print('Loading and preparing results... ') + print('Loading and preparing results...') tic = time.time() - anns = json.load(open(resFile)) + if type(resFile) == str or type(resFile) == unicode: + anns = json.load(open(resFile)) + elif type(resFile) == np.ndarray: + anns = self.loadNumpyAnnotations(resFile) + else: + anns = resFile assert type(anns) == list, 'results in not an array of objects' annsImgIds = [ann['image_id'] for ann in anns] assert set(annsImgIds) == (set(annsImgIds) & set(self.getImgIds())), \ @@ -315,18 +346,28 @@ def loadRes(self, resFile): res.dataset['categories'] = copy.deepcopy(self.dataset['categories']) for id, ann in enumerate(anns): # now only support compressed RLE format as segmentation results - ann['area'] = mask.area([ann['segmentation']])[0] + ann['area'] = maskUtils.area(ann['segmentation']) if not 'bbox' in ann: - ann['bbox'] = mask.toBbox([ann['segmentation']])[0] + ann['bbox'] = maskUtils.toBbox(ann['segmentation']) ann['id'] = id+1 ann['iscrowd'] = 0 - print('DONE (t=%0.2fs)'%(time.time()- tic)) + elif 'keypoints' in anns[0]: + res.dataset['categories'] = copy.deepcopy(self.dataset['categories']) + for id, ann in enumerate(anns): + s = ann['keypoints'] + x = s[0::3] + y = s[1::3] + x0,x1,y0,y1 = np.min(x), np.max(x), np.min(y), np.max(y) + ann['area'] = (x1-x0)*(y1-y0) + ann['id'] = id + 1 + ann['bbox'] = [x0,y0,x1-x0,y1-y0] + print('DONE (t={:0.2f}s)'.format(time.time()- tic)) res.dataset['annotations'] = anns res.createIndex() return res - def download(self, tarDir=None, imgIds=[]): + def download(self, tarDir = None, imgIds = [] ): ''' Download COCO images from mscoco.org server. :param tarDir (str): COCO results directory name @@ -347,5 +388,58 @@ def download(self, tarDir=None, imgIds=[]): tic = time.time() fname = os.path.join(tarDir, img['file_name']) if not os.path.exists(fname): - urllib.urlretrieve(img['coco_url'], fname) - print('downloaded %d/%d images (t=%.1fs)'%(i, N, time.time()- tic)) + urlretrieve(img['coco_url'], fname) + print('downloaded {}/{} images (t={:0.1f}s)'.format(i, N, time.time()- tic)) + + def loadNumpyAnnotations(self, data): + """ + Convert result data from a numpy array [Nx7] where each row contains {imageID,x1,y1,w,h,score,class} + :param data (numpy.ndarray) + :return: annotations (python nested list) + """ + print('Converting ndarray to lists...') + assert(type(data) == np.ndarray) + print(data.shape) + assert(data.shape[1] == 7) + N = data.shape[0] + ann = [] + for i in range(N): + if i % 1000000 == 0: + print('{}/{}'.format(i,N)) + ann += [{ + 'image_id' : int(data[i, 0]), + 'bbox' : [ data[i, 1], data[i, 2], data[i, 3], data[i, 4] ], + 'score' : data[i, 5], + 'category_id': int(data[i, 6]), + }] + return ann + + def annToRLE(self, ann): + """ + Convert annotation which can be polygons, uncompressed RLE to RLE. + :return: binary mask (numpy 2D array) + """ + t = self.imgs[ann['image_id']] + h, w = t['height'], t['width'] + segm = ann['segmentation'] + if type(segm) == list: + # polygon -- a single object might consist of multiple parts + # we merge all parts into one mask rle code + rles = maskUtils.frPyObjects(segm, h, w) + rle = maskUtils.merge(rles) + elif type(segm['counts']) == list: + # uncompressed RLE + rle = maskUtils.frPyObjects(segm, h, w) + else: + # rle + rle = ann['segmentation'] + return rle + + def annToMask(self, ann): + """ + Convert annotation which can be polygons, uncompressed RLE, or RLE to binary mask. + :return: binary mask (numpy 2D array) + """ + rle = self.annToRLE(ann) + m = maskUtils.decode(rle) + return m diff --git a/example/rcnn/rcnn/pycocotools/cocoeval.py b/example/rcnn/rcnn/pycocotools/cocoeval.py index 015c9f4ff8cc..8b78026d39e4 100644 --- a/example/rcnn/rcnn/pycocotools/cocoeval.py +++ b/example/rcnn/rcnn/pycocotools/cocoeval.py @@ -1,11 +1,27 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + __author__ = 'tsungyi' -from __future__ import print_function import numpy as np import datetime import time from collections import defaultdict -import mask +import mask as maskUtils import copy class COCOeval: @@ -27,8 +43,9 @@ class COCOeval: # recThrs - [0:.01:1] R=101 recall thresholds for evaluation # areaRng - [...] A=4 object area ranges for evaluation # maxDets - [1 10 100] M=3 thresholds on max detections per image - # useSegm - [1] if true evaluate against ground-truth segments - # useCats - [1] if true use category labels for evaluation # Note: if useSegm=0 the evaluation is run on bounding boxes. + # iouType - ['segm'] set iouType to 'segm', 'bbox' or 'keypoints' + # iouType replaced the now DEPRECATED useSegm parameter. + # useCats - [1] if true use category labels for evaluation # Note: if useCats=0 category labels are ignored as in proposal scoring. # Note: multiple areaRngs [Ax2] and maxDets [Mx1] can be specified. # @@ -57,13 +74,15 @@ class COCOeval: # Data, paper, and tutorials available at: http://mscoco.org/ # Code written by Piotr Dollar and Tsung-Yi Lin, 2015. # Licensed under the Simplified BSD License [see coco/license.txt] - def __init__(self, cocoGt=None, cocoDt=None): + def __init__(self, cocoGt=None, cocoDt=None, iouType='segm'): ''' Initialize CocoEval using coco APIs for gt and dt :param cocoGt: coco object with ground truth annotations :param cocoDt: coco object with detection results :return: None ''' + if not iouType: + print('iouType not specified. use default iouType segm') self.cocoGt = cocoGt # ground truth COCO API self.cocoDt = cocoDt # detections COCO API self.params = {} # evaluation parameters @@ -71,7 +90,7 @@ def __init__(self, cocoGt=None, cocoDt=None): self.eval = {} # accumulated evaluation results self._gts = defaultdict(list) # gt for evaluation self._dts = defaultdict(list) # dt for evaluation - self.params = Params() # parameters + self.params = Params(iouType=iouType) # parameters self._paramsEval = {} # parameters for evaluation self.stats = [] # result summarization self.ious = {} # ious between all gts and dts @@ -85,28 +104,11 @@ def _prepare(self): Prepare ._gts and ._dts for evaluation based on params :return: None ''' - # - def _toMask(objs, coco): - # modify segmentation by reference - for obj in objs: - t = coco.imgs[obj['image_id']] - if type(obj['segmentation']) == list: - if type(obj['segmentation'][0]) == dict: - print('debug') - obj['segmentation'] = mask.frPyObjects(obj['segmentation'],t['height'],t['width']) - if len(obj['segmentation']) == 1: - obj['segmentation'] = obj['segmentation'][0] - else: - # an object can have multiple polygon regions - # merge them into one RLE mask - obj['segmentation'] = mask.merge(obj['segmentation']) - elif type(obj['segmentation']) == dict and type(obj['segmentation']['counts']) == list: - obj['segmentation'] = mask.frPyObjects([obj['segmentation']],t['height'],t['width'])[0] - elif type(obj['segmentation']) == dict and \ - type(obj['segmentation']['counts'] == unicode or type(obj['segmentation']['counts']) == str): - pass - else: - raise Exception('segmentation format not supported.') + def _toMask(anns, coco): + # modify ann['segmentation'] by reference + for ann in anns: + rle = coco.annToRLE(ann) + ann['segmentation'] = rle p = self.params if p.useCats: gts=self.cocoGt.loadAnns(self.cocoGt.getAnnIds(imgIds=p.imgIds, catIds=p.catIds)) @@ -115,9 +117,16 @@ def _toMask(objs, coco): gts=self.cocoGt.loadAnns(self.cocoGt.getAnnIds(imgIds=p.imgIds)) dts=self.cocoDt.loadAnns(self.cocoDt.getAnnIds(imgIds=p.imgIds)) - if p.useSegm: + # convert ground truth to mask if iouType == 'segm' + if p.iouType == 'segm': _toMask(gts, self.cocoGt) _toMask(dts, self.cocoDt) + # set ignore flag + for gt in gts: + gt['ignore'] = gt['ignore'] if 'ignore' in gt else 0 + gt['ignore'] = 'iscrowd' in gt and gt['iscrowd'] + if p.iouType == 'keypoints': + gt['ignore'] = (gt['num_keypoints'] == 0) or gt['ignore'] self._gts = defaultdict(list) # gt for evaluation self._dts = defaultdict(list) # dt for evaluation for gt in gts: @@ -133,8 +142,13 @@ def evaluate(self): :return: None ''' tic = time.time() - print('Running per image evaluation... ') + print('Running per image evaluation...') p = self.params + # add backward compatibility if useSegm is specified in params + if not p.useSegm is None: + p.iouType = 'segm' if p.useSegm == 1 else 'bbox' + print('useSegm (deprecated) is not None. Running {} evaluation'.format(p.iouType)) + print('Evaluate annotation type *{}*'.format(p.iouType)) p.imgIds = list(np.unique(p.imgIds)) if p.useCats: p.catIds = list(np.unique(p.catIds)) @@ -145,7 +159,10 @@ def evaluate(self): # loop through images, area range, max detection number catIds = p.catIds if p.useCats else [-1] - computeIoU = self.computeIoU + if p.iouType == 'segm' or p.iouType == 'bbox': + computeIoU = self.computeIoU + elif p.iouType == 'keypoints': + computeIoU = self.computeOks self.ious = {(imgId, catId): computeIoU(imgId, catId) \ for imgId in p.imgIds for catId in catIds} @@ -159,7 +176,7 @@ def evaluate(self): ] self._paramsEval = copy.deepcopy(self.params) toc = time.time() - print('DONE (t=%0.2fs).'%(toc-tic)) + print('DONE (t={:0.2f}s).'.format(toc-tic)) def computeIoU(self, imgId, catId): p = self.params @@ -171,20 +188,66 @@ def computeIoU(self, imgId, catId): dt = [_ for cId in p.catIds for _ in self._dts[imgId,cId]] if len(gt) == 0 and len(dt) ==0: return [] - dt = sorted(dt, key=lambda x: -x['score']) + inds = np.argsort([-d['score'] for d in dt], kind='mergesort') + dt = [dt[i] for i in inds] if len(dt) > p.maxDets[-1]: dt=dt[0:p.maxDets[-1]] - if p.useSegm: + if p.iouType == 'segm': g = [g['segmentation'] for g in gt] d = [d['segmentation'] for d in dt] - else: + elif p.iouType == 'bbox': g = [g['bbox'] for g in gt] d = [d['bbox'] for d in dt] + else: + raise Exception('unknown iouType for iou computation') # compute iou between each dt and gt region iscrowd = [int(o['iscrowd']) for o in gt] - ious = mask.iou(d,g,iscrowd) + ious = maskUtils.iou(d,g,iscrowd) + return ious + + def computeOks(self, imgId, catId): + p = self.params + # dimention here should be Nxm + gts = self._gts[imgId, catId] + dts = self._dts[imgId, catId] + inds = np.argsort([-d['score'] for d in dts], kind='mergesort') + dts = [dts[i] for i in inds] + if len(dts) > p.maxDets[-1]: + dts = dts[0:p.maxDets[-1]] + # if len(gts) == 0 and len(dts) == 0: + if len(gts) == 0 or len(dts) == 0: + return [] + ious = np.zeros((len(dts), len(gts))) + sigmas = np.array([.26, .25, .25, .35, .35, .79, .79, .72, .72, .62,.62, 1.07, 1.07, .87, .87, .89, .89])/10.0 + vars = (sigmas * 2)**2 + k = len(sigmas) + # compute oks between each detection and ground truth object + for j, gt in enumerate(gts): + # create bounds for ignore regions(double the gt bbox) + g = np.array(gt['keypoints']) + xg = g[0::3]; yg = g[1::3]; vg = g[2::3] + k1 = np.count_nonzero(vg > 0) + bb = gt['bbox'] + x0 = bb[0] - bb[2]; x1 = bb[0] + bb[2] * 2 + y0 = bb[1] - bb[3]; y1 = bb[1] + bb[3] * 2 + for i, dt in enumerate(dts): + d = np.array(dt['keypoints']) + xd = d[0::3]; yd = d[1::3] + if k1>0: + # measure the per-keypoint distance if keypoints visible + dx = xd - xg + dy = yd - yg + else: + # measure minimum distance to keypoints in (x0,y0) & (x1,y1) + z = np.zeros((k)) + dx = np.max((z, x0-xd),axis=0)+np.max((z, xd-x1),axis=0) + dy = np.max((z, y0-yd),axis=0)+np.max((z, yd-y1),axis=0) + e = (dx**2 + dy**2) / vars / (gt['area']+np.spacing(1)) / 2 + if k1 > 0: + e=e[vg > 0] + ious[i, j] = np.sum(np.exp(-e)) / e.shape[0] return ious def evaluateImg(self, imgId, catId, aRng, maxDet): @@ -192,7 +255,6 @@ def evaluateImg(self, imgId, catId, aRng, maxDet): perform evaluation for single category and image :return: dict (single image results) ''' - # p = self.params if p.useCats: gt = self._gts[imgId,catId] @@ -204,23 +266,19 @@ def evaluateImg(self, imgId, catId, aRng, maxDet): return None for g in gt: - if 'ignore' not in g: - g['ignore'] = 0 - if g['iscrowd'] == 1 or g['ignore'] or (g['area']aRng[1]): + if g['ignore'] or (g['area']aRng[1]): g['_ignore'] = 1 else: g['_ignore'] = 0 # sort dt highest score first, sort gt ignore last - # gt = sorted(gt, key=lambda x: x['_ignore']) - gtind = [ind for (ind, g) in sorted(enumerate(gt), key=lambda (ind, g): g['_ignore']) ] - - gt = [gt[ind] for ind in gtind] - dt = sorted(dt, key=lambda x: -x['score'])[0:maxDet] + gtind = np.argsort([g['_ignore'] for g in gt], kind='mergesort') + gt = [gt[i] for i in gtind] + dtind = np.argsort([-d['score'] for d in dt], kind='mergesort') + dt = [dt[i] for i in dtind[0:maxDet]] iscrowd = [int(o['iscrowd']) for o in gt] # load computed ious - N_iou = len(self.ious[imgId, catId]) - ious = self.ious[imgId, catId][0:maxDet, np.array(gtind)] if N_iou >0 else self.ious[imgId, catId] + ious = self.ious[imgId, catId][:, gtind] if len(self.ious[imgId, catId]) > 0 else self.ious[imgId, catId] T = len(p.iouThrs) G = len(gt) @@ -245,7 +303,7 @@ def evaluateImg(self, imgId, catId, aRng, maxDet): # continue to next gt unless better match made if ious[dind,gind] < iou: continue - # match successful and best so far, store appropriately + # if match successful and best so far, store appropriately iou=ious[dind,gind] m=gind # if match made store id of match for both dt and gt @@ -278,7 +336,7 @@ def accumulate(self, p = None): :param p: input params for evaluation :return: None ''' - print('Accumulating evaluation results... ') + print('Accumulating evaluation results...') tic = time.time() if not self.evalImgs: print('Please run evaluate() first') @@ -306,7 +364,6 @@ def accumulate(self, p = None): m_list = [m for n, m in enumerate(p.maxDets) if m in setM] a_list = [n for n, a in enumerate(map(lambda x: tuple(x), p.areaRng)) if a in setA] i_list = [n for n, i in enumerate(p.imgIds) if i in setI] - # K0 = len(_pe.catIds) I0 = len(_pe.imgIds) A0 = len(_pe.areaRng) # retrieve E at each category, area range, and max number of detections @@ -315,8 +372,8 @@ def accumulate(self, p = None): for a, a0 in enumerate(a_list): Na = a0*I0 for m, maxDet in enumerate(m_list): - E = [self.evalImgs[Nk+Na+i] for i in i_list] - E = filter(None, E) + E = [self.evalImgs[Nk + Na + i] for i in i_list] + E = [e for e in E if not e is None] if len(E) == 0: continue dtScores = np.concatenate([e['dtScores'][0:maxDet] for e in E]) @@ -327,8 +384,8 @@ def accumulate(self, p = None): dtm = np.concatenate([e['dtMatches'][:,0:maxDet] for e in E], axis=1)[:,inds] dtIg = np.concatenate([e['dtIgnore'][:,0:maxDet] for e in E], axis=1)[:,inds] - gtIg = np.concatenate([e['gtIgnore'] for e in E]) - npig = len([ig for ig in gtIg if ig == 0]) + gtIg = np.concatenate([e['gtIgnore'] for e in E]) + npig = np.count_nonzero(gtIg==0 ) if npig == 0: continue tps = np.logical_and( dtm, np.logical_not(dtIg) ) @@ -357,7 +414,7 @@ def accumulate(self, p = None): if pr[i] > pr[i-1]: pr[i-1] = pr[i] - inds = np.searchsorted(rc, p.recThrs) + inds = np.searchsorted(rc, p.recThrs, side='left') try: for ri, pi in enumerate(inds): q[ri] = pr[pi] @@ -367,12 +424,12 @@ def accumulate(self, p = None): self.eval = { 'params': p, 'counts': [T, R, K, A, M], - 'date': datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), + 'date': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'), 'precision': precision, 'recall': recall, } toc = time.time() - print('DONE (t=%0.2fs).'%( toc-tic )) + print('DONE (t={:0.2f}s).'.format( toc-tic)) def summarize(self): ''' @@ -381,15 +438,14 @@ def summarize(self): ''' def _summarize( ap=1, iouThr=None, areaRng='all', maxDets=100 ): p = self.params - iStr = ' {:<18} {} @[ IoU={:<9} | area={:>6} | maxDets={:>3} ] = {}' - titleStr = 'Average Precision' if ap == 1 else 'Average Recall' - typeStr = '(AP)' if ap==1 else '(AR)' - iouStr = '%0.2f:%0.2f'%(p.iouThrs[0], p.iouThrs[-1]) if iouThr is None else '%0.2f'%(iouThr) - areaStr = areaRng - maxDetsStr = '%d'%(maxDets) - - aind = [i for i, aRng in enumerate(['all', 'small', 'medium', 'large']) if aRng == areaRng] - mind = [i for i, mDet in enumerate([1, 10, 100]) if mDet == maxDets] + iStr = ' {:<18} {} @[ IoU={:<9} | area={:>6s} | maxDets={:>3d} ] = {:0.3f}' + titleStr = 'Average Precision' if ap == 1 else 'Average Recall' + typeStr = '(AP)' if ap==1 else '(AR)' + iouStr = '{:0.2f}:{:0.2f}'.format(p.iouThrs[0], p.iouThrs[-1]) \ + if iouThr is None else '{:0.2f}'.format(iouThr) + + aind = [i for i, aRng in enumerate(p.areaRngLbl) if aRng == areaRng] + mind = [i for i, mDet in enumerate(p.maxDets) if mDet == maxDets] if ap == 1: # dimension of precision: [TxRxKxAxM] s = self.eval['precision'] @@ -397,34 +453,56 @@ def _summarize( ap=1, iouThr=None, areaRng='all', maxDets=100 ): if iouThr is not None: t = np.where(iouThr == p.iouThrs)[0] s = s[t] - # areaRng s = s[:,:,:,aind,mind] else: # dimension of recall: [TxKxAxM] s = self.eval['recall'] + if iouThr is not None: + t = np.where(iouThr == p.iouThrs)[0] + s = s[t] s = s[:,:,aind,mind] if len(s[s>-1])==0: mean_s = -1 else: mean_s = np.mean(s[s>-1]) - print(iStr.format(titleStr, typeStr, iouStr, areaStr, maxDetsStr, '%.3f'%(float(mean_s)))) + print(iStr.format(titleStr, typeStr, iouStr, areaRng, maxDets, mean_s)) return mean_s - + def _summarizeDets(): + stats = np.zeros((12,)) + stats[0] = _summarize(1) + stats[1] = _summarize(1, iouThr=.5, maxDets=self.params.maxDets[2]) + stats[2] = _summarize(1, iouThr=.75, maxDets=self.params.maxDets[2]) + stats[3] = _summarize(1, areaRng='small', maxDets=self.params.maxDets[2]) + stats[4] = _summarize(1, areaRng='medium', maxDets=self.params.maxDets[2]) + stats[5] = _summarize(1, areaRng='large', maxDets=self.params.maxDets[2]) + stats[6] = _summarize(0, maxDets=self.params.maxDets[0]) + stats[7] = _summarize(0, maxDets=self.params.maxDets[1]) + stats[8] = _summarize(0, maxDets=self.params.maxDets[2]) + stats[9] = _summarize(0, areaRng='small', maxDets=self.params.maxDets[2]) + stats[10] = _summarize(0, areaRng='medium', maxDets=self.params.maxDets[2]) + stats[11] = _summarize(0, areaRng='large', maxDets=self.params.maxDets[2]) + return stats + def _summarizeKps(): + stats = np.zeros((10,)) + stats[0] = _summarize(1, maxDets=20) + stats[1] = _summarize(1, maxDets=20, iouThr=.5) + stats[2] = _summarize(1, maxDets=20, iouThr=.75) + stats[3] = _summarize(1, maxDets=20, areaRng='medium') + stats[4] = _summarize(1, maxDets=20, areaRng='large') + stats[5] = _summarize(0, maxDets=20) + stats[6] = _summarize(0, maxDets=20, iouThr=.5) + stats[7] = _summarize(0, maxDets=20, iouThr=.75) + stats[8] = _summarize(0, maxDets=20, areaRng='medium') + stats[9] = _summarize(0, maxDets=20, areaRng='large') + return stats if not self.eval: raise Exception('Please run accumulate() first') - self.stats = np.zeros((12,)) - self.stats[0] = _summarize(1) - self.stats[1] = _summarize(1,iouThr=.5) - self.stats[2] = _summarize(1,iouThr=.75) - self.stats[3] = _summarize(1,areaRng='small') - self.stats[4] = _summarize(1,areaRng='medium') - self.stats[5] = _summarize(1,areaRng='large') - self.stats[6] = _summarize(0,maxDets=1) - self.stats[7] = _summarize(0,maxDets=10) - self.stats[8] = _summarize(0,maxDets=100) - self.stats[9] = _summarize(0,areaRng='small') - self.stats[10] = _summarize(0,areaRng='medium') - self.stats[11] = _summarize(0,areaRng='large') + iouType = self.params.iouType + if iouType == 'segm' or iouType == 'bbox': + summarize = _summarizeDets + elif iouType == 'keypoints': + summarize = _summarizeKps + self.stats = summarize() def __str__(self): self.summarize() @@ -433,13 +511,35 @@ class Params: ''' Params for coco evaluation api ''' - def __init__(self): + def setDetParams(self): self.imgIds = [] self.catIds = [] # np.arange causes trouble. the data point on arange is slightly larger than the true value - self.iouThrs = np.linspace(.5, 0.95, np.round((0.95-.5)/.05)+1, endpoint=True) - self.recThrs = np.linspace(.0, 1.00, np.round((1.00-.0)/.01)+1, endpoint=True) - self.maxDets = [1,10,100] - self.areaRng = [ [0**2,1e5**2], [0**2, 32**2], [32**2, 96**2], [96**2, 1e5**2] ] - self.useSegm = 0 - self.useCats = 1 \ No newline at end of file + self.iouThrs = np.linspace(.5, 0.95, np.round((0.95 - .5) / .05) + 1, endpoint=True) + self.recThrs = np.linspace(.0, 1.00, np.round((1.00 - .0) / .01) + 1, endpoint=True) + self.maxDets = [1, 10, 100] + self.areaRng = [[0 ** 2, 1e5 ** 2], [0 ** 2, 32 ** 2], [32 ** 2, 96 ** 2], [96 ** 2, 1e5 ** 2]] + self.areaRngLbl = ['all', 'small', 'medium', 'large'] + self.useCats = 1 + + def setKpParams(self): + self.imgIds = [] + self.catIds = [] + # np.arange causes trouble. the data point on arange is slightly larger than the true value + self.iouThrs = np.linspace(.5, 0.95, np.round((0.95 - .5) / .05) + 1, endpoint=True) + self.recThrs = np.linspace(.0, 1.00, np.round((1.00 - .0) / .01) + 1, endpoint=True) + self.maxDets = [20] + self.areaRng = [[0 ** 2, 1e5 ** 2], [32 ** 2, 96 ** 2], [96 ** 2, 1e5 ** 2]] + self.areaRngLbl = ['all', 'medium', 'large'] + self.useCats = 1 + + def __init__(self, iouType='segm'): + if iouType == 'segm' or iouType == 'bbox': + self.setDetParams() + elif iouType == 'keypoints': + self.setKpParams() + else: + raise Exception('iouType not supported') + self.iouType = iouType + # useSegm is deprecated + self.useSegm = None diff --git a/example/rcnn/rcnn/pycocotools/mask.py b/example/rcnn/rcnn/pycocotools/mask.py index c00e09b6e46e..48c050c594b6 100644 --- a/example/rcnn/rcnn/pycocotools/mask.py +++ b/example/rcnn/rcnn/pycocotools/mask.py @@ -1,6 +1,23 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + __author__ = 'tsungyi' -import _mask as _mask +import _mask # Interface for manipulating masks stored in RLE format. # @@ -73,10 +90,31 @@ # Code written by Piotr Dollar and Tsung-Yi Lin, 2015. # Licensed under the Simplified BSD License [see coco/license.txt] -encode = _mask.encode -decode = _mask.decode iou = _mask.iou merge = _mask.merge -area = _mask.area -toBbox = _mask.toBbox frPyObjects = _mask.frPyObjects + +def encode(bimask): + if len(bimask.shape) == 3: + return _mask.encode(bimask) + elif len(bimask.shape) == 2: + h, w = bimask.shape + return _mask.encode(bimask.reshape((h, w, 1), order='F'))[0] + +def decode(rleObjs): + if type(rleObjs) == list: + return _mask.decode(rleObjs) + else: + return _mask.decode([rleObjs])[:,:,0] + +def area(rleObjs): + if type(rleObjs) == list: + return _mask.area(rleObjs) + else: + return _mask.area([rleObjs])[0] + +def toBbox(rleObjs): + if type(rleObjs) == list: + return _mask.toBbox(rleObjs) + else: + return _mask.toBbox([rleObjs])[0] diff --git a/example/rcnn/rcnn/pycocotools/maskApi.c b/example/rcnn/rcnn/pycocotools/maskApi.c index 2b2d89116574..9dd660de1252 100644 --- a/example/rcnn/rcnn/pycocotools/maskApi.c +++ b/example/rcnn/rcnn/pycocotools/maskApi.c @@ -1,3 +1,22 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /************************************************************************** * Microsoft COCO Toolbox. version 2.0 * Data, paper, and tutorials available at: http://mscoco.org/ @@ -13,7 +32,7 @@ uint umax( uint a, uint b ) { return (a>b) ? a : b; } void rleInit( RLE *R, siz h, siz w, siz m, uint *cnts ) { R->h=h; R->w=w; R->m=m; R->cnts=(m==0)?0:malloc(sizeof(uint)*m); - if(cnts) for(siz j=0; jcnts[j]=cnts[j]; + siz j; if(cnts) for(j=0; jcnts[j]=cnts[j]; } void rleFree( RLE *R ) { @@ -21,12 +40,12 @@ void rleFree( RLE *R ) { } void rlesInit( RLE **R, siz n ) { - *R = (RLE*) malloc(sizeof(RLE)*n); - for(siz i=0; i0) { crowd=iscrowd!=NULL && iscrowd[g]; if(dt[d].h!=gt[g].h || dt[d].w!=gt[g].w) { o[g*m+d]=-1; continue; } - siz ka, kb, a, b; uint c, ca, cb, ct, i, u; bool va, vb; + siz ka, kb, a, b; uint c, ca, cb, ct, i, u; int va, vb; ca=dt[d].cnts[0]; ka=dt[d].m; va=vb=0; cb=gt[g].cnts[0]; kb=gt[g].m; a=b=1; i=u=0; ct=1; while( ct>0 ) { @@ -95,8 +114,19 @@ void rleIou( RLE *dt, RLE *gt, siz m, siz n, byte *iscrowd, double *o ) { } } +void rleNms( RLE *dt, siz n, uint *keep, double thr ) { + siz i, j; double u; + for( i=0; ithr) keep[j]=0; + } + } +} + void bbIou( BB dt, BB gt, siz m, siz n, byte *iscrowd, double *o ) { - double h, w, i, u, ga, da; siz g, d; bool crowd; + double h, w, i, u, ga, da; siz g, d; int crowd; for( g=0; gthr) keep[j]=0; + } + } +} + void rleToBbox( const RLE *R, BB bb, siz n ) { - for( siz i=0; i=dy && xs>xe) || (dxye); if(flip) { t=xs; xs=xe; xe=t; t=ys; ys=ye; ye=t; } s = dx>=dy ? (double)(ye-ys)/dx : (double)(xe-xs)/dy; - if(dx>=dy) for( int d=0; d<=dx; d++ ) { + if(dx>=dy) for( d=0; d<=dx; d++ ) { t=flip?dx-d:d; u[m]=t+xs; v[m]=(int)(ys+s*t+.5); m++; - } else for( int d=0; d<=dy; d++ ) { + } else for( d=0; d<=dy; d++ ) { t=flip?dy-d:d; v[m]=t+ys; u[m]=(int)(xs+s*t+.5); m++; } } - // get points along y-boundary and downsample + /* get points along y-boundary and downsample */ free(x); free(y); k=m; m=0; double xd, yd; x=malloc(sizeof(int)*k); y=malloc(sizeof(int)*k); for( j=1; jh) yd=h; yd=ceil(yd); x[m]=(int) xd; y[m]=(int) yd; m++; } - // compute rle encoding given y-boundary points + /* compute rle encoding given y-boundary points */ k=m; a=malloc(sizeof(uint)*(k+1)); for( j=0; jm, p=0; long x; bool more; + /* Similar to LEB128 but using 6 bits/char and ascii chars 48-111. */ + siz i, m=R->m, p=0; long x; int more; char *s=malloc(sizeof(char)*m*6); for( i=0; icnts[i]; if(i>2) x-=(long) R->cnts[i-2]; more=1; @@ -193,7 +234,7 @@ char* rleToString( const RLE *R ) { } void rleFrString( RLE *R, char *s, siz h, siz w ) { - siz m=0, p=0, k; long x; bool more; uint *cnts; + siz m=0, p=0, k; long x; int more; uint *cnts; while( s[m] ) m++; cnts=malloc(sizeof(uint)*m); m=0; while( s[p] ) { x=0; k=0; more=1; diff --git a/example/rcnn/rcnn/pycocotools/maskApi.h b/example/rcnn/rcnn/pycocotools/maskApi.h index ff16116c4781..56b4c0c4c704 100644 --- a/example/rcnn/rcnn/pycocotools/maskApi.h +++ b/example/rcnn/rcnn/pycocotools/maskApi.h @@ -1,3 +1,22 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /************************************************************************** * Microsoft COCO Toolbox. version 2.0 * Data, paper, and tutorials available at: http://mscoco.org/ @@ -5,7 +24,6 @@ * Licensed under the Simplified BSD License [see coco/license.txt] **************************************************************************/ #pragma once -#include typedef unsigned int uint; typedef unsigned long siz; @@ -13,43 +31,49 @@ typedef unsigned char byte; typedef double* BB; typedef struct { siz h, w, m; uint *cnts; } RLE; -// Initialize/destroy RLE. +/* Initialize/destroy RLE. */ void rleInit( RLE *R, siz h, siz w, siz m, uint *cnts ); void rleFree( RLE *R ); -// Initialize/destroy RLE array. +/* Initialize/destroy RLE array. */ void rlesInit( RLE **R, siz n ); void rlesFree( RLE **R, siz n ); -// Encode binary masks using RLE. +/* Encode binary masks using RLE. */ void rleEncode( RLE *R, const byte *mask, siz h, siz w, siz n ); -// Decode binary masks encoded via RLE. +/* Decode binary masks encoded via RLE. */ void rleDecode( const RLE *R, byte *mask, siz n ); -// Compute union or intersection of encoded masks. -void rleMerge( const RLE *R, RLE *M, siz n, bool intersect ); +/* Compute union or intersection of encoded masks. */ +void rleMerge( const RLE *R, RLE *M, siz n, int intersect ); -// Compute area of encoded masks. +/* Compute area of encoded masks. */ void rleArea( const RLE *R, siz n, uint *a ); -// Compute intersection over union between masks. +/* Compute intersection over union between masks. */ void rleIou( RLE *dt, RLE *gt, siz m, siz n, byte *iscrowd, double *o ); -// Compute intersection over union between bounding boxes. +/* Compute non-maximum suppression between bounding masks */ +void rleNms( RLE *dt, siz n, uint *keep, double thr ); + +/* Compute intersection over union between bounding boxes. */ void bbIou( BB dt, BB gt, siz m, siz n, byte *iscrowd, double *o ); -// Get bounding boxes surrounding encoded masks. +/* Compute non-maximum suppression between bounding boxes */ +void bbNms( BB dt, siz n, uint *keep, double thr ); + +/* Get bounding boxes surrounding encoded masks. */ void rleToBbox( const RLE *R, BB bb, siz n ); -// Convert bounding boxes to encoded masks. +/* Convert bounding boxes to encoded masks. */ void rleFrBbox( RLE *R, const BB bb, siz h, siz w, siz n ); -// Convert polygon to encoded mask. +/* Convert polygon to encoded mask. */ void rleFrPoly( RLE *R, const double *xy, siz k, siz h, siz w ); -// Get compressed string representation of encoded mask. +/* Get compressed string representation of encoded mask. */ char* rleToString( const RLE *R ); -// Convert from compressed string representation of encoded mask. +/* Convert from compressed string representation of encoded mask. */ void rleFrString( RLE *R, char *s, siz h, siz w ); diff --git a/example/rcnn/rcnn/pycocotools/setup.py b/example/rcnn/rcnn/pycocotools/setup.py index 5e836f1b4b6b..d7074e910ee5 100644 --- a/example/rcnn/rcnn/pycocotools/setup.py +++ b/example/rcnn/rcnn/pycocotools/setup.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + from distutils.core import setup from Cython.Build import cythonize from distutils.extension import Extension diff --git a/example/rcnn/rcnn/symbol/__init__.py b/example/rcnn/rcnn/symbol/__init__.py index f359ed821b8c..113b52c98abd 100644 --- a/example/rcnn/rcnn/symbol/__init__.py +++ b/example/rcnn/rcnn/symbol/__init__.py @@ -1,2 +1,19 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + from symbol_vgg import * from symbol_resnet import * diff --git a/example/rcnn/rcnn/symbol/proposal.py b/example/rcnn/rcnn/symbol/proposal.py index 397030db6d7c..64981513980b 100644 --- a/example/rcnn/rcnn/symbol/proposal.py +++ b/example/rcnn/rcnn/symbol/proposal.py @@ -1,20 +1,35 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + """ Proposal Operator transform anchor coordinates into ROI coordinates with prediction results on classification probability and bounding box prediction results, and image size and scale information. """ -from __future__ import print_function import mxnet as mx import numpy as np import numpy.random as npr from distutils.util import strtobool +from rcnn.logger import logger from rcnn.processing.bbox_transform import bbox_pred, clip_boxes from rcnn.processing.generate_anchor import generate_anchors from rcnn.processing.nms import py_nms_wrapper, cpu_nms_wrapper, gpu_nms_wrapper -DEBUG = False - class ProposalOperator(mx.operator.CustomOp): def __init__(self, feat_stride, scales, ratios, output_score, @@ -31,10 +46,8 @@ def __init__(self, feat_stride, scales, ratios, output_score, self._threshold = threshold self._rpn_min_size = rpn_min_size - if DEBUG: - print('feat_stride: {}'.format(self._feat_stride)) - print('anchors:') - print(self._anchors) + logger.debug('feat_stride: %s' % self._feat_stride) + logger.debug('anchors:\n%s' % self._anchors) def forward(self, is_train, req, in_data, out_data, aux): nms = gpu_nms_wrapper(self._threshold, in_data[0].context.device_id) @@ -64,17 +77,14 @@ def forward(self, is_train, req, in_data, out_data, aux): bbox_deltas = in_data[1].asnumpy() im_info = in_data[2].asnumpy()[0, :] - if DEBUG: - print('im_size: ({}, {})'.format(im_info[0], im_info[1])) - print('scale: {}'.format(im_info[2])) + logger.debug('im_info: %s' % im_info) # 1. Generate proposals from bbox_deltas and shifted anchors # use real image size instead of padded feature map sizes height, width = int(im_info[0] / self._feat_stride), int(im_info[1] / self._feat_stride) - if DEBUG: - print('score map size: {}'.format(scores.shape)) - print("resudial: {}".format((scores.shape[2] - height, scores.shape[3] - width))) + logger.debug('score map size: (%d, %d)' % (scores.shape[2], scores.shape[3])) + logger.debug('resudial: (%d, %d)' % (scores.shape[2] - height, scores.shape[3] - width)) # Enumerate all shifts shift_x = np.arange(0, width) * self._feat_stride diff --git a/example/rcnn/rcnn/symbol/proposal_target.py b/example/rcnn/rcnn/symbol/proposal_target.py index 3f28cb2cbebb..e0444f978b33 100644 --- a/example/rcnn/rcnn/symbol/proposal_target.py +++ b/example/rcnn/rcnn/symbol/proposal_target.py @@ -1,16 +1,32 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + """ Proposal Target Operator selects foreground and background roi and assigns label, bbox_transform to them. """ -from __future__ import print_function +import logging import mxnet as mx import numpy as np from distutils.util import strtobool +from ..logger import logger from rcnn.io.rcnn import sample_rois -DEBUG = False - class ProposalTargetOperator(mx.operator.CustomOp): def __init__(self, num_classes, batch_images, batch_rois, fg_fraction): @@ -20,7 +36,7 @@ def __init__(self, num_classes, batch_images, batch_rois, fg_fraction): self._batch_rois = batch_rois self._fg_fraction = fg_fraction - if DEBUG: + if logger.level == logging.DEBUG: self._count = 0 self._fg_num = 0 self._bg_num = 0 @@ -43,17 +59,17 @@ def forward(self, is_train, req, in_data, out_data, aux): rois, labels, bbox_targets, bbox_weights = \ sample_rois(all_rois, fg_rois_per_image, rois_per_image, self._num_classes, gt_boxes=gt_boxes) - if DEBUG: - print("labels=", labels) - print('num fg: {}'.format((labels > 0).sum())) - print('num bg: {}'.format((labels == 0).sum())) + if logger.level == logging.DEBUG: + logger.debug("labels: %s" % labels) + logger.debug('num fg: {}'.format((labels > 0).sum())) + logger.debug('num bg: {}'.format((labels == 0).sum())) self._count += 1 self._fg_num += (labels > 0).sum() self._bg_num += (labels == 0).sum() - print("self._count=", self._count) - print('num fg avg: {}'.format(self._fg_num / self._count)) - print('num bg avg: {}'.format(self._bg_num / self._count)) - print('ratio: {:.3f}'.format(float(self._fg_num) / float(self._bg_num))) + logger.debug("self._count: %d" % self._count) + logger.debug('num fg avg: %d' % (self._fg_num / self._count)) + logger.debug('num bg avg: %d' % (self._bg_num / self._count)) + logger.debug('ratio: %.3f' % (float(self._fg_num) / float(self._bg_num))) for ind, val in enumerate([rois, labels, bbox_targets, bbox_weights]): self.assign(out_data[ind], req[ind], val) diff --git a/example/rcnn/rcnn/symbol/symbol_resnet.py b/example/rcnn/rcnn/symbol/symbol_resnet.py index ad60de9e854d..f914d117eb18 100644 --- a/example/rcnn/rcnn/symbol/symbol_resnet.py +++ b/example/rcnn/rcnn/symbol/symbol_resnet.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import mxnet as mx import proposal import proposal_target diff --git a/example/rcnn/rcnn/symbol/symbol_vgg.py b/example/rcnn/rcnn/symbol/symbol_vgg.py index 34860a49e883..f04ba89dc1d4 100644 --- a/example/rcnn/rcnn/symbol/symbol_vgg.py +++ b/example/rcnn/rcnn/symbol/symbol_vgg.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import mxnet as mx import proposal import proposal_target @@ -130,7 +147,7 @@ def get_vgg_rcnn_test(num_classes=config.NUM_CLASSES): # shared convolutional layer relu5_3 = get_vgg_conv(data) - + # Fast R-CNN pool5 = mx.symbol.ROIPooling( name='roi_pool5', data=relu5_3, rois=rois, pooled_size=(7, 7), spatial_scale=1.0 / config.RCNN_FEAT_STRIDE) diff --git a/example/rcnn/rcnn/tools/reeval.py b/example/rcnn/rcnn/tools/reeval.py index a2e6264942de..a7ae898f41bd 100644 --- a/example/rcnn/rcnn/tools/reeval.py +++ b/example/rcnn/rcnn/tools/reeval.py @@ -1,9 +1,26 @@ -from __future__ import print_function +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import argparse import cPickle import os import mxnet as mx +from ..logger import logger from ..config import config, default, generate_config from ..dataset import * @@ -39,7 +56,7 @@ def parse_args(): def main(): args = parse_args() - print('Called with argument:', args) + logger.info('Called with argument: %s' % args) reeval(args) diff --git a/example/rcnn/rcnn/tools/test_rcnn.py b/example/rcnn/rcnn/tools/test_rcnn.py index 65dca7a6d0f4..2c5c22223f14 100644 --- a/example/rcnn/rcnn/tools/test_rcnn.py +++ b/example/rcnn/rcnn/tools/test_rcnn.py @@ -1,8 +1,25 @@ -from __future__ import print_function +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import argparse import pprint import mxnet as mx +from ..logger import logger from ..config import config, default, generate_config from ..symbol import * from ..dataset import * @@ -99,8 +116,8 @@ def parse_args(): def main(): args = parse_args() + logger.info('Called with argument: %s' % args) ctx = mx.gpu(args.gpu) - print(args) test_rcnn(args.network, args.dataset, args.image_set, args.root_path, args.dataset_path, ctx, args.prefix, args.epoch, args.vis, args.shuffle, args.has_rpn, args.proposal, args.thresh) diff --git a/example/rcnn/rcnn/tools/test_rpn.py b/example/rcnn/rcnn/tools/test_rpn.py index 9d0ff198e1b4..f2244a568d6a 100644 --- a/example/rcnn/rcnn/tools/test_rpn.py +++ b/example/rcnn/rcnn/tools/test_rpn.py @@ -1,8 +1,25 @@ -from __future__ import print_function +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import argparse import pprint import mxnet as mx +from ..logger import logger from ..config import config, default, generate_config from ..symbol import * from ..dataset import * @@ -89,7 +106,7 @@ def parse_args(): def main(): args = parse_args() - print('Called with argument:', args) + logger.info('Called with argument: %s' % args) ctx = mx.gpu(args.gpu) test_rpn(args.network, args.dataset, args.image_set, args.root_path, args.dataset_path, ctx, args.prefix, args.epoch, diff --git a/example/rcnn/rcnn/tools/train_rcnn.py b/example/rcnn/rcnn/tools/train_rcnn.py index 0669af047819..c5417b34c2dc 100644 --- a/example/rcnn/rcnn/tools/train_rcnn.py +++ b/example/rcnn/rcnn/tools/train_rcnn.py @@ -1,8 +1,25 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import argparse -import logging import pprint import mxnet as mx +from ..logger import logger from ..config import config, default, generate_config from ..symbol import * from ..core import callback, metric @@ -17,11 +34,6 @@ def train_rcnn(network, dataset, image_set, root_path, dataset_path, frequent, kvstore, work_load_list, no_flip, no_shuffle, resume, ctx, pretrained, epoch, prefix, begin_epoch, end_epoch, train_shared, lr, lr_step, proposal): - # set up logger - logging.basicConfig() - logger = logging.getLogger() - logger.setLevel(logging.INFO) - # set up config config.TRAIN.BATCH_IMAGES = 2 config.TRAIN.BATCH_ROIS = 128 @@ -36,7 +48,7 @@ def train_rcnn(network, dataset, image_set, root_path, dataset_path, input_batch_size = config.TRAIN.BATCH_IMAGES * batch_size # print config - pprint.pprint(config) + logger.info(pprint.pformat(config)) # load dataset and prepare imdb for training image_sets = [iset for iset in image_set.split('+')] @@ -53,6 +65,7 @@ def train_rcnn(network, dataset, image_set, root_path, dataset_path, # infer max shape max_data_shape = [('data', (input_batch_size, 3, max([v[0] for v in config.SCALES]), max([v[1] for v in config.SCALES])))] + logger.info('providing maximum shape %s' % max_data_shape) # infer shape data_shape_dict = dict(train_data.provide_data + train_data.provide_label) @@ -60,8 +73,7 @@ def train_rcnn(network, dataset, image_set, root_path, dataset_path, arg_shape_dict = dict(zip(sym.list_arguments(), arg_shape)) out_shape_dict = dict(zip(sym.list_outputs(), out_shape)) aux_shape_dict = dict(zip(sym.list_auxiliary_states(), aux_shape)) - print('output shape') - pprint.pprint(out_shape_dict) + logger.info('output shape %s' % pprint.pformat(out_shape_dict)) # load and initialize params if resume: @@ -115,7 +127,7 @@ def train_rcnn(network, dataset, image_set, root_path, dataset_path, lr_epoch_diff = [epoch - begin_epoch for epoch in lr_epoch if epoch > begin_epoch] lr = base_lr * (lr_factor ** (len(lr_epoch) - len(lr_epoch_diff))) lr_iters = [int(epoch * len(roidb) / batch_size) for epoch in lr_epoch_diff] - print('lr', lr, 'lr_epoch_diff', lr_epoch_diff, 'lr_iters', lr_iters) + logger.info('lr %f lr_epoch_diff %s lr_iters %s' % (lr, lr_epoch_diff, lr_iters)) lr_scheduler = mx.lr_scheduler.MultiFactorScheduler(lr_iters, lr_factor) # optimizer optimizer_params = {'momentum': 0.9, @@ -166,7 +178,7 @@ def parse_args(): def main(): args = parse_args() - print('Called with argument:', args) + logger.info('Called with argument: %s' % args) ctx = [mx.gpu(int(i)) for i in args.gpus.split(',')] train_rcnn(args.network, args.dataset, args.image_set, args.root_path, args.dataset_path, args.frequent, args.kvstore, args.work_load_list, args.no_flip, args.no_shuffle, args.resume, diff --git a/example/rcnn/rcnn/tools/train_rpn.py b/example/rcnn/rcnn/tools/train_rpn.py index 2c7267ea36ef..aaaf570a1fc3 100644 --- a/example/rcnn/rcnn/tools/train_rpn.py +++ b/example/rcnn/rcnn/tools/train_rpn.py @@ -1,9 +1,25 @@ -from __future__ import print_function +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import argparse -import logging import pprint import mxnet as mx +from ..logger import logger from ..config import config, default, generate_config from ..symbol import * from ..core import callback, metric @@ -17,11 +33,6 @@ def train_rpn(network, dataset, image_set, root_path, dataset_path, frequent, kvstore, work_load_list, no_flip, no_shuffle, resume, ctx, pretrained, epoch, prefix, begin_epoch, end_epoch, train_shared, lr, lr_step): - # set up logger - logging.basicConfig() - logger = logging.getLogger() - logger.setLevel(logging.INFO) - # setup config config.TRAIN.BATCH_IMAGES = 1 @@ -34,7 +45,7 @@ def train_rpn(network, dataset, image_set, root_path, dataset_path, input_batch_size = config.TRAIN.BATCH_IMAGES * batch_size # print config - pprint.pprint(config) + logger.info(pprint.pformat(config)) # load dataset and prepare imdb for training image_sets = [iset for iset in image_set.split('+')] @@ -53,7 +64,7 @@ def train_rpn(network, dataset, image_set, root_path, dataset_path, # infer max shape max_data_shape = [('data', (input_batch_size, 3, max([v[0] for v in config.SCALES]), max([v[1] for v in config.SCALES])))] max_data_shape, max_label_shape = train_data.infer_shape(max_data_shape) - print('providing maximum shape', max_data_shape, max_label_shape) + logger.info('providing maximum shape %s %s' % (max_data_shape, max_label_shape)) # infer shape data_shape_dict = dict(train_data.provide_data + train_data.provide_label) @@ -61,8 +72,7 @@ def train_rpn(network, dataset, image_set, root_path, dataset_path, arg_shape_dict = dict(zip(sym.list_arguments(), arg_shape)) out_shape_dict = dict(zip(sym.list_outputs(), out_shape)) aux_shape_dict = dict(zip(sym.list_auxiliary_states(), aux_shape)) - print('output shape') - pprint.pprint(out_shape_dict) + logger.info('output shape %s' % pprint.pformat(out_shape_dict)) # load and initialize params if resume: @@ -118,7 +128,7 @@ def train_rpn(network, dataset, image_set, root_path, dataset_path, lr_epoch_diff = [epoch - begin_epoch for epoch in lr_epoch if epoch > begin_epoch] lr = base_lr * (lr_factor ** (len(lr_epoch) - len(lr_epoch_diff))) lr_iters = [int(epoch * len(roidb) / batch_size) for epoch in lr_epoch_diff] - print('lr', lr, 'lr_epoch_diff', lr_epoch_diff, 'lr_iters', lr_iters) + logger.info('lr %f lr_epoch_diff %s lr_iters %s' % (lr, lr_epoch_diff, lr_iters)) lr_scheduler = mx.lr_scheduler.MultiFactorScheduler(lr_iters, lr_factor) # optimizer optimizer_params = {'momentum': 0.9, @@ -168,7 +178,7 @@ def parse_args(): def main(): args = parse_args() - print('Called with argument:', args) + logger.info('Called with argument: %s' % args) ctx = [mx.gpu(int(i)) for i in args.gpus.split(',')] train_rpn(args.network, args.dataset, args.image_set, args.root_path, args.dataset_path, args.frequent, args.kvstore, args.work_load_list, args.no_flip, args.no_shuffle, args.resume, diff --git a/example/rcnn/rcnn/utils/caffe_convert.py b/example/rcnn/rcnn/utils/caffe_convert.py deleted file mode 100644 index b5f0fbe27d14..000000000000 --- a/example/rcnn/rcnn/utils/caffe_convert.py +++ /dev/null @@ -1,75 +0,0 @@ -# This script will not work unless all paths are set right - -from __future__ import print_function -import os -import sys -import mxnet as mx -import numpy as np -fast_rcnn_path = None -sys.path.insert(0, os.path.join(fast_rcnn_path, 'caffe-fast-rcnn', 'python')) -sys.path.insert(0, os.path.join(fast_rcnn_path, 'lib')) -import caffe -from rcnn.symbol import get_symbol_vgg_test - -def load_model(caffeproto, caffemodel, arg_shape_dic): - def get_caffe_iter(layer_names, layers): - for layer_idx, layer in enumerate(layers): - layer_name = layer_names[layer_idx].replace('/', '_') - layer_type = layer.type - layer_blobs = layer.blobs - yield (layer_name, layer_type, layer_blobs) - - net_caffe = caffe.Net(caffeproto, caffemodel, caffe.TEST) - layer_names = net_caffe._layer_names - layers = net_caffe.layers - iter = '' - iter = get_caffe_iter(layer_names, layers) - first_conv = True - - arg_params = {} - for layer_name, layer_type, layer_blobs in iter: - if layer_type == 'Convolution' or layer_type == 'InnerProduct' or layer_type == 4 or layer_type == 14: - assert(len(layer_blobs) == 2) - wmat = np.array(layer_blobs[0].data).reshape(layer_blobs[0].num, layer_blobs[0].channels, layer_blobs[0].height, layer_blobs[0].width) - bias = np.array(layer_blobs[1].data) - if first_conv: - print('Swapping BGR of caffe into RGB in mxnet') - wmat[:, [0, 2], :, :] = wmat[:, [2, 0], :, :] - - assert(wmat.flags['C_CONTIGUOUS'] is True) - assert(bias.flags['C_CONTIGUOUS'] is True) - print('converting layer {0}, wmat shape = {1}, bias shape = {2}'.format(layer_name, wmat.shape, bias.shape)) - wmat = wmat.reshape((wmat.shape[0], -1)) - bias = bias.reshape((bias.shape[0], 1)) - weight_name = layer_name + "_weight" - bias_name = layer_name + "_bias" - - if weight_name not in arg_shape_dic: - print(weight_name + ' not found in arg_shape_dic.') - continue - wmat = wmat.reshape(arg_shape_dic[weight_name]) - arg_params[weight_name] = mx.nd.zeros(wmat.shape) - arg_params[weight_name][:] = wmat - - bias = bias.reshape(arg_shape_dic[bias_name]) - arg_params[bias_name] = mx.nd.zeros(bias.shape) - arg_params[bias_name][:] = bias - - if first_conv and (layer_type == 'Convolution' or layer_type == 4): - first_conv = False - - return arg_params - -proto_path = os.path.join(fast_rcnn_path, 'models', 'VGG16', 'test.prototxt') -model_path = os.path.join(fast_rcnn_path, 'data', 'fast_rcnn_models', 'vgg16_fast_rcnn_iter_40000.caffemodel') - -symbol = get_symbol_vgg_test() -arg_shapes, out_shapes, aux_shapes = symbol.infer_shape(**{'data': (1, 3, 224, 224), 'rois': (1, 5)}) -arg_shape_dic = { name: shape for name, shape in zip(symbol.list_arguments(), arg_shapes) } - -arg_params = load_model(proto_path, model_path, arg_shape_dic) - -model = mx.model.FeedForward(ctx=mx.cpu(), symbol=symbol, arg_params=arg_params, - aux_params={}, num_epoch=1, - learning_rate=0.01, momentum=0.9, wd=0.0001) -model.save('model/ref') diff --git a/example/rcnn/rcnn/utils/combine_model.py b/example/rcnn/rcnn/utils/combine_model.py index 5518dda4a989..eabe937be20c 100644 --- a/example/rcnn/rcnn/utils/combine_model.py +++ b/example/rcnn/rcnn/utils/combine_model.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + from load_model import load_checkpoint from save_model import save_checkpoint diff --git a/example/rcnn/rcnn/utils/load_data.py b/example/rcnn/rcnn/utils/load_data.py index d56882a5c9d8..816b3b3a405e 100644 --- a/example/rcnn/rcnn/utils/load_data.py +++ b/example/rcnn/rcnn/utils/load_data.py @@ -1,5 +1,22 @@ -from __future__ import print_function +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import numpy as np +from ..logger import logger from ..config import config from ..dataset import * @@ -47,6 +64,6 @@ def is_valid(entry): num = len(roidb) filtered_roidb = [entry for entry in roidb if is_valid(entry)] num_after = len(filtered_roidb) - print('filtered %d roidb entries: %d -> %d' % (num - num_after, num, num_after)) + logger.info('load data: filtered %d roidb entries: %d -> %d' % (num - num_after, num, num_after)) return filtered_roidb diff --git a/example/rcnn/rcnn/utils/load_model.py b/example/rcnn/rcnn/utils/load_model.py index 6f8354869b66..0dc0752600c4 100644 --- a/example/rcnn/rcnn/utils/load_model.py +++ b/example/rcnn/rcnn/utils/load_model.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import mxnet as mx diff --git a/example/rcnn/rcnn/utils/save_model.py b/example/rcnn/rcnn/utils/save_model.py index 1c9886973bd6..f27fb61b0f7a 100644 --- a/example/rcnn/rcnn/utils/save_model.py +++ b/example/rcnn/rcnn/utils/save_model.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import mxnet as mx diff --git a/example/rcnn/script/additional_deps.sh b/example/rcnn/script/additional_deps.sh index b06e75c7cc33..0e6599c77fd2 100755 --- a/example/rcnn/script/additional_deps.sh +++ b/example/rcnn/script/additional_deps.sh @@ -1,5 +1,23 @@ #!/usr/bin/env bash +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + + # install additional depts sudo apt install python-pip python-dev unzip python-matplotlib sudo pip install cython scikit-image easydict @@ -10,7 +28,6 @@ cp make/config.mk ./ echo "USE_CUDA=1" >>config.mk echo "USE_CUDA_PATH=/usr/local/cuda" >>config.mk echo "USE_CUDNN=1" >>config.mk -echo "EXTRA_OPERATORS = example/rcnn/operator" >>config.mk make -j$(nproc) pushd python python setup.py install --user diff --git a/example/rcnn/script/get_coco.sh b/example/rcnn/script/get_coco.sh index d49046cc6c19..a2f8f90e8a6d 100755 --- a/example/rcnn/script/get_coco.sh +++ b/example/rcnn/script/get_coco.sh @@ -1,5 +1,23 @@ #!/usr/bin/env bash +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + + # make a data folder if ! [ -e data ] then diff --git a/example/rcnn/script/get_pretrained_model.sh b/example/rcnn/script/get_pretrained_model.sh index f63128d800da..746be0b7ddd8 100755 --- a/example/rcnn/script/get_pretrained_model.sh +++ b/example/rcnn/script/get_pretrained_model.sh @@ -1,5 +1,23 @@ #!/usr/bin/env bash +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + + # make a model folder if ! [ -e model ] then diff --git a/example/rcnn/script/get_selective_search.sh b/example/rcnn/script/get_selective_search.sh index 728bd8fffc60..487c653b23a7 100755 --- a/example/rcnn/script/get_selective_search.sh +++ b/example/rcnn/script/get_selective_search.sh @@ -1,5 +1,23 @@ #!/usr/bin/env bash +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + + # make a data folder if ! [ -e data ] then diff --git a/example/rcnn/script/get_voc.sh b/example/rcnn/script/get_voc.sh index c0cefa9a5f77..060b79336619 100755 --- a/example/rcnn/script/get_voc.sh +++ b/example/rcnn/script/get_voc.sh @@ -1,5 +1,23 @@ #!/usr/bin/env bash +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + + # make a data folder if ! [ -e data ] then diff --git a/example/rcnn/script/resnet_voc07.sh b/example/rcnn/script/resnet_voc07.sh index a5d16ec250ac..3cb421f016c0 100755 --- a/example/rcnn/script/resnet_voc07.sh +++ b/example/rcnn/script/resnet_voc07.sh @@ -1,5 +1,23 @@ #!/usr/bin/env bash +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + + # run this experiment with # nohup bash script/resnet_voc07.sh 0,1 &> resnet_voc07.log & # to use gpu 0,1 to train, gpu 0 to test and write logs to resnet_voc07.log diff --git a/example/rcnn/script/resnet_voc0712.sh b/example/rcnn/script/resnet_voc0712.sh index c993d49589b5..aa2bd39499a4 100755 --- a/example/rcnn/script/resnet_voc0712.sh +++ b/example/rcnn/script/resnet_voc0712.sh @@ -1,5 +1,23 @@ #!/usr/bin/env bash +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + + # run this experiment with # nohup bash script/resnet_voc00712.sh 0,1 &> resnet_voc0712.log & # to use gpu 0,1 to train, gpu 0 to test and write logs to resnet_voc0712.log diff --git a/example/rcnn/script/vgg_alter_voc07.sh b/example/rcnn/script/vgg_alter_voc07.sh index 1345f4fd2a43..72ee0cddea2f 100755 --- a/example/rcnn/script/vgg_alter_voc07.sh +++ b/example/rcnn/script/vgg_alter_voc07.sh @@ -1,5 +1,23 @@ #!/usr/bin/env bash +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + + # run this experiment with # nohup bash script/vgg_alter_voc07.sh 0,1 &> vgg_voc07.log & # to use gpu 0,1 to train, gpu 0 to test and write logs to vgg_voc07.log diff --git a/example/rcnn/script/vgg_fast_rcnn.sh b/example/rcnn/script/vgg_fast_rcnn.sh index 7d863b5f73ad..cafd2ea66b3e 100755 --- a/example/rcnn/script/vgg_fast_rcnn.sh +++ b/example/rcnn/script/vgg_fast_rcnn.sh @@ -1,5 +1,23 @@ #!/usr/bin/env bash +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + + # run this experiment with # nohup bash script/vgg_fast_rcnn.sh 0,1 &> vgg_fast_rcnn.log & # to use gpu 0,1 to train, gpu 0 to test and write logs to vgg_fast_rcnn.log diff --git a/example/rcnn/script/vgg_voc07.sh b/example/rcnn/script/vgg_voc07.sh index 4b70f7c9705f..22249e153838 100755 --- a/example/rcnn/script/vgg_voc07.sh +++ b/example/rcnn/script/vgg_voc07.sh @@ -1,5 +1,23 @@ #!/usr/bin/env bash +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + + # run this experiment with # nohup bash script/vgg_voc07.sh 0,1 &> vgg_voc07.log & # to use gpu 0,1 to train, gpu 0 to test and write logs to vgg_voc07.log diff --git a/example/rcnn/script/vgg_voc0712.sh b/example/rcnn/script/vgg_voc0712.sh index ff2490492b2e..22416dad4878 100755 --- a/example/rcnn/script/vgg_voc0712.sh +++ b/example/rcnn/script/vgg_voc0712.sh @@ -1,5 +1,23 @@ #!/usr/bin/env bash +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + + # run this experiment with # nohup bash script/vgg_voc00712.sh 0,1 &> vgg_voc0712.log & # to use gpu 0,1 to train, gpu 0 to test and write logs to vgg_voc0712.log diff --git a/example/rcnn/test.py b/example/rcnn/test.py index 708efc8c7ddb..2989bc02a4f7 100644 --- a/example/rcnn/test.py +++ b/example/rcnn/test.py @@ -1,6 +1,23 @@ -from __future__ import print_function +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import argparse import mxnet as mx +from rcnn.logger import logger from rcnn.config import config, default, generate_config from rcnn.tools.test_rcnn import test_rcnn @@ -31,8 +48,8 @@ def parse_args(): def main(): args = parse_args() + logger.info('Called with argument: %s' % args) ctx = mx.gpu(args.gpu) - print(args) test_rcnn(args.network, args.dataset, args.image_set, args.root_path, args.dataset_path, ctx, args.prefix, args.epoch, args.vis, args.shuffle, args.has_rpn, args.proposal, args.thresh) diff --git a/example/rcnn/train_alternate.py b/example/rcnn/train_alternate.py index 991fb237d085..715816087a61 100644 --- a/example/rcnn/train_alternate.py +++ b/example/rcnn/train_alternate.py @@ -1,9 +1,24 @@ -from __future__ import print_function -import argparse -import logging +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +import argparse import mxnet as mx +from rcnn.logger import logger from rcnn.config import config, default, generate_config from rcnn.tools.train_rpn import train_rpn from rcnn.tools.test_rpn import test_rpn @@ -14,41 +29,36 @@ def alternate_train(args, ctx, pretrained, epoch, rpn_epoch, rpn_lr, rpn_lr_step, rcnn_epoch, rcnn_lr, rcnn_lr_step): - # set up logger - logging.basicConfig() - logger = logging.getLogger() - logger.setLevel(logging.INFO) - # basic config begin_epoch = 0 config.TRAIN.BG_THRESH_LO = 0.0 - logging.info('########## TRAIN RPN WITH IMAGENET INIT') + logger.info('########## TRAIN RPN WITH IMAGENET INIT') train_rpn(args.network, args.dataset, args.image_set, args.root_path, args.dataset_path, args.frequent, args.kvstore, args.work_load_list, args.no_flip, args.no_shuffle, args.resume, ctx, pretrained, epoch, 'model/rpn1', begin_epoch, rpn_epoch, train_shared=False, lr=rpn_lr, lr_step=rpn_lr_step) - logging.info('########## GENERATE RPN DETECTION') + logger.info('########## GENERATE RPN DETECTION') image_sets = [iset for iset in args.image_set.split('+')] for image_set in image_sets: test_rpn(args.network, args.dataset, image_set, args.root_path, args.dataset_path, ctx[0], 'model/rpn1', rpn_epoch, vis=False, shuffle=False, thresh=0) - logging.info('########## TRAIN RCNN WITH IMAGENET INIT AND RPN DETECTION') + logger.info('########## TRAIN RCNN WITH IMAGENET INIT AND RPN DETECTION') train_rcnn(args.network, args.dataset, args.image_set, args.root_path, args.dataset_path, args.frequent, args.kvstore, args.work_load_list, args.no_flip, args.no_shuffle, args.resume, ctx, pretrained, epoch, 'model/rcnn1', begin_epoch, rcnn_epoch, train_shared=False, lr=rcnn_lr, lr_step=rcnn_lr_step, proposal='rpn') - logging.info('########## TRAIN RPN WITH RCNN INIT') + logger.info('########## TRAIN RPN WITH RCNN INIT') train_rpn(args.network, args.dataset, args.image_set, args.root_path, args.dataset_path, args.frequent, args.kvstore, args.work_load_list, args.no_flip, args.no_shuffle, args.resume, ctx, 'model/rcnn1', rcnn_epoch, 'model/rpn2', begin_epoch, rpn_epoch, train_shared=True, lr=rpn_lr, lr_step=rpn_lr_step) - logging.info('########## GENERATE RPN DETECTION') + logger.info('########## GENERATE RPN DETECTION') image_sets = [iset for iset in args.image_set.split('+')] for image_set in image_sets: test_rpn(args.network, args.dataset, image_set, args.root_path, args.dataset_path, @@ -101,7 +111,7 @@ def parse_args(): def main(): args = parse_args() - print('Called with argument:', args) + logger.info('Called with argument: %s' % args) ctx = [mx.gpu(int(i)) for i in args.gpus.split(',')] alternate_train(args, ctx, args.pretrained, args.pretrained_epoch, args.rpn_epoch, args.rpn_lr, args.rpn_lr_step, diff --git a/example/rcnn/train_end2end.py b/example/rcnn/train_end2end.py index ac00120131c9..5c942936aa4c 100644 --- a/example/rcnn/train_end2end.py +++ b/example/rcnn/train_end2end.py @@ -1,10 +1,26 @@ -from __future__ import print_function +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import argparse -import logging import pprint import mxnet as mx import numpy as np +from rcnn.logger import logger from rcnn.config import config, default, generate_config from rcnn.symbol import * from rcnn.core import callback, metric @@ -16,11 +32,6 @@ def train_net(args, ctx, pretrained, epoch, prefix, begin_epoch, end_epoch, lr=0.001, lr_step='5'): - # set up logger - logging.basicConfig() - logger = logging.getLogger() - logger.setLevel(logging.INFO) - # setup config config.TRAIN.BATCH_IMAGES = 1 config.TRAIN.BATCH_ROIS = 128 @@ -36,7 +47,7 @@ def train_net(args, ctx, pretrained, epoch, prefix, begin_epoch, end_epoch, input_batch_size = config.TRAIN.BATCH_IMAGES * batch_size # print config - pprint.pprint(config) + logger.info(pprint.pformat(config)) # load dataset and prepare imdb for training image_sets = [iset for iset in args.image_set.split('+')] @@ -56,7 +67,7 @@ def train_net(args, ctx, pretrained, epoch, prefix, begin_epoch, end_epoch, max_data_shape = [('data', (input_batch_size, 3, max([v[0] for v in config.SCALES]), max([v[1] for v in config.SCALES])))] max_data_shape, max_label_shape = train_data.infer_shape(max_data_shape) max_data_shape.append(('gt_boxes', (input_batch_size, 100, 5))) - print('providing maximum shape', max_data_shape, max_label_shape) + logger.info('providing maximum shape %s %s' % (max_data_shape, max_label_shape)) # infer shape data_shape_dict = dict(train_data.provide_data + train_data.provide_label) @@ -64,8 +75,7 @@ def train_net(args, ctx, pretrained, epoch, prefix, begin_epoch, end_epoch, arg_shape_dict = dict(zip(sym.list_arguments(), arg_shape)) out_shape_dict = dict(zip(sym.list_outputs(), out_shape)) aux_shape_dict = dict(zip(sym.list_auxiliary_states(), aux_shape)) - print('output shape') - pprint.pprint(out_shape_dict) + logger.info('output shape %s' % pprint.pformat(out_shape_dict)) # load and initialize params if args.resume: @@ -127,7 +137,7 @@ def train_net(args, ctx, pretrained, epoch, prefix, begin_epoch, end_epoch, lr_epoch_diff = [epoch - begin_epoch for epoch in lr_epoch if epoch > begin_epoch] lr = base_lr * (lr_factor ** (len(lr_epoch) - len(lr_epoch_diff))) lr_iters = [int(epoch * len(roidb) / batch_size) for epoch in lr_epoch_diff] - print('lr', lr, 'lr_epoch_diff', lr_epoch_diff, 'lr_iters', lr_iters) + logger.info('lr %f lr_epoch_diff %s lr_iters %s' % (lr, lr_epoch_diff, lr_iters)) lr_scheduler = mx.lr_scheduler.MultiFactorScheduler(lr_iters, lr_factor) # optimizer optimizer_params = {'momentum': 0.9, @@ -176,7 +186,7 @@ def parse_args(): def main(): args = parse_args() - print('Called with argument:', args) + logger.info('Called with argument: %s' % args) ctx = [mx.gpu(int(i)) for i in args.gpus.split(',')] train_net(args, ctx, args.pretrained, args.pretrained_epoch, args.prefix, args.begin_epoch, args.end_epoch, lr=args.lr, lr_step=args.lr_step) diff --git a/example/recommenders/crossentropy.py b/example/recommenders/crossentropy.py index 79fee7439438..51648b0eb157 100644 --- a/example/recommenders/crossentropy.py +++ b/example/recommenders/crossentropy.py @@ -1,4 +1,22 @@ #!/usr/bin/env python + +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + """Cross-entropy loss layer for MXNet. """ import os @@ -12,13 +30,13 @@ class CrossEntropyLoss(mx.operator.CustomOp): """An output layer that calculates gradient for cross-entropy loss y * log(p) + (1-y) * log(p) for label "y" and prediction "p". - However, the output of this layer is the original prediction -- same as + However, the output of this layer is the original prediction -- same as the "data" input, making it useful for tasks like "predict". If you actually want to use the calculated loss, see CrossEntropyLoss op. This is useful for multi-label prediction where each possible output label is considered independently. - Cross-entropy loss provides a very large penalty for guessing + Cross-entropy loss provides a very large penalty for guessing the wrong answer (0 or 1) confidently. The gradient calculation is optimized for y only being 0 or 1. """ @@ -93,7 +111,7 @@ def infer_shape(self, in_shape): print("Simple test of cross-entropy") data = mx.symbol.Variable('data') labs = mx.symbol.Variable('labs') - net = mx.symbol.Custom(data=data, label=labs, name='ce', + net = mx.symbol.Custom(data=data, label=labs, name='ce', op_type='CrossEntropyLoss') rand = np.random.RandomState(seed=123) for i in range(20): diff --git a/example/recommenders/demo-MF.R b/example/recommenders/demo-MF.R new file mode 100644 index 000000000000..509aa04b9e11 --- /dev/null +++ b/example/recommenders/demo-MF.R @@ -0,0 +1,67 @@ +library(mxnet) +DF <- read.table("./ml-100k/u.data", header = F, sep = "\t") +names(DF) <- c("user", "item", "score", "time") +max_user <- max(DF$user) +max_item <- max(DF$item) +DF_mat_x <- data.matrix(t(DF[, 1:2])) +DF_y <- DF[, 3] +k <- 64 +user <- mx.symbol.Variable("user") +item <- mx.symbol.Variable("item") +score <- mx.symbol.Variable("label") +user1 <-mx.symbol.Embedding(data = mx.symbol.BlockGrad(user), input_dim = max_user, + output_dim = k, name = "user1") +item1 <- mx.symbol.Embedding(data = mx.symbol.BlockGrad(item), input_dim = max_item, + output_dim = k, name = "item1") +pred <- user1 * item1 +pred1 <- mx.symbol.sum_axis(pred, axis = 1, name = "pred1") +pred2 <- mx.symbol.Flatten(pred1, name = "pred2") +pred3 <- mx.symbol.LinearRegressionOutput(data = pred2, label = score, name = "pred3") +devices <- mx.cpu() +mx.set.seed(123) + +CustomIter <- setRefClass("CustomIter", fields = c("iter1", "iter2"), + contains = "Rcpp_MXArrayDataIter", + methods = list( + initialize = function(iter1, iter2) { + .self$iter1 <- iter1 + .self$iter2 <- iter2 + .self + }, + value = function() { + user <- .self$iter1$value()$data + item <- .self$iter2$value()$data + label <- .self$iter1$value()$label + list(user = user, + item = item, + label = label) + }, + iter.next = function() { + .self$iter1$iter.next() + .self$iter2$iter.next() + }, + reset = function() { + .self$iter1$reset() + .self$iter2$reset() + }, + num.pad = function() { + .self$iter1$num.pad() + }, + finalize = function() { + .self$iter1$finalize() + .self$iter2$finalize() + } + ) +) + +user_iter = mx.io.arrayiter(data = DF[, 1], label = DF[, 3], batch.size = k) + +item_iter = mx.io.arrayiter(data = DF[, 2], label = DF[, 3], batch.size = k) + +train_iter <- CustomIter$new(user_iter, item_iter) + +model <- mx.model.FeedForward.create(pred3, X = train_iter, ctx = devices, + num.round = 10, initializer = mx.init.uniform(0.07), + learning.rate = 0.07, eval.metric = mx.metric.rmse, + momentum = 0.9, epoch.end.callback = mx.callback.log.train.metric(1), + input.names = c("user", "item"), output.names = "label") diff --git a/example/recommenders/matrix_fact.py b/example/recommenders/matrix_fact.py index 90be41ed7f36..73f561a87959 100644 --- a/example/recommenders/matrix_fact.py +++ b/example/recommenders/matrix_fact.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import math import mxnet as mx import numpy as np diff --git a/example/recommenders/movielens_data.py b/example/recommenders/movielens_data.py index 157e8c209978..3d664fbb02aa 100644 --- a/example/recommenders/movielens_data.py +++ b/example/recommenders/movielens_data.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + """MovieLens data handling: download, parse, and expose as DataIter """ @@ -22,13 +39,13 @@ def load_mldata_iter(filename, batch_size): user = mx.nd.array(user) item = mx.nd.array(item) score = mx.nd.array(score) - return mx.io.NDArrayIter(data={'user':user,'item':item},label={'score':score}, + return mx.io.NDArrayIter(data={'user':user,'item':item},label={'score':score}, batch_size=batch_size, shuffle=True) def ensure_local_data(prefix): if not os.path.exists("%s.zip" % prefix): print("Downloading MovieLens data: %s" % prefix) - os.system("wget http://files.grouplens.org/datasets/movielens/%s.zip" % prefix) + os.system("wget http://files.grouplens.org/datasets/movielens/%s.zip" % prefix) os.system("unzip %s.zip" % prefix) @@ -36,7 +53,7 @@ def get_data_iter(batch_size, prefix='ml-100k'): """Returns a pair of NDArrayDataIter, one for train, one for test. """ ensure_local_data(prefix) - return (load_mldata_iter('./%s/u1.base' % prefix, batch_size), + return (load_mldata_iter('./%s/u1.base' % prefix, batch_size), load_mldata_iter('./%s/u1.test' % prefix, batch_size)) def max_id(fname): diff --git a/example/recommenders/negativesample.py b/example/recommenders/negativesample.py index ecbd85e6f407..0b4ea8477b6f 100644 --- a/example/recommenders/negativesample.py +++ b/example/recommenders/negativesample.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + """DataIter for negative sampling. """ import mxnet as mx @@ -8,7 +25,7 @@ class NegativeSamplingDataIter(mx.io.DataIter): Assumes that all the relevant inputs are in data, not labels. Drops (replaces) any labels in the original DataIter. - It only shuffles one of the input data columns, specified in the + It only shuffles one of the input data columns, specified in the constructor as shuffle_data_idx. So if the original input data has three columns, ('item_ids', 'item_words', 'users') and you want to keep the two "item_*" together, then set `shuffle_data_idx=2` @@ -46,7 +63,7 @@ def _clear_queue(self): self._sampled_queue = [] def _push_queue(self, data_list, labels): - """Takes a list of numpy arrays for data, + """Takes a list of numpy arrays for data, and a numpy array for labels. Converts to minibatches and puts it on the queue. """ diff --git a/example/recommenders/randomproj.py b/example/recommenders/randomproj.py index 539f50e0f647..ba080a07ec38 100644 --- a/example/recommenders/randomproj.py +++ b/example/recommenders/randomproj.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + """Random projection layers in MXNet as custom python ops. Currently slow and memory-inefficient, but functional. """ @@ -34,7 +51,7 @@ def random_unit_vecs(self, num_vecs, num_dims, rs): def _get_mask(self, idx, in_data): """Returns the mask by which to multiply the parts of the embedding layer. - In this version, we have no weights to apply. + In this version, we have no weights to apply. """ mask = idx >= 0 # bool False for -1 values that should be removed. shape=(b,mnz) mask = np.expand_dims(mask,2) # shape = (b,mnz,1) @@ -98,7 +115,7 @@ def _get_mask(self, idx, in_data): mask = np.expand_dims(mask,2) # shape = (b,mnz,1) mask = np.repeat(mask, self._proj_dim, axis=2) # shape = (b,mnz,d) return mask - + @mx.operator.register("SparseRandomProjection") class SparseRandomProjectionProp(RandomBagOfWordsProjectionProp): @@ -121,8 +138,8 @@ def infer_shape(self, in_shape): print("Simple test of proj layer") data = mx.symbol.Variable('data') vals = mx.symbol.Variable('vals') - net = mx.symbol.Custom(indexes=data, values=vals, name='rproj', - op_type='SparseRandomProjection', + net = mx.symbol.Custom(indexes=data, values=vals, name='rproj', + op_type='SparseRandomProjection', vocab_size=999, output_dim=29) d = mx.nd.zeros(shape=(3,100)) v = mx.nd.ones(shape=(3,100)) diff --git a/example/recommenders/recotools.py b/example/recommenders/recotools.py index f3681709db02..250baa5c07cf 100644 --- a/example/recommenders/recotools.py +++ b/example/recommenders/recotools.py @@ -1,7 +1,24 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import mxnet as mx from negativesample import NegativeSamplingDataIter -import randomproj +import randomproj import crossentropy def CosineLoss(a, b, label): diff --git a/example/recommenders/symbol_alexnet.py b/example/recommenders/symbol_alexnet.py index 13aa65131117..e5d02f0412cd 100644 --- a/example/recommenders/symbol_alexnet.py +++ b/example/recommenders/symbol_alexnet.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + """ Reference: @@ -5,7 +22,7 @@ """ import mxnet as mx -def features(input_data, num_features): +def features(input_data, in_channels): # stage 1 conv1 = mx.symbol.Convolution( data=input_data, kernel=(11, 11), stride=(4, 4), num_filter=96) @@ -40,6 +57,6 @@ def features(input_data, num_features): relu7 = mx.symbol.Activation(data=fc2, act_type="relu") dropout2 = mx.symbol.Dropout(data=relu7, p=0.5) # stage 6 - fc3 = mx.symbol.FullyConnected(data=dropout2, num_hidden=num_features) + fc3 = mx.symbol.FullyConnected(data=dropout2, num_hidden=in_channels) return fc3 diff --git a/example/reinforcement-learning/a3c/a3c.py b/example/reinforcement-learning/a3c/a3c.py index 19ab2305fb5e..4d89a24852c8 100644 --- a/example/reinforcement-learning/a3c/a3c.py +++ b/example/reinforcement-learning/a3c/a3c.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + from __future__ import print_function import mxnet as mx import numpy as np @@ -76,7 +93,7 @@ def train(): if args.kv_store == 'dist_sync': epoch_size /= kv.num_workers - + # disable kvstore for single device if 'local' in kv.type and ( args.gpus is None or len(args.gpus.split(',')) is 1): @@ -164,7 +181,7 @@ def train(): print('h', h[0].asnumpy()) err += (adv**2).mean() score += r[i] - final_score *= (1-D[i]) + final_score *= (1-D[i]) final_score += score * D[i] score *= 1-D[i] T += D[i].sum() diff --git a/example/reinforcement-learning/a3c/launcher.py b/example/reinforcement-learning/a3c/launcher.py index 8a4a7d17c73e..e0bda21891f0 100644 --- a/example/reinforcement-learning/a3c/launcher.py +++ b/example/reinforcement-learning/a3c/launcher.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + """Submission job for local jobs.""" # pylint: disable=invalid-name from __future__ import absolute_import diff --git a/example/reinforcement-learning/a3c/rl_data.py b/example/reinforcement-learning/a3c/rl_data.py index 0d16bca793a4..ad78975753bd 100644 --- a/example/reinforcement-learning/a3c/rl_data.py +++ b/example/reinforcement-learning/a3c/rl_data.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + from __future__ import print_function import mxnet as mx import numpy as np diff --git a/example/reinforcement-learning/a3c/sym.py b/example/reinforcement-learning/a3c/sym.py index d3e1767ea5b2..c48d752d2d0c 100644 --- a/example/reinforcement-learning/a3c/sym.py +++ b/example/reinforcement-learning/a3c/sym.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import mxnet as mx def get_symbol_atari(act_dim): diff --git a/example/reinforcement-learning/ddpg/ddpg.py b/example/reinforcement-learning/ddpg/ddpg.py index 4ded9b952273..aa34e4d92804 100644 --- a/example/reinforcement-learning/ddpg/ddpg.py +++ b/example/reinforcement-learning/ddpg/ddpg.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + from replay_mem import ReplayMem from utils import discount_return, sample_rewards import rllab.misc.logger as logger diff --git a/example/reinforcement-learning/ddpg/policies.py b/example/reinforcement-learning/ddpg/policies.py index 2a625c8872b3..2bae8f68cf0c 100644 --- a/example/reinforcement-learning/ddpg/policies.py +++ b/example/reinforcement-learning/ddpg/policies.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + from utils import define_policy import mxnet as mx @@ -40,7 +57,7 @@ def __init__( self.obs = mx.symbol.Variable("obs") self.act = define_policy( - self.obs, + self.obs, self.env_spec.action_space.flat_dim) def get_output_symbol(self): @@ -59,7 +76,7 @@ def define_loss(self, loss_exp): raise NotImplementedError - def define_exe(self, ctx, init, updater, input_shapes=None, args=None, + def define_exe(self, ctx, init, updater, input_shapes=None, args=None, grad_req=None): # define an executor, initializer and updater for batch version @@ -71,7 +88,7 @@ def define_exe(self, ctx, init, updater, input_shapes=None, args=None, for name, arr in self.arg_dict.items(): if name not in input_shapes: init(name, arr) - + self.updater = updater # define an executor for sampled single observation @@ -110,4 +127,4 @@ def get_action(self, obs): - \ No newline at end of file + diff --git a/example/reinforcement-learning/ddpg/qfuncs.py b/example/reinforcement-learning/ddpg/qfuncs.py index af9bdb659318..7dbc1d601d30 100644 --- a/example/reinforcement-learning/ddpg/qfuncs.py +++ b/example/reinforcement-learning/ddpg/qfuncs.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + from utils import define_qfunc import mxnet as mx @@ -18,7 +35,7 @@ def get_qvals(self, obs, act): class ContinuousMLPQ(QFunc): """ - Continous Multi-Layer Perceptron Q-Value Network + Continuous Multi-Layer Perceptron Q-Value Network for determnistic policy training. """ @@ -47,7 +64,7 @@ def define_loss(self, loss_exp): self.loss = mx.symbol.MakeLoss(loss_exp, name="qfunc_loss") self.loss = mx.symbol.Group([self.loss, mx.symbol.BlockGrad(self.qval)]) - def define_exe(self, ctx, init, updater, input_shapes=None, args=None, + def define_exe(self, ctx, init, updater, input_shapes=None, args=None, grad_req=None): # define an executor, initializer and updater for batch version loss @@ -55,11 +72,11 @@ def define_exe(self, ctx, init, updater, input_shapes=None, args=None, self.arg_arrays = self.exe.arg_arrays self.grad_arrays = self.exe.grad_arrays self.arg_dict = self.exe.arg_dict - + for name, arr in self.arg_dict.items(): if name not in input_shapes: init(name, arr) - + self.updater = updater def update_params(self, obs, act, yval): diff --git a/example/reinforcement-learning/ddpg/replay_mem.py b/example/reinforcement-learning/ddpg/replay_mem.py index 885d7da301f0..47e9bc843ac0 100644 --- a/example/reinforcement-learning/ddpg/replay_mem.py +++ b/example/reinforcement-learning/ddpg/replay_mem.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + from __future__ import print_function import numpy as np @@ -5,7 +22,7 @@ class ReplayMem(object): def __init__( - self, + self, obs_dim, act_dim, memory_size=1000000): diff --git a/example/reinforcement-learning/ddpg/run.py b/example/reinforcement-learning/ddpg/run.py index 0cd162ff5e9e..043cd997e647 100644 --- a/example/reinforcement-learning/ddpg/run.py +++ b/example/reinforcement-learning/ddpg/run.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + from ddpg import DDPG from rllab.envs.box2d.cartpole_env import CartpoleEnv from rllab.envs.normalized_env import normalize @@ -32,4 +49,4 @@ policy_lr=1e-4, seed=SEED) -algo.train() \ No newline at end of file +algo.train() diff --git a/example/reinforcement-learning/ddpg/strategies.py b/example/reinforcement-learning/ddpg/strategies.py index c346e9e2a133..d73ad060cc87 100644 --- a/example/reinforcement-learning/ddpg/strategies.py +++ b/example/reinforcement-learning/ddpg/strategies.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import numpy as np @@ -28,7 +45,7 @@ def __init__(self, env_spec, mu=0, theta=0.15, sigma=0.3): self.sigma = sigma self.action_space = env_spec.action_space self.state = np.ones(self.action_space.flat_dim) * self.mu - + def evolve_state(self): x = self.state @@ -47,9 +64,9 @@ def get_action(self, obs, policy): obs = obs.reshape((1, -1)) action = policy.get_action(obs) increment = self.evolve_state() - - return np.clip(action + increment, - self.action_space.low, + + return np.clip(action + increment, + self.action_space.low, self.action_space.high) diff --git a/example/reinforcement-learning/ddpg/utils.py b/example/reinforcement-learning/ddpg/utils.py index 8c063db76506..a9a445632fd2 100644 --- a/example/reinforcement-learning/ddpg/utils.py +++ b/example/reinforcement-learning/ddpg/utils.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import mxnet as mx import numpy as np diff --git a/example/reinforcement-learning/dqn/atari_game.py b/example/reinforcement-learning/dqn/atari_game.py index 369016fe134f..5c1314ffcf6c 100644 --- a/example/reinforcement-learning/dqn/atari_game.py +++ b/example/reinforcement-learning/dqn/atari_game.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + __author__ = 'sxjscience' import mxnet as mx diff --git a/example/reinforcement-learning/dqn/base.py b/example/reinforcement-learning/dqn/base.py index f433d5d7cbc0..ce82f2b1ad6c 100644 --- a/example/reinforcement-learning/dqn/base.py +++ b/example/reinforcement-learning/dqn/base.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + from __future__ import absolute_import, division, print_function import mxnet as mx diff --git a/example/reinforcement-learning/dqn/dqn_demo.py b/example/reinforcement-learning/dqn/dqn_demo.py index 000a796b5821..750da7a69a7c 100644 --- a/example/reinforcement-learning/dqn/dqn_demo.py +++ b/example/reinforcement-learning/dqn/dqn_demo.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import mxnet as mx import mxnet.ndarray as nd import numpy diff --git a/example/reinforcement-learning/dqn/dqn_run_test.py b/example/reinforcement-learning/dqn/dqn_run_test.py index 1a918eb92aab..2abf273978fa 100644 --- a/example/reinforcement-learning/dqn/dqn_run_test.py +++ b/example/reinforcement-learning/dqn/dqn_run_test.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import mxnet as mx import mxnet.ndarray as nd import numpy diff --git a/example/reinforcement-learning/dqn/game.py b/example/reinforcement-learning/dqn/game.py index 0e1b7f1bd651..e17cc6f03f41 100644 --- a/example/reinforcement-learning/dqn/game.py +++ b/example/reinforcement-learning/dqn/game.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + DEFAULT_MAX_EPISODE_STEP = 1000000 @@ -29,4 +46,4 @@ def current_state(self): return self.replay_memory.latest_slice() def play(self, a): - raise NotImplementedError \ No newline at end of file + raise NotImplementedError diff --git a/example/reinforcement-learning/dqn/operators.py b/example/reinforcement-learning/dqn/operators.py index e8180c4d3f10..0c9b588f1685 100644 --- a/example/reinforcement-learning/dqn/operators.py +++ b/example/reinforcement-learning/dqn/operators.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import mxnet as mx import mxnet.ndarray as nd import numpy diff --git a/example/reinforcement-learning/dqn/replay_memory.py b/example/reinforcement-learning/dqn/replay_memory.py index 42f4866c2a08..02691a01888a 100644 --- a/example/reinforcement-learning/dqn/replay_memory.py +++ b/example/reinforcement-learning/dqn/replay_memory.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + from __future__ import absolute_import, division, print_function import mxnet as mx diff --git a/example/reinforcement-learning/dqn/utils.py b/example/reinforcement-learning/dqn/utils.py index 7d84bba74524..bae11e18021d 100644 --- a/example/reinforcement-learning/dqn/utils.py +++ b/example/reinforcement-learning/dqn/utils.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + from __future__ import absolute_import, division, print_function import os diff --git a/example/reinforcement-learning/parallel_actor_critic/config.py b/example/reinforcement-learning/parallel_actor_critic/config.py index 48ef1d0c5a38..e962cf99be1f 100644 --- a/example/reinforcement-learning/parallel_actor_critic/config.py +++ b/example/reinforcement-learning/parallel_actor_critic/config.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import mxnet as mx diff --git a/example/reinforcement-learning/parallel_actor_critic/envs.py b/example/reinforcement-learning/parallel_actor_critic/envs.py index 09f30d73cf2d..a537df6a7630 100644 --- a/example/reinforcement-learning/parallel_actor_critic/envs.py +++ b/example/reinforcement-learning/parallel_actor_critic/envs.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import numpy as np diff --git a/example/reinforcement-learning/parallel_actor_critic/model.py b/example/reinforcement-learning/parallel_actor_critic/model.py index 8fba78f7b950..b90af6790544 100644 --- a/example/reinforcement-learning/parallel_actor_critic/model.py +++ b/example/reinforcement-learning/parallel_actor_critic/model.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + from itertools import chain import numpy as np import scipy.signal diff --git a/example/reinforcement-learning/parallel_actor_critic/train.py b/example/reinforcement-learning/parallel_actor_critic/train.py index d559ff346774..128a55030258 100644 --- a/example/reinforcement-learning/parallel_actor_critic/train.py +++ b/example/reinforcement-learning/parallel_actor_critic/train.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + """Trains an `Agent` using trajectories from multiple environments.""" import argparse diff --git a/example/rnn-time-major/bucket_io.py b/example/rnn-time-major/bucket_io.py index 5cf2c81967a8..950b0c05cfca 100644 --- a/example/rnn-time-major/bucket_io.py +++ b/example/rnn-time-major/bucket_io.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # pylint: disable=C0111,too-many-arguments,too-many-instance-attributes,too-many-locals,redefined-outer-name,fixme # pylint: disable=superfluous-parens, no-member, invalid-name from __future__ import print_function @@ -58,7 +75,7 @@ def default_gen_buckets(sentences, batch_size, the_vocab): tl = 0 buckets = [] - for l, n in len_dict.items(): # TODO: There are better heuristic ways to do this + for l, n in len_dict.items(): # TODO: There are better heuristic ways to do this if n + tl >= batch_size: buckets.append(l) tl = 0 @@ -217,7 +234,7 @@ def __iter__(self): i_idx = self.bucket_curr_idx[i_bucket] idx = self.bucket_idx_all[i_bucket][i_idx:i_idx+self.batch_size] self.bucket_curr_idx[i_bucket] += self.batch_size - + init_state_names = [x[0] for x in self.init_states] if self.time_major: diff --git a/example/rnn-time-major/get_ptb_data.sh b/example/rnn-time-major/get_ptb_data.sh index 1ec009aa2f99..d2641cb32b81 100755 --- a/example/rnn-time-major/get_ptb_data.sh +++ b/example/rnn-time-major/get_ptb_data.sh @@ -1,5 +1,23 @@ #!/usr/bin/env bash +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + + RNN_DIR=$(cd `dirname $0`; pwd) DATA_DIR="${RNN_DIR}/data/" diff --git a/example/rnn-time-major/rnn_cell_demo.py b/example/rnn-time-major/rnn_cell_demo.py index cb69c55770e0..c29d1ddea4e3 100644 --- a/example/rnn-time-major/rnn_cell_demo.py +++ b/example/rnn-time-major/rnn_cell_demo.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + """A simple demo of new RNN cell with PTB language model.""" ################################################################################ @@ -100,7 +117,7 @@ def sym_gen(seq_len): # RNN cell takes input of shape (time, batch, feature) rnn = mx.sym.RNN(data=embed, state_size=num_hidden, num_layers=num_lstm_layer, mode='lstm', - name='LSTM', + name='LSTM', # The following params can be omitted # provided we do not need to apply the # workarounds mentioned above @@ -134,7 +151,7 @@ def sym_gen(seq_len): if len(buckets) == 1: mod = mx.mod.Module(*sym_gen(buckets[0]), context=contexts) else: - mod = mx.mod.BucketingModule(sym_gen, + mod = mx.mod.BucketingModule(sym_gen, default_bucket_key=data_train.default_bucket_key, context=contexts) diff --git a/example/rnn/bucket_R/aclImdb_lstm_classification.R b/example/rnn/bucket_R/aclImdb_lstm_classification.R new file mode 100644 index 000000000000..bb5eaacf26dd --- /dev/null +++ b/example/rnn/bucket_R/aclImdb_lstm_classification.R @@ -0,0 +1,55 @@ +require("mxnet") + +source("mx.io.bucket.iter.R") +source("rnn.train.R") + +corpus_bucketed_train <- readRDS(file = "corpus_bucketed_train_100_200_300_500_800_left.rds") +corpus_bucketed_test <- readRDS(file = "corpus_bucketed_test_100_200_300_500_800_left.rds") + +vocab <- length(corpus_bucketed_test$dic) + +### Create iterators +batch.size <- 64 + +num.round <- 16 + +train.data <- mx.io.bucket.iter(buckets = corpus_bucketed_train$buckets, batch.size = batch.size, + data.mask.element = 0, shuffle = TRUE) + +eval.data <- mx.io.bucket.iter(buckets = corpus_bucketed_test$buckets, batch.size = batch.size, + data.mask.element = 0, shuffle = FALSE) + +mx.set.seed(0) +optimizer <- mx.opt.create("adadelta", rho = 0.92, epsilon = 1e-06, wd = 2e-04, clip_gradient = NULL, + rescale.grad = 1/batch.size) + +model_sentiment_lstm <- mx.rnn.buckets(train.data = train.data, begin.round = 1, + num.round = num.round, ctx = mx.cpu(), metric = mx.metric.accuracy, optimizer = optimizer, + num.rnn.layer = 2, num.embed = 16, num.hidden = 24, num.label = 2, input.size = vocab, + initializer = mx.init.Xavier(rnd_type = "gaussian", factor_type = "in", magnitude = 2), + dropout = 0.25, config = "seq-to-one", batch.end.callback = mx.callback.log.train.metric(period = 50), + verbose = TRUE) + +mx.model.save(model_sentiment_lstm, prefix = "model_sentiment_lstm", iteration = num.round) + +source("rnn.infer.R") + +model <- mx.model.load("model_sentiment_lstm", iteration = num.round) + +pred <- mx.rnn.infer.buckets(infer_iter = eval.data, model, "seq-to-one", ctx = mx.cpu()) + +ypred <- max.col(t(as.array(pred)), tie = "first") - 1 + +packer <- mxnet:::mx.nd.arraypacker() + +eval.data$reset() + +while (eval.data$iter.next()) { + packer$push(eval.data$value()$label) +} + +ylabel <- as.array(packer$get()) + +acc <- sum(ylabel == ypred)/length(ylabel) + +message(paste("Acc:", acc)) diff --git a/example/rnn/bucket_R/data_preprocessing.R b/example/rnn/bucket_R/data_preprocessing.R new file mode 100644 index 000000000000..c91e3fb5eb49 --- /dev/null +++ b/example/rnn/bucket_R/data_preprocessing.R @@ -0,0 +1,170 @@ +# download the IMDB dataset +if (!file.exists("aclImdb_v1.tar.gz")) { + download.file("http://ai.stanford.edu/~amaas/data/sentiment/aclImdb_v1.tar.gz", + "aclImdb_v1.tar.gz") + untar("aclImdb_v1.tar.gz") +} + +# install required packages +list.of.packages <- c("readr", "dplyr", "stringr", "stringi") +new.packages <- list.of.packages[!(list.of.packages %in% installed.packages()[, "Package"])] +if (length(new.packages)) install.packages(new.packages) + +require("readr") +require("dplyr") +require("stringr") +require("stringi") + +negative_train_list <- list.files("./aclImdb/train/neg/", full.names = T) +positive_train_list <- list.files("./aclImdb/train/pos/", full.names = T) + +negative_test_list <- list.files("./aclImdb/test/neg/", full.names = T) +positive_test_list <- list.files("./aclImdb/test/pos/", full.names = T) + +file_import <- function(file_list) { + import <- sapply(file_list, read_file) + return(import) +} + +negative_train_raw <- file_import(negative_train_list) +positive_train_raw <- file_import(positive_train_list) + +negative_test_raw <- file_import(negative_test_list) +positive_test_raw <- file_import(positive_test_list) + +train_raw <- c(negative_train_raw, positive_train_raw) +test_raw <- c(negative_test_raw, positive_test_raw) + +saveRDS(train_raw, file = "train_raw.rds") +saveRDS(test_raw, file = "test_raw.rds") + +################################################################ Pre-process a corpus composed of a vector of sequences Build a dictionnary +################################################################ removing too rare words +text_pre_process <- function(corpus, count_threshold = 10, dic = NULL) { + raw_vec <- corpus + raw_vec <- stri_enc_toascii(str = raw_vec) + + ### remove non-printable characters + raw_vec <- str_replace_all(string = raw_vec, pattern = "[^[:print:]]", replacement = "") + raw_vec <- str_to_lower(string = raw_vec) + raw_vec <- str_replace_all(string = raw_vec, pattern = "_", replacement = " ") + raw_vec <- str_replace_all(string = raw_vec, pattern = "\\bbr\\b", replacement = "") + raw_vec <- str_replace_all(string = raw_vec, pattern = "\\s+", replacement = " ") + raw_vec <- str_trim(string = raw_vec) + + ### Split raw sequence vectors into lists of word vectors (one list element per + ### sequence) + word_vec_list <- stri_split_boundaries(raw_vec, type = "word", skip_word_none = T, + skip_word_number = F, simplify = F) + + ### Build vocabulary + if (is.null(dic)) { + word_vec_unlist <- unlist(word_vec_list) + word_vec_table <- sort(table(word_vec_unlist), decreasing = T) + word_cutoff <- which.max(word_vec_table < count_threshold) + word_keep <- names(word_vec_table)[1:(word_cutoff - 1)] + stopwords <- c(letters, "an", "the", "br") + word_keep <- setdiff(word_keep, stopwords) + } else word_keep <- names(dic)[!dic == 0] + + ### Clean the sentences to keep only the curated list of words + word_vec_list <- lapply(word_vec_list, function(x) x[x %in% word_keep]) + + # sentence_vec<- stri_split_boundaries(raw_vec, type='sentence', simplify = T) + word_vec_length <- lapply(word_vec_list, length) %>% unlist() + + ### Build dictionnary + dic <- 1:length(word_keep) + names(dic) <- word_keep + dic <- c(`¤` = 0, dic) + + ### reverse dictionnary + rev_dic <- names(dic) + names(rev_dic) <- dic + + return(list(word_vec_list = word_vec_list, dic = dic, rev_dic = rev_dic)) +} + +################################################################ +make_bucket_data <- function(word_vec_list, labels, dic, seq_len = c(225), right_pad = T) { + ### Trunc sequence to max bucket length + word_vec_list <- lapply(word_vec_list, head, n = max(seq_len)) + + word_vec_length <- lapply(word_vec_list, length) %>% unlist() + bucketID <- cut(word_vec_length, breaks = c(0, seq_len, Inf), include.lowest = T, + labels = F) + # table(bucketID) + + ### Right or Left side Padding Pad sequences to their bucket length with + ### dictionnary 0-label + word_vec_list_pad <- lapply(1:length(word_vec_list), function(x) { + length(word_vec_list[[x]]) <- seq_len[bucketID[x]] + word_vec_list[[x]][is.na(word_vec_list[[x]])] <- names(dic[1]) + if (right_pad == F) + word_vec_list[[x]] <- rev(word_vec_list[[x]]) + return(word_vec_list[[x]]) + }) + + ### Assign sequences to buckets and unroll them in order to be reshaped into arrays + unrolled_arrays <- lapply(1:length(seq_len), function(x) unlist(word_vec_list_pad[bucketID == + x])) + + ### Assign labels to their buckets + bucketed_labels <- lapply(1:length(seq_len), function(x) labels[bucketID == x]) + names(bucketed_labels) <- as.character(seq_len) + + ### Assign the dictionnary to each bucket terms + unrolled_arrays_dic <- lapply(1:length(seq_len), function(x) dic[unrolled_arrays[[x]]]) + + # length(splitted_arrays_dic[[1]]) Reshape into arrays having each sequence into + # a column + features_arrays <- lapply(1:length(seq_len), function(x) array(unrolled_arrays_dic[[x]], + dim = c(seq_len[x], length(unrolled_arrays_dic[[x]])/seq_len[x]))) + + features <- lapply(1:length(seq_len), function(x) features_arrays[[x]][1:seq_len[x], + ]) + names(features) <- as.character(seq_len) + + ### Combine data and labels into buckets + buckets <- lapply(1:length(seq_len), function(x) c(list(data = features[[x]]), + list(label = bucketed_labels[[x]]))) + names(buckets) <- as.character(seq_len) + + ### reverse dictionnary + rev_dic <- names(dic) + names(rev_dic) <- dic + + return(list(buckets = buckets, dic = dic, rev_dic = rev_dic)) +} + + +corpus_preprocessed_train <- text_pre_process(corpus = train_raw, count_threshold = 10, + dic = NULL) + +# length(corpus_preprocessed_train$dic) + +corpus_preprocessed_test <- text_pre_process(corpus = test_raw, dic = corpus_preprocessed_train$dic) + +saveRDS(corpus_preprocessed_train, file = "corpus_preprocessed_train_10.rds") +saveRDS(corpus_preprocessed_test, file = "corpus_preprocessed_test_10.rds") + +corpus_preprocessed_train <- readRDS(file = "corpus_preprocessed_train_10.rds") +corpus_preprocessed_test <- readRDS(file = "corpus_preprocessed_test_10.rds") + + +corpus_bucketed_train <- make_bucket_data(word_vec_list = corpus_preprocessed_train$word_vec_list, + labels = rep(0:1, each = 12500), dic = corpus_preprocessed_train$dic, seq_len = c(100, + 200, 300, 500, 800), right_pad = F) + +# lapply(corpus_bucketed_train$buckets, function(x) length(x[[2]])) + + +corpus_bucketed_test <- make_bucket_data(word_vec_list = corpus_preprocessed_test$word_vec_list, + labels = rep(0:1, each = 12500), dic = corpus_preprocessed_test$dic, seq_len = c(100, + 200, 300, 500, 800), right_pad = F) + +# lapply(corpus_bucketed_test$buckets, function(x) length(x[[2]])) + + +saveRDS(corpus_bucketed_train, file = "corpus_bucketed_train_100_200_300_500_800_left.rds") +saveRDS(corpus_bucketed_test, file = "corpus_bucketed_test_100_200_300_500_800_left.rds") diff --git a/example/rnn/bucket_R/gru.cell.R b/example/rnn/bucket_R/gru.cell.R new file mode 100644 index 000000000000..5932cdf17efa --- /dev/null +++ b/example/rnn/bucket_R/gru.cell.R @@ -0,0 +1,54 @@ +# GRU cell symbol +gru.cell <- function(num.hidden, indata, prev.state, param, seqidx, layeridx, dropout = 0, + data_masking) { + i2h <- mx.symbol.FullyConnected(data = indata, weight = param$gates.i2h.weight, + bias = param$gates.i2h.bias, num.hidden = num.hidden * 2, name = paste0("t", + seqidx, ".l", layeridx, ".gates.i2h")) + + if (dropout > 0) + i2h <- mx.symbol.Dropout(data = i2h, p = dropout) + + if (!is.null(prev.state)) { + h2h <- mx.symbol.FullyConnected(data = prev.state$h, weight = param$gates.h2h.weight, + bias = param$gates.h2h.bias, num.hidden = num.hidden * 2, name = paste0("t", + seqidx, ".l", layeridx, ".gates.h2h")) + gates <- i2h + h2h + } else { + gates <- i2h + } + + split.gates <- mx.symbol.split(gates, num.outputs = 2, axis = 1, squeeze.axis = F, + name = paste0("t", seqidx, ".l", layeridx, ".split")) + + update.gate <- mx.symbol.Activation(split.gates[[1]], act.type = "sigmoid") + reset.gate <- mx.symbol.Activation(split.gates[[2]], act.type = "sigmoid") + + htrans.i2h <- mx.symbol.FullyConnected(data = indata, weight = param$trans.i2h.weight, + bias = param$trans.i2h.bias, num.hidden = num.hidden, name = paste0("t", + seqidx, ".l", layeridx, ".trans.i2h")) + + if (is.null(prev.state)) { + h.after.reset <- reset.gate * 0 + } else { + h.after.reset <- prev.state$h * reset.gate + } + + htrans.h2h <- mx.symbol.FullyConnected(data = h.after.reset, weight = param$trans.h2h.weight, + bias = param$trans.h2h.bias, num.hidden = num.hidden, name = paste0("t", + seqidx, ".l", layeridx, ".trans.h2h")) + + h.trans <- htrans.i2h + htrans.h2h + h.trans.active <- mx.symbol.Activation(h.trans, act.type = "tanh") + + if (is.null(prev.state)) { + next.h <- update.gate * h.trans.active + } else { + next.h <- prev.state$h + update.gate * (h.trans.active - prev.state$h) + } + + ### Add a mask - using the mask_array approach + data_mask_expand <- mx.symbol.Reshape(data = data_masking, shape = c(1, -2)) + next.h <- mx.symbol.broadcast_mul(lhs = next.h, rhs = data_mask_expand) + + return(list(h = next.h)) +} diff --git a/example/rnn/bucket_R/lstm.cell.R b/example/rnn/bucket_R/lstm.cell.R new file mode 100644 index 000000000000..3c7b0e456d20 --- /dev/null +++ b/example/rnn/bucket_R/lstm.cell.R @@ -0,0 +1,41 @@ +# LSTM cell symbol +lstm.cell <- function(num.hidden, indata, prev.state, param, seqidx, layeridx, dropout = 0, + data_masking) { + i2h <- mx.symbol.FullyConnected(data = indata, weight = param$i2h.weight, bias = param$i2h.bias, + num.hidden = num.hidden * 4, name = paste0("t", seqidx, ".l", layeridx, ".i2h")) + + if (dropout > 0) + i2h <- mx.symbol.Dropout(data = i2h, p = dropout) + + if (!is.null(prev.state)) { + h2h <- mx.symbol.FullyConnected(data = prev.state$h, weight = param$h2h.weight, + bias = param$h2h.bias, num.hidden = num.hidden * 4, name = paste0("t", + seqidx, ".l", layeridx, ".h2h")) + gates <- i2h + h2h + } else { + gates <- i2h + } + + split.gates <- mx.symbol.split(gates, num.outputs = 4, axis = 1, squeeze.axis = F, + name = paste0("t", seqidx, ".l", layeridx, ".slice")) + + in.gate <- mx.symbol.Activation(split.gates[[1]], act.type = "sigmoid") + in.transform <- mx.symbol.Activation(split.gates[[2]], act.type = "tanh") + forget.gate <- mx.symbol.Activation(split.gates[[3]], act.type = "sigmoid") + out.gate <- mx.symbol.Activation(split.gates[[4]], act.type = "sigmoid") + + if (is.null(prev.state)) { + next.c <- in.gate * in.transform + } else { + next.c <- (forget.gate * prev.state$c) + (in.gate * in.transform) + } + + next.h <- out.gate * mx.symbol.Activation(next.c, act.type = "tanh") + + ### Add a mask - using the mask_array approach + data_mask_expand <- mx.symbol.Reshape(data = data_masking, shape = c(1, -2)) + next.c <- mx.symbol.broadcast_mul(lhs = next.c, rhs = data_mask_expand) + next.h <- mx.symbol.broadcast_mul(lhs = next.h, rhs = data_mask_expand) + + return(list(c = next.c, h = next.h)) +} diff --git a/example/rnn/bucket_R/mx.io.bucket.iter.R b/example/rnn/bucket_R/mx.io.bucket.iter.R new file mode 100644 index 000000000000..61f87957ede0 --- /dev/null +++ b/example/rnn/bucket_R/mx.io.bucket.iter.R @@ -0,0 +1,92 @@ +BucketIter <- setRefClass("BucketIter", fields = c("buckets", "bucket.names", "batch.size", + "data.mask.element", "shuffle", "bucket.plan", "bucketID", "epoch", "batch", + "batch.per.epoch", "seed"), contains = "Rcpp_MXArrayDataIter", methods = list(initialize = function(buckets, + batch.size, data.mask.element = 0, shuffle = FALSE, seed = 123) { + .self$buckets <- buckets + .self$bucket.names <- names(.self$buckets) + .self$batch.size <- batch.size + .self$data.mask.element <- data.mask.element + .self$epoch <- 0 + .self$batch <- 0 + .self$shuffle <- shuffle + .self$batch.per.epoch <- 0 + .self$bucket.plan <- NULL + .self$bucketID <- NULL + .self$seed <- seed + .self +}, reset = function() { + buckets_nb <- length(bucket.names) + buckets_id <- 1:buckets_nb + buckets_size <- sapply(.self$buckets, function(x) { + dim(x$data)[length(dim(x$data))] + }) + batch_per_bucket <- floor(buckets_size/.self$batch.size) + # Number of batches per epoch given the batch_size + .self$batch.per.epoch <- sum(batch_per_bucket) + .self$epoch <- .self$epoch + 1 + .self$batch <- 0 + + if (.self$shuffle) { + set.seed(.self$seed) + bucket_plan_names <- sample(rep(names(batch_per_bucket), times = batch_per_bucket)) + .self$bucket.plan <- ave(bucket_plan_names == bucket_plan_names, bucket_plan_names, + FUN = cumsum) + names(.self$bucket.plan) <- bucket_plan_names + ### Return first BucketID at reset for initialization of the model + .self$bucketID <- .self$bucket.plan[1] + + .self$buckets <- lapply(.self$buckets, function(x) { + shuffle_id <- sample(ncol(x$data)) + if (length(dim(x$label)) == 0) { + list(data = x$data[, shuffle_id], label = x$label[shuffle_id]) + } else { + list(data = x$data[, shuffle_id], label = x$label[, shuffle_id]) + } + }) + } else { + bucket_plan_names <- rep(names(batch_per_bucket), times = batch_per_bucket) + .self$bucket.plan <- ave(bucket_plan_names == bucket_plan_names, bucket_plan_names, + FUN = cumsum) + names(.self$bucket.plan) <- bucket_plan_names + } +}, iter.next = function() { + .self$batch <- .self$batch + 1 + .self$bucketID <- .self$bucket.plan[batch] + if (.self$batch > .self$batch.per.epoch) { + return(FALSE) + } else { + return(TRUE) + } +}, value = function() { + # bucketID is a named integer: the integer indicates the batch id for the given + # bucket (used to fetch appropriate samples within the bucket) the name is the a + # character containing the sequence length of the bucket (used to unroll the rnn + # to appropriate sequence length) + idx <- (.self$bucketID - 1) * (.self$batch.size) + (1:batch.size) + data <- .self$buckets[[names(.self$bucketID)]]$data[, idx, drop = F] + data_mask_array <- (!data == 0) + if (length(dim(.self$buckets[[names(.self$bucketID)]]$label)) == 0) { + label <- .self$buckets[[names(.self$bucketID)]]$label[idx] + } else { + label <- .self$buckets[[names(.self$bucketID)]]$label[, idx, drop = F] + } + return(list(data = mx.nd.array(data), data.mask.array = mx.nd.array(data_mask_array), + label = mx.nd.array(label))) +}, finalize = function() { +})) + +# +#' Create Bucket Iter +#' +#' @param buckets The data array. +#' @param batch.size The batch size used to pack the array. +#' @param data.mask.element The element to mask +#' @param shuffle Whether shuffle the data +#' @param seed The random seed +#' +#' @export +mx.io.bucket.iter <- function(buckets, batch.size, data.mask.element = 0, shuffle = FALSE, + seed = 123) { + return(BucketIter$new(buckets = buckets, batch.size = batch.size, data.mask.element = data.mask.element, + shuffle = shuffle, seed = seed)) +} diff --git a/example/rnn/bucket_R/rnn.R b/example/rnn/bucket_R/rnn.R new file mode 100644 index 000000000000..ea02b959a7e3 --- /dev/null +++ b/example/rnn/bucket_R/rnn.R @@ -0,0 +1,208 @@ +library(mxnet) + +source("lstm.cell.R") +source("gru.cell.R") + +# unrolled RNN network +rnn.unroll <- function(num.rnn.layer, seq.len, input.size, num.embed, num.hidden, + num.label, dropout = 0, ignore_label = 0, init.state = NULL, config, cell.type = "lstm", + output_last_state = F) { + embed.weight <- mx.symbol.Variable("embed.weight") + cls.weight <- mx.symbol.Variable("cls.weight") + cls.bias <- mx.symbol.Variable("cls.bias") + + param.cells <- lapply(1:num.rnn.layer, function(i) { + if (cell.type == "lstm") { + cell <- list(i2h.weight = mx.symbol.Variable(paste0("l", i, ".i2h.weight")), + i2h.bias = mx.symbol.Variable(paste0("l", i, ".i2h.bias")), h2h.weight = mx.symbol.Variable(paste0("l", + i, ".h2h.weight")), h2h.bias = mx.symbol.Variable(paste0("l", i, + ".h2h.bias"))) + } else if (cell.type == "gru") { + cell <- list(gates.i2h.weight = mx.symbol.Variable(paste0("l", i, ".gates.i2h.weight")), + gates.i2h.bias = mx.symbol.Variable(paste0("l", i, ".gates.i2h.bias")), + gates.h2h.weight = mx.symbol.Variable(paste0("l", i, ".gates.h2h.weight")), + gates.h2h.bias = mx.symbol.Variable(paste0("l", i, ".gates.h2h.bias")), + trans.i2h.weight = mx.symbol.Variable(paste0("l", i, ".trans.i2h.weight")), + trans.i2h.bias = mx.symbol.Variable(paste0("l", i, ".trans.i2h.bias")), + trans.h2h.weight = mx.symbol.Variable(paste0("l", i, ".trans.h2h.weight")), + trans.h2h.bias = mx.symbol.Variable(paste0("l", i, ".trans.h2h.bias"))) + } + return(cell) + }) + + # embeding layer + label <- mx.symbol.Variable("label") + data <- mx.symbol.Variable("data") + data_mask_array <- mx.symbol.Variable("data.mask.array") + data_mask_array <- mx.symbol.stop_gradient(data_mask_array, name = "data.mask.array") + + embed <- mx.symbol.Embedding(data = data, input_dim = input.size, weight = embed.weight, + output_dim = num.embed, name = "embed") + + wordvec <- mx.symbol.split(data = embed, axis = 1, num.outputs = seq.len, squeeze_axis = T) + data_mask_split <- mx.symbol.split(data = data_mask_array, axis = 1, num.outputs = seq.len, + squeeze_axis = T) + + last.hidden <- list() + last.states <- list() + decode <- list() + softmax <- list() + fc <- list() + + for (seqidx in 1:seq.len) { + hidden <- wordvec[[seqidx]] + + for (i in 1:num.rnn.layer) { + if (seqidx == 1) { + prev.state <- init.state[[i]] + } else { + prev.state <- last.states[[i]] + } + + if (cell.type == "lstm") { + cell.symbol <- lstm.cell + } else if (cell.type == "gru") { + cell.symbol <- gru.cell + } + + next.state <- cell.symbol(num.hidden = num.hidden, indata = hidden, prev.state = prev.state, + param = param.cells[[i]], seqidx = seqidx, layeridx = i, dropout = dropout, + data_masking = data_mask_split[[seqidx]]) + hidden <- next.state$h + # if (dropout > 0) hidden <- mx.symbol.Dropout(data=hidden, p=dropout) + last.states[[i]] <- next.state + } + + # Decoding + if (config == "one-to-one") { + last.hidden <- c(last.hidden, hidden) + } + } + + if (config == "seq-to-one") { + fc <- mx.symbol.FullyConnected(data = hidden, weight = cls.weight, bias = cls.bias, + num.hidden = num.label) + + loss <- mx.symbol.SoftmaxOutput(data = fc, name = "sm", label = label, ignore_label = ignore_label) + + } else if (config == "one-to-one") { + last.hidden_expand <- lapply(last.hidden, function(i) mx.symbol.expand_dims(i, + axis = 1)) + concat <- mx.symbol.concat(last.hidden_expand, num.args = seq.len, dim = 1) + reshape <- mx.symbol.Reshape(concat, shape = c(num.hidden, -1)) + + fc <- mx.symbol.FullyConnected(data = reshape, weight = cls.weight, bias = cls.bias, + num.hidden = num.label) + + label <- mx.symbol.reshape(data = label, shape = c(-1)) + loss <- mx.symbol.SoftmaxOutput(data = fc, name = "sm", label = label, ignore_label = ignore_label) + + } + + if (output_last_state) { + group <- mx.symbol.Group(c(unlist(last.states), loss)) + return(group) + } else { + return(loss) + } +} + +########################################### mx.rnn.buckets +mx.rnn.buckets <- function(train.data, eval.data = NULL, num.rnn.layer, num.hidden, + num.embed, num.label, input.size, ctx = NULL, num.round = 1, initializer = mx.init.uniform(0.01), + dropout = 0, config = "one-to-one", optimizer = "sgd", batch.end.callback = NULL, + epoch.end.callback = NULL, begin.round = 1, metric = mx.metric.rmse, cell.type = "lstm", + kvstore = "local", verbose = FALSE) { + + if (!train.data$iter.next()) { + train.data$reset() + if (!train.data$iter.next()) + stop("Empty train.data") + } + + if (!is.null(eval.data)) { + if (!eval.data$iter.next()) { + eval.data$reset() + if (!eval.data$iter.next()) + stop("Empty eval.data") + } + } + + if (is.null(ctx)) + ctx <- mx.ctx.default() + if (is.mx.context(ctx)) { + ctx <- list(ctx) + } + if (!is.list(ctx)) + stop("ctx must be mx.context or list of mx.context") + if (is.character(optimizer)) { + if (is.numeric(input.shape)) { + ndim <- length(input.shape) + batchsize <- input.shape[[ndim]] + } else { + ndim <- length(input.shape[[1]]) + batchsize <- input.shape[[1]][[ndim]] + } + optimizer <- mx.opt.create(optimizer, rescale.grad = (1/batchsize), ...) + } + + # get unrolled lstm symbol + sym_list <- sapply(train.data$bucket.names, function(x) { + rnn.unroll(num.rnn.layer = num.rnn.layer, num.hidden = num.hidden, seq.len = as.integer(x), + input.size = input.size, num.embed = num.embed, num.label = num.label, + dropout = dropout, cell.type = cell.type, config = config) + }, simplify = F, USE.NAMES = T) + + # setup lstm model + symbol <- sym_list[[names(train.data$bucketID)]] + + arg.names <- symbol$arguments + input.names <- c("data", "data.mask.array") + input.shape <- sapply(input.names, function(n) { + dim(train.data$value()[[n]]) + }, simplify = FALSE) + output.names <- "label" + output.shape <- sapply(output.names, function(n) { + dim(train.data$value()[[n]]) + }, simplify = FALSE) + + params <- mx.model.init.params(symbol, input.shape, output.shape, initializer, + mx.cpu()) + + kvstore <- mxnet:::mx.model.create.kvstore(kvstore, params$arg.params, length(ctx), + verbose = verbose) + + ### Execute training - rnn.model.R + model <- mx.model.train.rnn.buckets(sym_list = sym_list, input.shape = input.shape, + output.shape = output.shape, arg.params = params$arg.params, aux.params = params$aux.params, + optimizer = optimizer, train.data = train.data, eval.data = eval.data, verbose = verbose, + begin.round = begin.round, end.round = num.round, metric = metric, ctx = ctx, + batch.end.callback = batch.end.callback, epoch.end.callback = epoch.end.callback, + kvstore = kvstore) + + return(model) +} + + +# get the argument name of data and label +mx.model.check.arguments <- function(symbol) { + data <- NULL + label <- NULL + for (nm in arguments(symbol)) { + if (mx.util.str.endswith(nm, "data")) { + if (!is.null(data)) { + stop("Multiple fields contains suffix data") + } else { + data <- nm + } + } + if (mx.util.str.endswith(nm, "label")) { + if (!is.null(label)) { + stop("Multiple fields contains suffix label") + } else { + label <- nm + } + } + } + return(c(data, label)) +} diff --git a/example/rnn/bucket_R/rnn.infer.R b/example/rnn/bucket_R/rnn.infer.R new file mode 100644 index 000000000000..41488aac898e --- /dev/null +++ b/example/rnn/bucket_R/rnn.infer.R @@ -0,0 +1,79 @@ +library(mxnet) + +source("rnn.R") + +mx.rnn.infer.buckets <- function(infer_iter, model, config, ctx = mx.cpu(), output_last_state = FALSE, + init.state = NULL, cell.type = "lstm") { + ### Infer parameters from model + if (cell.type == "lstm") { + num.rnn.layer <- round((length(model$arg.params) - 3)/4) + num.hidden <- dim(model$arg.params$l1.h2h.weight)[1] + } else if (cell.type == "gru") { + num.rnn.layer <- round((length(model$arg.params) - 3)/8) + num.hidden <- dim(model$arg.params$l1.gates.h2h.weight)[1] + } + + input.size <- dim(model$arg.params$embed.weight)[2] + num.embed <- dim(model$arg.params$embed.weight)[1] + num.label <- dim(model$arg.params$cls.bias) + + ### Initialise the iterator + infer_iter$reset() + infer_iter$iter.next() + batch_size <- infer_iter$batch.size + + # get unrolled lstm symbol + sym_list <- sapply(infer_iter$bucket.names, function(x) { + rnn.unroll(num.rnn.layer = num.rnn.layer, num.hidden = num.hidden, seq.len = as.integer(x), + input.size = input.size, num.embed = num.embed, num.label = num.label, + config = config, dropout = 0, init.state = init.state, cell.type = cell.type, + output_last_state = output_last_state) + }, simplify = F, USE.NAMES = T) + + symbol <- sym_list[[names(infer_iter$bucketID)]] + + input.shape <- lapply(infer_iter$value(), dim) + input.shape <- input.shape[names(input.shape) %in% arguments(symbol)] + + infer_shapes <- symbol$infer.shape(input.shape) + arg.params <- model$arg.params + aux.params <- model$aux.params + + input.names <- names(input.shape) + arg.names <- names(arg.params) + + # Grad request + grad_req <- rep("null", length(symbol$arguments)) + + # Arg array order + update_names <- c(input.names, arg.names) + arg_update_idx <- match(symbol$arguments, update_names) + + # Initial input shapes - need to be adapted for multi-devices - divide highest + # dimension by device nb + s <- sapply(input.shape, function(shape) { + mx.nd.zeros(shape = shape, ctx = mx.cpu()) + }) + + train.execs <- mxnet:::mx.symbol.bind(symbol = symbol, arg.arrays = c(s, arg.params)[arg_update_idx], + aux.arrays = aux.params, ctx = ctx, grad.req = grad_req) + + packer <- mxnet:::mx.nd.arraypacker() + infer_iter$reset() + while (infer_iter$iter.next()) { + # Get input data slice + dlist <- infer_iter$value()[input.names] + + symbol <- sym_list[[names(infer_iter$bucketID)]] + + texec <- mxnet:::mx.symbol.bind(symbol = symbol, arg.arrays = c(dlist, train.execs$arg.arrays[arg.names])[arg_update_idx], + aux.arrays = train.execs$aux.arrays, ctx = ctx, grad.req = grad_req) + + mx.exec.forward(texec, is.train = FALSE) + + out.preds <- mx.nd.copyto(texec$ref.outputs[[1]], mx.cpu()) + packer$push(out.preds) + } + infer_iter$reset() + return(packer$get()) +} diff --git a/example/rnn/bucket_R/rnn.train.R b/example/rnn/bucket_R/rnn.train.R new file mode 100644 index 000000000000..b833b2b1d37a --- /dev/null +++ b/example/rnn/bucket_R/rnn.train.R @@ -0,0 +1,206 @@ +library(mxnet) + +source("rnn.R") + +# Internal function to do multiple device training on RNN +mx.model.train.rnn.buckets <- function(ctx, sym_list, arg.params, aux.params, input.shape, + output.shape, begin.round, end.round, optimizer, train.data, eval.data, metric, + epoch.end.callback, batch.end.callback, kvstore, verbose = TRUE) { + symbol <- sym_list[[names(train.data$bucketID)]] + + input.names <- names(input.shape) + output.names <- names(output.shape) + arg.names <- names(arg.params) + + ndevice <- length(ctx) + if (verbose) + message(paste0("Start training with ", ndevice, " devices")) + input_slice <- mxnet:::mx.model.slice.shape(input.shape, ndevice) + output_slice <- mxnet:::mx.model.slice.shape(output.shape, ndevice) + + + # Grad request + grad_req <- rep("write", length(symbol$arguments)) + # grad_null_idx <- match(c(input.names, output.names), symbol$arguments) + grad_null_idx <- match(input.names, symbol$arguments) + grad_req[grad_null_idx] <- "null" + + # Arg array order + update_names <- c(input.names, output.names, arg.names) + arg_update_idx <- match(symbol$arguments, update_names) + + train.execs <- lapply(1:ndevice, function(i) { + s <- sapply(append(input_slice[[i]]$shape, output_slice[[i]]$shape), function(shape) { + mx.nd.zeros(shape = shape, ctx = mx.cpu()) + }) + mxnet:::mx.symbol.bind(symbol = symbol, arg.arrays = c(s, arg.params)[arg_update_idx], + aux.arrays = aux.params, ctx = mx.cpu(), grad.req = grad_req) + }) + + # KVStore related stuffs + params.index <- as.integer(mxnet:::mx.util.filter.null(lapply(1:length(train.execs[[1]]$ref.grad.arrays), + function(k) { + if (!is.null(train.execs[[1]]$ref.grad.arrays[[k]])) k else NULL + }))) + update.on.kvstore <- FALSE + if (!is.null(kvstore) && kvstore$update.on.kvstore) { + update.on.kvstore <- TRUE + kvstore$set.optimizer(optimizer) + } else { + updaters <- lapply(1:ndevice, function(i) { + mx.opt.get.updater(optimizer, train.execs[[i]]$ref.arg.arrays) + }) + } + + if (!is.null(kvstore)) { + kvstore$init(params.index, train.execs[[1]]$ref.arg.arrays[params.index]) + } + + for (iteration in begin.round:end.round) { + nbatch <- 0 + if (!is.null(metric)) { + train.metric <- metric$init() + } + train.data$reset() + while (train.data$iter.next()) { + dlist <- train.data$value() #[input.names] + symbol <- sym_list[[names(train.data$bucketID)]] + slices <- lapply(1:ndevice, function(i) { + s <- input_slice[[i]] + ret <- sapply(names(dlist), function(n) { + mxnet:::mx.nd.slice(dlist[[n]], s$begin, s$end) + }) + return(ret) + }) + + train.execs <- lapply(1:ndevice, function(i) { + s <- slices[[i]] + mxnet:::mx.symbol.bind(symbol = symbol, arg.arrays = c(s, train.execs[[i]]$arg.arrays[arg.names])[arg_update_idx], + aux.arrays = train.execs[[i]]$aux.arrays, ctx = ctx[[i]], grad.req = grad_req) + }) + + for (texec in train.execs) { + mx.exec.forward(texec, is.train = TRUE) + } + + out.preds <- lapply(train.execs, function(texec) { + mx.nd.copyto(texec$ref.outputs[[1]], mx.cpu()) + }) + + for (texec in train.execs) { + mx.exec.backward(texec) + } + + if (!is.null(kvstore)) { + # push the gradient + kvstore$push(params.index, lapply(train.execs, function(texec) { + texec$ref.grad.arrays[params.index] + }), -params.index) + } + if (update.on.kvstore) { + # pull back weight + kvstore$pull(params.index, lapply(train.execs, function(texec) { + texec$ref.arg.arrays[params.index] + }), -params.index) + } else { + # pull back gradient sums + if (!is.null(kvstore)) { + kvstore$pull(params.index, lapply(train.execs, function(texec) { + texec$ref.grad.arrays[params.index] + }), -params.index) + } + arg.blocks <- lapply(1:ndevice, function(i) { + updaters[[i]](train.execs[[i]]$ref.arg.arrays, train.execs[[i]]$ref.grad.arrays) + }) + for (i in 1:ndevice) { + mx.exec.update.arg.arrays(train.execs[[i]], arg.blocks[[i]], skip.null = TRUE) + } + } + + # Update the evaluation metrics + if (!is.null(metric)) { + # train.metric <- metric$update(dlist$label, out.preds, train.metric) + for (i in 1:ndevice) { + train.metric <- metric$update(slices[[i]][[length(slices[[i]])]], + out.preds[[i]], train.metric) + } + } + + nbatch <- nbatch + 1 + + if (!is.null(batch.end.callback)) { + batch.end.callback(iteration, nbatch, environment()) + } + } + + if (!is.null(metric)) { + result <- metric$get(train.metric) + if (verbose) + message(paste0("[", iteration, "] Train-", result$name, "=", result$value)) + } + + if (!is.null(eval.data)) { + if (!is.null(metric)) { + eval.metric <- metric$init() + } + eval.data$reset() + while (eval.data$iter.next()) { + # Get input data slice + dlist <- eval.data$value() #[input.names] + symbol <- sym_list[[names(eval.data$bucketID)]] + slices <- lapply(1:ndevice, function(i) { + s <- input_slice[[i]] + ret <- sapply(names(dlist), function(n) { + mxnet:::mx.nd.slice(dlist[[n]], s$begin, s$end) + }) + return(ret) + }) + + + train.execs <- lapply(1:ndevice, function(i) { + s <- slices[[i]] + mxnet:::mx.symbol.bind(symbol = symbol, arg.arrays = c(s, train.execs[[i]]$arg.arrays[arg.names])[arg_update_idx], + aux.arrays = train.execs[[i]]$aux.arrays, ctx = ctx[[i]], grad.req = grad_req) + }) + + for (texec in train.execs) { + mx.exec.forward(texec, is.train = FALSE) + } + + # copy outputs to CPU + out.preds <- lapply(train.execs, function(texec) { + mx.nd.copyto(texec$ref.outputs[[1]], mx.cpu()) + }) + + if (!is.null(metric)) { + for (i in 1:ndevice) { + eval.metric <- metric$update(slices[[i]][[length(slices[[i]])]], + out.preds[[i]], eval.metric) + } + } + } + + if (!is.null(metric)) { + result <- metric$get(eval.metric) + if (verbose) { + message(paste0("[", iteration, "] Validation-", result$name, "=", + result$value)) + } + } + } else { + eval.metric <- NULL + } + # get the model out + model <- mxnet:::mx.model.extract.model(symbol, train.execs) + + epoch_continue <- TRUE + if (!is.null(epoch.end.callback)) { + epoch_continue <- epoch.end.callback(iteration, 0, environment(), verbose = verbose) + } + + if (!epoch_continue) { + break + } + } + return(model) +} diff --git a/example/rnn/cudnn_lstm_bucketing.py b/example/rnn/cudnn_lstm_bucketing.py index 35914dea8fac..e9c3237f26fc 100644 --- a/example/rnn/cudnn_lstm_bucketing.py +++ b/example/rnn/cudnn_lstm_bucketing.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import numpy as np import mxnet as mx import argparse @@ -37,11 +54,12 @@ help='the batch size.') parser.add_argument('--disp-batches', type=int, default=50, help='show progress for every n batches') -# When training a deep, complex model, it's recommended to stack fused RNN cells (one -# layer per cell) together instead of one with all layers. The reason is that fused RNN -# cells doesn't set gradients to be ready until the computation for the entire layer is -# completed. Breaking a multi-layer fused RNN cell into several one-layer ones allows -# gradients to be processed ealier. This reduces communication overhead, especially with +# When training a deep, complex model *on multiple GPUs* it's recommended to +# stack fused RNN cells (one layer per cell) together instead of one with all +# layers. The reason is that fused RNN cells don't set gradients to be ready +# until the computation for the entire layer is completed. Breaking a +# multi-layer fused RNN cell into several one-layer ones allows gradients to be +# processed ealier. This reduces communication overhead, especially with # multiple GPUs. parser.add_argument('--stack-rnn', default=False, help='stack fused RNN cells to reduce communication overhead') @@ -134,13 +152,13 @@ def sym_gen(seq_len): eval_metric = mx.metric.Perplexity(invalid_label), kvstore = args.kv_store, optimizer = args.optimizer, - optimizer_params = opt_params, + optimizer_params = opt_params, initializer = mx.init.Xavier(factor_type="in", magnitude=2.34), arg_params = arg_params, aux_params = aux_params, begin_epoch = args.load_epoch, num_epoch = args.num_epochs, - batch_end_callback = mx.callback.Speedometer(args.batch_size, args.disp_batches), + batch_end_callback = mx.callback.Speedometer(args.batch_size, args.disp_batches, auto_reset=False), epoch_end_callback = mx.rnn.do_rnn_checkpoint(cell, args.model_prefix, 1) if args.model_prefix else None) diff --git a/example/rnn/get_ptb_data.sh b/example/rnn/get_ptb_data.sh index 1ec009aa2f99..d2641cb32b81 100755 --- a/example/rnn/get_ptb_data.sh +++ b/example/rnn/get_ptb_data.sh @@ -1,5 +1,23 @@ #!/usr/bin/env bash +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + + RNN_DIR=$(cd `dirname $0`; pwd) DATA_DIR="${RNN_DIR}/data/" diff --git a/example/rnn/lstm_bucketing.py b/example/rnn/lstm_bucketing.py index 4bc934a01ad0..2e7bc65d437a 100644 --- a/example/rnn/lstm_bucketing.py +++ b/example/rnn/lstm_bucketing.py @@ -1,6 +1,24 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import numpy as np import mxnet as mx import argparse +import os parser = argparse.ArgumentParser(description="Train RNN on Penn Tree Bank", formatter_class=argparse.ArgumentDefaultsHelpFormatter) @@ -32,6 +50,8 @@ def tokenize_text(fname, vocab=None, invalid_label=-1, start_label=0): + if not os.path.isfile(fname): + raise IOError("Please use get_ptb_data.sh to download requied file (data/ptb.train.txt)") lines = open(fname).readlines() lines = [filter(None, i.split(' ')) for i in lines] sentences, vocab = mx.rnn.encode_sentences(lines, vocab=vocab, invalid_label=invalid_label, @@ -104,4 +124,4 @@ def sym_gen(seq_len): 'wd': args.wd }, initializer = mx.init.Xavier(factor_type="in", magnitude=2.34), num_epoch = args.num_epochs, - batch_end_callback = mx.callback.Speedometer(args.batch_size, args.disp_batches)) + batch_end_callback = mx.callback.Speedometer(args.batch_size, args.disp_batches, auto_reset=False)) diff --git a/example/rnn/old/bucket_io.py b/example/rnn/old/bucket_io.py index f515e348c4f4..21f96ef196fa 100644 --- a/example/rnn/old/bucket_io.py +++ b/example/rnn/old/bucket_io.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # pylint: disable=C0111,too-many-arguments,too-many-instance-attributes,too-many-locals,redefined-outer-name,fixme # pylint: disable=superfluous-parens, no-member, invalid-name from __future__ import print_function @@ -58,7 +75,7 @@ def default_gen_buckets(sentences, batch_size, the_vocab): tl = 0 buckets = [] - for l, n in len_dict.items(): # TODO: There are better heuristic ways to do this + for l, n in len_dict.items(): # TODO: There are better heuristic ways to do this if n + tl >= batch_size: buckets.append(l) tl = 0 @@ -210,7 +227,7 @@ def make_data_iter_plan(self): self.data_buffer.append(data) if self.model_parallel: - # Transpose data if model parallel + # Transpose data if model parallel for i in range(len(self.data)): bucket_data = self.data[i] self.data[i] = np.transpose(bucket_data) @@ -222,8 +239,8 @@ def __iter__(self): i_idx = self.bucket_curr_idx[i_bucket] idx = self.bucket_idx_all[i_bucket][i_idx:i_idx+self.batch_size] self.bucket_curr_idx[i_bucket] += self.batch_size - - # Model parallelism + + # Model parallelism if self.model_parallel: if self.data[i_bucket][:, idx].shape[1] == 0: print("WARNING: detected shape " + str(self.data[i_bucket][:, idx].shape)) @@ -231,7 +248,7 @@ def __iter__(self): data[:] = self.data[i_bucket][:, idx] data_batch = ModelParallelBatch(data, self.buckets[i_bucket]) yield data_batch - + # Data parallelism else: init_state_names = [x[0] for x in self.init_states] @@ -239,7 +256,7 @@ def __iter__(self): for sentence in data: assert len(sentence) == self.buckets[i_bucket] - + label = self.label_buffer[i_bucket] label[:, :-1] = data[:, 1:] label[:, -1] = 0 @@ -255,4 +272,4 @@ def __iter__(self): def reset(self): - self.bucket_curr_idx = [0 for x in self.data] \ No newline at end of file + self.bucket_curr_idx = [0 for x in self.data] diff --git a/example/rnn/old/get_ptb_data.sh b/example/rnn/old/get_ptb_data.sh index 1ec009aa2f99..d2641cb32b81 100755 --- a/example/rnn/old/get_ptb_data.sh +++ b/example/rnn/old/get_ptb_data.sh @@ -1,5 +1,23 @@ #!/usr/bin/env bash +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + + RNN_DIR=$(cd `dirname $0`; pwd) DATA_DIR="${RNN_DIR}/data/" diff --git a/example/rnn/old/gru.py b/example/rnn/old/gru.py index 5b5138bd0388..e6ec0952334b 100644 --- a/example/rnn/old/gru.py +++ b/example/rnn/old/gru.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # pylint: disable=C0111,too-many-arguments,too-many-instance-attributes,too-many-locals,redefined-outer-name,fixme # pylint: disable=superfluous-parens, no-member, invalid-name import sys diff --git a/example/rnn/old/gru_bucketing.py b/example/rnn/old/gru_bucketing.py index 859d449121a2..226018c02685 100644 --- a/example/rnn/old/gru_bucketing.py +++ b/example/rnn/old/gru_bucketing.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # pylint: disable=C0111,too-many-arguments,too-many-instance-attributes,too-many-locals,redefined-outer-name,fixme # pylint: disable=superfluous-parens, no-member, invalid-name import sys diff --git a/example/rnn/old/lstm.py b/example/rnn/old/lstm.py index d67b0dbe5790..84509a3daf3d 100644 --- a/example/rnn/old/lstm.py +++ b/example/rnn/old/lstm.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # pylint:skip-file import sys sys.path.insert(0, "../../python") diff --git a/example/rnn/old/lstm_bucketing.py b/example/rnn/old/lstm_bucketing.py index 78fa4f89480e..3e3494776dc3 100644 --- a/example/rnn/old/lstm_bucketing.py +++ b/example/rnn/old/lstm_bucketing.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # pylint: disable=C0111,too-many-arguments,too-many-instance-attributes,too-many-locals,redefined-outer-name,fixme # pylint: disable=superfluous-parens, no-member, invalid-name import sys diff --git a/example/rnn/old/rnn.py b/example/rnn/old/rnn.py index 136c40380b53..fe7bdbd922fa 100644 --- a/example/rnn/old/rnn.py +++ b/example/rnn/old/rnn.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import sys sys.path.insert(0, "../../python/") import mxnet as mx diff --git a/example/rnn/old/rnn_cell_demo.py b/example/rnn/old/rnn_cell_demo.py index 2c798e2c9c13..3223e936c37f 100644 --- a/example/rnn/old/rnn_cell_demo.py +++ b/example/rnn/old/rnn_cell_demo.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + """A simple demo of new RNN cell with PTB language model.""" import os @@ -87,12 +104,12 @@ def sym_gen(seq_len): # RNN cell takes input of shape (time, batch, feature) rnn = mx.sym.RNN(data=embed_tm, state_size=num_hidden, num_layers=num_lstm_layer, mode='lstm', - name='LSTM', + name='LSTM', # The following params can be omitted # provided we do not need to apply the # workarounds mentioned above state=rnn_h_init, - state_cell=rnn_c_init, + state_cell=rnn_c_init, parameters=rnn_params) # the RNN cell output is of shape (time, batch, dim) diff --git a/example/rnn/old/rnn_model.py b/example/rnn/old/rnn_model.py index 2135abd357c9..6fe0d22ef3c9 100644 --- a/example/rnn/old/rnn_model.py +++ b/example/rnn/old/rnn_model.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # pylint: disable=C0111,too-many-arguments,too-many-instance-attributes,too-many-locals,redefined-outer-name,fixme # pylint: disable=superfluous-parens, no-member, invalid-name diff --git a/example/speech-demo/config_util.py b/example/speech-demo/config_util.py index 9e2ecc45abd7..6fd6a50a19fb 100644 --- a/example/speech-demo/config_util.py +++ b/example/speech-demo/config_util.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import re import os import sys diff --git a/example/speech-demo/decode_mxnet.py b/example/speech-demo/decode_mxnet.py index 1826e1265de7..deb9c30d79c7 100644 --- a/example/speech-demo/decode_mxnet.py +++ b/example/speech-demo/decode_mxnet.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import re import sys sys.path.insert(0, "../../python") @@ -80,7 +97,7 @@ def prepare_data(args): num_epoch = args.config.getint('train', 'num_epoch') model_name = get_checkpoint_path(args) logging.basicConfig(level=logging.DEBUG, format='%(asctime)-15s %(message)s') - + # load the model sym, arg_params, aux_params = mx.model.load_checkpoint(model_name, num_epoch) @@ -89,7 +106,7 @@ def prepare_data(args): buckets = list(map(int, re.split(r'\W+', buckets))) data_test = BucketSentenceIter(test_sets, buckets, batch_size, init_states, feat_dim=feat_dim, has_label=False) def sym_gen(seq_len): - sym = lstm_unroll(num_lstm_layer, seq_len, feat_dim, num_hidden=num_hidden, + sym = lstm_unroll(num_lstm_layer, seq_len, feat_dim, num_hidden=num_hidden, num_label=label_dim, take_softmax=True, num_hidden_proj=num_hidden_proj) data_names = ['data'] + state_names label_names = ['softmax_label'] @@ -102,7 +119,7 @@ def sym_gen(seq_len): data_test = SimpleIter(test_sets, batch_size, init_states, feat_dim=feat_dim, label_dim=label_dim, label_mean_sets=label_mean_sets, has_label=False) def sym_gen(seq_len): - sym = lstm_unroll(num_lstm_layer, seq_len, feat_dim, num_hidden=num_hidden, + sym = lstm_unroll(num_lstm_layer, seq_len, feat_dim, num_hidden=num_hidden, num_label=label_dim, take_softmax=False, num_hidden_proj=num_hidden_proj) data_names = ['data'] + state_names label_names = [] @@ -127,7 +144,7 @@ def sym_gen(seq_len): # set the parameters module.bind(data_shapes=data_test.provide_data, label_shapes=None, for_training=False) module.set_params(arg_params=arg_params, aux_params=aux_params) - + kaldiWriter = KaldiWriteOut(None, out_file) kaldiWriter.open_or_fd() for preds, i_batch, batch in module.iter_predict(data_test): @@ -142,7 +159,7 @@ def sym_gen(seq_len): elif decoding_method == METHOD_SIMPLE: for (ind, utt) in enumerate(batch.utt_id): if utt != "GAP_UTT": - posteriors = posteriors[:batch.utt_len,1:] - np.log(data_test.label_mean[1:]).T + posteriors = posteriors[:batch.utt_len[0],1:] - np.log(data_test.label_mean[1:]).T kaldiWriter.write(utt, posteriors) else: outputs = module.get_outputs() diff --git a/example/speech-demo/decode_mxnet.sh b/example/speech-demo/decode_mxnet.sh index e5209b8c76d5..d300d0e91c40 100755 --- a/example/speech-demo/decode_mxnet.sh +++ b/example/speech-demo/decode_mxnet.sh @@ -1,14 +1,32 @@ #!/bin/bash +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + + # Copyright 2012-2013 Karel Vesely, Daniel Povey # 2015 Yu Zhang # Apache 2.0 -# Begin configuration section. +# Begin configuration section. nnet= # Optionally pre-select network to use for getting state-likelihoods feature_transform= # Optionally pre-select feature transform (in front of nnet) model= # Optionally pre-select transition model -class_frame_counts= # Optionally pre-select class-counts used to compute PDF priors +class_frame_counts= # Optionally pre-select class-counts used to compute PDF priors stage=0 # stage=1 skips lattice generation nj=4 @@ -44,7 +62,7 @@ mkdir -p $dir/log echo $nj > $dir/num_jobs if [ -z "$model" ]; then # if --model was not specified on the command line... - if [ -z $iter ]; then model=$srcdir/final.mdl; + if [ -z $iter ]; then model=$srcdir/final.mdl; else model=$srcdir/$iter.mdl; fi fi diff --git a/example/speech-demo/io_func/convert2kaldi.py b/example/speech-demo/io_func/convert2kaldi.py index ffa8c4d6b3a9..eac8ee695a9b 100644 --- a/example/speech-demo/io_func/convert2kaldi.py +++ b/example/speech-demo/io_func/convert2kaldi.py @@ -1,4 +1,21 @@ -# Copyright 2013 Yajie Miao Carnegie Mellon University +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +# Copyright 2013 Yajie Miao Carnegie Mellon University # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -28,7 +45,7 @@ def _nnet2kaldi(nnet_spec, set_layer_num = -1, filein='nnet.in', _nnet2kaldi_main(nnet_spec, set_layer_num=set_layer_num, filein=filein, fileout=fileout, activation=activation, withfinal=withfinal, maxout=False) -def _nnet2kaldi_maxout(nnet_spec, pool_size = 1, set_layer_num = -1, +def _nnet2kaldi_maxout(nnet_spec, pool_size = 1, set_layer_num = -1, filein='nnet.in', fileout='nnet.out', activation='sigmoid', withfinal=True): _nnet2kaldi_main(nnet_spec, set_layer_num=set_layer_num, filein=filein, fileout=fileout, activation=activation, withfinal=withfinal, @@ -110,4 +127,4 @@ def _nnet2kaldi_main(nnet_spec, set_layer_num = -1, filein='nnet.in', fout.write('[ ' + b_layer.strip() + ' ]' + '\n') fout.write(' ' + str(output_size) + ' ' + str(output_size) + '\n') - fout.close(); \ No newline at end of file + fout.close(); diff --git a/example/speech-demo/io_func/feat_io.py b/example/speech-demo/io_func/feat_io.py index 83d417eb0ffb..6a7e424d1e65 100644 --- a/example/speech-demo/io_func/feat_io.py +++ b/example/speech-demo/io_func/feat_io.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + from __future__ import print_function import os import sys diff --git a/example/speech-demo/io_func/feat_readers/common.py b/example/speech-demo/io_func/feat_readers/common.py index a7b6413082ce..742d3e25a1c7 100644 --- a/example/speech-demo/io_func/feat_readers/common.py +++ b/example/speech-demo/io_func/feat_readers/common.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import numpy import os @@ -55,4 +72,4 @@ def getReader(fileformat, featureFile, labelFile): return reader_kaldi.kaldiReader(featureFile, labelFile) else: msg = "Error: Specified format '{}' is not supported".format(fileformat) - raise Exception(msg) \ No newline at end of file + raise Exception(msg) diff --git a/example/speech-demo/io_func/feat_readers/reader_atrack.py b/example/speech-demo/io_func/feat_readers/reader_atrack.py index 0bf1deeac95e..e8db0fd14da2 100644 --- a/example/speech-demo/io_func/feat_readers/reader_atrack.py +++ b/example/speech-demo/io_func/feat_readers/reader_atrack.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import numpy import numpy as num import stats @@ -30,11 +47,11 @@ def Read(self): -1.677172 -1076449904 -1867655489 -1.562828 -1077409088 -1073035073 """ - + f = open(self.featureFile, "rb") header = num.fromfile(f, dtype=num.dtype('>i4'), count=7) self.checkHeader(header) - + frameSize = header[1] numSamples = header[2] @@ -46,4 +63,4 @@ def Read(self): self._markDone() - return a, ReadLabel(self.labelFile) \ No newline at end of file + return a, ReadLabel(self.labelFile) diff --git a/example/speech-demo/io_func/feat_readers/reader_bvec.py b/example/speech-demo/io_func/feat_readers/reader_bvec.py index ac68bf477c05..3a0f745b92ea 100644 --- a/example/speech-demo/io_func/feat_readers/reader_bvec.py +++ b/example/speech-demo/io_func/feat_readers/reader_bvec.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + from __future__ import print_function import struct import array @@ -22,7 +39,7 @@ def Read(self): print('Num samples = {}'.format(numSamples)) print('dim = {}'.format(dim)) - dt = numpy.dtype([('sample',(numpy.float32,dim))]) + dt = numpy.dtype([('sample',(numpy.float32,dim))]) samples = numpy.fromfile(f,dt.newbyteorder('>'),count=numSamples) self._markDone() diff --git a/example/speech-demo/io_func/feat_readers/reader_htk.py b/example/speech-demo/io_func/feat_readers/reader_htk.py index b04d6f3e52ee..dca24d9bd35c 100644 --- a/example/speech-demo/io_func/feat_readers/reader_htk.py +++ b/example/speech-demo/io_func/feat_readers/reader_htk.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import numpy import stats from common import * @@ -24,7 +41,7 @@ def Read(self): # print 'Sample period = {}'.format(sampPeriod) # print 'Sample size = {}'.format(sampSize) # print 'Sample kind = {}'.format(sampKind) - dt = numpy.dtype([('sample',(numpy.float32,sampSize/4))]) + dt = numpy.dtype([('sample',(numpy.float32,sampSize/4))]) samples = numpy.fromfile(f,dt.newbyteorder('>' if self.byteOrder==ByteOrder.BigEndian else '<'),count=numSamples) self._markDone() @@ -33,5 +50,5 @@ def Read(self): labels = None else: labels = ReadLabel(self.labelFile) - + return samples[:]['sample'], labels diff --git a/example/speech-demo/io_func/feat_readers/reader_kaldi.py b/example/speech-demo/io_func/feat_readers/reader_kaldi.py index bad6a9d3e989..345934a91790 100644 --- a/example/speech-demo/io_func/feat_readers/reader_kaldi.py +++ b/example/speech-demo/io_func/feat_readers/reader_kaldi.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + from common import * import random @@ -95,7 +112,7 @@ def Read(self): feat_rows = kaldi.MatrixF_NumRows(feat_value) feat_cols = kaldi.MatrixF_NumCols(feat_value) feat_data = kaldi.MatrixF_Data(feat_value) - + # never use numpy.ndarray(buf=) or numpy.ctypeslib.as_array # because you don't know if Python or C owns buffer # (even if you numpy.copy() resulting array) @@ -114,7 +131,7 @@ def Read(self): if self.targets_rspecifier is not None: if kaldi.RAPReader_HasKey(self.targets_reader, utt): tgt_value = kaldi.RAPReader_Value(self.targets_reader, utt) - + tgts = numpy.empty((feat_rows,), dtype=numpy.int32) # ok to use memmove because this is 1-dimensional array I made in C (no stride) tgts_numpy_ptr = ctypes.cast(tgts.ctypes.data, c_int_ptr) @@ -125,7 +142,7 @@ def Read(self): tgts = None else: tgts = None - + kaldi.SBFMReader_Next(self.feature_reader) #print "FEATS:", feats[0:5][0:5] diff --git a/example/speech-demo/io_func/feat_readers/stats.py b/example/speech-demo/io_func/feat_readers/stats.py index 70033ebae456..a2c847359dc9 100644 --- a/example/speech-demo/io_func/feat_readers/stats.py +++ b/example/speech-demo/io_func/feat_readers/stats.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + from __future__ import print_function import numpy diff --git a/example/speech-demo/io_func/feat_readers/writer_kaldi.py b/example/speech-demo/io_func/feat_readers/writer_kaldi.py index f331160a4f3d..0f8fb938087f 100644 --- a/example/speech-demo/io_func/feat_readers/writer_kaldi.py +++ b/example/speech-demo/io_func/feat_readers/writer_kaldi.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import sys import numpy import struct diff --git a/example/speech-demo/io_func/info.py b/example/speech-demo/io_func/info.py index 64bb77d49736..eaf95ab983bb 100644 --- a/example/speech-demo/io_func/info.py +++ b/example/speech-demo/io_func/info.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import os _mydir = os.path.dirname(__file__) or '.' diff --git a/example/speech-demo/io_func/kaldi_parser.py b/example/speech-demo/io_func/kaldi_parser.py index 8b1d67893b79..10a373d7138f 100644 --- a/example/speech-demo/io_func/kaldi_parser.py +++ b/example/speech-demo/io_func/kaldi_parser.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + from __future__ import print_function import struct import numpy as num @@ -199,4 +216,4 @@ def file2nnet_binary(filename): fout.write(' ' + str(int(layers[i + 1])) + ' ' + str(output_size) + '\n') else: fout.write(' ' + str(output_size) + ' ' + str(output_size) + '\n') -""" \ No newline at end of file +""" diff --git a/example/speech-demo/io_func/model_io.py b/example/speech-demo/io_func/model_io.py index 18496634e462..8b6e0436c22b 100755 --- a/example/speech-demo/io_func/model_io.py +++ b/example/speech-demo/io_func/model_io.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + from __future__ import print_function import numpy as np import os @@ -59,7 +76,7 @@ def _nnet2file(layers, set_layer_num = -1, filename='nnet.out', activation='sigm nnet_dict[dict_a] = array_2_string(layers[i].delta_params[0].get_value()) dict_a = str(i) + ' ' + activation + ' db' nnet_dict[dict_a] = array_2_string(layers[i].delta_params[1].get_value()) - + if layers[i].kahan: logger.info("Loading hidden kahan") dict_a = str(i) + ' ' + activation + ' W_carry' @@ -71,9 +88,9 @@ def _nnet2file(layers, set_layer_num = -1, filename='nnet.out', activation='sigm #dict_a = str(i) + ' ' + activation + ' db_carry' #nnet_dict[dict_a] = array_2_string(layers[i].delta_params_carry[1].get_value()) - if withfinal: + if withfinal: logger.info("Saving final layer ") - + dict_a = 'logreg W' nnet_dict[dict_a] = array_2_string((1.0 - factor[-1]) * layers[-1].params[0].get_value()) dict_a = 'logreg b' @@ -96,7 +113,7 @@ def _nnet2file(layers, set_layer_num = -1, filename='nnet.out', activation='sigm #dict_a = 'logreg db_carry' #nnet_dict[dict_a] = array_2_string(layers[-1].delta_params_carry[1].get_value()) - utils.pickle_save(nnet_dict, filename) + utils.pickle_save(nnet_dict, filename) def zero(x): x.set_value(np.zeros_like(x.get_value(borrow=True), dtype=theano.config.floatX)) @@ -147,14 +164,14 @@ def _file2nnet(layers, set_layer_num = -1, filename='nnet.in', activation='sigmo dict_key = str(i) + ' ' + activation + ' W' layers[i].params[0].set_value(factors[i] * factor * np.asarray(string_2_array(nnet_dict[dict_key]), dtype=theano.config.floatX)) - dict_key = str(i) + ' ' + activation + ' b' + dict_key = str(i) + ' ' + activation + ' b' layers[i].params[1].set_value(np.asarray(string_2_array(nnet_dict[dict_key]), dtype=theano.config.floatX)) if gradients: dict_key = str(i) + ' ' + activation + ' dW' layers[i].delta_params[0].set_value(np.asarray(string_2_array(nnet_dict[dict_key]), dtype=theano.config.floatX)) - dict_key = str(i) + ' ' + activation + ' db' - layers[i].delta_params[1].set_value(np.asarray(string_2_array(nnet_dict[dict_key]), dtype=theano.config.floatX)) + dict_key = str(i) + ' ' + activation + ' db' + layers[i].delta_params[1].set_value(np.asarray(string_2_array(nnet_dict[dict_key]), dtype=theano.config.floatX)) else: zero(layers[i].delta_params[0]) zero(layers[i].delta_params[1]) @@ -164,12 +181,12 @@ def _file2nnet(layers, set_layer_num = -1, filename='nnet.in', activation='sigmo logger.info("Loading hidden kahan") dict_key = str(i) + ' ' + activation + ' W_carry' layers[i].params_carry[0].set_value(np.asarray(string_2_array(nnet_dict[dict_key]), dtype=theano.config.floatX)) - dict_key = str(i) + ' ' + activation + ' b_carry' - layers[i].params_carry[1].set_value(np.asarray(string_2_array(nnet_dict[dict_key]), dtype=theano.config.floatX)) + dict_key = str(i) + ' ' + activation + ' b_carry' + layers[i].params_carry[1].set_value(np.asarray(string_2_array(nnet_dict[dict_key]), dtype=theano.config.floatX)) #dict_key = str(i) + ' ' + activation + ' dW_carry' #layers[i].delta_params_carry[0].set_value(np.asarray(string_2_array(nnet_dict[dict_key]), dtype=theano.config.floatX)) - #dict_key = str(i) + ' ' + activation + ' db_carry' - #layers[i].delta_params_carry[1].set_value(np.asarray(string_2_array(nnet_dict[dict_key]), dtype=theano.config.floatX)) + #dict_key = str(i) + ' ' + activation + ' db_carry' + #layers[i].delta_params_carry[1].set_value(np.asarray(string_2_array(nnet_dict[dict_key]), dtype=theano.config.floatX)) if layers[i].sync: layers[i].params_sync[0].set_value(layers[i].params[0].get_value().astype('float32')) @@ -197,12 +214,12 @@ def _file2nnet(layers, set_layer_num = -1, filename='nnet.in', activation='sigmo logger.info("Loading softmax kahan") dict_key = 'logreg W_carry' layers[-1].params_carry[0].set_value(np.asarray(string_2_array(nnet_dict[dict_key]), dtype=theano.config.floatX)) - dict_key = 'logreg b_carry' - layers[-1].params_carry[1].set_value(np.asarray(string_2_array(nnet_dict[dict_key]), dtype=theano.config.floatX)) + dict_key = 'logreg b_carry' + layers[-1].params_carry[1].set_value(np.asarray(string_2_array(nnet_dict[dict_key]), dtype=theano.config.floatX)) #dict_key = 'logreg dW_carry' #layers[-1].delta_params_carry[0].set_value(np.asarray(string_2_array(nnet_dict[dict_key]), dtype=theano.config.floatX)) - #dict_key = 'logreg db_carry' - #layers[-1].delta_params_carry[1].set_value(np.asarray(string_2_array(nnet_dict[dict_key]), dtype=theano.config.floatX)) + #dict_key = 'logreg db_carry' + #layers[-1].delta_params_carry[1].set_value(np.asarray(string_2_array(nnet_dict[dict_key]), dtype=theano.config.floatX)) if layers[-1].sync: layers[-1].params_sync[0].set_value(layers[-1].params[0].get_value().astype('float32')) @@ -220,10 +237,10 @@ def _cnn2file(conv_layers, filename='nnet.out', activation='sigmoid', withfinal= for i in xrange(n_layers): conv_layer = conv_layers[i] filter_shape = conv_layer.filter_shape - + for next_X in xrange(filter_shape[0]): for this_X in xrange(filter_shape[1]): - dict_a = 'W ' + str(i) + ' ' + str(next_X) + ' ' + str(this_X) + dict_a = 'W ' + str(i) + ' ' + str(next_X) + ' ' + str(this_X) if i == 0: nnet_dict[dict_a] = array_2_string(input_factor * (conv_layer.W.get_value())[next_X, this_X]) else: @@ -231,7 +248,7 @@ def _cnn2file(conv_layers, filename='nnet.out', activation='sigmoid', withfinal= dict_a = 'b ' + str(i) nnet_dict[dict_a] = array_2_string(conv_layer.b.get_value()) - + with open(filename, 'wb') as fp: json.dump(nnet_dict, fp, indent=2, sort_keys = True) fp.flush() @@ -252,7 +269,7 @@ def _file2cnn(conv_layers, filename='nnet.in', activation='sigmoid', withfinal=T dict_a = 'W ' + str(i) + ' ' + str(next_X) + ' ' + str(this_X) W_array[next_X, this_X, :, :] = factor * np.asarray(string_2_array(nnet_dict[dict_a])) - conv_layer.W.set_value(W_array) + conv_layer.W.set_value(W_array) dict_a = 'b ' + str(i) - conv_layer.b.set_value(np.asarray(string_2_array(nnet_dict[dict_a]), dtype=theano.config.floatX)) + conv_layer.b.set_value(np.asarray(string_2_array(nnet_dict[dict_a]), dtype=theano.config.floatX)) diff --git a/example/speech-demo/io_func/regr_feat_io.py b/example/speech-demo/io_func/regr_feat_io.py index 2f3c4ec9ffd0..a1737bf9ab32 100644 --- a/example/speech-demo/io_func/regr_feat_io.py +++ b/example/speech-demo/io_func/regr_feat_io.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import os import sys import random @@ -36,7 +53,7 @@ def read_by_matrix(self): def make_shared(self): self.input.make_shared() self.output.make_shared() - + def get_shared(self): iret = self.input.get_shared() oret = self.output.get_shared() @@ -56,13 +73,13 @@ def current_utt_id(self): def load_next_block(self): a = self.input.load_next_block() - b = self.output.load_next_block() + b = self.output.load_next_block() assert(a == b) return a def get_state(self): a = self.input.get_state() - b = self.output.get_state() + b = self.output.get_state() assert(a[0] == b[0]) assert(a[2] == b[2]) assert(a[3] == b[3]) @@ -72,4 +89,4 @@ def get_state(self): def set_state(self, state): self.input.set_state(state) - self.output.set_state(state) + self.output.set_state(state) diff --git a/example/speech-demo/io_func/utils.py b/example/speech-demo/io_func/utils.py index 513261ea6f4f..4ba8496c7fb7 100644 --- a/example/speech-demo/io_func/utils.py +++ b/example/speech-demo/io_func/utils.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import sys, subprocess, pickle, os, json, logging, socket import logging.config import datetime diff --git a/example/speech-demo/io_util.py b/example/speech-demo/io_util.py index 926f20fbb58d..e5bd74cb6fa7 100644 --- a/example/speech-demo/io_util.py +++ b/example/speech-demo/io_util.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import mxnet as mx import numpy as np import sys @@ -310,7 +327,7 @@ def __init__(self, train_sets, batch_size, init_states, truncate_len=20, delay=5 self.data = [mx.nd.zeros((batch_size, truncate_len, feat_dim))] if has_label: self.label = [mx.nd.zeros((batch_size, truncate_len))] - + self.init_state_names = [x[0] for x in init_states] self.init_state_arrays = [mx.nd.zeros(x[1]) for x in init_states] @@ -368,14 +385,14 @@ def __iter__(self): next_utt_idx = self.batch_size is_pad = [False] * self.batch_size pad = 0 - + if self.time_major: np_data_buffer = np.zeros((self.truncate_len, self.batch_size, self.feat_dim)) np_label_buffer = np.zeros((self.truncate_len, self.batch_size)) else: np_data_buffer = np.zeros((self.batch_size, self.truncate_len, self.feat_dim)) np_label_buffer = np.zeros((self.batch_size, self.truncate_len)) - + utt_id_buffer = [None] * self.batch_size data_names = [self.data_name] + self.init_state_names @@ -432,7 +449,7 @@ def __iter__(self): else: np_data_buffer[i, :n_take, :] = fea_utt[idx_take] np_label_buffer[i, :n_take] = self.labels[idx][idx_take] - + if n_take < self.truncate_len: if self.time_major: np_data_buffer[n_take:, i, :] = 0 @@ -440,7 +457,7 @@ def __iter__(self): else: np_data_buffer[i, n_take:, :] = 0 np_label_buffer[i, n_take:] = 0 - + effective_sample_count -= self.truncate_len - n_take utt_inside_idx[i] += n_take @@ -450,11 +467,11 @@ def __iter__(self): if pad == self.batch_size: # finished all the senteces break - + self.data[0][:] = np_data_buffer self.label[0][:] = np_label_buffer - - data_batch = SimpleBatch(data_names, + + data_batch = SimpleBatch(data_names, self.data + self.init_state_arrays, label_names, self.label, bucket_key=None, utt_id=utt_id_buffer, diff --git a/example/speech-demo/lstm_proj.py b/example/speech-demo/lstm_proj.py index ae2271c800b7..a27518c604b0 100644 --- a/example/speech-demo/lstm_proj.py +++ b/example/speech-demo/lstm_proj.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # pylint:skip-file import mxnet as mx import numpy as np @@ -17,7 +34,7 @@ def lstm(num_hidden, indata, prev_state, param, seqidx, layeridx, dropout=0., nu """LSTM Cell symbol""" if dropout > 0.: indata = mx.sym.Dropout(data=indata, p=dropout) - + i2h = mx.sym.FullyConnected(data=indata, weight=param.i2h_weight, bias=param.i2h_bias, @@ -115,7 +132,7 @@ def lstm_unroll(num_lstm_layer, seq_len, input_size, pred = mx.sym.Reshape(pred, shape=(-1, num_label)) label = mx.sym.Reshape(label, shape=(-1,)) if take_softmax: - sm = mx.sym.SoftmaxOutput(data=pred, label=label, ignore_label=0, + sm = mx.sym.SoftmaxOutput(data=pred, label=label, ignore_label=0, use_ignore=True, name='softmax') else: sm = pred diff --git a/example/speech-demo/make_stats.py b/example/speech-demo/make_stats.py index 440f514729d0..64991db20ad8 100644 --- a/example/speech-demo/make_stats.py +++ b/example/speech-demo/make_stats.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import re import sys sys.path.insert(0, "../../python") diff --git a/example/speech-demo/python_wrap/ctypes.cc b/example/speech-demo/python_wrap/ctypes.cc index cd77d471ba08..a2c79468ed30 100644 --- a/example/speech-demo/python_wrap/ctypes.cc +++ b/example/speech-demo/python_wrap/ctypes.cc @@ -1,3 +1,22 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 "util/table-types.h" @@ -143,7 +162,7 @@ extern "C" { RAPReader* RAPReader_new_char(char * rspecifier) { return new RAPReader(rspecifier); - } + } //bool HasKey (const std::string &key) int RAPReader_HasKey(RAPReader* r, char * key) { @@ -178,7 +197,7 @@ extern "C" { } vals[row] = pair.first; } - + return vals; } diff --git a/example/speech-demo/python_wrap/example_usage/example.py b/example/speech-demo/python_wrap/example_usage/example.py index 766bb6ebb3b5..d930327f196d 100644 --- a/example/speech-demo/python_wrap/example_usage/example.py +++ b/example/speech-demo/python_wrap/example_usage/example.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + from __future__ import print_function import ctypes import numpy @@ -53,7 +70,7 @@ def decl(f, restype, argtypes): print("-------- Kaldi SBFMReader and MatrixF class example --------") reader = kaldi.SBFMReader_new_char("scp:data.scp") - + # data.scp has exactly one utterance, assert it's there assert(not kaldi.SBFMReader_Done(reader)) @@ -63,7 +80,7 @@ def decl(f, restype, argtypes): feat_rows = kaldi.MatrixF_NumRows(feat_value) feat_cols = kaldi.MatrixF_NumCols(feat_value) feat_data = kaldi.MatrixF_Data(feat_value) - + # never use numpy.ndarray(buf=) or numpy.ctypeslib.as_array # because you don't know if Python or C owns buffer # (even if you numpy.copy() resulting array) diff --git a/example/speech-demo/run_ami.sh b/example/speech-demo/run_ami.sh index 6c4dc13bf0ff..0103fd1832ac 100755 --- a/example/speech-demo/run_ami.sh +++ b/example/speech-demo/run_ami.sh @@ -1,5 +1,23 @@ #!/bin/bash +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + + # This script trains and evaluate LSTM models. There is no # discriminative training yet. # In this recipe, MXNet directly read Kaldi features and labels, diff --git a/example/speech-demo/run_timit.sh b/example/speech-demo/run_timit.sh index 4bc037dd62de..023ae6f2291f 100755 --- a/example/speech-demo/run_timit.sh +++ b/example/speech-demo/run_timit.sh @@ -1,5 +1,23 @@ #!/bin/bash +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + + # This script trains and evaluate LSTM models. There is no # discriminative training yet. # In this recipe, MXNet directly read Kaldi features and labels, diff --git a/example/speech-demo/speechSGD.py b/example/speech-demo/speechSGD.py index 37fb89d51ead..931f40afc062 100644 --- a/example/speech-demo/speechSGD.py +++ b/example/speech-demo/speechSGD.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import mxnet as mx from mxnet.ndarray import NDArray, zeros, clip, sqrt @@ -44,7 +61,7 @@ def create_state(self, index, weight): return None else: return zeros(weight.shape, weight.context, dtype=weight.dtype) - + def _get_lr(self, index): """get learning rate for index. diff --git a/example/speech-demo/tests/test_nothing.py b/example/speech-demo/tests/test_nothing.py index 1436522acd3a..d6e810f6e9e1 100644 --- a/example/speech-demo/tests/test_nothing.py +++ b/example/speech-demo/tests/test_nothing.py @@ -1,2 +1,19 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + def test_nothing(): - pass \ No newline at end of file + pass diff --git a/example/speech-demo/tests/test_system.py b/example/speech-demo/tests/test_system.py index 9d2a4b9a0f18..a64879ae44ba 100644 --- a/example/speech-demo/tests/test_system.py +++ b/example/speech-demo/tests/test_system.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + from __future__ import print_function from pdnn.run_DNN import run_DNN from pdnn.run_RBM import run_RBM @@ -37,7 +54,7 @@ def test_rbm_dnn(): "with_final": 1 } mnist_conf["train_rbm"]["max_iters"] = 0 - run_RBM(mnist_conf) + run_RBM(mnist_conf) def test_sda_dnn(): banner("sda dnn") @@ -60,7 +77,7 @@ def test_sda_dnn(): "with_final": 1 } mnist_conf["train_sda"]["max_iters"] = 1 - run_SDA(mnist_conf) + run_SDA(mnist_conf) def test_dnn_eval(): banner("dnn cv") @@ -82,7 +99,7 @@ def test_dnn_eval(): eval_DNN(mnist_conf) mnist_conf["eval_dnn"] = {"mode": "per-feat", "batch_size": 1024} - eval_DNN(mnist_conf) + eval_DNN(mnist_conf) def test_dropout(): banner("dropout") diff --git a/example/speech-demo/train_lstm_proj.py b/example/speech-demo/train_lstm_proj.py index d2a7a2744253..5749b0c39df7 100644 --- a/example/speech-demo/train_lstm_proj.py +++ b/example/speech-demo/train_lstm_proj.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import re import sys sys.path.insert(0, "../../python") @@ -129,7 +146,7 @@ def do_training(training_method, args, module, data_train, data_val): mkpath(os.path.dirname(get_checkpoint_path(args))) batch_size = data_train.batch_size - batch_end_callbacks = [mx.callback.Speedometer(batch_size, + batch_end_callbacks = [mx.callback.Speedometer(batch_size, args.config.getint('train', 'show_every'))] eval_allow_extra = True if training_method == METHOD_TBPTT else False eval_metric = [mx.metric.np(CrossEntropy, allow_extra_outputs=eval_allow_extra), diff --git a/example/speech_recognition/README.md b/example/speech_recognition/README.md index 69961b1bdc5c..00d166602403 100644 --- a/example/speech_recognition/README.md +++ b/example/speech_recognition/README.md @@ -123,3 +123,18 @@ The new file should implement two functions, prepare_data() and arch(), for buil Run the following line after preparing the files.
    python main.py --configfile custom.cfg --archfile arch_custom
    + +*** +## **Further more** +You can prepare full LibriSpeech dataset by following the instruction on https://github.com/baidu-research/ba-dls-deepspeech +**Change flac_to_wav.sh script of baidu to flac_to_wav.sh in repository to avoid bug** +```bash +git clone https://github.com/baidu-research/ba-dls-deepspeech +cd ba-dls-deepspeech +./download.sh +cp -f /path/to/example/flac_to_wav.sh ./ +./flac_to_wav.sh +python create_desc_json.py /path/to/ba-dls-deepspeech/LibriSpeech/train-clean-100 train_corpus.json +python create_desc_json.py /path/to/ba-dls-deepspeech/LibriSpeech/dev-clean validation_corpus.json +python create_desc_json.py /path/to/ba-dls-deepspeech/LibriSpeech/test-clean test_corpus.json +``` diff --git a/example/speech_recognition/arch_deepspeech.py b/example/speech_recognition/arch_deepspeech.py index 92f1002a2f01..e5b3d43ac07d 100644 --- a/example/speech_recognition/arch_deepspeech.py +++ b/example/speech_recognition/arch_deepspeech.py @@ -1,6 +1,29 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +# pylint: disable=C0111, too-many-statements, too-many-locals +# pylint: too-many-arguments,too-many-instance-attributes,too-many-locals,redefined-outer-name,fixme +# pylint: disable=superfluous-parens, no-member, invalid-name +""" +architecture file for deep speech 2 model +""" import json import math - +import argparse import mxnet as mx from stt_layer_batchnorm import batchnorm @@ -13,6 +36,9 @@ def prepare_data(args): + """ + set atual shape of data + """ rnn_type = args.config.get("arch", "rnn_type") num_rnn_layer = args.config.getint("arch", "num_rnn_layer") num_hidden_rnn_list = json.loads(args.config.get("arch", "num_hidden_rnn_list")) @@ -20,26 +46,29 @@ def prepare_data(args): batch_size = args.config.getint("common", "batch_size") if rnn_type == 'lstm': - init_c = [('l%d_init_c' % l, (batch_size, num_hidden_rnn_list[l])) for l in range(num_rnn_layer)] - init_h = [('l%d_init_h' % l, (batch_size, num_hidden_rnn_list[l])) for l in range(num_rnn_layer)] + init_c = [('l%d_init_c' % l, (batch_size, num_hidden_rnn_list[l])) + for l in range(num_rnn_layer)] + init_h = [('l%d_init_h' % l, (batch_size, num_hidden_rnn_list[l])) + for l in range(num_rnn_layer)] elif rnn_type == 'bilstm': - forward_init_c = [('forward_l%d_init_c' % l, (batch_size, num_hidden_rnn_list[l])) for l in - range(num_rnn_layer)] - backward_init_c = [('backward_l%d_init_c' % l, (batch_size, num_hidden_rnn_list[l])) for l in - range(num_rnn_layer)] + forward_init_c = [('forward_l%d_init_c' % l, (batch_size, num_hidden_rnn_list[l])) + for l in range(num_rnn_layer)] + backward_init_c = [('backward_l%d_init_c' % l, (batch_size, num_hidden_rnn_list[l])) + for l in range(num_rnn_layer)] init_c = forward_init_c + backward_init_c - forward_init_h = [('forward_l%d_init_h' % l, (batch_size, num_hidden_rnn_list[l])) for l in - range(num_rnn_layer)] - backward_init_h = [('backward_l%d_init_h' % l, (batch_size, num_hidden_rnn_list[l])) for l in - range(num_rnn_layer)] + forward_init_h = [('forward_l%d_init_h' % l, (batch_size, num_hidden_rnn_list[l])) + for l in range(num_rnn_layer)] + backward_init_h = [('backward_l%d_init_h' % l, (batch_size, num_hidden_rnn_list[l])) + for l in range(num_rnn_layer)] init_h = forward_init_h + backward_init_h elif rnn_type == 'gru': - init_h = [('l%d_init_h' % l, (batch_size, num_hidden_rnn_list[l])) for l in range(num_rnn_layer)] + init_h = [('l%d_init_h' % l, (batch_size, num_hidden_rnn_list[l])) + for l in range(num_rnn_layer)] elif rnn_type == 'bigru': - forward_init_h = [('forward_l%d_init_h' % l, (batch_size, num_hidden_rnn_list[l])) for l in - range(num_rnn_layer)] - backward_init_h = [('backward_l%d_init_h' % l, (batch_size, num_hidden_rnn_list[l])) for l in - range(num_rnn_layer)] + forward_init_h = [('forward_l%d_init_h' % l, (batch_size, num_hidden_rnn_list[l])) + for l in range(num_rnn_layer)] + backward_init_h = [('backward_l%d_init_h' % l, (batch_size, num_hidden_rnn_list[l])) + for l in range(num_rnn_layer)] init_h = forward_init_h + backward_init_h else: raise Exception('network type should be one of the lstm,bilstm,gru,bigru') @@ -51,115 +80,143 @@ def prepare_data(args): return init_states -def arch(args): - mode = args.config.get("common", "mode") - if mode == "train": - channel_num = args.config.getint("arch", "channel_num") - conv_layer1_filter_dim = tuple(json.loads(args.config.get("arch", "conv_layer1_filter_dim"))) - conv_layer1_stride = tuple(json.loads(args.config.get("arch", "conv_layer1_stride"))) - conv_layer2_filter_dim = tuple(json.loads(args.config.get("arch", "conv_layer2_filter_dim"))) - conv_layer2_stride = tuple(json.loads(args.config.get("arch", "conv_layer2_stride"))) - - rnn_type = args.config.get("arch", "rnn_type") - num_rnn_layer = args.config.getint("arch", "num_rnn_layer") - num_hidden_rnn_list = json.loads(args.config.get("arch", "num_hidden_rnn_list")) - - is_batchnorm = args.config.getboolean("arch", "is_batchnorm") - - seq_len = args.config.getint('arch', 'max_t_count') - num_label = args.config.getint('arch', 'max_label_length') - - num_rear_fc_layers = args.config.getint("arch", "num_rear_fc_layers") - num_hidden_rear_fc_list = json.loads(args.config.get("arch", "num_hidden_rear_fc_list")) - act_type_rear_fc_list = json.loads(args.config.get("arch", "act_type_rear_fc_list")) - # model symbol generation - # input preparation - data = mx.sym.Variable('data') - label = mx.sym.Variable('label') - - net = mx.sym.Reshape(data=data, shape=(-4, -1, 1, 0, 0)) - net = conv(net=net, - channels=channel_num, - filter_dimension=conv_layer1_filter_dim, - stride=conv_layer1_stride, - no_bias=is_batchnorm - ) - if is_batchnorm: - # batch norm normalizes axis 1 - net = batchnorm(net) - - net = conv(net=net, - channels=channel_num, - filter_dimension=conv_layer2_filter_dim, - stride=conv_layer2_stride, - no_bias=is_batchnorm - ) - if is_batchnorm: - # batch norm normalizes axis 1 - net = batchnorm(net) - net = mx.sym.transpose(data=net, axes=(0, 2, 1, 3)) - net = mx.sym.Reshape(data=net, shape=(0, 0, -3)) - seq_len_after_conv_layer1 = int( - math.floor((seq_len - conv_layer1_filter_dim[0]) / conv_layer1_stride[0])) + 1 - seq_len_after_conv_layer2 = int( - math.floor((seq_len_after_conv_layer1 - conv_layer2_filter_dim[0]) / conv_layer2_stride[0])) + 1 - net = slice_symbol_to_seq_symobls(net=net, seq_len=seq_len_after_conv_layer2, axis=1) - if rnn_type == "bilstm": - net = bi_lstm_unroll(net=net, +def arch(args, seq_len=None): + """ + define deep speech 2 network + """ + if isinstance(args, argparse.Namespace): + mode = args.config.get("common", "mode") + is_bucketing = args.config.getboolean("arch", "is_bucketing") + if mode == "train" or is_bucketing: + channel_num = args.config.getint("arch", "channel_num") + conv_layer1_filter_dim = \ + tuple(json.loads(args.config.get("arch", "conv_layer1_filter_dim"))) + conv_layer1_stride = tuple(json.loads(args.config.get("arch", "conv_layer1_stride"))) + conv_layer2_filter_dim = \ + tuple(json.loads(args.config.get("arch", "conv_layer2_filter_dim"))) + conv_layer2_stride = tuple(json.loads(args.config.get("arch", "conv_layer2_stride"))) + + rnn_type = args.config.get("arch", "rnn_type") + num_rnn_layer = args.config.getint("arch", "num_rnn_layer") + num_hidden_rnn_list = json.loads(args.config.get("arch", "num_hidden_rnn_list")) + + is_batchnorm = args.config.getboolean("arch", "is_batchnorm") + + if seq_len is None: + seq_len = args.config.getint('arch', 'max_t_count') + + num_label = args.config.getint('arch', 'max_label_length') + + num_rear_fc_layers = args.config.getint("arch", "num_rear_fc_layers") + num_hidden_rear_fc_list = json.loads(args.config.get("arch", "num_hidden_rear_fc_list")) + act_type_rear_fc_list = json.loads(args.config.get("arch", "act_type_rear_fc_list")) + # model symbol generation + # input preparation + data = mx.sym.Variable('data') + label = mx.sym.Variable('label') + + net = mx.sym.Reshape(data=data, shape=(-4, -1, 1, 0, 0)) + net = conv(net=net, + channels=channel_num, + filter_dimension=conv_layer1_filter_dim, + stride=conv_layer1_stride, + no_bias=is_batchnorm, + name='conv1') + if is_batchnorm: + # batch norm normalizes axis 1 + net = batchnorm(net, name="conv1_batchnorm") + + net = conv(net=net, + channels=channel_num, + filter_dimension=conv_layer2_filter_dim, + stride=conv_layer2_stride, + no_bias=is_batchnorm, + name='conv2') + if is_batchnorm: + # batch norm normalizes axis 1 + net = batchnorm(net, name="conv2_batchnorm") + + net = mx.sym.transpose(data=net, axes=(0, 2, 1, 3)) + net = mx.sym.Reshape(data=net, shape=(0, 0, -3)) + seq_len_after_conv_layer1 = int( + math.floor((seq_len - conv_layer1_filter_dim[0]) / conv_layer1_stride[0])) + 1 + seq_len_after_conv_layer2 = int( + math.floor((seq_len_after_conv_layer1 - conv_layer2_filter_dim[0]) + / conv_layer2_stride[0])) + 1 + net = slice_symbol_to_seq_symobls(net=net, seq_len=seq_len_after_conv_layer2, axis=1) + if rnn_type == "bilstm": + net = bi_lstm_unroll(net=net, + seq_len=seq_len_after_conv_layer2, + num_hidden_lstm_list=num_hidden_rnn_list, + num_lstm_layer=num_rnn_layer, + dropout=0., + is_batchnorm=is_batchnorm, + is_bucketing=is_bucketing) + elif rnn_type == "gru": + net = gru_unroll(net=net, seq_len=seq_len_after_conv_layer2, - num_hidden_lstm_list=num_hidden_rnn_list, - num_lstm_layer=num_rnn_layer, + num_hidden_gru_list=num_hidden_rnn_list, + num_gru_layer=num_rnn_layer, dropout=0., - is_batchnorm=is_batchnorm) - elif rnn_type == "gru": - net = gru_unroll(net=net, - seq_len=seq_len_after_conv_layer2, - num_hidden_gru_list=num_hidden_rnn_list, - num_gru_layer=num_rnn_layer, - dropout=0., - is_batchnorm=is_batchnorm) - elif rnn_type == "bigru": - net = bi_gru_unroll(net=net, + is_batchnorm=is_batchnorm, + is_bucketing=is_bucketing) + elif rnn_type == "bigru": + net = bi_gru_unroll(net=net, + seq_len=seq_len_after_conv_layer2, + num_hidden_gru_list=num_hidden_rnn_list, + num_gru_layer=num_rnn_layer, + dropout=0., + is_batchnorm=is_batchnorm, + is_bucketing=is_bucketing) + else: + raise Exception('rnn_type should be one of the followings, bilstm,gru,bigru') + + # rear fc layers + net = sequence_fc(net=net, seq_len=seq_len_after_conv_layer2, + num_layer=num_rear_fc_layers, prefix="rear", + num_hidden_list=num_hidden_rear_fc_list, + act_type_list=act_type_rear_fc_list, + is_batchnorm=is_batchnorm) + # warpctc layer + net = warpctc_layer(net=net, seq_len=seq_len_after_conv_layer2, - num_hidden_gru_list=num_hidden_rnn_list, - num_gru_layer=num_rnn_layer, - dropout=0., - is_batchnorm=is_batchnorm) + label=label, + num_label=num_label, + character_classes_count= + (args.config.getint('arch', 'n_classes') + 1)) + args.config.set('arch', 'max_t_count', str(seq_len_after_conv_layer2)) + return net + elif mode == 'load' or mode == 'predict': + conv_layer1_filter_dim = \ + tuple(json.loads(args.config.get("arch", "conv_layer1_filter_dim"))) + conv_layer1_stride = tuple(json.loads(args.config.get("arch", "conv_layer1_stride"))) + conv_layer2_filter_dim = \ + tuple(json.loads(args.config.get("arch", "conv_layer2_filter_dim"))) + conv_layer2_stride = tuple(json.loads(args.config.get("arch", "conv_layer2_stride"))) + if seq_len is None: + seq_len = args.config.getint('arch', 'max_t_count') + seq_len_after_conv_layer1 = int( + math.floor((seq_len - conv_layer1_filter_dim[0]) / conv_layer1_stride[0])) + 1 + seq_len_after_conv_layer2 = int( + math.floor((seq_len_after_conv_layer1 - conv_layer2_filter_dim[0]) + / conv_layer2_stride[0])) + 1 + + args.config.set('arch', 'max_t_count', str(seq_len_after_conv_layer2)) else: - raise Exception('rnn_type should be one of the followings, bilstm,gru,bigru') - - # rear fc layers - net = sequence_fc(net=net, seq_len=seq_len_after_conv_layer2, num_layer=num_rear_fc_layers, prefix="rear", - num_hidden_list=num_hidden_rear_fc_list, act_type_list=act_type_rear_fc_list, - is_batchnorm=is_batchnorm) - if is_batchnorm: - hidden_all = [] - # batch norm normalizes axis 1 - for seq_index in range(seq_len_after_conv_layer2): - hidden = net[seq_index] - hidden = batchnorm(hidden) - hidden_all.append(hidden) - net = hidden_all - - # warpctc layer - net = warpctc_layer(net=net, - seq_len=seq_len_after_conv_layer2, - label=label, - num_label=num_label, - character_classes_count=(args.config.getint('arch', 'n_classes') + 1) - ) - args.config.set('arch', 'max_t_count', str(seq_len_after_conv_layer2)) - return net - else: - conv_layer1_filter_dim = tuple(json.loads(args.config.get("arch", "conv_layer1_filter_dim"))) - conv_layer1_stride = tuple(json.loads(args.config.get("arch", "conv_layer1_stride"))) - conv_layer2_filter_dim = tuple(json.loads(args.config.get("arch", "conv_layer2_filter_dim"))) - conv_layer2_stride = tuple(json.loads(args.config.get("arch", "conv_layer2_stride"))) - seq_len = args.config.getint('arch', 'max_t_count') - seq_len_after_conv_layer1 = int( - math.floor((seq_len - conv_layer1_filter_dim[0]) / conv_layer1_stride[0])) + 1 - seq_len_after_conv_layer2 = int( - math.floor((seq_len_after_conv_layer1 - conv_layer2_filter_dim[0]) / conv_layer2_stride[0])) + 1 - args.config.set('arch', 'max_t_count', str(seq_len_after_conv_layer2)) + raise Exception('mode must be the one of the followings - train,predict,load') + + +class BucketingArch(object): + def __init__(self, args): + self.args = args + def sym_gen(self, seq_len): + args = self.args + net = arch(args, seq_len) + init_states = prepare_data(args) + init_state_names = [x[0] for x in init_states] + init_state_names.insert(0, 'data') + return net, init_state_names, ('label',) + def get_sym_gen(self): + return self.sym_gen diff --git a/example/speech_recognition/config_util.py b/example/speech_recognition/config_util.py index e8b7fda23506..09733917f924 100644 --- a/example/speech_recognition/config_util.py +++ b/example/speech_recognition/config_util.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import argparse import os import re diff --git a/example/speech_recognition/deepspeech.cfg b/example/speech_recognition/deepspeech.cfg index 13cf578c679a..4f0f49699771 100644 --- a/example/speech_recognition/deepspeech.cfg +++ b/example/speech_recognition/deepspeech.cfg @@ -3,23 +3,27 @@ mode = train #ex: gpu0,gpu1,gpu2,gpu3 context = gpu0,gpu1,gpu2 +#context = gpu0 # checkpoint prefix, check point will be saved under checkpoints folder with prefix -prefix = deep +prefix = deep_bucket # when mode is load or predict, model will be loaded from the file name with model_file under checkpoints -model_file = deepspeechn_epoch1n_batch-0009 +model_file = deep_bucketn_epoch0n_batch-0018 batch_size = 12 +#batch_size=4 # log will be saved by the log_filename -log_filename = deep.log +log_filename = deep_bucket.log # checkpoint set n to save checkpoints after n epoch save_checkpoint_every_n_epoch = 1 -save_checkpoint_every_n_batch = 1000 +save_checkpoint_every_n_batch = 3000 is_bi_graphemes = True -tensorboard_log_dir = tblog/deep +tensorboard_log_dir = tblog/deep_bucket # if random_seed is -1 then it gets random seed from timestamp mx_random_seed = -1 random_seed = -1 +kvstore_option = device [data] +max_duration = 16.0 train_json = ./train_corpus_all.json test_json = ./test_corpus.json val_json = ./test_corpus.json @@ -50,31 +54,49 @@ rnn_type = bigru #vanilla_lstm or fc_lstm (no effect when network_type is gru, bigru) lstm_type = fc_lstm is_batchnorm = True +is_bucketing = True +buckets = [200, 300, 400, 500, 600, 700, 800, 900, 1000, 1100, 1200, 1300, 1400, 1500, 1600] [train] num_epoch = 70 learning_rate = 0.0003 # constant learning rate annealing by factor learning_rate_annealing = 1.1 -# supports only sgd and adam -optimizer = sgd -# for sgd -momentum = 0.9 -# set to 0 to disable gradient clipping -clip_gradient = 0 initializer = Xavier init_scale = 2 factor_type = in -weight_decay = 0. # show progress every how nth batches show_every = 100 save_optimizer_states = True -normalize_target_k = 13000 +normalize_target_k = 100000 # overwrite meta files(feats_mean,feats_std,unicode_en_baidu_bi_graphemes.csv) overwrite_meta_files = True +overwrite_bi_graphemes_dictionary = False +# save feature extracted from soundfile as csvfile, it can take too much disk space +save_feature_as_csvfile = False enable_logging_train_metric = True enable_logging_validation_metric = True [load] load_optimizer_states = True is_start_from_batch = True + +[optimizer] +optimizer = sgd +# define parameters for optimizer +# optimizer_params_dictionary should use " not ' as string wrapper +# sgd/nag +optimizer_params_dictionary={"momentum":0.9} +# dcasgd +# optimizer_params_dictionary={"momentum":0.9, "lamda":1.0} +# adam +# optimizer_params_dictionary={"beta1":0.9,"beta2":0.999} +# adagrad +# optimizer_params_dictionary={"eps":1e-08} +# rmsprop +# optimizer_params_dictionary={"gamma1":0.9, "gamma2":0.9,"epsilon":1e-08} +# adadelta +# optimizer_params_dictionary={"rho":0.95, "epsilon":1e-08} +# set to 0 to disable gradient clipping +clip_gradient = 100 +weight_decay = 0. diff --git a/example/speech_recognition/default.cfg b/example/speech_recognition/default.cfg index 853a04aebbdd..127c492b6166 100644 --- a/example/speech_recognition/default.cfg +++ b/example/speech_recognition/default.cfg @@ -6,20 +6,22 @@ context = gpu0 # checkpoint prefix, check point will be saved under checkpoints folder with prefix prefix = test_fc # when mode is load or predict, model will be loaded from the file name with model_file under checkpoints -model_file = test_fc-0001 +model_file = test_fc-0040 batch_size = 2 # log will be saved by the log_filename log_filename = test.log # checkpoint set n to save checkpoints after n epoch -save_checkpoint_every_n_epoch = 1 +save_checkpoint_every_n_epoch = 20 save_checkpoint_every_n_batch = 1000 is_bi_graphemes = False tensorboard_log_dir = tblog/libri_sample # if random_seed is -1 then it gets random seed from timestamp -mx_random_seed = -1 -random_seed = -1 +mx_random_seed = 1234 +random_seed = 1234 +kvstore_option = device [data] +max_duration = 16.0 train_json = ./Libri_sample.json test_json = ./Libri_sample.json val_json = ./Libri_sample.json @@ -37,8 +39,8 @@ conv_layer1_stride = [2, 2] conv_layer2_filter_dim = [11, 21] conv_layer2_stride = [1, 2] -num_rnn_layer = 3 -num_hidden_rnn_list = [1760, 1760, 1760] +num_rnn_layer = 1 +num_hidden_rnn_list = [1760] num_hidden_proj = 0 num_rear_fc_layers = 0 @@ -50,33 +52,49 @@ rnn_type = bigru #vanilla_lstm or fc_lstm (no effect when network_type is gru, bigru) lstm_type = fc_lstm is_batchnorm = True +is_bucketing = False +buckets = [] [train] -num_epoch = 70 - +num_epoch = 50 learning_rate = 0.005 # constant learning rate annealing by factor learning_rate_annealing = 1.1 -# supports only sgd and adam -optimizer = adam -# for sgd -momentum = 0.9 -# set to 0 to disable gradient clipping -clip_gradient = 0 - initializer = Xavier init_scale = 2 factor_type = in -weight_decay = 0.00001 # show progress every nth batches show_every = 1 save_optimizer_states = True normalize_target_k = 2 # overwrite meta files(feats_mean,feats_std,unicode_en_baidu_bi_graphemes.csv) overwrite_meta_files = True +overwrite_bi_graphemes_dictionary = False +# save feature extracted from soundfile as csvfile, it can take too much disk space +save_feature_as_csvfile = False enable_logging_train_metric = True enable_logging_validation_metric = True [load] load_optimizer_states = True is_start_from_batch = False + +[optimizer] +optimizer = adam +# define parameters for optimizer +# optimizer_params_dictionary should use " not ' as string wrapper +# sgd/nag +# optimizer_params_dictionary={"momentum":0.9} +# dcasgd +# optimizer_params_dictionary={"momentum":0.9, "lamda":1.0} +# adam +optimizer_params_dictionary={"beta1":0.9,"beta2":0.999} +# adagrad +# optimizer_params_dictionary={"eps":1e-08} +# rmsprop +# optimizer_params_dictionary={"gamma1":0.9, "gamma2":0.9,"epsilon":1e-08} +# adadelta +# optimizer_params_dictionary={"rho":0.95, "epsilon":1e-08} +# set to 0 to disable gradient clipping +clip_gradient = 0 +weight_decay = 0. diff --git a/example/speech_recognition/flac_to_wav.sh b/example/speech_recognition/flac_to_wav.sh new file mode 100644 index 000000000000..7fd53f8719e1 --- /dev/null +++ b/example/speech_recognition/flac_to_wav.sh @@ -0,0 +1,25 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +# Convert all .flac files within this folder to .wav files + +find . -iname "*.flac" | wc + +for flacfile in `find . -iname "*.flac"` +do + sox "${flacfile%.*}.flac" -e signed -b 16 -c 1 -r 16000 "${flacfile%.*}.wav" +done diff --git a/example/speech_recognition/label_util.py b/example/speech_recognition/label_util.py index 3eb56c516e04..dab1d1ef1b40 100644 --- a/example/speech_recognition/label_util.py +++ b/example/speech_recognition/label_util.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # -*- coding: utf-8 -*- import csv diff --git a/example/speech_recognition/log_util.py b/example/speech_recognition/log_util.py index 097cfbd1a00a..e61407f5f4d5 100644 --- a/example/speech_recognition/log_util.py +++ b/example/speech_recognition/log_util.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import logging import logging.handlers diff --git a/example/speech_recognition/main.py b/example/speech_recognition/main.py index 398a8a537e01..e45026343de7 100644 --- a/example/speech_recognition/main.py +++ b/example/speech_recognition/main.py @@ -1,34 +1,49 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +import json +import os import sys - -sys.path.insert(0, "../../python") +from collections import namedtuple +from datetime import datetime from config_util import parse_args, parse_contexts, generate_file_path from train import do_training import mxnet as mx from stt_io_iter import STTIter from label_util import LabelUtil from log_util import LogUtil - import numpy as np from stt_datagenerator import DataGenerator from stt_metric import STTMetric -from datetime import datetime from stt_bi_graphemes_util import generate_bi_graphemes_dictionary -######################################## -########## FOR JUPYTER NOTEBOOK -import os +from stt_bucketing_module import STTBucketingModule +from stt_io_bucketingiter import BucketSTTIter +sys.path.insert(0, "../../python") # os.environ['MXNET_ENGINE_TYPE'] = "NaiveEngine" os.environ['MXNET_ENGINE_TYPE'] = "ThreadedEnginePerDevice" os.environ['MXNET_ENABLE_GPU_P2P'] = "0" - class WHCS: width = 0 height = 0 channel = 0 stride = 0 - class ConfigLogger(object): def __init__(self, log): self.__log = log @@ -42,9 +57,25 @@ def write(self, data): line = data.strip() self.__log.info(line) +def load_labelutil(labelUtil, is_bi_graphemes, language="en"): + if language == "en": + if is_bi_graphemes: + try: + labelUtil.load_unicode_set("resources/unicodemap_en_baidu_bi_graphemes.csv") + except: + raise Exception("There is no resources/unicodemap_en_baidu_bi_graphemes.csv." + + " Please set overwrite_bi_graphemes_dictionary True at train section") + else: + labelUtil.load_unicode_set("resources/unicodemap_en_baidu.csv") + else: + raise Exception("Error: Language Type: %s" % language) + + def load_data(args): mode = args.config.get('common', 'mode') + if mode not in ['train', 'predict', 'load']: + raise Exception('mode must be the one of the followings - train,predict,load') batch_size = args.config.getint('common', 'batch_size') whcs = WHCS() @@ -56,101 +87,77 @@ def load_data(args): model_name = args.config.get('common', 'prefix') is_bi_graphemes = args.config.getboolean('common', 'is_bi_graphemes') overwrite_meta_files = args.config.getboolean('train', 'overwrite_meta_files') + overwrite_bi_graphemes_dictionary = args.config.getboolean('train', 'overwrite_bi_graphemes_dictionary') + max_duration = args.config.getfloat('data', 'max_duration') language = args.config.get('data', 'language') - is_bi_graphemes = args.config.getboolean('common', 'is_bi_graphemes') + log = LogUtil().getlogger() labelUtil = LabelUtil.getInstance() - if language == "en": - if is_bi_graphemes: - try: - labelUtil.load_unicode_set("resources/unicodemap_en_baidu_bi_graphemes.csv") - except: - raise Exception("There is no resources/unicodemap_en_baidu_bi_graphemes.csv. Please set overwrite_meta_files at train section True") - else: - labelUtil.load_unicode_set("resources/unicodemap_en_baidu.csv") - else: - raise Exception("Error: Language Type: %s" % language) - args.config.set('arch', 'n_classes', str(labelUtil.get_count())) - - if mode == 'predict': - test_json = args.config.get('data', 'test_json') - datagen = DataGenerator(save_dir=save_dir, model_name=model_name) - datagen.load_train_data(test_json) - datagen.get_meta_from_file(np.loadtxt(generate_file_path(save_dir, model_name, 'feats_mean')), - np.loadtxt(generate_file_path(save_dir, model_name, 'feats_std'))) - elif mode =="train" or mode == "load": + if mode == "train" or mode == "load": data_json = args.config.get('data', 'train_json') val_json = args.config.get('data', 'val_json') datagen = DataGenerator(save_dir=save_dir, model_name=model_name) - datagen.load_train_data(data_json) - #test bigramphems - - if overwrite_meta_files and is_bi_graphemes: - generate_bi_graphemes_dictionary(datagen.train_texts) - + datagen.load_train_data(data_json, max_duration=max_duration) + datagen.load_validation_data(val_json, max_duration=max_duration) + if is_bi_graphemes: + if not os.path.isfile("resources/unicodemap_en_baidu_bi_graphemes.csv") or overwrite_bi_graphemes_dictionary: + load_labelutil(labelUtil=labelUtil, is_bi_graphemes=False, language=language) + generate_bi_graphemes_dictionary(datagen.train_texts+datagen.val_texts) + load_labelutil(labelUtil=labelUtil, is_bi_graphemes=is_bi_graphemes, language=language) args.config.set('arch', 'n_classes', str(labelUtil.get_count())) if mode == "train": if overwrite_meta_files: + log.info("Generate mean and std from samples") normalize_target_k = args.config.getint('train', 'normalize_target_k') datagen.sample_normalize(normalize_target_k, True) else: - datagen.get_meta_from_file(np.loadtxt(generate_file_path(save_dir, model_name, 'feats_mean')), - np.loadtxt(generate_file_path(save_dir, model_name, 'feats_std'))) - datagen.load_validation_data(val_json) - + log.info("Read mean and std from meta files") + datagen.get_meta_from_file( + np.loadtxt(generate_file_path(save_dir, model_name, 'feats_mean')), + np.loadtxt(generate_file_path(save_dir, model_name, 'feats_std'))) elif mode == "load": # get feat_mean and feat_std to normalize dataset - datagen.get_meta_from_file(np.loadtxt(generate_file_path(save_dir, model_name, 'feats_mean')), - np.loadtxt(generate_file_path(save_dir, model_name, 'feats_std'))) - datagen.load_validation_data(val_json) - else: - raise Exception( - 'Define mode in the cfg file first. train or predict or load can be the candidate for the mode.') + datagen.get_meta_from_file( + np.loadtxt(generate_file_path(save_dir, model_name, 'feats_mean')), + np.loadtxt(generate_file_path(save_dir, model_name, 'feats_std'))) + + elif mode == 'predict': + test_json = args.config.get('data', 'test_json') + datagen = DataGenerator(save_dir=save_dir, model_name=model_name) + datagen.load_train_data(test_json, max_duration=max_duration) + labelutil = load_labelutil(labelUtil, is_bi_graphemes, language="en") + args.config.set('arch', 'n_classes', str(labelUtil.get_count())) + datagen.get_meta_from_file( + np.loadtxt(generate_file_path(save_dir, model_name, 'feats_mean')), + np.loadtxt(generate_file_path(save_dir, model_name, 'feats_std'))) is_batchnorm = args.config.getboolean('arch', 'is_batchnorm') - if batch_size == 1 and is_batchnorm: + if batch_size == 1 and is_batchnorm and (mode == 'train' or mode == 'load'): raise Warning('batch size 1 is too small for is_batchnorm') # sort file paths by its duration in ascending order to implement sortaGrad - if mode == "train" or mode == "load": max_t_count = datagen.get_max_seq_length(partition="train") - max_label_length = datagen.get_max_label_length(partition="train",is_bi_graphemes=is_bi_graphemes) + max_label_length = \ + datagen.get_max_label_length(partition="train", is_bi_graphemes=is_bi_graphemes) elif mode == "predict": max_t_count = datagen.get_max_seq_length(partition="test") - max_label_length = datagen.get_max_label_length(partition="test",is_bi_graphemes=is_bi_graphemes) - else: - raise Exception( - 'Define mode in the cfg file first. train or predict or load can be the candidate for the mode.') + max_label_length = \ + datagen.get_max_label_length(partition="test", is_bi_graphemes=is_bi_graphemes) args.config.set('arch', 'max_t_count', str(max_t_count)) args.config.set('arch', 'max_label_length', str(max_label_length)) from importlib import import_module prepare_data_template = import_module(args.config.get('arch', 'arch_file')) init_states = prepare_data_template.prepare_data(args) - if mode == "train": - sort_by_duration = True - else: - sort_by_duration = False - - data_loaded = STTIter(partition="train", - count=datagen.count, - datagen=datagen, - batch_size=batch_size, - num_label=max_label_length, - init_states=init_states, - seq_length=max_t_count, - width=whcs.width, - height=whcs.height, - sort_by_duration=sort_by_duration, - is_bi_graphemes=is_bi_graphemes) - - if mode == 'predict': - return data_loaded, args - else: - validation_loaded = STTIter(partition="validation", - count=datagen.val_count, + sort_by_duration = (mode == "train") + is_bucketing = args.config.getboolean('arch', 'is_bucketing') + save_feature_as_csvfile = args.config.getboolean('train', 'save_feature_as_csvfile') + if is_bucketing: + buckets = json.loads(args.config.get('arch', 'buckets')) + data_loaded = BucketSTTIter(partition="train", + count=datagen.count, datagen=datagen, batch_size=batch_size, num_label=max_label_length, @@ -158,37 +165,91 @@ def load_data(args): seq_length=max_t_count, width=whcs.width, height=whcs.height, - sort_by_duration=False, - is_bi_graphemes=is_bi_graphemes) + sort_by_duration=sort_by_duration, + is_bi_graphemes=is_bi_graphemes, + buckets=buckets, + save_feature_as_csvfile=save_feature_as_csvfile) + else: + data_loaded = STTIter(partition="train", + count=datagen.count, + datagen=datagen, + batch_size=batch_size, + num_label=max_label_length, + init_states=init_states, + seq_length=max_t_count, + width=whcs.width, + height=whcs.height, + sort_by_duration=sort_by_duration, + is_bi_graphemes=is_bi_graphemes, + save_feature_as_csvfile=save_feature_as_csvfile) + + if mode == 'train' or mode == 'load': + if is_bucketing: + validation_loaded = BucketSTTIter(partition="validation", + count=datagen.val_count, + datagen=datagen, + batch_size=batch_size, + num_label=max_label_length, + init_states=init_states, + seq_length=max_t_count, + width=whcs.width, + height=whcs.height, + sort_by_duration=False, + is_bi_graphemes=is_bi_graphemes, + buckets=buckets, + save_feature_as_csvfile=save_feature_as_csvfile) + else: + validation_loaded = STTIter(partition="validation", + count=datagen.val_count, + datagen=datagen, + batch_size=batch_size, + num_label=max_label_length, + init_states=init_states, + seq_length=max_t_count, + width=whcs.width, + height=whcs.height, + sort_by_duration=False, + is_bi_graphemes=is_bi_graphemes, + save_feature_as_csvfile=save_feature_as_csvfile) return data_loaded, validation_loaded, args + elif mode == 'predict': + return data_loaded, args def load_model(args, contexts, data_train): # load model from model_name prefix and epoch of model_num_epoch with gpu contexts of contexts mode = args.config.get('common', 'mode') load_optimizer_states = args.config.getboolean('load', 'load_optimizer_states') - is_start_from_batch = args.config.getboolean('load','is_start_from_batch') + is_start_from_batch = args.config.getboolean('load', 'is_start_from_batch') from importlib import import_module symbol_template = import_module(args.config.get('arch', 'arch_file')) - model_loaded = symbol_template.arch(args) + is_bucketing = args.config.getboolean('arch', 'is_bucketing') if mode == 'train': + if is_bucketing: + bucketing_arch = symbol_template.BucketingArch(args) + model_loaded = bucketing_arch.get_sym_gen() + else: + model_loaded = symbol_template.arch(args) model_num_epoch = None - else: + elif mode == 'load' or mode == 'predict': model_file = args.config.get('common', 'model_file') model_name = os.path.splitext(model_file)[0] - model_num_epoch = int(model_name[-4:]) + if is_bucketing: + bucketing_arch = symbol_template.BucketingArch(args) + model_loaded = bucketing_arch.get_sym_gen() + else: + model_path = 'checkpoints/' + str(model_name[:-5]) - model_path = 'checkpoints/' + str(model_name[:-5]) - - data_names = [x[0] for x in data_train.provide_data] - label_names = [x[0] for x in data_train.provide_label] + data_names = [x[0] for x in data_train.provide_data] + label_names = [x[0] for x in data_train.provide_label] - model_loaded = mx.module.Module.load(prefix=model_path, epoch=model_num_epoch, context=contexts, - data_names=data_names, label_names=label_names, - load_optimizer_states=load_optimizer_states) + model_loaded = mx.module.Module.load( + prefix=model_path, epoch=model_num_epoch, context=contexts, + data_names=data_names, label_names=label_names, + load_optimizer_states=load_optimizer_states) if is_start_from_batch: import re model_num_epoch = int(re.findall('\d+', model_file)[0]) @@ -198,7 +259,8 @@ def load_model(args, contexts, data_train): if __name__ == '__main__': if len(sys.argv) <= 1: - raise Exception('cfg file path must be provided. ex)python main.py --configfile examplecfg.cfg') + raise Exception('cfg file path must be provided. ' + + 'ex)python main.py --configfile examplecfg.cfg') args = parse_args(sys.argv[1]) # set parameters from cfg file # give random seed @@ -206,9 +268,9 @@ def load_model(args, contexts, data_train): mx_random_seed = args.config.getint('common', 'mx_random_seed') # random seed for shuffling data list if random_seed != -1: - random.seed(random_seed) + np.random.seed(random_seed) # set mx.random.seed to give seed for parameter initialization - if mx_random_seed !=-1: + if mx_random_seed != -1: mx.random.seed(mx_random_seed) else: mx.random.seed(hash(datetime.now())) @@ -220,22 +282,23 @@ def load_model(args, contexts, data_train): mode = args.config.get('common', 'mode') if mode not in ['train', 'predict', 'load']: raise Exception( - 'Define mode in the cfg file first. train or predict or load can be the candidate for the mode.') + 'Define mode in the cfg file first. ' + + 'train or predict or load can be the candidate for the mode.') # get meta file where character to number conversions are defined contexts = parse_contexts(args) num_gpu = len(contexts) batch_size = args.config.getint('common', 'batch_size') - # check the number of gpus is positive divisor of the batch size for data parallel if batch_size % num_gpu != 0: raise Exception('num_gpu should be positive divisor of batch_size') - - if mode == "predict": - data_train, args = load_data(args) - elif mode == "train" or mode == "load": + if mode == "train" or mode == "load": data_train, data_val, args = load_data(args) + elif mode == "predict": + data_train, args = load_data(args) + is_batchnorm = args.config.getboolean('arch', 'is_batchnorm') + is_bucketing = args.config.getboolean('arch', 'is_bucketing') # log current config config_logger = ConfigLogger(log) @@ -243,28 +306,63 @@ def load_model(args, contexts, data_train): # load model model_loaded, model_num_epoch = load_model(args, contexts, data_train) - # if mode is 'train', it trains the model if mode == 'train': - data_names = [x[0] for x in data_train.provide_data] - label_names = [x[0] for x in data_train.provide_label] - module = mx.mod.Module(model_loaded, context=contexts, data_names=data_names, label_names=label_names) + if is_bucketing: + module = STTBucketingModule( + sym_gen=model_loaded, + default_bucket_key=data_train.default_bucket_key, + context=contexts + ) + else: + data_names = [x[0] for x in data_train.provide_data] + label_names = [x[0] for x in data_train.provide_label] + module = mx.mod.Module(model_loaded, context=contexts, + data_names=data_names, label_names=label_names) do_training(args=args, module=module, data_train=data_train, data_val=data_val) # if mode is 'load', it loads model from the checkpoint and continues the training. elif mode == 'load': - do_training(args=args, module=model_loaded, data_train=data_train, data_val=data_val, begin_epoch=model_num_epoch+1) + do_training(args=args, module=model_loaded, data_train=data_train, data_val=data_val, + begin_epoch=model_num_epoch + 1) # if mode is 'predict', it predict label from the input by the input model elif mode == 'predict': # predict through data - model_loaded.bind(for_training=False, data_shapes=data_train.provide_data, - label_shapes=data_train.provide_label) + if is_bucketing: + max_t_count = args.config.getint('arch', 'max_t_count') + load_optimizer_states = args.config.getboolean('load', 'load_optimizer_states') + model_file = args.config.get('common', 'model_file') + model_name = os.path.splitext(model_file)[0] + model_num_epoch = int(model_name[-4:]) + + model_path = 'checkpoints/' + str(model_name[:-5]) + model = STTBucketingModule( + sym_gen=model_loaded, + default_bucket_key=data_train.default_bucket_key, + context=contexts + ) + + model.bind(data_shapes=data_train.provide_data, + label_shapes=data_train.provide_label, + for_training=True) + _, arg_params, aux_params = mx.model.load_checkpoint(model_path, model_num_epoch) + model.set_params(arg_params, aux_params) + model_loaded = model + else: + model_loaded.bind(for_training=False, data_shapes=data_train.provide_data, + label_shapes=data_train.provide_label) max_t_count = args.config.getint('arch', 'max_t_count') - eval_metric = STTMetric(batch_size=batch_size, num_gpu=num_gpu, seq_length=max_t_count) - is_batchnorm = args.config.getboolean('arch', 'is_batchnorm') - if is_batchnorm : + eval_metric = STTMetric(batch_size=batch_size, num_gpu=num_gpu) + if is_batchnorm: for nbatch, data_batch in enumerate(data_train): - # when is_train = False it leads to high cer when batch_norm - model_loaded.forward(data_batch, is_train=True) + model_loaded.forward(data_batch, is_train=False) model_loaded.update_metric(eval_metric, data_batch.label) - else : - model_loaded.score(eval_data=data_train, num_batch=None, eval_metric=eval_metric, reset=True) + else: + #model_loaded.score(eval_data=data_train, num_batch=None, + # eval_metric=eval_metric, reset=True) + for nbatch, data_batch in enumerate(data_train): + model_loaded.forward(data_batch, is_train=False) + model_loaded.update_metric(eval_metric, data_batch.label) + else: + raise Exception( + 'Define mode in the cfg file first. ' + + 'train or predict or load can be the candidate for the mode') diff --git a/example/speech_recognition/singleton.py b/example/speech_recognition/singleton.py index 16f129b41017..aa9531b9443c 100644 --- a/example/speech_recognition/singleton.py +++ b/example/speech_recognition/singleton.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import logging as log class Singleton: diff --git a/example/speech_recognition/stt_bi_graphemes_util.py b/example/speech_recognition/stt_bi_graphemes_util.py index b8246a09c137..7ac83142b7cf 100644 --- a/example/speech_recognition/stt_bi_graphemes_util.py +++ b/example/speech_recognition/stt_bi_graphemes_util.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import csv from collections import Counter diff --git a/example/speech_recognition/stt_bucketing_module.py b/example/speech_recognition/stt_bucketing_module.py new file mode 100644 index 000000000000..073f6bf649bf --- /dev/null +++ b/example/speech_recognition/stt_bucketing_module.py @@ -0,0 +1,30 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +import mxnet as mx + + +class STTBucketingModule(mx.mod.BucketingModule): + + def save_checkpoint(self, prefix, epoch, save_optimizer_states=False): + symbol, data_names, label_names = self._sym_gen(self._default_bucket_key) + symbol.save('%s-symbol.json' % prefix) + param_name = '%s-%04d.params' % (prefix, epoch) + self.save_params(param_name) + if save_optimizer_states: + state_name = '%s-%04d.states' % (prefix, epoch) + self._curr_module.save_optimizer_states(state_name) diff --git a/example/speech_recognition/stt_datagenerator.py b/example/speech_recognition/stt_datagenerator.py index 390de432e751..8fafa7909377 100644 --- a/example/speech_recognition/stt_datagenerator.py +++ b/example/speech_recognition/stt_datagenerator.py @@ -1,8 +1,24 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + from __future__ import absolute_import, division, print_function import json import random - import numpy as np from stt_utils import calc_feat_dim, spectrogram_from_file @@ -10,6 +26,7 @@ from log_util import LogUtil from label_util import LabelUtil from stt_bi_graphemes_util import generate_bi_graphemes_label +from multiprocessing import cpu_count, Process, Manager class DataGenerator(object): def __init__(self, save_dir, model_name, step=10, window=20, max_freq=8000, desc_file=None): @@ -32,7 +49,7 @@ def __init__(self, save_dir, model_name, step=10, window=20, max_freq=8000, desc # 1d 161 length of array filled with 1s self.feats_std = np.ones((self.feat_dim,)) self.max_input_length = 0 - self.max_length_list_in_batch =[] + self.max_length_list_in_batch = [] # 1d 161 length of array filled with random value #[0.0, 1.0) self.rng = random.Random() @@ -48,14 +65,15 @@ def get_meta_from_file(self, feats_mean, feats_std): self.feats_mean = feats_mean self.feats_std = feats_std - def featurize(self, audio_clip, overwrite=False): + def featurize(self, audio_clip, overwrite=False, save_feature_as_csvfile=False): """ For a given audio clip, calculate the log of its Fourier Transform Params: audio_clip(str): Path to the audio clip """ return spectrogram_from_file( audio_clip, step=self.step, window=self.window, - max_freq=self.max_freq, overwrite=overwrite) + max_freq=self.max_freq, overwrite=overwrite, + save_feature_as_csvfile=save_feature_as_csvfile) def load_metadata_from_desc_file(self, desc_file, partition='train', max_duration=16.0,): @@ -107,11 +125,11 @@ def load_metadata_from_desc_file(self, desc_file, partition='train', raise Exception("Invalid partition to load metadata. " "Must be train/validation/test") - def load_train_data(self, desc_file): - self.load_metadata_from_desc_file(desc_file, 'train') + def load_train_data(self, desc_file, max_duration): + self.load_metadata_from_desc_file(desc_file, 'train', max_duration=max_duration) - def load_validation_data(self, desc_file): - self.load_metadata_from_desc_file(desc_file, 'validation') + def load_validation_data(self, desc_file, max_duration): + self.load_metadata_from_desc_file(desc_file, 'validation', max_duration=max_duration) @staticmethod def sort_by_duration(durations, audio_paths, texts): @@ -146,10 +164,11 @@ def get_max_seq_length(self, partition): "Must be train/validation/test") max_duration_indexes = durations.index(max(durations)) max_seq_length = self.featurize(audio_paths[max_duration_indexes]).shape[0] - self.max_seq_length=max_seq_length + self.max_seq_length = max_seq_length return max_seq_length - def prepare_minibatch(self, audio_paths, texts, overwrite=False, is_bi_graphemes=False): + def prepare_minibatch(self, audio_paths, texts, overwrite=False, + is_bi_graphemes=False, seq_length=-1, save_feature_as_csvfile=False): """ Featurize a minibatch of audio, zero pad them and return a dictionary Params: audio_paths (list(str)): List of paths to audio files @@ -162,12 +181,15 @@ def prepare_minibatch(self, audio_paths, texts, overwrite=False, is_bi_graphemes # Features is a list of (timesteps, feature_dim) arrays # Calculate the features for each audio clip, as the log of the # Fourier Transform of the audio - features = [self.featurize(a, overwrite=overwrite) for a in audio_paths] + features = [self.featurize(a, overwrite=overwrite, save_feature_as_csvfile=save_feature_as_csvfile) for a in audio_paths] input_lengths = [f.shape[0] for f in features] feature_dim = features[0].shape[1] mb_size = len(features) # Pad all the inputs so that they are all the same length - x = np.zeros((mb_size, self.max_seq_length, feature_dim)) + if seq_length == -1: + x = np.zeros((mb_size, self.max_seq_length, feature_dim)) + else: + x = np.zeros((mb_size, seq_length, feature_dim)) y = np.zeros((mb_size, self.max_label_length)) labelUtil = LabelUtil.getInstance() label_lengths = [] @@ -199,34 +221,59 @@ def iterate_validation(self, minibatch_size=16): return self.iterate(self.val_audio_paths, self.val_texts, minibatch_size) + def preprocess_sample_normalize(self, threadIndex, audio_paths, overwrite, return_dict): + if len(audio_paths) > 0: + audio_clip = audio_paths[0] + feat = self.featurize(audio_clip=audio_clip, overwrite=overwrite) + feat_squared = np.square(feat) + count = float(feat.shape[0]) + dim = feat.shape[1] + if len(audio_paths) > 1: + for audio_path in audio_paths[1:]: + next_feat = self.featurize(audio_clip=audio_path, overwrite=overwrite) + next_feat_squared = np.square(next_feat) + feat_vertically_stacked = np.concatenate((feat, next_feat)).reshape(-1, dim) + feat = np.sum(feat_vertically_stacked, axis=0, keepdims=True) + feat_squared_vertically_stacked = np.concatenate( + (feat_squared, next_feat_squared)).reshape(-1, dim) + feat_squared = np.sum(feat_squared_vertically_stacked, axis=0, keepdims=True) + count += float(next_feat.shape[0]) + return_dict[threadIndex] = {'feat': feat, 'feat_squared': feat_squared, 'count': count} + def sample_normalize(self, k_samples=1000, overwrite=False): """ Estimate the mean and std of the features from the training set Params: k_samples (int): Use this number of samples for estimation """ + log = LogUtil().getlogger() + log.info("Calculating mean and std from samples") # if k_samples is negative then it goes through total dataset if k_samples < 0: - audio_paths_iter = iter(self.audio_paths) + audio_paths = self.audio_paths + # using sample else: k_samples = min(k_samples, len(self.train_audio_paths)) samples = self.rng.sample(self.train_audio_paths, k_samples) - audio_paths_iter = iter(samples) - audio_clip = audio_paths_iter.next() - feat = self.featurize(audio_clip=audio_clip, overwrite=overwrite) - feat_squared = np.square(feat) - count = float(feat.shape[0]) - dim = feat.shape[1] - - for iter_index in range(len(samples) - 1): - next_feat = self.featurize(audio_clip=audio_paths_iter.next(), overwrite=overwrite) - next_feat_squared = np.square(next_feat) - feat_vertically_stacked = np.concatenate((feat, next_feat)).reshape(-1, dim) - feat = np.sum(feat_vertically_stacked, axis=0, keepdims=True) - feat_squared_vertically_stacked = np.concatenate((feat_squared, next_feat_squared)).reshape(-1, dim) - feat_squared = np.sum(feat_squared_vertically_stacked, axis=0, keepdims=True) - count = count + float(next_feat.shape[0]) + audio_paths = samples + manager = Manager() + return_dict = manager.dict() + jobs = [] + for threadIndex in range(cpu_count()): + proc = Process(target=self.preprocess_sample_normalize, args=(threadIndex, audio_paths, overwrite, return_dict)) + jobs.append(proc) + proc.start() + for proc in jobs: + proc.join() + + feat = np.sum(np.vstack([item['feat'] for item in return_dict.values()]), axis=0) + count = sum([item['count'] for item in return_dict.values()]) + feat_squared = np.sum(np.vstack([item['feat_squared'] for item in return_dict.values()]), axis=0) + self.feats_mean = feat / float(count) self.feats_std = np.sqrt(feat_squared / float(count) - np.square(self.feats_mean)) - np.savetxt(generate_file_path(self.save_dir, self.model_name, 'feats_mean'), self.feats_mean) - np.savetxt(generate_file_path(self.save_dir, self.model_name, 'feats_std'), self.feats_std) + np.savetxt( + generate_file_path(self.save_dir, self.model_name, 'feats_mean'), self.feats_mean) + np.savetxt( + generate_file_path(self.save_dir, self.model_name, 'feats_std'), self.feats_std) + log.info("End calculating mean and std from samples") diff --git a/example/speech_recognition/stt_io_bucketingiter.py b/example/speech_recognition/stt_io_bucketingiter.py new file mode 100644 index 000000000000..41b93f3bb9e5 --- /dev/null +++ b/example/speech_recognition/stt_io_bucketingiter.py @@ -0,0 +1,165 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +from __future__ import print_function +import mxnet as mx +import sys +sys.path.insert(0, "../../python") + +import bisect +import random +import numpy as np + +BATCH_SIZE = 1 +SEQ_LENGTH = 0 +NUM_GPU = 1 + + +def get_label(buf, num_lable): + ret = np.zeros(num_lable) + for i in range(len(buf)): + ret[i] = int(buf[i]) + return ret + + +class BucketSTTIter(mx.io.DataIter): + def __init__(self, count, datagen, batch_size, num_label, init_states, seq_length, width, height, + sort_by_duration=True, + is_bi_graphemes=False, + partition="train", + buckets=[], + save_feature_as_csvfile=False + ): + super(BucketSTTIter, self).__init__() + + self.maxLabelLength = num_label + # global param + self.batch_size = batch_size + self.count = count + self.num_label = num_label + self.init_states = init_states + self.init_state_arrays = [mx.nd.zeros(x[1]) for x in init_states] + self.width = width + self.height = height + self.datagen = datagen + self.label = None + self.is_bi_graphemes = is_bi_graphemes + # self.partition = datagen.partition + if partition == 'train': + durations = datagen.train_durations + audio_paths = datagen.train_audio_paths + texts = datagen.train_texts + elif partition == 'validation': + durations = datagen.val_durations + audio_paths = datagen.val_audio_paths + texts = datagen.val_texts + elif partition == 'test': + durations = datagen.test_durations + audio_paths = datagen.test_audio_paths + texts = datagen.test_texts + else: + raise Exception("Invalid partition to load metadata. " + "Must be train/validation/test") + # if sortagrad + if sort_by_duration: + durations, audio_paths, texts = datagen.sort_by_duration(durations, + audio_paths, + texts) + else: + durations = durations + audio_paths = audio_paths + texts = texts + self.trainDataList = zip(durations, audio_paths, texts) + + self.trainDataIter = iter(self.trainDataList) + self.is_first_epoch = True + + data_lengths = [int(d*100) for d in durations] + if len(buckets) == 0: + buckets = [i for i, j in enumerate(np.bincount(data_lengths)) + if j >= batch_size] + if len(buckets) == 0: + raise Exception('There is no valid buckets. It may occured by large batch_size for each buckets. max bincount:%d batch_size:%d' % (max(np.bincount(data_lengths)), batch_size)) + buckets.sort() + ndiscard = 0 + self.data = [[] for _ in buckets] + for i, sent in enumerate(data_lengths): + buck = bisect.bisect_left(buckets, sent) + if buck == len(buckets): + ndiscard += 1 + continue + self.data[buck].append(self.trainDataList[i]) + if ndiscard != 0: + print("WARNING: discarded %d sentences longer than the largest bucket."% ndiscard) + + self.buckets = buckets + self.nddata = [] + self.ndlabel = [] + self.default_bucket_key = max(buckets) + + self.idx = [] + for i, buck in enumerate(self.data): + self.idx.extend([(i, j) for j in range(0, len(buck) - batch_size + 1, batch_size)]) + self.curr_idx = 0 + + self.provide_data = [('data', (self.batch_size, self.default_bucket_key , width * height))] + init_states + self.provide_label = [('label', (self.batch_size, self.maxLabelLength))] + self.save_feature_as_csvfile=save_feature_as_csvfile + + #self.reset() + + def reset(self): + """Resets the iterator to the beginning of the data.""" + self.curr_idx = 0 + random.shuffle(self.idx) + for buck in self.data: + np.random.shuffle(buck) + + def next(self): + """Returns the next batch of data.""" + if self.curr_idx == len(self.idx): + raise StopIteration + i, j = self.idx[self.curr_idx] + self.curr_idx += 1 + + audio_paths = [] + texts = [] + for duration, audio_path, text in self.data[i][j:j+self.batch_size]: + audio_paths.append(audio_path) + texts.append(text) + + if self.is_first_epoch: + data_set = self.datagen.prepare_minibatch(audio_paths, texts, overwrite=True, + is_bi_graphemes=self.is_bi_graphemes, + seq_length=self.buckets[i], + save_feature_as_csvfile=self.save_feature_as_csvfile) + else: + data_set = self.datagen.prepare_minibatch(audio_paths, texts, overwrite=False, + is_bi_graphemes=self.is_bi_graphemes, + seq_length=self.buckets[i], + save_feature_as_csvfile=self.save_feature_as_csvfile) + + data_all = [mx.nd.array(data_set['x'])] + self.init_state_arrays + label_all = [mx.nd.array(data_set['y'])] + + self.label = label_all + provide_data = [('data', (self.batch_size, self.buckets[i], self.width * self.height))] + self.init_states + + return mx.io.DataBatch(data_all, label_all, pad=0, + bucket_key=self.buckets[i], + provide_data=provide_data, + provide_label=self.provide_label) diff --git a/example/speech_recognition/stt_io_iter.py b/example/speech_recognition/stt_io_iter.py index 70c31ce92dde..6c9bacd1a526 100644 --- a/example/speech_recognition/stt_io_iter.py +++ b/example/speech_recognition/stt_io_iter.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + from __future__ import print_function import sys @@ -31,7 +48,8 @@ def provide_label(self): class STTIter(mx.io.DataIter): def __init__(self, count, datagen, batch_size, num_label, init_states, seq_length, width, height, sort_by_duration=True, - is_bi_graphemes=False, partition="train",): + is_bi_graphemes=False, partition="train", + save_feature_as_csvfile=False): super(STTIter, self).__init__() self.batch_size = batch_size self.num_label = num_label @@ -75,6 +93,7 @@ def __init__(self, count, datagen, batch_size, num_label, init_states, seq_lengt self.trainDataIter = iter(self.trainDataList) self.is_first_epoch = True + self.save_feature_as_csvfile = save_feature_as_csvfile def __iter__(self): init_state_names = [x[0] for x in self.init_states] @@ -92,9 +111,9 @@ def __iter__(self): audio_paths.append(audio_path) texts.append(text) if self.is_first_epoch: - data_set = self.datagen.prepare_minibatch(audio_paths, texts, overwrite=True, is_bi_graphemes=self.is_bi_graphemes) + data_set = self.datagen.prepare_minibatch(audio_paths, texts, overwrite=True, is_bi_graphemes=self.is_bi_graphemes, save_feature_as_csvfile=self.save_feature_as_csvfile) else: - data_set = self.datagen.prepare_minibatch(audio_paths, texts, overwrite=False, is_bi_graphemes=self.is_bi_graphemes) + data_set = self.datagen.prepare_minibatch(audio_paths, texts, overwrite=False, is_bi_graphemes=self.is_bi_graphemes, save_feature_as_csvfile=self.save_feature_as_csvfile) data_all = [mx.nd.array(data_set['x'])] + self.init_state_arrays label_all = [mx.nd.array(data_set['y'])] @@ -103,7 +122,6 @@ def __iter__(self): data_batch = SimpleBatch(data_names, data_all, label_names, label_all) yield data_batch - self.is_first_epoch = False def reset(self): pass diff --git a/example/speech_recognition/stt_layer_batchnorm.py b/example/speech_recognition/stt_layer_batchnorm.py index 86e75aa49557..eb61ba6e597e 100644 --- a/example/speech_recognition/stt_layer_batchnorm.py +++ b/example/speech_recognition/stt_layer_batchnorm.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import mxnet as mx @@ -6,7 +23,7 @@ def batchnorm(net, beta=None, eps=0.001, momentum=0.9, - fix_gamma=True, + fix_gamma=False, use_global_stats=False, output_mean_var=False, name=None): @@ -18,7 +35,8 @@ def batchnorm(net, momentum=momentum, fix_gamma=fix_gamma, use_global_stats=use_global_stats, - output_mean_var=output_mean_var + output_mean_var=output_mean_var, + name=name ) else: net = mx.sym.BatchNorm(data=net, @@ -26,6 +44,7 @@ def batchnorm(net, momentum=momentum, fix_gamma=fix_gamma, use_global_stats=use_global_stats, - output_mean_var=output_mean_var + output_mean_var=output_mean_var, + name=name ) return net diff --git a/example/speech_recognition/stt_layer_conv.py b/example/speech_recognition/stt_layer_conv.py index 5ec292557f04..c34ddf21844d 100644 --- a/example/speech_recognition/stt_layer_conv.py +++ b/example/speech_recognition/stt_layer_conv.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import mxnet as mx @@ -8,20 +25,22 @@ def conv(net, weight=None, bias=None, act_type="relu", - no_bias=False + no_bias=False, + name=None ): # 2d convolution's input should have the shape of 4D (batch_size,1,seq_len,feat_dim) if weight is None or bias is None: # ex) filter_dimension = (41,11) , stride=(2,2) - net = mx.sym.Convolution(data=net, num_filter=channels, kernel=filter_dimension, stride=stride, no_bias=no_bias) + net = mx.sym.Convolution(data=net, num_filter=channels, kernel=filter_dimension, stride=stride, no_bias=no_bias, + name=name) elif weight is None or bias is not None: net = mx.sym.Convolution(data=net, num_filter=channels, kernel=filter_dimension, stride=stride, bias=bias, - no_bias=no_bias) + no_bias=no_bias, name=name) elif weight is not None or bias is None: net = mx.sym.Convolution(data=net, num_filter=channels, kernel=filter_dimension, stride=stride, weight=weight, - no_bias=no_bias) + no_bias=no_bias, name=name) else: net = mx.sym.Convolution(data=net, num_filter=channels, kernel=filter_dimension, stride=stride, weight=weight, - bias=bias, no_bias=no_bias) + bias=bias, no_bias=no_bias, name=name) net = mx.sym.Activation(data=net, act_type=act_type) return net diff --git a/example/speech_recognition/stt_layer_fc.py b/example/speech_recognition/stt_layer_fc.py index b3db2034a3ad..b3db1b163ffa 100644 --- a/example/speech_recognition/stt_layer_fc.py +++ b/example/speech_recognition/stt_layer_fc.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import mxnet as mx from stt_layer_batchnorm import batchnorm @@ -8,29 +25,30 @@ def fc(net, act_type, weight=None, bias=None, - no_bias=False + no_bias=False, + name=None ): # when weight and bias doesn't have specific name if weight is None and bias is None: - net = mx.sym.FullyConnected(data=net, num_hidden=num_hidden, no_bias=no_bias) + net = mx.sym.FullyConnected(data=net, num_hidden=num_hidden, no_bias=no_bias, name=name) # when weight doesn't have specific name but bias has elif weight is None and bias is not None: if no_bias: - net = mx.sym.FullyConnected(data=net, num_hidden=num_hidden, no_bias=no_bias) + net = mx.sym.FullyConnected(data=net, num_hidden=num_hidden, no_bias=no_bias, name=name) else: - net = mx.sym.FullyConnected(data=net, num_hidden=num_hidden, bias=bias, no_bias=no_bias) + net = mx.sym.FullyConnected(data=net, num_hidden=num_hidden, bias=bias, no_bias=no_bias, name=name) # when bias doesn't have specific name but weight has elif weight is not None and bias is None: - net = mx.sym.FullyConnected(data=net, num_hidden=num_hidden, weight=weight, no_bias=no_bias) + net = mx.sym.FullyConnected(data=net, num_hidden=num_hidden, weight=weight, no_bias=no_bias, name=name) # when weight and bias specific name else: if no_bias: - net = mx.sym.FullyConnected(data=net, num_hidden=num_hidden, weight=weight, no_bias=no_bias) + net = mx.sym.FullyConnected(data=net, num_hidden=num_hidden, weight=weight, no_bias=no_bias, name=name) else: - net = mx.sym.FullyConnected(data=net, num_hidden=num_hidden, weight=weight, bias=bias, no_bias=no_bias) + net = mx.sym.FullyConnected(data=net, num_hidden=num_hidden, weight=weight, bias=bias, no_bias=no_bias, name=name) # activation if act_type is not None: - net = mx.sym.Activation(data=net, act_type=act_type) + net = mx.sym.Activation(data=net, act_type=act_type, name="%s_activation" % name) return net @@ -41,7 +59,7 @@ def sequence_fc(net, num_hidden_list=[], act_type_list=[], is_batchnorm=False, - dropout_rate=0 + dropout_rate=0, ): if num_layer == len(num_hidden_list) == len(act_type_list): if num_layer > 0: @@ -81,13 +99,16 @@ def sequence_fc(net, num_hidden=num_hidden_list[layer_index], act_type=None, weight=weight_list[layer_index], - no_bias=is_batchnorm + no_bias=is_batchnorm, + name="%s_t%d_l%d_fc" % (prefix, seq_index, layer_index) ) # last layer doesn't have batchnorm hidden = batchnorm(net=hidden, gamma=gamma_list[layer_index], - beta=beta_list[layer_index]) - hidden = mx.sym.Activation(data=hidden, act_type=act_type_list[layer_index]) + beta=beta_list[layer_index], + name="%s_t%d_l%d_batchnorm" % (prefix, seq_index, layer_index)) + hidden = mx.sym.Activation(data=hidden, act_type=act_type_list[layer_index], + name="%s_t%d_l%d_activation" % (prefix, seq_index, layer_index)) else: hidden = fc(net=hidden, num_hidden=num_hidden_list[layer_index], diff --git a/example/speech_recognition/stt_layer_gru.py b/example/speech_recognition/stt_layer_gru.py index 8b044746dfcf..0dd132825235 100644 --- a/example/speech_recognition/stt_layer_gru.py +++ b/example/speech_recognition/stt_layer_gru.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + from collections import namedtuple import mxnet as mx @@ -15,7 +32,7 @@ "param_blocks"]) -def gru(num_hidden, indata, prev_state, param, seqidx, layeridx, dropout=0., is_batchnorm=False, gamma=None, beta=None): +def gru(num_hidden, indata, prev_state, param, seqidx, layeridx, dropout=0., is_batchnorm=False, gamma=None, beta=None, name=None): """ GRU Cell symbol Reference: @@ -31,7 +48,10 @@ def gru(num_hidden, indata, prev_state, param, seqidx, layeridx, dropout=0., is_ name="t%d_l%d_gates_i2h" % (seqidx, layeridx)) if is_batchnorm: - i2h = batchnorm(net=i2h, gamma=gamma, beta=beta) + if name is not None: + i2h = batchnorm(net=i2h, gamma=gamma, beta=beta, name="%s_batchnorm" % name) + else: + i2h = batchnorm(net=i2h, gamma=gamma, beta=beta) h2h = mx.sym.FullyConnected(data=prev_state.h, weight=param.gates_h2h_weight, bias=param.gates_h2h_bias, @@ -53,15 +73,15 @@ def gru(num_hidden, indata, prev_state, param, seqidx, layeridx, dropout=0., is_ weight=param.trans_h2h_weight, bias=param.trans_h2h_bias, num_hidden=num_hidden, - name="t%d_l%d_trans_i2h" % (seqidx, layeridx)) + name="t%d_l%d_trans_h2h" % (seqidx, layeridx)) h_trans = htrans_i2h + htrans_h2h h_trans_active = mx.sym.Activation(h_trans, act_type="tanh") next_h = prev_state.h + update_gate * (h_trans_active - prev_state.h) return GRUState(h=next_h) -def gru_unroll(net, num_gru_layer, seq_len, num_hidden_gru_list, dropout=0., is_batchnorm=False, prefix="", - direction="forward"): +def gru_unroll(net, num_gru_layer, seq_len, num_hidden_gru_list, dropout=0., is_batchnorm=False, prefix="", + direction="forward", is_bucketing=False): if num_gru_layer > 0: param_cells = [] last_states = [] @@ -81,9 +101,14 @@ def gru_unroll(net, num_gru_layer, seq_len, num_hidden_gru_list, dropout=0., is_ if is_batchnorm: batchnorm_gamma = [] batchnorm_beta = [] - for seqidx in range(seq_len): - batchnorm_gamma.append(mx.sym.Variable(prefix + "t%d_i2h_gamma" % seqidx)) - batchnorm_beta.append(mx.sym.Variable(prefix + "t%d_i2h_beta" % seqidx)) + if is_bucketing: + for l in range(num_gru_layer): + batchnorm_gamma.append(mx.sym.Variable(prefix + "l%d_i2h_gamma" % l)) + batchnorm_beta.append(mx.sym.Variable(prefix + "l%d_i2h_beta" % l)) + else: + for seqidx in range(seq_len): + batchnorm_gamma.append(mx.sym.Variable(prefix + "t%d_i2h_gamma" % seqidx)) + batchnorm_beta.append(mx.sym.Variable(prefix + "t%d_i2h_beta" % seqidx)) hidden_all = [] for seqidx in range(seq_len): @@ -103,19 +128,33 @@ def gru_unroll(net, num_gru_layer, seq_len, num_hidden_gru_list, dropout=0., is_ else: dp_ratio = dropout if is_batchnorm: - next_state = gru(num_hidden_gru_list[i], indata=hidden, - prev_state=last_states[i], - param=param_cells[i], - seqidx=k, layeridx=i, dropout=dp_ratio, - is_batchnorm=is_batchnorm, - gamma=batchnorm_gamma[k], - beta=batchnorm_beta[k]) + if is_bucketing: + next_state = gru(num_hidden_gru_list[i], indata=hidden, + prev_state=last_states[i], + param=param_cells[i], + seqidx=k, layeridx=i, dropout=dp_ratio, + is_batchnorm=is_batchnorm, + gamma=batchnorm_gamma[i], + beta=batchnorm_beta[i], + name=prefix + ("t%d_l%d" % (seqidx, i)) + ) + else: + next_state = gru(num_hidden_gru_list[i], indata=hidden, + prev_state=last_states[i], + param=param_cells[i], + seqidx=k, layeridx=i, dropout=dp_ratio, + is_batchnorm=is_batchnorm, + gamma=batchnorm_gamma[k], + beta=batchnorm_beta[k], + name=prefix + ("t%d_l%d" % (seqidx, i)) + ) else: next_state = gru(num_hidden_gru_list[i], indata=hidden, prev_state=last_states[i], param=param_cells[i], seqidx=k, layeridx=i, dropout=dp_ratio, - is_batchnorm=is_batchnorm) + is_batchnorm=is_batchnorm, + name=prefix) hidden = next_state.h last_states[i] = next_state # decoder @@ -133,7 +172,7 @@ def gru_unroll(net, num_gru_layer, seq_len, num_hidden_gru_list, dropout=0., is_ return net -def bi_gru_unroll(net, num_gru_layer, seq_len, num_hidden_gru_list, dropout=0., is_batchnorm=False): +def bi_gru_unroll(net, num_gru_layer, seq_len, num_hidden_gru_list, dropout=0., is_batchnorm=False, is_bucketing=False): if num_gru_layer > 0: net_forward = gru_unroll(net=net, num_gru_layer=num_gru_layer, @@ -142,7 +181,8 @@ def bi_gru_unroll(net, num_gru_layer, seq_len, num_hidden_gru_list, dropout=0., dropout=dropout, is_batchnorm=is_batchnorm, prefix="forward_", - direction="forward") + direction="forward", + is_bucketing=is_bucketing) net_backward = gru_unroll(net=net, num_gru_layer=num_gru_layer, seq_len=seq_len, @@ -150,7 +190,8 @@ def bi_gru_unroll(net, num_gru_layer, seq_len, num_hidden_gru_list, dropout=0., dropout=dropout, is_batchnorm=is_batchnorm, prefix="backward_", - direction="backward") + direction="backward", + is_bucketing=is_bucketing) hidden_all = [] for i in range(seq_len): hidden_all.append(mx.sym.Concat(*[net_forward[i], net_backward[i]], dim=1)) @@ -159,7 +200,7 @@ def bi_gru_unroll(net, num_gru_layer, seq_len, num_hidden_gru_list, dropout=0., def bi_gru_unroll_two_input_two_output(net1, net2, num_gru_layer, seq_len, num_hidden_gru_list, dropout=0., - is_batchnorm=False): + is_batchnorm=False, is_bucketing=False): if num_gru_layer > 0: net_forward = gru_unroll(net=net1, num_gru_layer=num_gru_layer, @@ -168,7 +209,8 @@ def bi_gru_unroll_two_input_two_output(net1, net2, num_gru_layer, seq_len, num_h dropout=dropout, is_batchnorm=is_batchnorm, prefix="forward_", - direction="forward") + direction="forward", + is_bucketing=is_bucketing) net_backward = gru_unroll(net=net2, num_gru_layer=num_gru_layer, seq_len=seq_len, @@ -176,7 +218,8 @@ def bi_gru_unroll_two_input_two_output(net1, net2, num_gru_layer, seq_len, num_h dropout=dropout, is_batchnorm=is_batchnorm, prefix="backward_", - direction="backward") + direction="backward", + is_bucketing=is_bucketing) return net_forward, net_backward else: return net1, net2 diff --git a/example/speech_recognition/stt_layer_lstm.py b/example/speech_recognition/stt_layer_lstm.py index 19e37369b1b0..4adbbd4bec1f 100644 --- a/example/speech_recognition/stt_layer_lstm.py +++ b/example/speech_recognition/stt_layer_lstm.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # pylint:skip-file from collections import namedtuple @@ -16,7 +33,7 @@ "param_blocks"]) -def vanilla_lstm(num_hidden, indata, prev_state, param, seqidx, layeridx, is_batchnorm=False, gamma=None, beta=None): +def vanilla_lstm(num_hidden, indata, prev_state, param, seqidx, layeridx, is_batchnorm=False, gamma=None, beta=None, name=None): """LSTM Cell symbol""" i2h = mx.sym.FullyConnected(data=indata, weight=param.i2h_weight, @@ -24,7 +41,10 @@ def vanilla_lstm(num_hidden, indata, prev_state, param, seqidx, layeridx, is_bat num_hidden=num_hidden * 4, name="t%d_l%d_i2h" % (seqidx, layeridx)) if is_batchnorm: - i2h = batchnorm(net=i2h, gamma=gamma, beta=beta) + if name is not None: + i2h = batchnorm(net=i2h, gamma=gamma, beta=beta, name="%s_batchnorm" % name) + else: + i2h = batchnorm(net=i2h, gamma=gamma, beta=beta) h2h = mx.sym.FullyConnected(data=prev_state.h, weight=param.h2h_weight, bias=param.h2h_bias, @@ -43,7 +63,7 @@ def vanilla_lstm(num_hidden, indata, prev_state, param, seqidx, layeridx, is_bat def lstm(num_hidden, indata, prev_state, param, seqidx, layeridx, dropout=0., num_hidden_proj=0, is_batchnorm=False, - gamma=None, beta=None): + gamma=None, beta=None, name=None): """LSTM Cell symbol""" # dropout input if dropout > 0.: @@ -55,7 +75,10 @@ def lstm(num_hidden, indata, prev_state, param, seqidx, layeridx, dropout=0., nu num_hidden=num_hidden * 4, name="t%d_l%d_i2h" % (seqidx, layeridx)) if is_batchnorm: - i2h = batchnorm(net=i2h, gamma=gamma, beta=beta) + if name is not None: + i2h = batchnorm(net=i2h, gamma=gamma, beta=beta, name="%s_batchnorm" % name) + else: + i2h = batchnorm(net=i2h, gamma=gamma, beta=beta) h2h = mx.sym.FullyConnected(data=prev_state.h, weight=param.h2h_weight, @@ -96,7 +119,7 @@ def lstm(num_hidden, indata, prev_state, param, seqidx, layeridx, dropout=0., nu def lstm_unroll(net, num_lstm_layer, seq_len, num_hidden_lstm_list, dropout=0., num_hidden_proj=0, - lstm_type='fc_lstm', is_batchnorm=False, prefix="", direction="forward"): + lstm_type='fc_lstm', is_batchnorm=False, prefix="", direction="forward", is_bucketing=False): if num_lstm_layer > 0: param_cells = [] last_states = [] @@ -121,9 +144,14 @@ def lstm_unroll(net, num_lstm_layer, seq_len, num_hidden_lstm_list, dropout=0., if is_batchnorm: batchnorm_gamma = [] batchnorm_beta = [] - for seqidx in range(seq_len): - batchnorm_gamma.append(mx.sym.Variable(prefix + "t%d_i2h_gamma" % seqidx)) - batchnorm_beta.append(mx.sym.Variable(prefix + "t%d_i2h_beta" % seqidx)) + if is_bucketing: + for l in range(num_lstm_layer): + batchnorm_gamma.append(mx.sym.Variable(prefix + "l%d_i2h_gamma" % l)) + batchnorm_beta.append(mx.sym.Variable(prefix + "l%d_i2h_beta" % l)) + else: + for seqidx in range(seq_len): + batchnorm_gamma.append(mx.sym.Variable(prefix + "t%d_i2h_gamma" % seqidx)) + batchnorm_beta.append(mx.sym.Variable(prefix + "t%d_i2h_beta" % seqidx)) hidden_all = [] for seqidx in range(seq_len): @@ -145,18 +173,20 @@ def lstm_unroll(net, num_lstm_layer, seq_len, num_hidden_lstm_list, dropout=0., if lstm_type == 'fc_lstm': if is_batchnorm: - next_state = lstm(num_hidden_lstm_list[i], - indata=hidden, - prev_state=last_states[i], - param=param_cells[i], - seqidx=k, - layeridx=i, - dropout=dp, - num_hidden_proj=num_hidden_proj, - is_batchnorm=is_batchnorm, - gamma=batchnorm_gamma[k], - beta=batchnorm_beta[k] - ) + if is_bucketing: + next_state = lstm(num_hidden_lstm_list[i], + indata=hidden, + prev_state=last_states[i], + param=param_cells[i], + seqidx=k, + layeridx=i, + dropout=dp, + num_hidden_proj=num_hidden_proj, + is_batchnorm=is_batchnorm, + gamma=batchnorm_gamma[i], + beta=batchnorm_beta[i], + name=prefix + ("t%d_l%d" % (seqidx, i)) + ) else: next_state = lstm(num_hidden_lstm_list[i], indata=hidden, @@ -166,7 +196,8 @@ def lstm_unroll(net, num_lstm_layer, seq_len, num_hidden_lstm_list, dropout=0., layeridx=i, dropout=dp, num_hidden_proj=num_hidden_proj, - is_batchnorm=is_batchnorm + is_batchnorm=is_batchnorm, + name=prefix + ("t%d_l%d" % (seqidx, i)) ) elif lstm_type == 'vanilla_lstm': if is_batchnorm: @@ -175,15 +206,17 @@ def lstm_unroll(net, num_lstm_layer, seq_len, num_hidden_lstm_list, dropout=0., param=param_cells[i], seqidx=k, layeridx=i, is_batchnorm=is_batchnorm, - gamma=batchnorm_gamma[k], - beta=batchnorm_beta[k] + gamma=batchnorm_gamma[i], + beta=batchnorm_beta[i], + name=prefix + ("t%d_l%d" % (seqidx, i)) ) else: next_state = vanilla_lstm(num_hidden_lstm_list[i], indata=hidden, prev_state=last_states[i], param=param_cells[i], seqidx=k, layeridx=i, - is_batchnorm=is_batchnorm + is_batchnorm=is_batchnorm, + name=prefix + ("t%d_l%d" % (seqidx, i)) ) else: raise Exception("lstm type %s error" % lstm_type) @@ -206,7 +239,7 @@ def lstm_unroll(net, num_lstm_layer, seq_len, num_hidden_lstm_list, dropout=0., def bi_lstm_unroll(net, num_lstm_layer, seq_len, num_hidden_lstm_list, dropout=0., num_hidden_proj=0, - lstm_type='fc_lstm', is_batchnorm=False): + lstm_type='fc_lstm', is_batchnorm=False, is_bucketing=False): if num_lstm_layer > 0: net_forward = lstm_unroll(net=net, num_lstm_layer=num_lstm_layer, @@ -217,7 +250,8 @@ def bi_lstm_unroll(net, num_lstm_layer, seq_len, num_hidden_lstm_list, dropout=0 lstm_type=lstm_type, is_batchnorm=is_batchnorm, prefix="forward_", - direction="forward") + direction="forward", + is_bucketing=is_bucketing) net_backward = lstm_unroll(net=net, num_lstm_layer=num_lstm_layer, @@ -228,7 +262,8 @@ def bi_lstm_unroll(net, num_lstm_layer, seq_len, num_hidden_lstm_list, dropout=0 lstm_type=lstm_type, is_batchnorm=is_batchnorm, prefix="backward_", - direction="backward") + direction="backward", + is_bucketing=is_bucketing) hidden_all = [] for i in range(seq_len): hidden_all.append(mx.sym.Concat(*[net_forward[i], net_backward[i]], dim=1)) @@ -239,7 +274,9 @@ def bi_lstm_unroll(net, num_lstm_layer, seq_len, num_hidden_lstm_list, dropout=0 # bilistm_2to1 def bi_lstm_unroll_two_input_two_output(net1, net2, num_lstm_layer, seq_len, num_hidden_lstm_list, dropout=0., num_hidden_proj=0, - lstm_type='fc_lstm', is_batchnorm=False): + lstm_type='fc_lstm', + is_batchnorm=False, + is_bucketing=False): if num_lstm_layer > 0: net_forward = lstm_unroll(net=net1, num_lstm_layer=num_lstm_layer, @@ -250,7 +287,8 @@ def bi_lstm_unroll_two_input_two_output(net1, net2, num_lstm_layer, seq_len, num lstm_type=lstm_type, is_batchnorm=is_batchnorm, prefix="forward_", - direction="forward") + direction="forward", + is_bucketing=is_bucketing) net_backward = lstm_unroll(net=net2, num_lstm_layer=num_lstm_layer, @@ -261,7 +299,8 @@ def bi_lstm_unroll_two_input_two_output(net1, net2, num_lstm_layer, seq_len, num lstm_type=lstm_type, is_batchnorm=is_batchnorm, prefix="backward_", - direction="backward") + direction="backward", + is_bucketing=is_bucketing) return net_forward, net_backward else: return net1, net2 diff --git a/example/speech_recognition/stt_layer_slice.py b/example/speech_recognition/stt_layer_slice.py index 6b434ec1049b..ac7eae9ae884 100644 --- a/example/speech_recognition/stt_layer_slice.py +++ b/example/speech_recognition/stt_layer_slice.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import mxnet as mx diff --git a/example/speech_recognition/stt_layer_warpctc.py b/example/speech_recognition/stt_layer_warpctc.py index 9f97adfe5de1..c821f9c666ab 100644 --- a/example/speech_recognition/stt_layer_warpctc.py +++ b/example/speech_recognition/stt_layer_warpctc.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import mxnet as mx diff --git a/example/speech_recognition/stt_metric.py b/example/speech_recognition/stt_metric.py index 0fc2bd11d906..fc1916b40c38 100644 --- a/example/speech_recognition/stt_metric.py +++ b/example/speech_recognition/stt_metric.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import mxnet as mx import numpy as np @@ -19,12 +36,11 @@ def check_label_shapes(labels, preds, shape=0): class STTMetric(mx.metric.EvalMetric): - def __init__(self, batch_size, num_gpu, seq_length, is_epoch_end=False, is_logging=True): + def __init__(self, batch_size, num_gpu, is_epoch_end=False, is_logging=True): super(STTMetric, self).__init__('STTMetric') self.batch_size = batch_size self.num_gpu = num_gpu - self.seq_length = seq_length self.total_n_label = 0 self.total_l_dist = 0 self.is_epoch_end = is_epoch_end @@ -37,15 +53,17 @@ def update(self, labels, preds): log = LogUtil().getlogger() labelUtil = LabelUtil.getInstance() self.batch_loss = 0. + for label, pred in zip(labels, preds): label = label.asnumpy() pred = pred.asnumpy() - for i in range(int(int(self.batch_size) / int(self.num_gpu))): + seq_length = len(pred) / int(int(self.batch_size) / int(self.num_gpu)) + for i in range(int(int(self.batch_size) / int(self.num_gpu))): l = remove_blank(label[i]) p = [] - for k in range(int(self.seq_length)): + for k in range(int(seq_length)): p.append(np.argmax(pred[k * int(int(self.batch_size) / int(self.num_gpu)) + i])) p = pred_best(p) @@ -60,7 +78,7 @@ def update(self, labels, preds): self.num_inst += 1 self.sum_metric += this_cer if self.is_epoch_end: - loss = ctc_loss(l, pred, i, int(self.seq_length), int(self.batch_size), int(self.num_gpu)) + loss = ctc_loss(l, pred, i, int(seq_length), int(self.batch_size), int(self.num_gpu)) self.batch_loss += loss if self.is_logging: log.info("loss: %f " % loss) diff --git a/example/speech_recognition/stt_utils.py b/example/speech_recognition/stt_utils.py index 6a32f0e57c2d..0539d59f37af 100644 --- a/example/speech_recognition/stt_utils.py +++ b/example/speech_recognition/stt_utils.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import logging import os import os.path @@ -92,7 +109,7 @@ def spectrogram(samples, fft_length=256, sample_rate=2, hop_length=128): def spectrogram_from_file(filename, step=10, window=20, max_freq=None, - eps=1e-14, overwrite=False): + eps=1e-14, overwrite=False, save_feature_as_csvfile=False): """ Calculate the log of linear spectrogram from FFT energy Params: filename (str): Path to the audio file @@ -126,7 +143,8 @@ def spectrogram_from_file(filename, step=10, window=20, max_freq=None, ind = np.where(freqs <= max_freq)[0][-1] + 1 res = np.transpose(np.log(pxx[:ind, :] + eps)) - np.savetxt(csvfilename, res) + if save_feature_as_csvfile: + np.savetxt(csvfilename, res) return res else: return np.loadtxt(csvfilename) diff --git a/example/speech_recognition/train.py b/example/speech_recognition/train.py index 37f00fc4dd90..0d04e4e47a5f 100644 --- a/example/speech_recognition/train.py +++ b/example/speech_recognition/train.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import sys sys.path.insert(0, "../../python") @@ -7,7 +24,9 @@ from stt_metric import STTMetric #tensorboard setting from tensorboard import SummaryWriter -import numpy as np +import json +from stt_bucketing_module import STTBucketingModule + def get_initializer(args): @@ -28,6 +47,7 @@ def __init__(self, learning_rate=0.001): def __call__(self, num_update): return self.learning_rate + def do_training(args, module, data_train, data_val, begin_epoch=0): from distutils.dir_util import mkpath from log_util import LogUtil @@ -35,7 +55,7 @@ def do_training(args, module, data_train, data_val, begin_epoch=0): log = LogUtil().getlogger() mkpath(os.path.dirname(get_checkpoint_path(args))) - seq_len = args.config.get('arch', 'max_t_count') + #seq_len = args.config.get('arch', 'max_t_count') batch_size = args.config.getint('common', 'batch_size') save_checkpoint_every_n_epoch = args.config.getint('common', 'save_checkpoint_every_n_epoch') save_checkpoint_every_n_batch = args.config.getint('common', 'save_checkpoint_every_n_batch') @@ -44,27 +64,48 @@ def do_training(args, module, data_train, data_val, begin_epoch=0): contexts = parse_contexts(args) num_gpu = len(contexts) - eval_metric = STTMetric(batch_size=batch_size, num_gpu=num_gpu, seq_length=seq_len,is_logging=enable_logging_validation_metric,is_epoch_end=True) + eval_metric = STTMetric(batch_size=batch_size, num_gpu=num_gpu, is_logging=enable_logging_validation_metric,is_epoch_end=True) # tensorboard setting - loss_metric = STTMetric(batch_size=batch_size, num_gpu=num_gpu, seq_length=seq_len,is_logging=enable_logging_train_metric,is_epoch_end=False) + loss_metric = STTMetric(batch_size=batch_size, num_gpu=num_gpu, is_logging=enable_logging_train_metric,is_epoch_end=False) - optimizer = args.config.get('train', 'optimizer') - momentum = args.config.getfloat('train', 'momentum') + optimizer = args.config.get('optimizer', 'optimizer') learning_rate = args.config.getfloat('train', 'learning_rate') learning_rate_annealing = args.config.getfloat('train', 'learning_rate_annealing') mode = args.config.get('common', 'mode') num_epoch = args.config.getint('train', 'num_epoch') - clip_gradient = args.config.getfloat('train', 'clip_gradient') - weight_decay = args.config.getfloat('train', 'weight_decay') + clip_gradient = args.config.getfloat('optimizer', 'clip_gradient') + weight_decay = args.config.getfloat('optimizer', 'weight_decay') save_optimizer_states = args.config.getboolean('train', 'save_optimizer_states') show_every = args.config.getint('train', 'show_every') + optimizer_params_dictionary = json.loads(args.config.get('optimizer', 'optimizer_params_dictionary')) + kvstore_option = args.config.get('common', 'kvstore_option') n_epoch=begin_epoch + is_bucketing = args.config.getboolean('arch', 'is_bucketing') if clip_gradient == 0: clip_gradient = None + if is_bucketing and mode == 'load': + model_file = args.config.get('common', 'model_file') + model_name = os.path.splitext(model_file)[0] + model_num_epoch = int(model_name[-4:]) + + model_path = 'checkpoints/' + str(model_name[:-5]) + symbol, data_names, label_names = module(1600) + model = STTBucketingModule( + sym_gen=module, + default_bucket_key=data_train.default_bucket_key, + context=contexts) + data_train.reset() - module.bind(data_shapes=data_train.provide_data, + model.bind(data_shapes=data_train.provide_data, + label_shapes=data_train.provide_label, + for_training=True) + _, arg_params, aux_params = mx.model.load_checkpoint(model_path, model_num_epoch) + model.set_params(arg_params, aux_params) + module = model + else: + module.bind(data_shapes=data_train.provide_data, label_shapes=data_train.provide_label, for_training=True) @@ -75,41 +116,32 @@ def do_training(args, module, data_train, data_val, begin_epoch=0): lr_scheduler = SimpleLRScheduler(learning_rate=learning_rate) def reset_optimizer(force_init=False): - if optimizer == "sgd": - module.init_optimizer(kvstore='device', - optimizer=optimizer, - optimizer_params={'lr_scheduler': lr_scheduler, - 'momentum': momentum, - 'clip_gradient': clip_gradient, - 'wd': weight_decay}, - force_init=force_init) - elif optimizer == "adam": - module.init_optimizer(kvstore='device', - optimizer=optimizer, - optimizer_params={'lr_scheduler': lr_scheduler, - #'momentum': momentum, - 'clip_gradient': clip_gradient, - 'wd': weight_decay}, - force_init=force_init) - else: - raise Exception('Supported optimizers are sgd and adam. If you want to implement others define them in train.py') + optimizer_params = {'lr_scheduler': lr_scheduler, + 'clip_gradient': clip_gradient, + 'wd': weight_decay} + optimizer_params.update(optimizer_params_dictionary) + module.init_optimizer(kvstore=kvstore_option, + optimizer=optimizer, + optimizer_params=optimizer_params, + force_init=force_init) if mode == "train": reset_optimizer(force_init=True) else: reset_optimizer(force_init=False) + data_train.reset() + data_train.is_first_epoch = True #tensorboard setting tblog_dir = args.config.get('common', 'tensorboard_log_dir') summary_writer = SummaryWriter(tblog_dir) + while True: if n_epoch >= num_epoch: break - loss_metric.reset() log.info('---------train---------') for nbatch, data_batch in enumerate(data_train): - module.forward_backward(data_batch) module.update() # tensorboard setting @@ -136,6 +168,7 @@ def reset_optimizer(force_init=False): assert curr_acc is not None, 'cannot find Acc_exclude_padding in eval metric' data_train.reset() + data_train.is_first_epoch = False # tensorboard setting train_cer, train_n_label, train_l_dist, train_ctc_loss = loss_metric.get_name_value() diff --git a/example/ssd/README.md b/example/ssd/README.md index 8703a7cbbf73..5759fca611f2 100644 --- a/example/ssd/README.md +++ b/example/ssd/README.md @@ -17,6 +17,8 @@ remarkable traits of MXNet. Due to the permission issue, this example is maintained in this [repository](https://github.com/zhreshold/mxnet-ssd) separately. You can use the link regarding specific per example [issues](https://github.com/zhreshold/mxnet-ssd/issues). ### What's new +* Added multiple trained models. +* Added a much simpler way to compose network from mainstream classification networks (resnet, inception...) and [Guide](symbol/README.md). * Update to the latest version according to caffe version, with 5% mAP increase. * Use C++ record iterator based on back-end multi-thread engine to achieve huge speed up on multi-gpu environments. * Monitor validation mAP during training. @@ -30,11 +32,12 @@ Due to the permission issue, this example is maintained in this [repository](htt ![demo3](https://cloud.githubusercontent.com/assets/3307514/19171086/a9346842-8be0-11e6-8011-c17716b22ad3.png) ### mAP -| Model | Training data | Test data | mAP | -|:-----------------:|:----------------:|:---------:|:----:| -| [VGG16_reduced 300x300](https://github.com/zhreshold/mxnet-ssd/releases/download/v0.5-beta/vgg16_ssd_300_voc0712_trainval.zip) | VOC07+12 trainval| VOC07 test| 77.8| -| [VGG16_reduced 512x512](https://github.com/zhreshold/mxnet-ssd/releases/download/v0.5-beta/vgg16_ssd_512_voc0712_trainval.zip) | VOC07+12 trainval | VOC07 test| 79.9| -*More to be added* +| Model | Training data | Test data | mAP | Note | +|:-----------------:|:----------------:|:---------:|:----:|:-----| +| [VGG16_reduced 300x300](https://github.com/zhreshold/mxnet-ssd/releases/download/v0.5-beta/vgg16_ssd_300_voc0712_trainval.zip) | VOC07+12 trainval| VOC07 test| 77.8| fast | +| [VGG16_reduced 512x512](https://github.com/zhreshold/mxnet-ssd/releases/download/v0.5-beta/vgg16_ssd_512_voc0712_trainval.zip) | VOC07+12 trainval | VOC07 test| 79.9| slow | +| [Inception-v3 512x512](https://github.com/zhreshold/mxnet-ssd/releases/download/v0.6/inceptionv3_ssd_512_voc0712_trainval.zip) | VOC07+12 trainval| VOC07 test| 78.9 | fastest | +| [Resnet-50 512x512](https://github.com/zhreshold/mxnet-ssd/releases/download/v0.6/resnet50_ssd_512_voc0712_trainval.zip) | VOC07+12 trainval| VOC07 test| 78.9 | fast | ### Speed | Model | GPU | CUDNN | Batch-size | FPS* | @@ -65,13 +68,14 @@ Remember to enable CUDA if you want to be able to train, since CPU training is insanely slow. Using CUDNN is optional, but highly recommended. ### Try the demo -* Download the pretrained model: [`ssd_300_voc_0712.zip`](https://github.com/zhreshold/mxnet-ssd/releases/download/v0.5-beta/vgg16_ssd_300_voc0712_trainval.zip), and extract to `model/` directory. +* Download the pretrained model: [`ssd_resnet50_0712.zip`](https://github.com/zhreshold/mxnet-ssd/releases/download/v0.6/resnet50_ssd_512_voc0712_trainval.zip), and extract to `model/` directory. * Run ``` -# cd /path/to/mxnet/example/ssd -python demo.py +# cd /path/to/mxnet-ssd +python demo.py --gpu 0 # play with examples: python demo.py --epoch 0 --images ./data/demo/dog.jpg --thresh 0.5 +python demo.py --cpu --network resnet50 --data-shape 512 # wait for library to load for the first time ``` * Check `python demo.py --help` for more options. @@ -93,7 +97,7 @@ tar -xvf VOCtrainval_11-May-2012.tar tar -xvf VOCtrainval_06-Nov-2007.tar tar -xvf VOCtest_06-Nov-2007.tar ``` -* We are goint to use `trainval` set in VOC2007/2012 as a common strategy. +* We are going to use `trainval` set in VOC2007/2012 as a common strategy. The suggested directory structure is to store `VOC2007` and `VOC2012` directories in the same `VOCdevkit` folder. * Then link `VOCdevkit` folder to `data/VOCdevkit` by default: @@ -114,12 +118,12 @@ python tools/prepare_dataset.py --dataset pascal --year 2007 --set test --target # cd /path/to/mxnet/example/ssd python train.py ``` -* By default, this example will use `batch-size=32` and `learning_rate=0.004`. +* By default, this example will use `batch-size=32` and `learning_rate=0.002`. You might need to change the parameters a bit if you have different configurations. Check `python train.py --help` for more training options. For example, if you have 4 GPUs, use: ``` # note that a perfect training parameter set is yet to be discovered for multi-GPUs -python train.py --gpus 0,1,2,3 --batch-size 128 --lr 0.001 +python train.py --gpus 0,1,2,3 --batch-size 32 ``` ### Evalute trained model @@ -148,3 +152,12 @@ python convert_model.py deploy.prototxt name_of_pretrained_caffe_model.caffemode python demo.py --prefix ssd_converted --epoch 1 --deploy ``` There is no guarantee that conversion will always work, but at least it's good for now. + +### Legacy models +Since the new interface for composing network is introduced, the old models have inconsistent names for weights. +You can still load the previous model by rename the symbol to `legacy_xxx.py` +and call with `python train/demo.py --network legacy_xxx ` +For example: +``` +python demo.py --network 'legacy_vgg16_ssd_300.py' --prefix model/ssd_300 --epoch 0 +``` diff --git a/example/ssd/config/config.py b/example/ssd/config/config.py index 931ad16f14eb..38a07b5e655d 100644 --- a/example/ssd/config/config.py +++ b/example/ssd/config/config.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import os from utils import DotDict, namedtuple_with_defaults, zip_namedtuple, config_as_dict @@ -53,7 +70,7 @@ cfg.train.rand_mirror_prob = 0.5 cfg.train.shuffle = True cfg.train.seed = 233 -cfg.train.preprocess_threads = 6 +cfg.train.preprocess_threads = 48 cfg.train = config_as_dict(cfg.train) # convert to normal dict # validation @@ -64,4 +81,5 @@ cfg.valid.rand_mirror_prob = 0 cfg.valid.shuffle = False cfg.valid.seed = 0 +cfg.valid.preprocess_threads = 32 cfg.valid = config_as_dict(cfg.valid) # convert to normal dict diff --git a/example/ssd/config/utils.py b/example/ssd/config/utils.py index 1d66655e8bee..5c8af6a4dd93 100644 --- a/example/ssd/config/utils.py +++ b/example/ssd/config/utils.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import collections class DotDict(dict): diff --git a/example/ssd/data/demo/download_demo_images.py b/example/ssd/data/demo/download_demo_images.py index 8546aa5e939e..554ba7e4e1aa 100755 --- a/example/ssd/data/demo/download_demo_images.py +++ b/example/ssd/data/demo/download_demo_images.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import os wd = os.path.dirname(os.path.realpath(__file__)) diff --git a/example/ssd/dataset/concat_db.py b/example/ssd/dataset/concat_db.py index da9e151054c3..cb6c99e34fc1 100644 --- a/example/ssd/dataset/concat_db.py +++ b/example/ssd/dataset/concat_db.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + from imdb import Imdb import random diff --git a/example/ssd/dataset/imdb.py b/example/ssd/dataset/imdb.py index 95b082d594d9..4fbb5d85c873 100644 --- a/example/ssd/dataset/imdb.py +++ b/example/ssd/dataset/imdb.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import numpy as np import os.path as osp @@ -14,7 +31,7 @@ def __init__(self, name): self.name = name self.classes = [] self.num_classes = 0 - self.image_set_index = [] + self.image_set_index = None self.num_images = 0 self.labels = None self.padding = 0 @@ -59,9 +76,22 @@ def save_imglist(self, fname=None, root=None, shuffle=False): fname : str saved filename """ + def progress_bar(count, total, suffix=''): + import sys + bar_len = 24 + filled_len = int(round(bar_len * count / float(total))) + + percents = round(100.0 * count / float(total), 1) + bar = '=' * filled_len + '-' * (bar_len - filled_len) + sys.stdout.write('[%s] %s%s ...%s\r' % (bar, percents, '%', suffix)) + sys.stdout.flush() + str_list = [] for index in range(self.num_images): + progress_bar(index, self.num_images) label = self.label_from_index(index) + if label.size < 1: + continue path = self.image_path_from_index(index) if root: path = osp.relpath(path, root) @@ -78,3 +108,20 @@ def save_imglist(self, fname=None, root=None, shuffle=False): f.write(line) else: raise RuntimeError("No image in imdb") + + def _load_class_names(self, filename, dirname): + """ + load class names from text file + + Parameters: + ---------- + filename: str + file stores class names + dirname: str + file directory + """ + full_path = osp.join(dirname, filename) + classes = [] + with open(full_path, 'r') as f: + classes = [l.strip() for l in f.readlines()] + return classes diff --git a/example/ssd/dataset/iterator.py b/example/ssd/dataset/iterator.py index 5cefece1c147..8b6857b94edf 100644 --- a/example/ssd/dataset/iterator.py +++ b/example/ssd/dataset/iterator.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import mxnet as mx import numpy as np import cv2 diff --git a/example/ssd/dataset/mscoco.py b/example/ssd/dataset/mscoco.py new file mode 100644 index 000000000000..ff2a753ddc6f --- /dev/null +++ b/example/ssd/dataset/mscoco.py @@ -0,0 +1,132 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +import os +import numpy as np +from imdb import Imdb +from pycocotools.coco import COCO + + +class Coco(Imdb): + """ + Implementation of Imdb for MSCOCO dataset: https://http://mscoco.org + + Parameters: + ---------- + anno_file : str + annotation file for coco, a json file + image_dir : str + image directory for coco images + shuffle : bool + whether initially shuffle image list + + """ + def __init__(self, anno_file, image_dir, shuffle=True, names='mscoco.names'): + assert os.path.isfile(anno_file), "Invalid annotation file: " + anno_file + basename = os.path.splitext(os.path.basename(anno_file))[0] + super(Coco, self).__init__('coco_' + basename) + self.image_dir = image_dir + + self.classes = self._load_class_names(names, + os.path.join(os.path.dirname(__file__), 'names')) + + self.num_classes = len(self.classes) + self._load_all(anno_file, shuffle) + self.num_images = len(self.image_set_index) + + + def image_path_from_index(self, index): + """ + given image index, find out full path + + Parameters: + ---------- + index: int + index of a specific image + Returns: + ---------- + full path of this image + """ + assert self.image_set_index is not None, "Dataset not initialized" + name = self.image_set_index[index] + image_file = os.path.join(self.image_dir, 'images', name) + assert os.path.isfile(image_file), 'Path does not exist: {}'.format(image_file) + return image_file + + def label_from_index(self, index): + """ + given image index, return preprocessed ground-truth + + Parameters: + ---------- + index: int + index of a specific image + Returns: + ---------- + ground-truths of this image + """ + assert self.labels is not None, "Labels not processed" + return self.labels[index] + + def _load_all(self, anno_file, shuffle): + """ + initialize all entries given annotation json file + + Parameters: + ---------- + anno_file: str + annotation json file + shuffle: bool + whether to shuffle image list + """ + image_set_index = [] + labels = [] + coco = COCO(anno_file) + img_ids = coco.getImgIds() + for img_id in img_ids: + # filename + image_info = coco.loadImgs(img_id)[0] + filename = image_info["file_name"] + subdir = filename.split('_')[1] + height = image_info["height"] + width = image_info["width"] + # label + anno_ids = coco.getAnnIds(imgIds=img_id) + annos = coco.loadAnns(anno_ids) + label = [] + for anno in annos: + cat_id = int(anno["category_id"]) + bbox = anno["bbox"] + assert len(bbox) == 4 + xmin = float(bbox[0]) / width + ymin = float(bbox[1]) / height + xmax = xmin + float(bbox[2]) / width + ymax = ymin + float(bbox[3]) / height + label.append([cat_id, xmin, ymin, xmax, ymax, 0]) + if label: + labels.append(np.array(label)) + image_set_index.append(os.path.join(subdir, filename)) + + if shuffle: + import random + indices = range(len(image_set_index)) + random.shuffle(indices) + image_set_index = [image_set_index[i] for i in indices] + labels = [labels[i] for i in indices] + # store the results + self.image_set_index = image_set_index + self.labels = labels diff --git a/example/ssd/dataset/names/mscoco.names b/example/ssd/dataset/names/mscoco.names new file mode 100644 index 000000000000..ca76c80b5b2c --- /dev/null +++ b/example/ssd/dataset/names/mscoco.names @@ -0,0 +1,80 @@ +person +bicycle +car +motorbike +aeroplane +bus +train +truck +boat +traffic light +fire hydrant +stop sign +parking meter +bench +bird +cat +dog +horse +sheep +cow +elephant +bear +zebra +giraffe +backpack +umbrella +handbag +tie +suitcase +frisbee +skis +snowboard +sports ball +kite +baseball bat +baseball glove +skateboard +surfboard +tennis racket +bottle +wine glass +cup +fork +knife +spoon +bowl +banana +apple +sandwich +orange +broccoli +carrot +hot dog +pizza +donut +cake +chair +sofa +pottedplant +bed +diningtable +toilet +tvmonitor +laptop +mouse +remote +keyboard +cell phone +microwave +oven +toaster +sink +refrigerator +book +clock +vase +scissors +teddy bear +hair drier +toothbrush diff --git a/example/ssd/dataset/names/pascal_voc.names b/example/ssd/dataset/names/pascal_voc.names new file mode 100644 index 000000000000..8420ab35ede7 --- /dev/null +++ b/example/ssd/dataset/names/pascal_voc.names @@ -0,0 +1,20 @@ +aeroplane +bicycle +bird +boat +bottle +bus +car +cat +chair +cow +diningtable +dog +horse +motorbike +person +pottedplant +sheep +sofa +train +tvmonitor diff --git a/example/ssd/dataset/pascal_voc.py b/example/ssd/dataset/pascal_voc.py index 2c61be74e156..d9868905514c 100644 --- a/example/ssd/dataset/pascal_voc.py +++ b/example/ssd/dataset/pascal_voc.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + from __future__ import print_function import os import numpy as np @@ -24,7 +41,8 @@ class PascalVoc(Imdb): is_train : boolean if true, will load annotations """ - def __init__(self, image_set, year, devkit_path, shuffle=False, is_train=False): + def __init__(self, image_set, year, devkit_path, shuffle=False, is_train=False, + names='pascal_voc.names'): super(PascalVoc, self).__init__('voc_' + year + '_' + image_set) self.image_set = image_set self.year = year @@ -33,11 +51,8 @@ def __init__(self, image_set, year, devkit_path, shuffle=False, is_train=False): self.extension = '.jpg' self.is_train = is_train - self.classes = ['aeroplane', 'bicycle', 'bird', 'boat', - 'bottle', 'bus', 'car', 'cat', 'chair', - 'cow', 'diningtable', 'dog', 'horse', - 'motorbike', 'person', 'pottedplant', - 'sheep', 'sofa', 'train', 'tvmonitor'] + self.classes = self._load_class_names(names, + os.path.join(os.path.dirname(__file__), 'names')) self.config = {'use_difficult': True, 'comp_id': 'comp4',} diff --git a/example/ssd/dataset/pycocotools/README.md b/example/ssd/dataset/pycocotools/README.md new file mode 100755 index 000000000000..d358f53105da --- /dev/null +++ b/example/ssd/dataset/pycocotools/README.md @@ -0,0 +1,2 @@ +This is a modified version of https://github.com/pdollar/coco python API. +No `make` is required, but this will not support mask functions. diff --git a/example/ssd/dataset/pycocotools/__init__.py b/example/ssd/dataset/pycocotools/__init__.py new file mode 100755 index 000000000000..2f4e0d430df9 --- /dev/null +++ b/example/ssd/dataset/pycocotools/__init__.py @@ -0,0 +1,18 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +__author__ = 'tylin' diff --git a/example/ssd/dataset/pycocotools/coco.py b/example/ssd/dataset/pycocotools/coco.py new file mode 100755 index 000000000000..4dd54ad69d15 --- /dev/null +++ b/example/ssd/dataset/pycocotools/coco.py @@ -0,0 +1,452 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +__author__ = 'tylin' +__version__ = '2.0' +# Interface for accessing the Microsoft COCO dataset. + +# Microsoft COCO is a large image dataset designed for object detection, +# segmentation, and caption generation. pycocotools is a Python API that +# assists in loading, parsing and visualizing the annotations in COCO. +# Please visit http://mscoco.org/ for more information on COCO, including +# for the data, paper, and tutorials. The exact format of the annotations +# is also described on the COCO website. For example usage of the pycocotools +# please see pycocotools_demo.ipynb. In addition to this API, please download both +# the COCO images and annotations in order to run the demo. + +# An alternative to using the API is to load the annotations directly +# into Python dictionary +# Using the API provides additional utility functions. Note that this API +# supports both *instance* and *caption* annotations. In the case of +# captions not all functions are defined (e.g. categories are undefined). + +# The following API functions are defined: +# COCO - COCO api class that loads COCO annotation file and prepare data structures. +# decodeMask - Decode binary mask M encoded via run-length encoding. +# encodeMask - Encode binary mask M using run-length encoding. +# getAnnIds - Get ann ids that satisfy given filter conditions. +# getCatIds - Get cat ids that satisfy given filter conditions. +# getImgIds - Get img ids that satisfy given filter conditions. +# loadAnns - Load anns with the specified ids. +# loadCats - Load cats with the specified ids. +# loadImgs - Load imgs with the specified ids. +# annToMask - Convert segmentation in an annotation to binary mask. +# showAnns - Display the specified annotations. +# loadRes - Load algorithm results and create API for accessing them. +# download - Download COCO images from mscoco.org server. +# Throughout the API "ann"=annotation, "cat"=category, and "img"=image. +# Help on each functions can be accessed by: "help COCO>function". + +# See also COCO>decodeMask, +# COCO>encodeMask, COCO>getAnnIds, COCO>getCatIds, +# COCO>getImgIds, COCO>loadAnns, COCO>loadCats, +# COCO>loadImgs, COCO>annToMask, COCO>showAnns + +# Microsoft COCO Toolbox. version 2.0 +# Data, paper, and tutorials available at: http://mscoco.org/ +# Code written by Piotr Dollar and Tsung-Yi Lin, 2014. +# Licensed under the Simplified BSD License [see bsd.txt] + +import json +import time +import matplotlib.pyplot as plt +from matplotlib.collections import PatchCollection +from matplotlib.patches import Polygon +import numpy as np +import copy +import itertools +# from . import mask as maskUtils +import os +from collections import defaultdict +import sys +PYTHON_VERSION = sys.version_info[0] +if PYTHON_VERSION == 2: + from urllib import urlretrieve +elif PYTHON_VERSION == 3: + from urllib.request import urlretrieve + +class COCO: + def __init__(self, annotation_file=None): + """ + Constructor of Microsoft COCO helper class for reading and visualizing annotations. + :param annotation_file (str): location of annotation file + :param image_folder (str): location to the folder that hosts images. + :return: + """ + # load dataset + self.dataset,self.anns,self.cats,self.imgs = dict(),dict(),dict(),dict() + self.imgToAnns, self.catToImgs = defaultdict(list), defaultdict(list) + if not annotation_file == None: + print('loading annotations into memory...') + tic = time.time() + dataset = json.load(open(annotation_file, 'r')) + assert type(dataset)==dict, 'annotation file format {} not supported'.format(type(dataset)) + print('Done (t={:0.2f}s)'.format(time.time()- tic)) + self.dataset = dataset + self.createIndex() + + def createIndex(self): + # create index + print('creating index...') + anns, cats, imgs = {}, {}, {} + imgToAnns,catToImgs = defaultdict(list),defaultdict(list) + if 'annotations' in self.dataset: + for ann in self.dataset['annotations']: + imgToAnns[ann['image_id']].append(ann) + anns[ann['id']] = ann + + if 'images' in self.dataset: + for img in self.dataset['images']: + imgs[img['id']] = img + + if 'categories' in self.dataset: + for cat in self.dataset['categories']: + cats[cat['id']] = cat + + if 'annotations' in self.dataset and 'categories' in self.dataset: + for ann in self.dataset['annotations']: + catToImgs[ann['category_id']].append(ann['image_id']) + + print('index created!') + + # create class members + self.anns = anns + self.imgToAnns = imgToAnns + self.catToImgs = catToImgs + self.imgs = imgs + self.cats = cats + + def info(self): + """ + Print information about the annotation file. + :return: + """ + for key, value in self.dataset['info'].items(): + print('{}: {}'.format(key, value)) + + def getAnnIds(self, imgIds=[], catIds=[], areaRng=[], iscrowd=None): + """ + Get ann ids that satisfy given filter conditions. default skips that filter + :param imgIds (int array) : get anns for given imgs + catIds (int array) : get anns for given cats + areaRng (float array) : get anns for given area range (e.g. [0 inf]) + iscrowd (boolean) : get anns for given crowd label (False or True) + :return: ids (int array) : integer array of ann ids + """ + imgIds = imgIds if type(imgIds) == list else [imgIds] + catIds = catIds if type(catIds) == list else [catIds] + + if len(imgIds) == len(catIds) == len(areaRng) == 0: + anns = self.dataset['annotations'] + else: + if not len(imgIds) == 0: + lists = [self.imgToAnns[imgId] for imgId in imgIds if imgId in self.imgToAnns] + anns = list(itertools.chain.from_iterable(lists)) + else: + anns = self.dataset['annotations'] + anns = anns if len(catIds) == 0 else [ann for ann in anns if ann['category_id'] in catIds] + anns = anns if len(areaRng) == 0 else [ann for ann in anns if ann['area'] > areaRng[0] and ann['area'] < areaRng[1]] + if not iscrowd == None: + ids = [ann['id'] for ann in anns if ann['iscrowd'] == iscrowd] + else: + ids = [ann['id'] for ann in anns] + return ids + + def getCatIds(self, catNms=[], supNms=[], catIds=[]): + """ + filtering parameters. default skips that filter. + :param catNms (str array) : get cats for given cat names + :param supNms (str array) : get cats for given supercategory names + :param catIds (int array) : get cats for given cat ids + :return: ids (int array) : integer array of cat ids + """ + catNms = catNms if type(catNms) == list else [catNms] + supNms = supNms if type(supNms) == list else [supNms] + catIds = catIds if type(catIds) == list else [catIds] + + if len(catNms) == len(supNms) == len(catIds) == 0: + cats = self.dataset['categories'] + else: + cats = self.dataset['categories'] + cats = cats if len(catNms) == 0 else [cat for cat in cats if cat['name'] in catNms] + cats = cats if len(supNms) == 0 else [cat for cat in cats if cat['supercategory'] in supNms] + cats = cats if len(catIds) == 0 else [cat for cat in cats if cat['id'] in catIds] + ids = [cat['id'] for cat in cats] + return ids + + def getImgIds(self, imgIds=[], catIds=[]): + ''' + Get img ids that satisfy given filter conditions. + :param imgIds (int array) : get imgs for given ids + :param catIds (int array) : get imgs with all given cats + :return: ids (int array) : integer array of img ids + ''' + imgIds = imgIds if type(imgIds) == list else [imgIds] + catIds = catIds if type(catIds) == list else [catIds] + + if len(imgIds) == len(catIds) == 0: + ids = self.imgs.keys() + else: + ids = set(imgIds) + for i, catId in enumerate(catIds): + if i == 0 and len(ids) == 0: + ids = set(self.catToImgs[catId]) + else: + ids &= set(self.catToImgs[catId]) + return list(ids) + + def loadAnns(self, ids=[]): + """ + Load anns with the specified ids. + :param ids (int array) : integer ids specifying anns + :return: anns (object array) : loaded ann objects + """ + if type(ids) == list: + return [self.anns[id] for id in ids] + elif type(ids) == int: + return [self.anns[ids]] + + def loadCats(self, ids=[]): + """ + Load cats with the specified ids. + :param ids (int array) : integer ids specifying cats + :return: cats (object array) : loaded cat objects + """ + if type(ids) == list: + return [self.cats[id] for id in ids] + elif type(ids) == int: + return [self.cats[ids]] + + def loadImgs(self, ids=[]): + """ + Load anns with the specified ids. + :param ids (int array) : integer ids specifying img + :return: imgs (object array) : loaded img objects + """ + if type(ids) == list: + return [self.imgs[id] for id in ids] + elif type(ids) == int: + return [self.imgs[ids]] + + def showAnns(self, anns): + """ + Display the specified annotations. + :param anns (array of object): annotations to display + :return: None + """ + if len(anns) == 0: + return 0 + if 'segmentation' in anns[0] or 'keypoints' in anns[0]: + datasetType = 'instances' + elif 'caption' in anns[0]: + datasetType = 'captions' + else: + raise Exception('datasetType not supported') + if datasetType == 'instances': + ax = plt.gca() + ax.set_autoscale_on(False) + polygons = [] + color = [] + for ann in anns: + c = (np.random.random((1, 3))*0.6+0.4).tolist()[0] + if 'segmentation' in ann: + if type(ann['segmentation']) == list: + # polygon + for seg in ann['segmentation']: + poly = np.array(seg).reshape((int(len(seg)/2), 2)) + polygons.append(Polygon(poly)) + color.append(c) + else: + # mask + t = self.imgs[ann['image_id']] + if type(ann['segmentation']['counts']) == list: + # rle = maskUtils.frPyObjects([ann['segmentation']], t['height'], t['width']) + raise NotImplementedError("maskUtils disabled!") + else: + rle = [ann['segmentation']] + # m = maskUtils.decode(rle) + raise NotImplementedError("maskUtils disabled!") + img = np.ones( (m.shape[0], m.shape[1], 3) ) + if ann['iscrowd'] == 1: + color_mask = np.array([2.0,166.0,101.0])/255 + if ann['iscrowd'] == 0: + color_mask = np.random.random((1, 3)).tolist()[0] + for i in range(3): + img[:,:,i] = color_mask[i] + ax.imshow(np.dstack( (img, m*0.5) )) + if 'keypoints' in ann and type(ann['keypoints']) == list: + # turn skeleton into zero-based index + sks = np.array(self.loadCats(ann['category_id'])[0]['skeleton'])-1 + kp = np.array(ann['keypoints']) + x = kp[0::3] + y = kp[1::3] + v = kp[2::3] + for sk in sks: + if np.all(v[sk]>0): + plt.plot(x[sk],y[sk], linewidth=3, color=c) + plt.plot(x[v>0], y[v>0],'o',markersize=8, markerfacecolor=c, markeredgecolor='k',markeredgewidth=2) + plt.plot(x[v>1], y[v>1],'o',markersize=8, markerfacecolor=c, markeredgecolor=c, markeredgewidth=2) + p = PatchCollection(polygons, facecolor=color, linewidths=0, alpha=0.4) + ax.add_collection(p) + p = PatchCollection(polygons, facecolor='none', edgecolors=color, linewidths=2) + ax.add_collection(p) + elif datasetType == 'captions': + for ann in anns: + print(ann['caption']) + + def loadRes(self, resFile): + """ + Load result file and return a result api object. + :param resFile (str) : file name of result file + :return: res (obj) : result api object + """ + res = COCO() + res.dataset['images'] = [img for img in self.dataset['images']] + + print('Loading and preparing results...') + tic = time.time() + if type(resFile) == str or type(resFile) == unicode: + anns = json.load(open(resFile)) + elif type(resFile) == np.ndarray: + anns = self.loadNumpyAnnotations(resFile) + else: + anns = resFile + assert type(anns) == list, 'results in not an array of objects' + annsImgIds = [ann['image_id'] for ann in anns] + assert set(annsImgIds) == (set(annsImgIds) & set(self.getImgIds())), \ + 'Results do not correspond to current coco set' + if 'caption' in anns[0]: + imgIds = set([img['id'] for img in res.dataset['images']]) & set([ann['image_id'] for ann in anns]) + res.dataset['images'] = [img for img in res.dataset['images'] if img['id'] in imgIds] + for id, ann in enumerate(anns): + ann['id'] = id+1 + elif 'bbox' in anns[0] and not anns[0]['bbox'] == []: + res.dataset['categories'] = copy.deepcopy(self.dataset['categories']) + for id, ann in enumerate(anns): + bb = ann['bbox'] + x1, x2, y1, y2 = [bb[0], bb[0]+bb[2], bb[1], bb[1]+bb[3]] + if not 'segmentation' in ann: + ann['segmentation'] = [[x1, y1, x1, y2, x2, y2, x2, y1]] + ann['area'] = bb[2]*bb[3] + ann['id'] = id+1 + ann['iscrowd'] = 0 + elif 'segmentation' in anns[0]: + res.dataset['categories'] = copy.deepcopy(self.dataset['categories']) + for id, ann in enumerate(anns): + # now only support compressed RLE format as segmentation results + # ann['area'] = maskUtils.area(ann['segmentation']) + raise NotImplementedError("maskUtils disabled!") + if not 'bbox' in ann: + # ann['bbox'] = maskUtils.toBbox(ann['segmentation']) + raise NotImplementedError("maskUtils disabled!") + ann['id'] = id+1 + ann['iscrowd'] = 0 + elif 'keypoints' in anns[0]: + res.dataset['categories'] = copy.deepcopy(self.dataset['categories']) + for id, ann in enumerate(anns): + s = ann['keypoints'] + x = s[0::3] + y = s[1::3] + x0,x1,y0,y1 = np.min(x), np.max(x), np.min(y), np.max(y) + ann['area'] = (x1-x0)*(y1-y0) + ann['id'] = id + 1 + ann['bbox'] = [x0,y0,x1-x0,y1-y0] + print('DONE (t={:0.2f}s)'.format(time.time()- tic)) + + res.dataset['annotations'] = anns + res.createIndex() + return res + + def download(self, tarDir = None, imgIds = [] ): + ''' + Download COCO images from mscoco.org server. + :param tarDir (str): COCO results directory name + imgIds (list): images to be downloaded + :return: + ''' + if tarDir is None: + print('Please specify target directory') + return -1 + if len(imgIds) == 0: + imgs = self.imgs.values() + else: + imgs = self.loadImgs(imgIds) + N = len(imgs) + if not os.path.exists(tarDir): + os.makedirs(tarDir) + for i, img in enumerate(imgs): + tic = time.time() + fname = os.path.join(tarDir, img['file_name']) + if not os.path.exists(fname): + urlretrieve(img['coco_url'], fname) + print('downloaded {}/{} images (t={:0.1f}s)'.format(i, N, time.time()- tic)) + + def loadNumpyAnnotations(self, data): + """ + Convert result data from a numpy array [Nx7] where each row contains {imageID,x1,y1,w,h,score,class} + :param data (numpy.ndarray) + :return: annotations (python nested list) + """ + print('Converting ndarray to lists...') + assert(type(data) == np.ndarray) + print(data.shape) + assert(data.shape[1] == 7) + N = data.shape[0] + ann = [] + for i in range(N): + if i % 1000000 == 0: + print('{}/{}'.format(i,N)) + ann += [{ + 'image_id' : int(data[i, 0]), + 'bbox' : [ data[i, 1], data[i, 2], data[i, 3], data[i, 4] ], + 'score' : data[i, 5], + 'category_id': int(data[i, 6]), + }] + return ann + + def annToRLE(self, ann): + """ + Convert annotation which can be polygons, uncompressed RLE to RLE. + :return: binary mask (numpy 2D array) + """ + t = self.imgs[ann['image_id']] + h, w = t['height'], t['width'] + segm = ann['segmentation'] + if type(segm) == list: + # polygon -- a single object might consist of multiple parts + # we merge all parts into one mask rle code + # rles = maskUtils.frPyObjects(segm, h, w) + # rle = maskUtils.merge(rles) + raise NotImplementedError("maskUtils disabled!") + elif type(segm['counts']) == list: + # uncompressed RLE + # rle = maskUtils.frPyObjects(segm, h, w) + raise NotImplementedError("maskUtils disabled!") + else: + # rle + rle = ann['segmentation'] + return rle + + def annToMask(self, ann): + """ + Convert annotation which can be polygons, uncompressed RLE, or RLE to binary mask. + :return: binary mask (numpy 2D array) + """ + rle = self.annToRLE(ann) + # m = maskUtils.decode(rle) + raise NotImplementedError("maskUtils disabled!") + return m diff --git a/example/ssd/dataset/testdb.py b/example/ssd/dataset/testdb.py index 7477d77c0aef..9a4b985d8e6b 100644 --- a/example/ssd/dataset/testdb.py +++ b/example/ssd/dataset/testdb.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import os from imdb import Imdb diff --git a/example/ssd/dataset/yolo_format.py b/example/ssd/dataset/yolo_format.py index ce6605f8c637..f1b73d032293 100644 --- a/example/ssd/dataset/yolo_format.py +++ b/example/ssd/dataset/yolo_format.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import os import numpy as np from imdb import Imdb diff --git a/example/ssd/demo.py b/example/ssd/demo.py index ededbdb59b88..521267550b41 100644 --- a/example/ssd/demo.py +++ b/example/ssd/demo.py @@ -1,19 +1,30 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import argparse import tools.find_mxnet import mxnet as mx import os -import importlib import sys from detect.detector import Detector +from symbol.symbol_factory import get_symbol -CLASSES = ('aeroplane', 'bicycle', 'bird', 'boat', - 'bottle', 'bus', 'car', 'cat', 'chair', - 'cow', 'diningtable', 'dog', 'horse', - 'motorbike', 'person', 'pottedplant', - 'sheep', 'sofa', 'train', 'tvmonitor') - -def get_detector(net, prefix, epoch, data_shape, mean_pixels, ctx, - nms_thresh=0.5, force_nms=True): +def get_detector(net, prefix, epoch, data_shape, mean_pixels, ctx, num_class, + nms_thresh=0.5, force_nms=True, nms_topk=400): """ wrapper for initialize a detector @@ -31,23 +42,25 @@ def get_detector(net, prefix, epoch, data_shape, mean_pixels, ctx, mean pixel values (R, G, B) ctx : mx.ctx running context, mx.cpu() or mx.gpu(?) + num_class : int + number of classes + nms_thresh : float + non-maximum suppression threshold force_nms : bool force suppress different categories """ - sys.path.append(os.path.join(os.getcwd(), 'symbol')) if net is not None: - net = importlib.import_module("symbol_" + net) \ - .get_symbol(len(CLASSES), nms_thresh, force_nms) - detector = Detector(net, prefix + "_" + str(data_shape), epoch, \ - data_shape, mean_pixels, ctx=ctx) + net = get_symbol(net, data_shape, num_classes=num_class, nms_thresh=nms_thresh, + force_nms=force_nms, nms_topk=nms_topk) + detector = Detector(net, prefix, epoch, data_shape, mean_pixels, ctx=ctx) return detector def parse_args(): parser = argparse.ArgumentParser(description='Single-shot detection network demo') - parser.add_argument('--network', dest='network', type=str, default='vgg16_ssd_300', - choices=['vgg16_ssd_300', 'vgg16_ssd_512'], help='which network to use') + parser.add_argument('--network', dest='network', type=str, default='resnet50', + help='which network to use') parser.add_argument('--images', dest='images', type=str, default='./data/demo/dog.jpg', - help='run demo with images, use comma(without space) to seperate multiple images') + help='run demo with images, use comma to seperate multiple images') parser.add_argument('--dir', dest='dir', nargs='?', help='demo image directory, optional', type=str) parser.add_argument('--ext', dest='extension', help='image extension, optional', @@ -55,12 +68,13 @@ def parse_args(): parser.add_argument('--epoch', dest='epoch', help='epoch of trained model', default=0, type=int) parser.add_argument('--prefix', dest='prefix', help='trained model prefix', - default=os.path.join(os.getcwd(), 'model', 'ssd'), type=str) + default=os.path.join(os.getcwd(), 'model', 'ssd_'), + type=str) parser.add_argument('--cpu', dest='cpu', help='(override GPU) use CPU to detect', action='store_true', default=False) parser.add_argument('--gpu', dest='gpu_id', type=int, default=0, help='GPU device id to detect with') - parser.add_argument('--data-shape', dest='data_shape', type=int, default=300, + parser.add_argument('--data-shape', dest='data_shape', type=int, default=512, help='set image shape') parser.add_argument('--mean-r', dest='mean_r', type=float, default=123, help='red mean value') @@ -78,9 +92,29 @@ def parse_args(): help='show detection time') parser.add_argument('--deploy', dest='deploy_net', action='store_true', default=False, help='Load network from json file, rather than from symbol') + parser.add_argument('--class-names', dest='class_names', type=str, + default='aeroplane, bicycle, bird, boat, bottle, bus, \ + car, cat, chair, cow, diningtable, dog, horse, motorbike, \ + person, pottedplant, sheep, sofa, train, tvmonitor', + help='string of comma separated names, or text filename') args = parser.parse_args() return args +def parse_class_names(class_names): + """ parse # classes and class_names if applicable """ + if len(class_names) > 0: + if os.path.isfile(class_names): + # try to open it to read class names + with open(class_names, 'r') as f: + class_names = [l.strip() for l in f.readlines()] + else: + class_names = [c.strip() for c in class_names.split(',')] + for name in class_names: + assert len(name) > 0 + else: + raise RuntimeError("No valid class_name provided...") + return class_names + if __name__ == '__main__': args = parse_args() if args.cpu: @@ -93,10 +127,15 @@ def parse_args(): assert len(image_list) > 0, "No valid image specified to detect" network = None if args.deploy_net else args.network - detector = get_detector(network, args.prefix, args.epoch, + class_names = parse_class_names(args.class_names) + if args.prefix.endswith('_'): + prefix = args.prefix + args.network + '_' + str(args.data_shape) + else: + prefix = args.prefix + detector = get_detector(network, prefix, args.epoch, args.data_shape, (args.mean_r, args.mean_g, args.mean_b), - ctx, args.nms_thresh, args.force_nms) + ctx, len(class_names), args.nms_thresh, args.force_nms) # run detection detector.detect_and_visualize(image_list, args.dir, args.extension, - CLASSES, args.thresh, args.show_timer) + class_names, args.thresh, args.show_timer) diff --git a/example/ssd/deploy.py b/example/ssd/deploy.py index 264314a59f70..415f334fdc2d 100644 --- a/example/ssd/deploy.py +++ b/example/ssd/deploy.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + from __future__ import print_function import argparse import tools.find_mxnet @@ -5,32 +22,41 @@ import os import importlib import sys +from symbol.symbol_factory import get_symbol def parse_args(): parser = argparse.ArgumentParser(description='Convert a trained model to deploy model') - parser.add_argument('--network', dest='network', type=str, default='vgg16_ssd_300', - choices=['vgg16_ssd_300', 'vgg16_ssd_512'], help='which network to use') + parser.add_argument('--network', dest='network', type=str, default='vgg16_reduced', + help='which network to use') parser.add_argument('--epoch', dest='epoch', help='epoch of trained model', default=0, type=int) parser.add_argument('--prefix', dest='prefix', help='trained model prefix', - default=os.path.join(os.getcwd(), 'model', 'ssd_300'), type=str) + default=os.path.join(os.getcwd(), 'model', 'ssd_'), type=str) + parser.add_argument('--data-shape', dest='data_shape', type=int, default=300, + help='data shape') parser.add_argument('--num-class', dest='num_classes', help='number of classes', default=20, type=int) parser.add_argument('--nms', dest='nms_thresh', type=float, default=0.5, help='non-maximum suppression threshold, default 0.5') parser.add_argument('--force', dest='force_nms', type=bool, default=True, help='force non-maximum suppression on different class') + parser.add_argument('--topk', dest='nms_topk', type=int, default=400, + help='apply nms only to top k detections based on scores.') args = parser.parse_args() return args if __name__ == '__main__': args = parse_args() - sys.path.append(os.path.join(os.getcwd(), 'symbol')) - net = importlib.import_module("symbol_" + args.network) \ - .get_symbol(args.num_classes, args.nms_thresh, args.force_nms) - _, arg_params, aux_params = mx.model.load_checkpoint(args.prefix, args.epoch) + net = get_symbol(args.network, args.data_shape, + num_classes=args.num_classes, nms_thresh=args.nms_thresh, + force_suppress=args.force_nms, nms_topk=args.nms_topk) + if args.prefix.endswith('_'): + prefix = args.prefix + args.network + '_' + str(args.data_shape) + else: + prefix = args.prefix + _, arg_params, aux_params = mx.model.load_checkpoint(prefix, args.epoch) # new name - tmp = args.prefix.rsplit('/', 1) + tmp = prefix.rsplit('/', 1) save_prefix = '/deploy_'.join(tmp) mx.model.save_checkpoint(save_prefix, args.epoch, net, arg_params, aux_params) print("Saved model: {}-{:04d}.param".format(save_prefix, args.epoch)) diff --git a/example/ssd/detect/detector.py b/example/ssd/detect/detector.py index 19b78f63f561..b6adac110cf7 100644 --- a/example/ssd/detect/detector.py +++ b/example/ssd/detect/detector.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + from __future__ import print_function import mxnet as mx import numpy as np diff --git a/example/ssd/evaluate.py b/example/ssd/evaluate.py index a38a7f6e6a6e..4e7f0a4b9173 100644 --- a/example/ssd/evaluate.py +++ b/example/ssd/evaluate.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import argparse import tools.find_mxnet import mxnet as mx @@ -5,30 +22,27 @@ import sys from evaluate.evaluate_net import evaluate_net -CLASSES = ('aeroplane', 'bicycle', 'bird', 'boat', - 'bottle', 'bus', 'car', 'cat', 'chair', - 'cow', 'diningtable', 'dog', 'horse', - 'motorbike', 'person', 'pottedplant', - 'sheep', 'sofa', 'train', 'tvmonitor') - def parse_args(): parser = argparse.ArgumentParser(description='Evaluate a network') parser.add_argument('--rec-path', dest='rec_path', help='which record file to use', default=os.path.join(os.getcwd(), 'data', 'val.rec'), type=str) parser.add_argument('--list-path', dest='list_path', help='which list file to use', default="", type=str) - parser.add_argument('--network', dest='network', type=str, default='vgg16_ssd_300', - choices=['vgg16_ssd_300', 'vgg16_ssd_512'], help='which network to use') + parser.add_argument('--network', dest='network', type=str, default='vgg16_reduced', + help='which network to use') parser.add_argument('--batch-size', dest='batch_size', type=int, default=32, help='evaluation batch size') parser.add_argument('--num-class', dest='num_class', type=int, default=20, help='number of classes') - parser.add_argument('--class-names', dest='class_names', type=str, default=",".join(CLASSES), + parser.add_argument('--class-names', dest='class_names', type=str, + default='aeroplane, bicycle, bird, boat, bottle, bus, \ + car, cat, chair, cow, diningtable, dog, horse, motorbike, \ + person, pottedplant, sheep, sofa, train, tvmonitor', help='string of comma separated names, or text filename') parser.add_argument('--epoch', dest='epoch', help='epoch of pretrained model', default=0, type=int) parser.add_argument('--prefix', dest='prefix', help='load model prefix', - default=os.path.join(os.getcwd(), 'model', 'ssd'), type=str) + default=os.path.join(os.getcwd(), 'model', 'ssd_'), type=str) parser.add_argument('--gpus', dest='gpu_id', help='GPU devices to evaluate with', default='0', type=str) parser.add_argument('--cpu', dest='cpu', help='use cpu to evaluate, this can be slow', @@ -79,9 +93,13 @@ def parse_args(): class_names = None network = None if args.deploy_net else args.network + if args.prefix.endswith('_'): + prefix = args.prefix + args.network + else: + prefix = args.prefix evaluate_net(network, args.rec_path, num_class, (args.mean_r, args.mean_g, args.mean_b), args.data_shape, - args.prefix, args.epoch, ctx, batch_size=args.batch_size, + prefix, args.epoch, ctx, batch_size=args.batch_size, path_imglist=args.list_path, nms_thresh=args.nms_thresh, force_nms=args.force_nms, ovp_thresh=args.overlap_thresh, use_difficult=args.use_difficult, class_names=class_names, diff --git a/example/ssd/evaluate/eval_metric.py b/example/ssd/evaluate/eval_metric.py index f475bb336ddb..bb2b77b3d52b 100644 --- a/example/ssd/evaluate/eval_metric.py +++ b/example/ssd/evaluate/eval_metric.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import mxnet as mx import numpy as np @@ -17,16 +34,17 @@ class MApMetric(mx.metric.EvalMetric): prediction index in network output list """ def __init__(self, ovp_thresh=0.5, use_difficult=False, class_names=None, pred_idx=0): + super(MApMetric, self).__init__('mAP') if class_names is None: - super(MApMetric, self).__init__("mAP") + self.num = None else: - assert isinstance(class_names, list) + assert isinstance(class_names, (list, tuple)) for name in class_names: assert isinstance(name, str), "must provide names as str" num = len(class_names) - super(MApMetric, self).__init__(class_names + ["mAP"], num + 1) - self.records = dict() - self.counts = dict() + self.name = class_names + ['mAP'] + self.num = num + 1 + self.reset() self.ovp_thresh = ovp_thresh self.use_difficult = use_difficult self.class_names = class_names @@ -34,7 +52,12 @@ def __init__(self, ovp_thresh=0.5, use_difficult=False, class_names=None, pred_i def reset(self): """Clear the internal statistics to initial state.""" - super(MApMetric, self).reset() + if getattr(self, 'num', None) is None: + self.num_inst = 0 + self.sum_metric = 0.0 + else: + self.num_inst = [0] * self.num + self.sum_metric = [0.0] * self.num self.records = dict() self.counts = dict() @@ -104,6 +127,8 @@ def iou(x, ys): for i in range(labels[0].shape[0]): # get as numpy arrays label = labels[0][i].asnumpy() + if np.sum(label[:, 0] >= 0) < 1: + continue pred = preds[self.pred_idx][i].asnumpy() # calculate for each class while (pred.shape[0] > 0): @@ -118,7 +143,9 @@ def iou(x, ys): dets[dets[:,1].argsort()[::-1]] records = np.hstack((dets[:, 1][:, np.newaxis], np.zeros((dets.shape[0], 1)))) # ground-truths - gts = label[np.where(label[:, 0].astype(int) == cid)[0], :] + label_indices = np.where(label[:, 0].astype(int) == cid)[0] + gts = label[label_indices, :] + label = np.delete(label, label_indices, axis=0) if gts.size > 0: found = [False] * gts.shape[0] for j in range(dets.shape[0]): @@ -157,6 +184,16 @@ def iou(x, ys): if records.size > 0: self._insert(cid, records, gt_count) + # add missing class if not present in prediction + while (label.shape[0] > 0): + cid = int(label[0, 0]) + label_indices = np.where(label[:, 0].astype(int) == cid)[0] + label = np.delete(label, label_indices, axis=0) + if cid < 0: + continue + gt_count = label_indices.size + self._insert(cid, np.array([[0, 0]]), gt_count) + def _update(self): """ update num_inst and sum_metric """ aps = [] @@ -176,6 +213,7 @@ def _update(self): def _recall_prec(self, record, count): """ get recall and precision from internal records """ + record = np.delete(record, np.where(record[:, 1].astype(int) == 0)[0], axis=0) sorted_records = record[record[:,0].argsort()[::-1]] tp = np.cumsum(sorted_records[:, 1].astype(int) == 1) fp = np.cumsum(sorted_records[:, 1].astype(int) == 2) diff --git a/example/ssd/evaluate/eval_voc.py b/example/ssd/evaluate/eval_voc.py index d16856e35009..0ba7f7eaf843 100644 --- a/example/ssd/evaluate/eval_voc.py +++ b/example/ssd/evaluate/eval_voc.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + """ given a pascal voc imdb, compute mAP """ diff --git a/example/ssd/evaluate/evaluate_net.py b/example/ssd/evaluate/evaluate_net.py index 8d86f8eefd56..7f1a32dea518 100644 --- a/example/ssd/evaluate/evaluate_net.py +++ b/example/ssd/evaluate/evaluate_net.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + from __future__ import print_function import os import sys @@ -7,6 +24,7 @@ from config.config import cfg from evaluate.eval_metric import MApMetric, VOC07MApMetric import logging +from symbol.symbol_factory import get_symbol def evaluate_net(net, path_imgrec, num_classes, mean_pixels, data_shape, model_prefix, epoch, ctx=mx.cpu(), batch_size=1, @@ -71,9 +89,8 @@ class names in string, must correspond to num_classes if set if net is None: net = load_net else: - sys.path.append(os.path.join(cfg.ROOT_DIR, 'symbol')) - net = importlib.import_module("symbol_" + net) \ - .get_symbol(num_classes, nms_thresh, force_nms) + net = get_symbol(net, data_shape[1], num_classes=num_classes, + nms_thresh=nms_thresh, force_suppress=force_nms) if not 'label' in net.list_arguments(): label = mx.sym.Variable(name='label') net = mx.sym.Group([net, label]) diff --git a/example/ssd/symbol/README.md b/example/ssd/symbol/README.md new file mode 100644 index 000000000000..8fee31985a0d --- /dev/null +++ b/example/ssd/symbol/README.md @@ -0,0 +1,49 @@ +## How to compose SSD network on top of mainstream classification networks + +1. Have the base network ready in this directory as `name.py`, such as `inceptionv3.py`. +2. Add configuration to `symbol_factory.py`, an example would be: +``` +if network == 'vgg16_reduced': + if data_shape >= 448: + from_layers = ['relu4_3', 'relu7', '', '', '', '', ''] + num_filters = [512, -1, 512, 256, 256, 256, 256] + strides = [-1, -1, 2, 2, 2, 2, 1] + pads = [-1, -1, 1, 1, 1, 1, 1] + sizes = [[.07, .1025], [.15,.2121], [.3, .3674], [.45, .5196], [.6, .6708], \ + [.75, .8216], [.9, .9721]] + ratios = [[1,2,.5], [1,2,.5,3,1./3], [1,2,.5,3,1./3], [1,2,.5,3,1./3], \ + [1,2,.5,3,1./3], [1,2,.5], [1,2,.5]] + normalizations = [20, -1, -1, -1, -1, -1, -1] + steps = [] if data_shape != 512 else [x / 512.0 for x in + [8, 16, 32, 64, 128, 256, 512]] + else: + from_layers = ['relu4_3', 'relu7', '', '', '', ''] + num_filters = [512, -1, 512, 256, 256, 256] + strides = [-1, -1, 2, 2, 1, 1] + pads = [-1, -1, 1, 1, 0, 0] + sizes = [[.1, .141], [.2,.272], [.37, .447], [.54, .619], [.71, .79], [.88, .961]] + ratios = [[1,2,.5], [1,2,.5,3,1./3], [1,2,.5,3,1./3], [1,2,.5,3,1./3], \ + [1,2,.5], [1,2,.5]] + normalizations = [20, -1, -1, -1, -1, -1] + steps = [] if data_shape != 300 else [x / 300.0 for x in [8, 16, 32, 64, 100, 300]] + return locals() +elif network == 'inceptionv3': + from_layers = ['ch_concat_mixed_7_chconcat', 'ch_concat_mixed_10_chconcat', '', '', '', ''] + num_filters = [-1, -1, 512, 256, 256, 128] + strides = [-1, -1, 2, 2, 2, 2] + pads = [-1, -1, 1, 1, 1, 1] + sizes = [[.1, .141], [.2,.272], [.37, .447], [.54, .619], [.71, .79], [.88, .961]] + ratios = [[1,2,.5], [1,2,.5,3,1./3], [1,2,.5,3,1./3], [1,2,.5,3,1./3], \ + [1,2,.5], [1,2,.5]] + normalizations = -1 + steps = [] + return locals() +``` +Here `from_layers` indicate the feature layer you would like to extract from the base network. +`''` indicate that we want add extra new layers on top of the last feature layer, +and the number of filters must be specified in `num_filters`. Similarly, `strides` and `pads` +are required to compose these new layers. `sizes` and `ratios` are the parameters controlling +the anchor generation algorithm. `normalizations` is used to normalize and rescale feature if +not `-1`. `steps`: optional, used to calculate the anchor sliding steps. + +3. Train or test with arguments `--network name --data-shape xxx --pretrained pretrained_model` diff --git a/example/ssd/symbol/common.py b/example/ssd/symbol/common.py index 12ea71826e22..ea58c1599add 100644 --- a/example/ssd/symbol/common.py +++ b/example/ssd/symbol/common.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import mxnet as mx import numpy as np @@ -6,6 +23,42 @@ def conv_act_layer(from_layer, name, num_filter, kernel=(1,1), pad=(0,0), \ """ wrapper for a small Convolution group + Parameters: + ---------- + from_layer : mx.symbol + continue on which layer + name : str + base name of the new layers + num_filter : int + how many filters to use in Convolution layer + kernel : tuple (int, int) + kernel size (h, w) + pad : tuple (int, int) + padding size (h, w) + stride : tuple (int, int) + stride size (h, w) + act_type : str + activation type, can be relu... + use_batchnorm : bool + whether to use batch normalization + + Returns: + ---------- + (conv, relu) mx.Symbols + """ + conv = mx.symbol.Convolution(data=from_layer, kernel=kernel, pad=pad, \ + stride=stride, num_filter=num_filter, name="{}_conv".format(name)) + if use_batchnorm: + conv = mx.symbol.BatchNorm(data=conv, name="{}_bn".format(name)) + relu = mx.symbol.Activation(data=conv, act_type=act_type, \ + name="{}_{}".format(name, act_type)) + return relu + +def legacy_conv_act_layer(from_layer, name, num_filter, kernel=(1,1), pad=(0,0), \ + stride=(1,1), act_type="relu", use_batchnorm=False): + """ + wrapper for a small Convolution group + Parameters: ---------- from_layer : mx.symbol @@ -40,9 +93,66 @@ def conv_act_layer(from_layer, name, num_filter, kernel=(1,1), pad=(0,0), \ relu = mx.symbol.BatchNorm(data=relu, name="bn{}".format(name)) return conv, relu +def multi_layer_feature(body, from_layers, num_filters, strides, pads, min_filter=128): + """Wrapper function to extract features from base network, attaching extra + layers and SSD specific layers + + Parameters + ---------- + from_layers : list of str + feature extraction layers, use '' for add extra layers + For example: + from_layers = ['relu4_3', 'fc7', '', '', '', ''] + which means extract feature from relu4_3 and fc7, adding 4 extra layers + on top of fc7 + num_filters : list of int + number of filters for extra layers, you can use -1 for extracted features, + however, if normalization and scale is applied, the number of filter for + that layer must be provided. + For example: + num_filters = [512, -1, 512, 256, 256, 256] + strides : list of int + strides for the 3x3 convolution appended, -1 can be used for extracted + feature layers + pads : list of int + paddings for the 3x3 convolution, -1 can be used for extracted layers + min_filter : int + minimum number of filters used in 1x1 convolution + + Returns + ------- + list of mx.Symbols + + """ + # arguments check + assert len(from_layers) > 0 + assert isinstance(from_layers[0], str) and len(from_layers[0].strip()) > 0 + assert len(from_layers) == len(num_filters) == len(strides) == len(pads) + + internals = body.get_internals() + layers = [] + for k, params in enumerate(zip(from_layers, num_filters, strides, pads)): + from_layer, num_filter, s, p = params + if from_layer.strip(): + # extract from base network + layer = internals[from_layer.strip() + '_output'] + layers.append(layer) + else: + # attach from last feature layer + assert len(layers) > 0 + assert num_filter > 0 + layer = layers[-1] + num_1x1 = max(min_filter, num_filter // 2) + conv_1x1 = conv_act_layer(layer, 'multi_feat_%d_conv_1x1' % (k), + num_1x1, kernel=(1, 1), pad=(0, 0), stride=(1, 1), act_type='relu') + conv_3x3 = conv_act_layer(conv_1x1, 'multi_feat_%d_conv_3x3' % (k), + num_filter, kernel=(3, 3), pad=(p, p), stride=(s, s), act_type='relu') + layers.append(conv_3x3) + return layers + def multibox_layer(from_layers, num_classes, sizes=[.2, .95], ratios=[1], normalization=-1, num_channels=[], - clip=True, interm_layer=0, steps=[]): + clip=False, interm_layer=0, steps=[]): """ the basic aggregation module for SSD detection. Takes in multiple layers, generate multiple object detection targets by customized layers @@ -106,7 +216,7 @@ def multibox_layer(from_layers, num_classes, sizes=[.2, .95], normalization = [normalization] * len(from_layers) assert len(normalization) == len(from_layers) - assert sum(x > 0 for x in normalization) == len(num_channels), \ + assert sum(x > 0 for x in normalization) <= len(num_channels), \ "must provide number of channels for each normalized layer" if steps: @@ -125,7 +235,8 @@ def multibox_layer(from_layers, num_classes, sizes=[.2, .95], mode="channel", name="{}_norm".format(from_name)) scale = mx.symbol.Variable(name="{}_scale".format(from_name), shape=(1, num_channels.pop(0), 1, 1), - init=mx.init.Constant(normalization[k])) + init=mx.init.Constant(normalization[k]), + attr={'__wd_mult__': '0.1'}) from_layer = mx.symbol.broadcast_mul(lhs=scale, rhs=from_layer) if interm_layer > 0: from_layer = mx.symbol.Convolution(data=from_layer, kernel=(3,3), \ diff --git a/example/ssd/symbol/inceptionv3.py b/example/ssd/symbol/inceptionv3.py new file mode 100644 index 000000000000..6022ce505a8f --- /dev/null +++ b/example/ssd/symbol/inceptionv3.py @@ -0,0 +1,185 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +""" +Inception V3, suitable for images with around 299 x 299 + +Reference: + +Szegedy, Christian, et al. "Rethinking the Inception Architecture for Computer Vision." arXiv preprint arXiv:1512.00567 (2015). +""" +import mxnet as mx + +def Conv(data, num_filter, kernel=(1, 1), stride=(1, 1), pad=(0, 0), name=None, suffix=''): + conv = mx.sym.Convolution(data=data, num_filter=num_filter, kernel=kernel, stride=stride, pad=pad, no_bias=True, name='%s%s_conv2d' %(name, suffix)) + bn = mx.sym.BatchNorm(data=conv, name='%s%s_batchnorm' %(name, suffix), fix_gamma=True) + act = mx.sym.Activation(data=bn, act_type='relu', name='%s%s_relu' %(name, suffix)) + return act + + +def Inception7A(data, + num_1x1, + num_3x3_red, num_3x3_1, num_3x3_2, + num_5x5_red, num_5x5, + pool, proj, + name): + tower_1x1 = Conv(data, num_1x1, name=('%s_conv' % name)) + tower_5x5 = Conv(data, num_5x5_red, name=('%s_tower' % name), suffix='_conv') + tower_5x5 = Conv(tower_5x5, num_5x5, kernel=(5, 5), pad=(2, 2), name=('%s_tower' % name), suffix='_conv_1') + tower_3x3 = Conv(data, num_3x3_red, name=('%s_tower_1' % name), suffix='_conv') + tower_3x3 = Conv(tower_3x3, num_3x3_1, kernel=(3, 3), pad=(1, 1), name=('%s_tower_1' % name), suffix='_conv_1') + tower_3x3 = Conv(tower_3x3, num_3x3_2, kernel=(3, 3), pad=(1, 1), name=('%s_tower_1' % name), suffix='_conv_2') + pooling = mx.sym.Pooling(data=data, kernel=(3, 3), stride=(1, 1), pad=(1, 1), pool_type=pool, name=('%s_pool_%s_pool' % (pool, name))) + cproj = Conv(pooling, proj, name=('%s_tower_2' % name), suffix='_conv') + concat = mx.sym.Concat(*[tower_1x1, tower_5x5, tower_3x3, cproj], name='ch_concat_%s_chconcat' % name) + return concat + +# First Downsample +def Inception7B(data, + num_3x3, + num_d3x3_red, num_d3x3_1, num_d3x3_2, + pool, + name): + tower_3x3 = Conv(data, num_3x3, kernel=(3, 3), pad=(0, 0), stride=(2, 2), name=('%s_conv' % name)) + tower_d3x3 = Conv(data, num_d3x3_red, name=('%s_tower' % name), suffix='_conv') + tower_d3x3 = Conv(tower_d3x3, num_d3x3_1, kernel=(3, 3), pad=(1, 1), stride=(1, 1), name=('%s_tower' % name), suffix='_conv_1') + tower_d3x3 = Conv(tower_d3x3, num_d3x3_2, kernel=(3, 3), pad=(0, 0), stride=(2, 2), name=('%s_tower' % name), suffix='_conv_2') + pooling = mx.symbol.Pooling(data=data, kernel=(3, 3), stride=(2, 2), pad=(0,0), pool_type="max", name=('max_pool_%s_pool' % name)) + concat = mx.sym.Concat(*[tower_3x3, tower_d3x3, pooling], name='ch_concat_%s_chconcat' % name) + return concat + +def Inception7C(data, + num_1x1, + num_d7_red, num_d7_1, num_d7_2, + num_q7_red, num_q7_1, num_q7_2, num_q7_3, num_q7_4, + pool, proj, + name): + tower_1x1 = Conv(data=data, num_filter=num_1x1, kernel=(1, 1), name=('%s_conv' % name)) + tower_d7 = Conv(data=data, num_filter=num_d7_red, name=('%s_tower' % name), suffix='_conv') + tower_d7 = Conv(data=tower_d7, num_filter=num_d7_1, kernel=(1, 7), pad=(0, 3), name=('%s_tower' % name), suffix='_conv_1') + tower_d7 = Conv(data=tower_d7, num_filter=num_d7_2, kernel=(7, 1), pad=(3, 0), name=('%s_tower' % name), suffix='_conv_2') + tower_q7 = Conv(data=data, num_filter=num_q7_red, name=('%s_tower_1' % name), suffix='_conv') + tower_q7 = Conv(data=tower_q7, num_filter=num_q7_1, kernel=(7, 1), pad=(3, 0), name=('%s_tower_1' % name), suffix='_conv_1') + tower_q7 = Conv(data=tower_q7, num_filter=num_q7_2, kernel=(1, 7), pad=(0, 3), name=('%s_tower_1' % name), suffix='_conv_2') + tower_q7 = Conv(data=tower_q7, num_filter=num_q7_3, kernel=(7, 1), pad=(3, 0), name=('%s_tower_1' % name), suffix='_conv_3') + tower_q7 = Conv(data=tower_q7, num_filter=num_q7_4, kernel=(1, 7), pad=(0, 3), name=('%s_tower_1' % name), suffix='_conv_4') + pooling = mx.sym.Pooling(data=data, kernel=(3, 3), stride=(1, 1), pad=(1, 1), pool_type=pool, name=('%s_pool_%s_pool' % (pool, name))) + cproj = Conv(data=pooling, num_filter=proj, kernel=(1, 1), name=('%s_tower_2' % name), suffix='_conv') + # concat + concat = mx.sym.Concat(*[tower_1x1, tower_d7, tower_q7, cproj], name='ch_concat_%s_chconcat' % name) + return concat + +def Inception7D(data, + num_3x3_red, num_3x3, + num_d7_3x3_red, num_d7_1, num_d7_2, num_d7_3x3, + pool, + name): + tower_3x3 = Conv(data=data, num_filter=num_3x3_red, name=('%s_tower' % name), suffix='_conv') + tower_3x3 = Conv(data=tower_3x3, num_filter=num_3x3, kernel=(3, 3), pad=(0,0), stride=(2, 2), name=('%s_tower' % name), suffix='_conv_1') + tower_d7_3x3 = Conv(data=data, num_filter=num_d7_3x3_red, name=('%s_tower_1' % name), suffix='_conv') + tower_d7_3x3 = Conv(data=tower_d7_3x3, num_filter=num_d7_1, kernel=(1, 7), pad=(0, 3), name=('%s_tower_1' % name), suffix='_conv_1') + tower_d7_3x3 = Conv(data=tower_d7_3x3, num_filter=num_d7_2, kernel=(7, 1), pad=(3, 0), name=('%s_tower_1' % name), suffix='_conv_2') + tower_d7_3x3 = Conv(data=tower_d7_3x3, num_filter=num_d7_3x3, kernel=(3, 3), stride=(2, 2), name=('%s_tower_1' % name), suffix='_conv_3') + pooling = mx.sym.Pooling(data=data, kernel=(3, 3), stride=(2, 2), pool_type=pool, name=('%s_pool_%s_pool' % (pool, name))) + # concat + concat = mx.sym.Concat(*[tower_3x3, tower_d7_3x3, pooling], name='ch_concat_%s_chconcat' % name) + return concat + +def Inception7E(data, + num_1x1, + num_d3_red, num_d3_1, num_d3_2, + num_3x3_d3_red, num_3x3, num_3x3_d3_1, num_3x3_d3_2, + pool, proj, + name): + tower_1x1 = Conv(data=data, num_filter=num_1x1, kernel=(1, 1), name=('%s_conv' % name)) + tower_d3 = Conv(data=data, num_filter=num_d3_red, name=('%s_tower' % name), suffix='_conv') + tower_d3_a = Conv(data=tower_d3, num_filter=num_d3_1, kernel=(1, 3), pad=(0, 1), name=('%s_tower' % name), suffix='_mixed_conv') + tower_d3_b = Conv(data=tower_d3, num_filter=num_d3_2, kernel=(3, 1), pad=(1, 0), name=('%s_tower' % name), suffix='_mixed_conv_1') + tower_3x3_d3 = Conv(data=data, num_filter=num_3x3_d3_red, name=('%s_tower_1' % name), suffix='_conv') + tower_3x3_d3 = Conv(data=tower_3x3_d3, num_filter=num_3x3, kernel=(3, 3), pad=(1, 1), name=('%s_tower_1' % name), suffix='_conv_1') + tower_3x3_d3_a = Conv(data=tower_3x3_d3, num_filter=num_3x3_d3_1, kernel=(1, 3), pad=(0, 1), name=('%s_tower_1' % name), suffix='_mixed_conv') + tower_3x3_d3_b = Conv(data=tower_3x3_d3, num_filter=num_3x3_d3_2, kernel=(3, 1), pad=(1, 0), name=('%s_tower_1' % name), suffix='_mixed_conv_1') + pooling = mx.sym.Pooling(data=data, kernel=(3, 3), stride=(1, 1), pad=(1, 1), pool_type=pool, name=('%s_pool_%s_pool' % (pool, name))) + cproj = Conv(data=pooling, num_filter=proj, kernel=(1, 1), name=('%s_tower_2' % name), suffix='_conv') + # concat + concat = mx.sym.Concat(*[tower_1x1, tower_d3_a, tower_d3_b, tower_3x3_d3_a, tower_3x3_d3_b, cproj], name='ch_concat_%s_chconcat' % name) + return concat + +# In[49]: + +def get_symbol(num_classes=1000, **kwargs): + data = mx.symbol.Variable(name="data") + # stage 1 + conv = Conv(data, 32, kernel=(3, 3), stride=(2, 2), name="conv") + conv_1 = Conv(conv, 32, kernel=(3, 3), name="conv_1") + conv_2 = Conv(conv_1, 64, kernel=(3, 3), pad=(1, 1), name="conv_2") + pool = mx.sym.Pooling(data=conv_2, kernel=(3, 3), stride=(2, 2), pool_type="max", name="pool") + # stage 2 + conv_3 = Conv(pool, 80, kernel=(1, 1), name="conv_3") + conv_4 = Conv(conv_3, 192, kernel=(3, 3), name="conv_4") + pool1 = mx.sym.Pooling(data=conv_4, kernel=(3, 3), stride=(2, 2), pool_type="max", name="pool1") + # stage 3 + in3a = Inception7A(pool1, 64, + 64, 96, 96, + 48, 64, + "avg", 32, "mixed") + in3b = Inception7A(in3a, 64, + 64, 96, 96, + 48, 64, + "avg", 64, "mixed_1") + in3c = Inception7A(in3b, 64, + 64, 96, 96, + 48, 64, + "avg", 64, "mixed_2") + in3d = Inception7B(in3c, 384, + 64, 96, 96, + "max", "mixed_3") + # stage 4 + in4a = Inception7C(in3d, 192, + 128, 128, 192, + 128, 128, 128, 128, 192, + "avg", 192, "mixed_4") + in4b = Inception7C(in4a, 192, + 160, 160, 192, + 160, 160, 160, 160, 192, + "avg", 192, "mixed_5") + in4c = Inception7C(in4b, 192, + 160, 160, 192, + 160, 160, 160, 160, 192, + "avg", 192, "mixed_6") + in4d = Inception7C(in4c, 192, + 192, 192, 192, + 192, 192, 192, 192, 192, + "avg", 192, "mixed_7") + in4e = Inception7D(in4d, 192, 320, + 192, 192, 192, 192, + "max", "mixed_8") + # stage 5 + in5a = Inception7E(in4e, 320, + 384, 384, 384, + 448, 384, 384, 384, + "avg", 192, "mixed_9") + in5b = Inception7E(in5a, 320, + 384, 384, 384, + 448, 384, 384, 384, + "max", 192, "mixed_10") + # pool + pool = mx.sym.Pooling(data=in5b, kernel=(8, 8), stride=(1, 1), pool_type="avg", name="global_pool") + flatten = mx.sym.Flatten(data=pool, name="flatten") + fc1 = mx.symbol.FullyConnected(data=flatten, num_hidden=num_classes, name='fc1') + softmax = mx.symbol.SoftmaxOutput(data=fc1, name='softmax') + return softmax diff --git a/example/ssd/symbol/symbol_vgg16_ssd_300.py b/example/ssd/symbol/legacy_vgg16_ssd_300.py similarity index 83% rename from example/ssd/symbol/symbol_vgg16_ssd_300.py rename to example/ssd/symbol/legacy_vgg16_ssd_300.py index e40674661300..c1f8ea7cb88e 100644 --- a/example/ssd/symbol/symbol_vgg16_ssd_300.py +++ b/example/ssd/symbol/legacy_vgg16_ssd_300.py @@ -1,8 +1,26 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import mxnet as mx -from common import conv_act_layer +from common import legacy_conv_act_layer from common import multibox_layer -def get_symbol_train(num_classes=20, nms_thresh=0.5, force_suppress=False, nms_topk=400): +def get_symbol_train(num_classes=20, nms_thresh=0.5, force_suppress=False, + nms_topk=400, **kwargs): """ Single-shot multi-box detection with VGG 16 layers ConvNet This is a modified version, with fc6/fc7 layers replaced by conv layers @@ -96,21 +114,21 @@ def get_symbol_train(num_classes=20, nms_thresh=0.5, force_suppress=False, nms_t # drop7 = mx.symbol.Dropout(data=relu7, p=0.5, name="drop7") ### ssd extra layers ### - conv8_1, relu8_1 = conv_act_layer(relu7, "8_1", 256, kernel=(1,1), pad=(0,0), \ + conv8_1, relu8_1 = legacy_conv_act_layer(relu7, "8_1", 256, kernel=(1,1), pad=(0,0), \ stride=(1,1), act_type="relu", use_batchnorm=False) - conv8_2, relu8_2 = conv_act_layer(relu8_1, "8_2", 512, kernel=(3,3), pad=(1,1), \ + conv8_2, relu8_2 = legacy_conv_act_layer(relu8_1, "8_2", 512, kernel=(3,3), pad=(1,1), \ stride=(2,2), act_type="relu", use_batchnorm=False) - conv9_1, relu9_1 = conv_act_layer(relu8_2, "9_1", 128, kernel=(1,1), pad=(0,0), \ + conv9_1, relu9_1 = legacy_conv_act_layer(relu8_2, "9_1", 128, kernel=(1,1), pad=(0,0), \ stride=(1,1), act_type="relu", use_batchnorm=False) - conv9_2, relu9_2 = conv_act_layer(relu9_1, "9_2", 256, kernel=(3,3), pad=(1,1), \ + conv9_2, relu9_2 = legacy_conv_act_layer(relu9_1, "9_2", 256, kernel=(3,3), pad=(1,1), \ stride=(2,2), act_type="relu", use_batchnorm=False) - conv10_1, relu10_1 = conv_act_layer(relu9_2, "10_1", 128, kernel=(1,1), pad=(0,0), \ + conv10_1, relu10_1 = legacy_conv_act_layer(relu9_2, "10_1", 128, kernel=(1,1), pad=(0,0), \ stride=(1,1), act_type="relu", use_batchnorm=False) - conv10_2, relu10_2 = conv_act_layer(relu10_1, "10_2", 256, kernel=(3,3), pad=(0,0), \ + conv10_2, relu10_2 = legacy_conv_act_layer(relu10_1, "10_2", 256, kernel=(3,3), pad=(0,0), \ stride=(1,1), act_type="relu", use_batchnorm=False) - conv11_1, relu11_1 = conv_act_layer(relu10_2, "11_1", 128, kernel=(1,1), pad=(0,0), \ + conv11_1, relu11_1 = legacy_conv_act_layer(relu10_2, "11_1", 128, kernel=(1,1), pad=(0,0), \ stride=(1,1), act_type="relu", use_batchnorm=False) - conv11_2, relu11_2 = conv_act_layer(relu11_1, "11_2", 256, kernel=(3,3), pad=(0,0), \ + conv11_2, relu11_2 = legacy_conv_act_layer(relu11_1, "11_2", 256, kernel=(3,3), pad=(0,0), \ stride=(1,1), act_type="relu", use_batchnorm=False) # specific parameters for VGG16 network @@ -154,7 +172,8 @@ def get_symbol_train(num_classes=20, nms_thresh=0.5, force_suppress=False, nms_t out = mx.symbol.Group([cls_prob, loc_loss, cls_label, det]) return out -def get_symbol(num_classes=20, nms_thresh=0.5, force_suppress=False, nms_topk=400): +def get_symbol(num_classes=20, nms_thresh=0.5, force_suppress=False, + nms_topk=400, **kwargs): """ Single-shot multi-box detection with VGG 16 layers ConvNet This is a modified version, with fc6/fc7 layers replaced by conv layers diff --git a/example/ssd/symbol/symbol_vgg16_ssd_512.py b/example/ssd/symbol/legacy_vgg16_ssd_512.py similarity index 82% rename from example/ssd/symbol/symbol_vgg16_ssd_512.py rename to example/ssd/symbol/legacy_vgg16_ssd_512.py index 1cc243b0406b..6cc3aa274a73 100644 --- a/example/ssd/symbol/symbol_vgg16_ssd_512.py +++ b/example/ssd/symbol/legacy_vgg16_ssd_512.py @@ -1,5 +1,22 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import mxnet as mx -from common import conv_act_layer +from common import legacy_conv_act_layer from common import multibox_layer def get_symbol_train(num_classes=20, nms_thresh=0.5, force_suppress=False, nms_topk=400): @@ -96,25 +113,25 @@ def get_symbol_train(num_classes=20, nms_thresh=0.5, force_suppress=False, nms_t # drop7 = mx.symbol.Dropout(data=relu7, p=0.5, name="drop7") ### ssd extra layers ### - conv8_1, relu8_1 = conv_act_layer(relu7, "8_1", 256, kernel=(1,1), pad=(0,0), \ + conv8_1, relu8_1 = legacy_conv_act_layer(relu7, "8_1", 256, kernel=(1,1), pad=(0,0), \ stride=(1,1), act_type="relu", use_batchnorm=False) - conv8_2, relu8_2 = conv_act_layer(relu8_1, "8_2", 512, kernel=(3,3), pad=(1,1), \ + conv8_2, relu8_2 = legacy_conv_act_layer(relu8_1, "8_2", 512, kernel=(3,3), pad=(1,1), \ stride=(2,2), act_type="relu", use_batchnorm=False) - conv9_1, relu9_1 = conv_act_layer(relu8_2, "9_1", 128, kernel=(1,1), pad=(0,0), \ + conv9_1, relu9_1 = legacy_conv_act_layer(relu8_2, "9_1", 128, kernel=(1,1), pad=(0,0), \ stride=(1,1), act_type="relu", use_batchnorm=False) - conv9_2, relu9_2 = conv_act_layer(relu9_1, "9_2", 256, kernel=(3,3), pad=(1,1), \ + conv9_2, relu9_2 = legacy_conv_act_layer(relu9_1, "9_2", 256, kernel=(3,3), pad=(1,1), \ stride=(2,2), act_type="relu", use_batchnorm=False) - conv10_1, relu10_1 = conv_act_layer(relu9_2, "10_1", 128, kernel=(1,1), pad=(0,0), \ + conv10_1, relu10_1 = legacy_conv_act_layer(relu9_2, "10_1", 128, kernel=(1,1), pad=(0,0), \ stride=(1,1), act_type="relu", use_batchnorm=False) - conv10_2, relu10_2 = conv_act_layer(relu10_1, "10_2", 256, kernel=(3,3), pad=(1,1), \ + conv10_2, relu10_2 = legacy_conv_act_layer(relu10_1, "10_2", 256, kernel=(3,3), pad=(1,1), \ stride=(2,2), act_type="relu", use_batchnorm=False) - conv11_1, relu11_1 = conv_act_layer(relu10_2, "11_1", 128, kernel=(1,1), pad=(0,0), \ + conv11_1, relu11_1 = legacy_conv_act_layer(relu10_2, "11_1", 128, kernel=(1,1), pad=(0,0), \ stride=(1,1), act_type="relu", use_batchnorm=False) - conv11_2, relu11_2 = conv_act_layer(relu11_1, "11_2", 256, kernel=(3,3), pad=(1,1), \ + conv11_2, relu11_2 = legacy_conv_act_layer(relu11_1, "11_2", 256, kernel=(3,3), pad=(1,1), \ stride=(2,2), act_type="relu", use_batchnorm=False) - conv12_1, relu12_1 = conv_act_layer(relu11_2, "12_1", 128, kernel=(1,1), pad=(0,0), \ + conv12_1, relu12_1 = legacy_conv_act_layer(relu11_2, "12_1", 128, kernel=(1,1), pad=(0,0), \ stride=(1,1), act_type="relu", use_batchnorm=False) - conv12_2, relu12_2 = conv_act_layer(relu12_1, "12_2", 256, kernel=(4,4), pad=(1,1), \ + conv12_2, relu12_2 = legacy_conv_act_layer(relu12_1, "12_2", 256, kernel=(4,4), pad=(1,1), \ stride=(1,1), act_type="relu", use_batchnorm=False) # specific parameters for VGG16 network diff --git a/example/image-classification/symbols/resnet_fp16.py b/example/ssd/symbol/resnet.py old mode 100755 new mode 100644 similarity index 71% rename from example/image-classification/symbols/resnet_fp16.py rename to example/ssd/symbol/resnet.py index 04226110dffc..d7dc3cc5bd76 --- a/example/image-classification/symbols/resnet_fp16.py +++ b/example/ssd/symbol/resnet.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + ''' Adapted from https://github.com/tornadomeet/ResNet/blob/master/symbol_resnet.py Original author Wei Wu @@ -7,7 +24,6 @@ Kaiming He, Xiangyu Zhang, Shaoqing Ren, Jian Sun. "Identity Mappings in Deep Residual Networks" ''' import mxnet as mx -import numpy as np def residual_unit(data, num_filter, stride, dim_match, name, bottle_neck=True, bn_mom=0.9, workspace=256, memonger=False): """Return ResNet Unit symbol for building ResNet @@ -32,28 +48,20 @@ def residual_unit(data, num_filter, stride, dim_match, name, bottle_neck=True, b # the same as https://github.com/facebook/fb.resnet.torch#notes, a bit difference with origin paper bn1 = mx.sym.BatchNorm(data=data, fix_gamma=False, eps=2e-5, momentum=bn_mom, name=name + '_bn1') act1 = mx.sym.Activation(data=bn1, act_type='relu', name=name + '_relu1') - weight = mx.symbol.Variable(name=name + '_conv1_weight', dtype=np.float32) - weight = mx.symbol.Cast(data=weight, dtype=np.float16) - conv1 = mx.sym.Convolution(data=act1, weight=weight, num_filter=int(num_filter*0.25), kernel=(1,1), stride=(1,1), pad=(0,0), + conv1 = mx.sym.Convolution(data=act1, num_filter=int(num_filter*0.25), kernel=(1,1), stride=(1,1), pad=(0,0), no_bias=True, workspace=workspace, name=name + '_conv1') bn2 = mx.sym.BatchNorm(data=conv1, fix_gamma=False, eps=2e-5, momentum=bn_mom, name=name + '_bn2') act2 = mx.sym.Activation(data=bn2, act_type='relu', name=name + '_relu2') - weight = mx.symbol.Variable(name=name + '_conv2_weight', dtype=np.float32) - weight = mx.symbol.Cast(data=weight, dtype=np.float16) - conv2 = mx.sym.Convolution(data=act2, weight=weight, num_filter=int(num_filter*0.25), kernel=(3,3), stride=stride, pad=(1,1), + conv2 = mx.sym.Convolution(data=act2, num_filter=int(num_filter*0.25), kernel=(3,3), stride=stride, pad=(1,1), no_bias=True, workspace=workspace, name=name + '_conv2') bn3 = mx.sym.BatchNorm(data=conv2, fix_gamma=False, eps=2e-5, momentum=bn_mom, name=name + '_bn3') act3 = mx.sym.Activation(data=bn3, act_type='relu', name=name + '_relu3') - weight = mx.symbol.Variable(name=name + '_conv3_weight', dtype=np.float32) - weight = mx.symbol.Cast(data=weight, dtype=np.float16) - conv3 = mx.sym.Convolution(data=act3, weight=weight, num_filter=num_filter, kernel=(1,1), stride=(1,1), pad=(0,0), no_bias=True, + conv3 = mx.sym.Convolution(data=act3, num_filter=num_filter, kernel=(1,1), stride=(1,1), pad=(0,0), no_bias=True, workspace=workspace, name=name + '_conv3') if dim_match: shortcut = data else: - weight = mx.symbol.Variable(name=name + '_sc_weight', dtype=np.float32) - weight = mx.symbol.Cast(data=weight, dtype=np.float16) - shortcut = mx.sym.Convolution(data=act1, weight=weight, num_filter=num_filter, kernel=(1,1), stride=stride, no_bias=True, + shortcut = mx.sym.Convolution(data=act1, num_filter=num_filter, kernel=(1,1), stride=stride, no_bias=True, workspace=workspace, name=name+'_sc') if memonger: shortcut._set_attr(mirror_stage='True') @@ -61,22 +69,16 @@ def residual_unit(data, num_filter, stride, dim_match, name, bottle_neck=True, b else: bn1 = mx.sym.BatchNorm(data=data, fix_gamma=False, momentum=bn_mom, eps=2e-5, name=name + '_bn1') act1 = mx.sym.Activation(data=bn1, act_type='relu', name=name + '_relu1') - weight = mx.symbol.Variable(name=name + '_conv1_weight', dtype=np.float32) - weight = mx.symbol.Cast(data=weight, dtype=np.float16) - conv1 = mx.sym.Convolution(data=act1, weight=weight, num_filter=num_filter, kernel=(3,3), stride=stride, pad=(1,1), + conv1 = mx.sym.Convolution(data=act1, num_filter=num_filter, kernel=(3,3), stride=stride, pad=(1,1), no_bias=True, workspace=workspace, name=name + '_conv1') bn2 = mx.sym.BatchNorm(data=conv1, fix_gamma=False, momentum=bn_mom, eps=2e-5, name=name + '_bn2') act2 = mx.sym.Activation(data=bn2, act_type='relu', name=name + '_relu2') - weight = mx.symbol.Variable(name=name + '_conv2_weight', dtype=np.float32) - weight = mx.symbol.Cast(data=weight, dtype=np.float16) - conv2 = mx.sym.Convolution(data=act2, weight=weight, num_filter=num_filter, kernel=(3,3), stride=(1,1), pad=(1,1), + conv2 = mx.sym.Convolution(data=act2, num_filter=num_filter, kernel=(3,3), stride=(1,1), pad=(1,1), no_bias=True, workspace=workspace, name=name + '_conv2') if dim_match: shortcut = data else: - weight = mx.symbol.Variable(name=name + '_sc_weight', dtype=np.float32) - weight = mx.symbol.Cast(data=weight, dtype=np.float16) - shortcut = mx.sym.Convolution(data=act1, weight=weight, num_filter=num_filter, kernel=(1,1), stride=stride, no_bias=True, + shortcut = mx.sym.Convolution(data=act1, num_filter=num_filter, kernel=(1,1), stride=stride, no_bias=True, workspace=workspace, name=name+'_sc') if memonger: shortcut._set_attr(mirror_stage='True') @@ -102,16 +104,14 @@ def resnet(units, num_stages, filter_list, num_classes, image_shape, bottle_neck num_unit = len(units) assert(num_unit == num_stages) data = mx.sym.Variable(name='data') - data = mx.symbol.Cast(data=data, dtype=np.float16) + data = mx.sym.identity(data=data, name='id') data = mx.sym.BatchNorm(data=data, fix_gamma=True, eps=2e-5, momentum=bn_mom, name='bn_data') (nchannel, height, width) = image_shape - weight = mx.symbol.Variable(name='conv0_weight', dtype=np.float32) - weight = mx.symbol.Cast(data=weight, dtype=np.float16) if height <= 32: # such as cifar10 - body = mx.sym.Convolution(data=data, weight=weight, num_filter=filter_list[0], kernel=(3, 3), stride=(1,1), pad=(1, 1), + body = mx.sym.Convolution(data=data, num_filter=filter_list[0], kernel=(3, 3), stride=(1,1), pad=(1, 1), no_bias=True, name="conv0", workspace=workspace) else: # often expected to be 224 such as imagenet - body = mx.sym.Convolution(data=data, weight=weight, num_filter=filter_list[0], kernel=(7, 7), stride=(2,2), pad=(3, 3), + body = mx.sym.Convolution(data=data, num_filter=filter_list[0], kernel=(7, 7), stride=(2,2), pad=(3, 3), no_bias=True, name="conv0", workspace=workspace) body = mx.sym.BatchNorm(data=body, fix_gamma=False, eps=2e-5, momentum=bn_mom, name='bn0') body = mx.sym.Activation(data=body, act_type='relu', name='relu0') @@ -129,12 +129,7 @@ def resnet(units, num_stages, filter_list, num_classes, image_shape, bottle_neck # Although kernel is not used here when global_pool=True, we should put one pool1 = mx.symbol.Pooling(data=relu1, global_pool=True, kernel=(7, 7), pool_type='avg', name='pool1') flat = mx.symbol.Flatten(data=pool1) - weight = mx.symbol.Variable(name='fc1_weight', dtype=np.float32) - bias = mx.symbol.Variable(name='fc1_bias', dtype=np.float32) - weight = mx.symbol.Cast(data=weight, dtype=np.float16) - bias = mx.symbol.Cast(data=bias, dtype=np.float16) - fc1 = mx.symbol.FullyConnected(data=flat, weight=weight, bias=bias, num_hidden=num_classes, name='fc1') - fc1 = mx.symbol.Cast(data=fc1, dtype=np.float32) + fc1 = mx.symbol.FullyConnected(data=flat, num_hidden=num_classes, name='fc1') return mx.symbol.SoftmaxOutput(data=fc1, name='softmax') def get_symbol(num_classes, num_layers, image_shape, conv_workspace=256, **kwargs): @@ -155,7 +150,7 @@ def get_symbol(num_classes, num_layers, image_shape, conv_workspace=256, **kwarg filter_list = [16, 16, 32, 64] bottle_neck = False else: - raise ValueError("no experiments done on num_layers {}, you can do it youself".format(num_layers)) + raise ValueError("no experiments done on num_layers {}, you can do it yourself".format(num_layers)) units = per_unit * num_stages else: if num_layers >= 50: @@ -180,7 +175,7 @@ def get_symbol(num_classes, num_layers, image_shape, conv_workspace=256, **kwarg elif num_layers == 269: units = [3, 30, 48, 8] else: - raise ValueError("no experiments done on num_layers {}, you can do it youself".format(num_layers)) + raise ValueError("no experiments done on num_layers {}, you can do it yourself".format(num_layers)) return resnet(units = units, num_stages = num_stages, diff --git a/example/ssd/symbol/symbol_builder.py b/example/ssd/symbol/symbol_builder.py new file mode 100644 index 000000000000..4cd7f88ea312 --- /dev/null +++ b/example/ssd/symbol/symbol_builder.py @@ -0,0 +1,183 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +import mxnet as mx +from common import multi_layer_feature, multibox_layer + + +def import_module(module_name): + """Helper function to import module""" + import sys, os + import importlib + sys.path.append(os.path.dirname(__file__)) + return importlib.import_module(module_name) + +def get_symbol_train(network, num_classes, from_layers, num_filters, strides, pads, + sizes, ratios, normalizations=-1, steps=[], min_filter=128, + nms_thresh=0.5, force_suppress=False, nms_topk=400, **kwargs): + """Build network symbol for training SSD + + Parameters + ---------- + network : str + base network symbol name + num_classes : int + number of object classes not including background + from_layers : list of str + feature extraction layers, use '' for add extra layers + For example: + from_layers = ['relu4_3', 'fc7', '', '', '', ''] + which means extract feature from relu4_3 and fc7, adding 4 extra layers + on top of fc7 + num_filters : list of int + number of filters for extra layers, you can use -1 for extracted features, + however, if normalization and scale is applied, the number of filter for + that layer must be provided. + For example: + num_filters = [512, -1, 512, 256, 256, 256] + strides : list of int + strides for the 3x3 convolution appended, -1 can be used for extracted + feature layers + pads : list of int + paddings for the 3x3 convolution, -1 can be used for extracted layers + sizes : list or list of list + [min_size, max_size] for all layers or [[], [], []...] for specific layers + ratios : list or list of list + [ratio1, ratio2...] for all layers or [[], [], ...] for specific layers + normalizations : int or list of int + use normalizations value for all layers or [...] for specific layers, + -1 indicate no normalizations and scales + steps : list + specify steps for each MultiBoxPrior layer, leave empty, it will calculate + according to layer dimensions + min_filter : int + minimum number of filters used in 1x1 convolution + nms_thresh : float + non-maximum suppression threshold + force_suppress : boolean + whether suppress different class objects + nms_topk : int + apply NMS to top K detections + + Returns + ------- + mx.Symbol + + """ + label = mx.sym.Variable('label') + body = import_module(network).get_symbol(num_classes, **kwargs) + layers = multi_layer_feature(body, from_layers, num_filters, strides, pads, + min_filter=min_filter) + + loc_preds, cls_preds, anchor_boxes = multibox_layer(layers, \ + num_classes, sizes=sizes, ratios=ratios, normalization=normalizations, \ + num_channels=num_filters, clip=False, interm_layer=0, steps=steps) + + tmp = mx.contrib.symbol.MultiBoxTarget( + *[anchor_boxes, label, cls_preds], overlap_threshold=.5, \ + ignore_label=-1, negative_mining_ratio=3, minimum_negative_samples=0, \ + negative_mining_thresh=.5, variances=(0.1, 0.1, 0.2, 0.2), + name="multibox_target") + loc_target = tmp[0] + loc_target_mask = tmp[1] + cls_target = tmp[2] + + cls_prob = mx.symbol.SoftmaxOutput(data=cls_preds, label=cls_target, \ + ignore_label=-1, use_ignore=True, grad_scale=1., multi_output=True, \ + normalization='valid', name="cls_prob") + loc_loss_ = mx.symbol.smooth_l1(name="loc_loss_", \ + data=loc_target_mask * (loc_preds - loc_target), scalar=1.0) + loc_loss = mx.symbol.MakeLoss(loc_loss_, grad_scale=1., \ + normalization='valid', name="loc_loss") + + # monitoring training status + cls_label = mx.symbol.MakeLoss(data=cls_target, grad_scale=0, name="cls_label") + det = mx.contrib.symbol.MultiBoxDetection(*[cls_prob, loc_preds, anchor_boxes], \ + name="detection", nms_threshold=nms_thresh, force_suppress=force_suppress, + variances=(0.1, 0.1, 0.2, 0.2), nms_topk=nms_topk) + det = mx.symbol.MakeLoss(data=det, grad_scale=0, name="det_out") + + # group output + out = mx.symbol.Group([cls_prob, loc_loss, cls_label, det]) + return out + +def get_symbol(network, num_classes, from_layers, num_filters, sizes, ratios, + strides, pads, normalizations=-1, steps=[], min_filter=128, + nms_thresh=0.5, force_suppress=False, nms_topk=400, **kwargs): + """Build network for testing SSD + + Parameters + ---------- + network : str + base network symbol name + num_classes : int + number of object classes not including background + from_layers : list of str + feature extraction layers, use '' for add extra layers + For example: + from_layers = ['relu4_3', 'fc7', '', '', '', ''] + which means extract feature from relu4_3 and fc7, adding 4 extra layers + on top of fc7 + num_filters : list of int + number of filters for extra layers, you can use -1 for extracted features, + however, if normalization and scale is applied, the number of filter for + that layer must be provided. + For example: + num_filters = [512, -1, 512, 256, 256, 256] + strides : list of int + strides for the 3x3 convolution appended, -1 can be used for extracted + feature layers + pads : list of int + paddings for the 3x3 convolution, -1 can be used for extracted layers + sizes : list or list of list + [min_size, max_size] for all layers or [[], [], []...] for specific layers + ratios : list or list of list + [ratio1, ratio2...] for all layers or [[], [], ...] for specific layers + normalizations : int or list of int + use normalizations value for all layers or [...] for specific layers, + -1 indicate no normalizations and scales + steps : list + specify steps for each MultiBoxPrior layer, leave empty, it will calculate + according to layer dimensions + min_filter : int + minimum number of filters used in 1x1 convolution + nms_thresh : float + non-maximum suppression threshold + force_suppress : boolean + whether suppress different class objects + nms_topk : int + apply NMS to top K detections + + Returns + ------- + mx.Symbol + + """ + body = import_module(network).get_symbol(num_classes, **kwargs) + layers = multi_layer_feature(body, from_layers, num_filters, strides, pads, + min_filter=min_filter) + + loc_preds, cls_preds, anchor_boxes = multibox_layer(layers, \ + num_classes, sizes=sizes, ratios=ratios, normalization=normalizations, \ + num_channels=num_filters, clip=False, interm_layer=0, steps=steps) + + cls_prob = mx.symbol.SoftmaxActivation(data=cls_preds, mode='channel', \ + name='cls_prob') + out = mx.contrib.symbol.MultiBoxDetection(*[cls_prob, loc_preds, anchor_boxes], \ + name="detection", nms_threshold=nms_thresh, force_suppress=force_suppress, + variances=(0.1, 0.1, 0.2, 0.2), nms_topk=nms_topk) + return out diff --git a/example/ssd/symbol/symbol_factory.py b/example/ssd/symbol/symbol_factory.py new file mode 100644 index 000000000000..c451cd61ab83 --- /dev/null +++ b/example/ssd/symbol/symbol_factory.py @@ -0,0 +1,139 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +"""Presets for various network configurations""" +import logging +import symbol_builder + +def get_config(network, data_shape, **kwargs): + """Configuration factory for various networks + + Parameters + ---------- + network : str + base network name, such as vgg_reduced, inceptionv3, resnet... + data_shape : int + input data dimension + kwargs : dict + extra arguments + """ + if network == 'vgg16_reduced': + if data_shape >= 448: + from_layers = ['relu4_3', 'relu7', '', '', '', '', ''] + num_filters = [512, -1, 512, 256, 256, 256, 256] + strides = [-1, -1, 2, 2, 2, 2, 1] + pads = [-1, -1, 1, 1, 1, 1, 1] + sizes = [[.07, .1025], [.15,.2121], [.3, .3674], [.45, .5196], [.6, .6708], \ + [.75, .8216], [.9, .9721]] + ratios = [[1,2,.5], [1,2,.5,3,1./3], [1,2,.5,3,1./3], [1,2,.5,3,1./3], \ + [1,2,.5,3,1./3], [1,2,.5], [1,2,.5]] + normalizations = [20, -1, -1, -1, -1, -1, -1] + steps = [] if data_shape != 512 else [x / 512.0 for x in + [8, 16, 32, 64, 128, 256, 512]] + else: + from_layers = ['relu4_3', 'relu7', '', '', '', ''] + num_filters = [512, -1, 512, 256, 256, 256] + strides = [-1, -1, 2, 2, 1, 1] + pads = [-1, -1, 1, 1, 0, 0] + sizes = [[.1, .141], [.2,.272], [.37, .447], [.54, .619], [.71, .79], [.88, .961]] + ratios = [[1,2,.5], [1,2,.5,3,1./3], [1,2,.5,3,1./3], [1,2,.5,3,1./3], \ + [1,2,.5], [1,2,.5]] + normalizations = [20, -1, -1, -1, -1, -1] + steps = [] if data_shape != 300 else [x / 300.0 for x in [8, 16, 32, 64, 100, 300]] + if not (data_shape == 300 or data_shape == 512): + logging.warn('data_shape %d was not tested, use with caucious.' % data_shape) + return locals() + elif network == 'inceptionv3': + from_layers = ['ch_concat_mixed_7_chconcat', 'ch_concat_mixed_10_chconcat', '', '', '', ''] + num_filters = [-1, -1, 512, 256, 256, 128] + strides = [-1, -1, 2, 2, 2, 2] + pads = [-1, -1, 1, 1, 1, 1] + sizes = [[.1, .141], [.2,.272], [.37, .447], [.54, .619], [.71, .79], [.88, .961]] + ratios = [[1,2,.5], [1,2,.5,3,1./3], [1,2,.5,3,1./3], [1,2,.5,3,1./3], \ + [1,2,.5], [1,2,.5]] + normalizations = -1 + steps = [] + return locals() + elif network == 'resnet50': + num_layers = 50 + image_shape = '3,224,224' # resnet require it as shape check + network = 'resnet' + from_layers = ['_plus12', '_plus15', '', '', '', ''] + num_filters = [-1, -1, 512, 256, 256, 128] + strides = [-1, -1, 2, 2, 2, 2] + pads = [-1, -1, 1, 1, 1, 1] + sizes = [[.1, .141], [.2,.272], [.37, .447], [.54, .619], [.71, .79], [.88, .961]] + ratios = [[1,2,.5], [1,2,.5,3,1./3], [1,2,.5,3,1./3], [1,2,.5,3,1./3], \ + [1,2,.5], [1,2,.5]] + normalizations = -1 + steps = [] + return locals() + elif network == 'resnet101': + num_layers = 101 + image_shape = '3,224,224' + network = 'resnet' + from_layers = ['_plus12', '_plus15', '', '', '', ''] + num_filters = [-1, -1, 512, 256, 256, 128] + strides = [-1, -1, 2, 2, 2, 2] + pads = [-1, -1, 1, 1, 1, 1] + sizes = [[.1, .141], [.2,.272], [.37, .447], [.54, .619], [.71, .79], [.88, .961]] + ratios = [[1,2,.5], [1,2,.5,3,1./3], [1,2,.5,3,1./3], [1,2,.5,3,1./3], \ + [1,2,.5], [1,2,.5]] + normalizations = -1 + steps = [] + return locals() + else: + msg = 'No configuration found for %s with data_shape %d' % (network, data_shape) + raise NotImplementedError(msg) + +def get_symbol_train(network, data_shape, **kwargs): + """Wrapper for get symbol for train + + Parameters + ---------- + network : str + name for the base network symbol + data_shape : int + input shape + kwargs : dict + see symbol_builder.get_symbol_train for more details + """ + if network.startswith('legacy'): + logging.warn('Using legacy model.') + return symbol_builder.import_module(network).get_symbol_train(**kwargs) + config = get_config(network, data_shape, **kwargs).copy() + config.update(kwargs) + return symbol_builder.get_symbol_train(**config) + +def get_symbol(network, data_shape, **kwargs): + """Wrapper for get symbol for test + + Parameters + ---------- + network : str + name for the base network symbol + data_shape : int + input shape + kwargs : dict + see symbol_builder.get_symbol for more details + """ + if network.startswith('legacy'): + logging.warn('Using legacy model.') + return symbol_builder.import_module(network).get_symbol(**kwargs) + config = get_config(network, data_shape, **kwargs).copy() + config.update(kwargs) + return symbol_builder.get_symbol(**config) diff --git a/example/ssd/symbol/vgg16_reduced.py b/example/ssd/symbol/vgg16_reduced.py new file mode 100644 index 000000000000..16535e6dc22d --- /dev/null +++ b/example/ssd/symbol/vgg16_reduced.py @@ -0,0 +1,103 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +import mxnet as mx + +def get_symbol(num_classes=1000, **kwargs): + """ + VGG 16 layers network + This is a modified version, with fc6/fc7 layers replaced by conv layers + And the network is slightly smaller than original VGG 16 network + """ + data = mx.symbol.Variable(name="data") + label = mx.symbol.Variable(name="label") + + # group 1 + conv1_1 = mx.symbol.Convolution( + data=data, kernel=(3, 3), pad=(1, 1), num_filter=64, name="conv1_1") + relu1_1 = mx.symbol.Activation(data=conv1_1, act_type="relu", name="relu1_1") + conv1_2 = mx.symbol.Convolution( + data=relu1_1, kernel=(3, 3), pad=(1, 1), num_filter=64, name="conv1_2") + relu1_2 = mx.symbol.Activation(data=conv1_2, act_type="relu", name="relu1_2") + pool1 = mx.symbol.Pooling( + data=relu1_2, pool_type="max", kernel=(2, 2), stride=(2, 2), name="pool1") + # group 2 + conv2_1 = mx.symbol.Convolution( + data=pool1, kernel=(3, 3), pad=(1, 1), num_filter=128, name="conv2_1") + relu2_1 = mx.symbol.Activation(data=conv2_1, act_type="relu", name="relu2_1") + conv2_2 = mx.symbol.Convolution( + data=relu2_1, kernel=(3, 3), pad=(1, 1), num_filter=128, name="conv2_2") + relu2_2 = mx.symbol.Activation(data=conv2_2, act_type="relu", name="relu2_2") + pool2 = mx.symbol.Pooling( + data=relu2_2, pool_type="max", kernel=(2, 2), stride=(2, 2), name="pool2") + # group 3 + conv3_1 = mx.symbol.Convolution( + data=pool2, kernel=(3, 3), pad=(1, 1), num_filter=256, name="conv3_1") + relu3_1 = mx.symbol.Activation(data=conv3_1, act_type="relu", name="relu3_1") + conv3_2 = mx.symbol.Convolution( + data=relu3_1, kernel=(3, 3), pad=(1, 1), num_filter=256, name="conv3_2") + relu3_2 = mx.symbol.Activation(data=conv3_2, act_type="relu", name="relu3_2") + conv3_3 = mx.symbol.Convolution( + data=relu3_2, kernel=(3, 3), pad=(1, 1), num_filter=256, name="conv3_3") + relu3_3 = mx.symbol.Activation(data=conv3_3, act_type="relu", name="relu3_3") + pool3 = mx.symbol.Pooling( + data=relu3_3, pool_type="max", kernel=(2, 2), stride=(2, 2), \ + pooling_convention="full", name="pool3") + # group 4 + conv4_1 = mx.symbol.Convolution( + data=pool3, kernel=(3, 3), pad=(1, 1), num_filter=512, name="conv4_1") + relu4_1 = mx.symbol.Activation(data=conv4_1, act_type="relu", name="relu4_1") + conv4_2 = mx.symbol.Convolution( + data=relu4_1, kernel=(3, 3), pad=(1, 1), num_filter=512, name="conv4_2") + relu4_2 = mx.symbol.Activation(data=conv4_2, act_type="relu", name="relu4_2") + conv4_3 = mx.symbol.Convolution( + data=relu4_2, kernel=(3, 3), pad=(1, 1), num_filter=512, name="conv4_3") + relu4_3 = mx.symbol.Activation(data=conv4_3, act_type="relu", name="relu4_3") + pool4 = mx.symbol.Pooling( + data=relu4_3, pool_type="max", kernel=(2, 2), stride=(2, 2), name="pool4") + # group 5 + conv5_1 = mx.symbol.Convolution( + data=pool4, kernel=(3, 3), pad=(1, 1), num_filter=512, name="conv5_1") + relu5_1 = mx.symbol.Activation(data=conv5_1, act_type="relu", name="relu5_1") + conv5_2 = mx.symbol.Convolution( + data=relu5_1, kernel=(3, 3), pad=(1, 1), num_filter=512, name="conv5_2") + relu5_2 = mx.symbol.Activation(data=conv5_2, act_type="relu", name="relu5_2") + conv5_3 = mx.symbol.Convolution( + data=relu5_2, kernel=(3, 3), pad=(1, 1), num_filter=512, name="conv5_3") + relu5_3 = mx.symbol.Activation(data=conv5_3, act_type="relu", name="relu5_3") + pool5 = mx.symbol.Pooling( + data=relu5_3, pool_type="max", kernel=(3, 3), stride=(1, 1), + pad=(1,1), name="pool5") + # group 6 + conv6 = mx.symbol.Convolution( + data=pool5, kernel=(3, 3), pad=(6, 6), dilate=(6, 6), + num_filter=1024, name="fc6") + relu6 = mx.symbol.Activation(data=conv6, act_type="relu", name="relu6") + # drop6 = mx.symbol.Dropout(data=relu6, p=0.5, name="drop6") + # group 7 + conv7 = mx.symbol.Convolution( + data=relu6, kernel=(1, 1), pad=(0, 0), num_filter=1024, name="fc7") + relu7 = mx.symbol.Activation(data=conv7, act_type="relu", name="relu7") + # drop7 = mx.symbol.Dropout(data=relu7, p=0.5, name="drop7") + + gpool = mx.symbol.Pooling(data=relu7, pool_type='avg', kernel=(7, 7), + global_pool=True, name='global_pool') + conv8 = mx.symbol.Convolution(data=gpool, num_filter=num_classes, kernel=(1, 1), + name='fc8') + flat = mx.symbol.Flatten(data=conv8) + softmax = mx.symbol.SoftmaxOutput(data=flat, name='softmax') + return softmax diff --git a/example/ssd/tools/caffe_converter/caffe_parse/caffe.proto b/example/ssd/tools/caffe_converter/caffe_parse/caffe.proto index d7d66dd8bdb1..7df0537c9955 100644 --- a/example/ssd/tools/caffe_converter/caffe_parse/caffe.proto +++ b/example/ssd/tools/caffe_converter/caffe_parse/caffe.proto @@ -560,7 +560,7 @@ message TransformationParameter { // mean_file and mean_value cannot be specified at the same time optional string mean_file = 4; - // if specified can be repeated once (would substract it from all the channels) + // if specified can be repeated once (would subtract it from all the channels) // or can be repeated the same number of times as channels // (would subtract them from the corresponding channel) repeated float mean_value = 5; diff --git a/example/ssd/tools/caffe_converter/caffe_parse/parse_from_protobuf.py b/example/ssd/tools/caffe_converter/caffe_parse/parse_from_protobuf.py index b828ca28dff0..862049a770b1 100644 --- a/example/ssd/tools/caffe_converter/caffe_parse/parse_from_protobuf.py +++ b/example/ssd/tools/caffe_converter/caffe_parse/parse_from_protobuf.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + from google.protobuf import text_format import numpy as np import caffe_parse.caffe_pb2 as caffe_pb2 diff --git a/example/ssd/tools/caffe_converter/convert_model.py b/example/ssd/tools/caffe_converter/convert_model.py index a06b655b53d9..f17a3f250ecf 100644 --- a/example/ssd/tools/caffe_converter/convert_model.py +++ b/example/ssd/tools/caffe_converter/convert_model.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + from __future__ import print_function import sys import os.path as osp diff --git a/example/ssd/tools/caffe_converter/convert_symbol.py b/example/ssd/tools/caffe_converter/convert_symbol.py index 63b044a46c97..10510aa92569 100644 --- a/example/ssd/tools/caffe_converter/convert_symbol.py +++ b/example/ssd/tools/caffe_converter/convert_symbol.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + from __future__ import print_function from google.protobuf import text_format import argparse diff --git a/example/ssd/tools/caffe_converter/make_win32.bat b/example/ssd/tools/caffe_converter/make_win32.bat index 7d354dcaeb6c..1ee8e89f018f 100644 --- a/example/ssd/tools/caffe_converter/make_win32.bat +++ b/example/ssd/tools/caffe_converter/make_win32.bat @@ -1,3 +1,20 @@ +rem Licensed to the Apache Software Foundation (ASF) under one +rem or more contributor license agreements. See the NOTICE file +rem distributed with this work for additional information +rem regarding copyright ownership. The ASF licenses this file +rem to you under the Apache License, Version 2.0 (the +rem "License"); you may not use this file except in compliance +rem with the License. You may obtain a copy of the License at +rem +rem http://www.apache.org/licenses/LICENSE-2.0 +rem +rem Unless required by applicable law or agreed to in writing, +rem software distributed under the License is distributed on an +rem "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +rem KIND, either express or implied. See the License for the +rem specific language governing permissions and limitations +rem under the License. + @protoc --python_out=./ ./caffe_parse/caffe.proto @echo done. @pause diff --git a/example/ssd/tools/caffe_converter/mean_image.py b/example/ssd/tools/caffe_converter/mean_image.py index d28a750271e0..e07c6fb281c0 100644 --- a/example/ssd/tools/caffe_converter/mean_image.py +++ b/example/ssd/tools/caffe_converter/mean_image.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import mxnet as mx import numpy as np import argparse diff --git a/example/ssd/tools/find_mxnet.py b/example/ssd/tools/find_mxnet.py index 66545f38ba0e..0ad64cca01d7 100644 --- a/example/ssd/tools/find_mxnet.py +++ b/example/ssd/tools/find_mxnet.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + try: import mxnet as mx except ImportError: diff --git a/example/ssd/tools/prepare_coco.sh b/example/ssd/tools/prepare_coco.sh new file mode 100644 index 000000000000..fd34bd55513a --- /dev/null +++ b/example/ssd/tools/prepare_coco.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash + +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +python $DIR/prepare_dataset.py --dataset coco --set train2014,valminusminival2014 --target $DIR/../data/train.lst --root $DIR/../data/coco +python $DIR/prepare_dataset.py --dataset coco --set minival2014 --target $DIR/../data/val.lst --shuffle False --root $DIR/../data/coco diff --git a/example/ssd/tools/prepare_dataset.py b/example/ssd/tools/prepare_dataset.py index 7bd696840766..9b4fceb221ca 100644 --- a/example/ssd/tools/prepare_dataset.py +++ b/example/ssd/tools/prepare_dataset.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + from __future__ import print_function import sys, os import argparse @@ -5,6 +22,7 @@ curr_path = os.path.abspath(os.path.dirname(__file__)) sys.path.append(os.path.join(curr_path, '..')) from dataset.pascal_voc import PascalVoc +from dataset.mscoco import Coco from dataset.concat_db import ConcatDB def load_pascal(image_set, year, devkit_path, shuffle=False): @@ -46,6 +64,30 @@ def load_pascal(image_set, year, devkit_path, shuffle=False): else: return imdbs[0] +def load_coco(image_set, dirname, shuffle=False): + """ + wrapper function for loading ms coco dataset + + Parameters: + ---------- + image_set : str + train2014, val2014, valminusminival2014, minival2014 + dirname: str + root dir for coco + shuffle: boolean + initial shuffle + """ + anno_files = ['instances_' + y.strip() + '.json' for y in image_set.split(',')] + assert anno_files, "No image set specified" + imdbs = [] + for af in anno_files: + af_path = os.path.join(dirname, 'annotations', af) + imdbs.append(Coco(af_path, dirname, shuffle=shuffle)) + if len(imdbs) > 1: + return ConcatDB(imdbs, shuffle) + else: + return imdbs[0] + def parse_args(): parser = argparse.ArgumentParser(description='Prepare lists for dataset') parser.add_argument('--dataset', dest='dataset', help='dataset to use', @@ -69,6 +111,11 @@ def parse_args(): args = parse_args() if args.dataset == 'pascal': db = load_pascal(args.set, args.year, args.root_path, args.shuffle) + print("saving list to disk...") + db.save_imglist(args.target, root=args.root_path) + elif args.dataset == 'coco': + db = load_coco(args.set, args.root_path, args.shuffle) + print("saving list to disk...") db.save_imglist(args.target, root=args.root_path) else: raise NotImplementedError("No implementation for dataset: " + args.dataset) diff --git a/example/ssd/tools/prepare_pascal.sh b/example/ssd/tools/prepare_pascal.sh index 954327aeefa6..1c23cd5b85c9 100644 --- a/example/ssd/tools/prepare_pascal.sh +++ b/example/ssd/tools/prepare_pascal.sh @@ -1,4 +1,22 @@ #!/usr/bin/env bash + +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" python $DIR/prepare_dataset.py --dataset pascal --year 2007,2012 --set trainval --target $DIR/../data/train.lst python $DIR/prepare_dataset.py --dataset pascal --year 2007 --set test --target $DIR/../data/val.lst --shuffle False diff --git a/example/ssd/tools/rand_sampler.py b/example/ssd/tools/rand_sampler.py index d2ed3ad9afe7..7f0cb6f8ba3d 100644 --- a/example/ssd/tools/rand_sampler.py +++ b/example/ssd/tools/rand_sampler.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import numpy as np import math diff --git a/example/ssd/tools/visualize_net.py b/example/ssd/tools/visualize_net.py index e619c230bb90..b3b714a7f49b 100644 --- a/example/ssd/tools/visualize_net.py +++ b/example/ssd/tools/visualize_net.py @@ -1,13 +1,31 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + from __future__ import print_function import find_mxnet import mxnet as mx -import importlib import argparse -import sys +import sys, os +sys.path.append(os.path.join(os.path.dirname(__file__), '..', 'symbol')) +import symbol_factory + parser = argparse.ArgumentParser(description='network visualization') -parser.add_argument('--network', type=str, default='vgg16_ssd_300', - choices = ['vgg16_ssd_300', 'vgg16_ssd_512'], +parser.add_argument('--network', type=str, default='vgg16_reduced', help = 'the cnn to use') parser.add_argument('--num-classes', type=int, default=20, help='the number of classes') @@ -16,13 +34,11 @@ parser.add_argument('--train', action='store_true', default=False, help='show train net') args = parser.parse_args() -sys.path.append('../symbol') - if not args.train: - net = importlib.import_module("symbol_" + args.network).get_symbol(args.num_classes) + net = symbol_factory.get_symbol(args.network, args.data_shape, num_classes=args.num_classes) a = mx.viz.plot_network(net, shape={"data":(1,3,args.data_shape,args.data_shape)}, \ node_attrs={"shape":'rect', "fixedsize":'false'}) - a.render("ssd_" + args.network) + a.render("ssd_" + args.network + '_' + str(args.data_shape)) else: - net = importlib.import_module("symbol_" + args.network).get_symbol_train(args.num_classes) + net = symbol_factory.get_symbol_train(args.network, args.data_shape, num_classes=args.num_classes) print(net.tojson()) diff --git a/example/ssd/train.py b/example/ssd/train.py index fcd5fb95e8f1..f08aafb97b8f 100644 --- a/example/ssd/train.py +++ b/example/ssd/train.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import argparse import tools.find_mxnet import mxnet as mx @@ -15,8 +32,8 @@ def parse_args(): default=os.path.join(os.getcwd(), 'data', 'val.rec'), type=str) parser.add_argument('--val-list', dest='val_list', help='validation list to use', default="", type=str) - parser.add_argument('--network', dest='network', type=str, default='vgg16_ssd_300', - choices=['vgg16_ssd_300', 'vgg16_ssd_512'], help='which network to use') + parser.add_argument('--network', dest='network', type=str, default='vgg16_reduced', + help='which network to use') parser.add_argument('--batch-size', dest='batch_size', type=int, default=32, help='training batch size') parser.add_argument('--resume', dest='resume', type=int, default=-1, @@ -41,7 +58,7 @@ def parse_args(): help='set image shape') parser.add_argument('--label-width', dest='label_width', type=int, default=350, help='force padding label width to sync across train and validation') - parser.add_argument('--lr', dest='learning_rate', type=float, default=0.004, + parser.add_argument('--lr', dest='learning_rate', type=float, default=0.002, help='learning rate') parser.add_argument('--momentum', dest='momentum', type=float, default=0.9, help='momentum') @@ -53,7 +70,7 @@ def parse_args(): help='green mean value') parser.add_argument('--mean-b', dest='mean_b', type=float, default=104, help='blue mean value') - parser.add_argument('--lr-steps', dest='lr_refactor_step', type=str, default='150, 200', + parser.add_argument('--lr-steps', dest='lr_refactor_step', type=str, default='80, 160', help='refactor learning rate at specified epochs') parser.add_argument('--lr-factor', dest='lr_refactor_ratio', type=str, default=0.1, help='ratio to refactor learning rate') @@ -92,9 +109,9 @@ def parse_class_names(args): num_class = args.num_class if len(args.class_names) > 0: if os.path.isfile(args.class_names): - # try to open it to read class names - with open(args.class_names, 'r') as f: - class_names = [l.strip() for l in f.readlines()] + # try to open it to read class names + with open(args.class_names, 'r') as f: + class_names = [l.strip() for l in f.readlines()] else: class_names = [c.strip() for c in args.class_names.split(',')] assert len(class_names) == num_class, str(len(class_names)) diff --git a/example/ssd/train/metric.py b/example/ssd/train/metric.py index fa631a5263fc..731f8fcc19f4 100644 --- a/example/ssd/train/metric.py +++ b/example/ssd/train/metric.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import mxnet as mx import numpy as np @@ -5,8 +22,22 @@ class MultiBoxMetric(mx.metric.EvalMetric): """Calculate metrics for Multibox training """ def __init__(self, eps=1e-8): - super(MultiBoxMetric, self).__init__(['CrossEntropy', 'SmoothL1'], 2) + super(MultiBoxMetric, self).__init__('MultiBox') self.eps = eps + self.num = 2 + self.name = ['CrossEntropy', 'SmoothL1'] + self.reset() + + def reset(self): + """ + override reset behavior + """ + if getattr(self, 'num', None) is None: + self.num_inst = 0 + self.sum_metric = 0.0 + else: + self.num_inst = [0] * self.num + self.sum_metric = [0.0] * self.num def update(self, labels, preds): """ diff --git a/example/ssd/train/train_net.py b/example/ssd/train/train_net.py index 3f73ee880ef5..767e3244d406 100644 Binary files a/example/ssd/train/train_net.py and b/example/ssd/train/train_net.py differ diff --git a/example/stochastic-depth/sd_cifar10.py b/example/stochastic-depth/sd_cifar10.py index 9c6f2736600d..c123562cf7ef 100644 --- a/example/stochastic-depth/sd_cifar10.py +++ b/example/stochastic-depth/sd_cifar10.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + ########################################################################################### # Implementation of the stochastic depth algorithm described in the paper # diff --git a/example/stochastic-depth/sd_mnist.py b/example/stochastic-depth/sd_mnist.py index 8a13d4bb532d..7eb93741ff5a 100644 --- a/example/stochastic-depth/sd_mnist.py +++ b/example/stochastic-depth/sd_mnist.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + ################################################################################ # A sanity check mainly for debugging purpose. See sd_cifar10.py for a non-trivial # example of stochastic depth on cifar10. diff --git a/example/stochastic-depth/sd_module.py b/example/stochastic-depth/sd_module.py index ae8cfe0ba255..f30913d550e2 100644 --- a/example/stochastic-depth/sd_module.py +++ b/example/stochastic-depth/sd_module.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import logging import mxnet as mx import numpy as np diff --git a/example/svm_mnist/README.md b/example/svm_mnist/README.md index 082c2053f27e..408f5108b44a 100644 --- a/example/svm_mnist/README.md +++ b/example/svm_mnist/README.md @@ -1,6 +1,6 @@ # Use case with Support Vector Machine -To ensure that not only the implementation is learning, but is able to outsmart the softmax, as [this article](arxiv.org/pdf/1306.0239.pdf) suggests, I ran svm_mnist.py script. It was based on the MNIST experiment description on the article and [this tutorial](https://github.com/dmlc/mxnet-gtc-tutorial/blob/master/tutorial.ipynb). +To ensure that not only the implementation is learning, but is able to outsmart the softmax, as [this article](https://arxiv.org/pdf/1306.0239.pdf) suggests, I ran svm_mnist.py script. It was based on the MNIST experiment description on the article and [this tutorial](https://github.com/dmlc/mxnet-gtc-tutorial/blob/master/tutorial.ipynb). ## To this you will need @@ -8,4 +8,4 @@ To ensure that not only the implementation is learning, but is able to outsmart * [Numpy](http://www.scipy.org/scipylib/download.html) * [Sklearn](http://scikit-learn.org/stable/install.html) -I recommend installing [matplot](http://matplotlib.org/users/installing.html) to visualize examples \ No newline at end of file +I recommend installing [matplot](http://matplotlib.org/users/installing.html) to visualize examples diff --git a/example/svm_mnist/svm_mnist.py b/example/svm_mnist/svm_mnist.py index d2925e94dfb3..679540198d28 100644 --- a/example/svm_mnist/svm_mnist.py +++ b/example/svm_mnist/svm_mnist.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + ############################################################# ## Please read the README.md document for better reference ## @@ -41,8 +58,7 @@ Y = mnist.target[p] X_show = mnist.data[p] -# This is just to normalize the input to a value inside [0,1], -# and separate train set and test set +# This is just to normalize the input and separate train set and test set X = X.astype(np.float32)/255 X_train = X[:60000] X_test = X[60000:] @@ -52,28 +68,28 @@ # Article's suggestion on batch size batch_size = 200 -train_iter = mx.io.NDArrayIter(X_train, Y_train, batch_size=batch_size) -test_iter = mx.io.NDArrayIter(X_test, Y_test, batch_size=batch_size) - -# A quick work around to prevent mxnet complaining the lack of a softmax_label -train_iter.label = mx.io._init_data(Y_train, allow_empty=True, default_name='svm_label') -test_iter.label = mx.io._init_data(Y_test, allow_empty=True, default_name='svm_label') +train_iter = mx.io.NDArrayIter(X_train, Y_train, batch_size=batch_size, label_name='svm_label') +test_iter = mx.io.NDArrayIter(X_test, Y_test, batch_size=batch_size, label_name='svm_label') # Here we instatiate and fit the model for our data # The article actually suggests using 400 epochs, # But I reduced to 10, for convinience -model = mx.model.FeedForward( - ctx = mx.cpu(0), # Run on CPU 0 +mod = mx.mod.Module( + context = mx.cpu(0), # Run on CPU 0 symbol = mlp, # Use the network we just defined - num_epoch = 10, # Train for 10 epochs - learning_rate = 0.1, # Learning rate - momentum = 0.9, # Momentum for SGD with momentum - wd = 0.00001, # Weight decay for regularization - ) -model.fit( - X=train_iter, # Training data set + label_names = ['svm_label'], +) +mod.fit( + train_data=train_iter, eval_data=test_iter, # Testing data set. MXNet computes scores on test set every epoch - batch_end_callback = mx.callback.Speedometer(batch_size, 200)) # Logging module to print out progress + batch_end_callback = mx.callback.Speedometer(batch_size, 200), # Logging module to print out progress + num_epoch = 10, # Train for 10 epochs + optimizer_params = { + 'learning_rate': 0.1, # Learning rate + 'momentum': 0.9, # Momentum for SGD with momentum + 'wd': 0.00001, # Weight decay for regularization + }, +) # Uncomment to view an example # plt.imshow((X_show[0].reshape((28,28))*255).astype(np.uint8), cmap='Greys_r') @@ -81,4 +97,4 @@ # print 'Result:', model.predict(X_test[0:1])[0].argmax() # Now it prints how good did the network did for this configuration -print('Accuracy:', model.score(test_iter)*100, '%') \ No newline at end of file +print('Accuracy:', mod.score(test_iter, mx.metric.Accuracy())[0][1]*100, '%') diff --git a/example/torch/data.py b/example/torch/data.py index d39821f52145..0ca8e1fd6653 100644 --- a/example/torch/data.py +++ b/example/torch/data.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # pylint: skip-file """ data iterator for mnist """ import sys diff --git a/example/torch/torch_function.py b/example/torch/torch_function.py index 4ea4558475ec..af285de22713 100644 --- a/example/torch/torch_function.py +++ b/example/torch/torch_function.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + from __future__ import print_function import mxnet as mx x = mx.th.randn(2, 2, ctx=mx.cpu(0)) diff --git a/example/torch/torch_module.py b/example/torch/torch_module.py index 02eacc311d73..1595173b02d4 100644 --- a/example/torch/torch_module.py +++ b/example/torch/torch_module.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # pylint: skip-file from data import mnist_iterator import mxnet as mx diff --git a/example/utils/get_data.py b/example/utils/get_data.py index 64a753218225..861d16cdbad9 100644 --- a/example/utils/get_data.py +++ b/example/utils/get_data.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import os import mxnet as mx diff --git a/example/warpctc/infer_ocr.py b/example/warpctc/infer_ocr.py index 2d496f06b1f4..d469990ff937 100644 --- a/example/warpctc/infer_ocr.py +++ b/example/warpctc/infer_ocr.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # coding=utf-8 # pylint: disable=C0111,too-many-arguments,too-many-instance-attributes,too-many-locals,redefined-outer-name,fixme # pylint: disable=superfluous-parens, no-member, invalid-name diff --git a/example/warpctc/lstm.py b/example/warpctc/lstm.py index 4be4a0d914f1..9e0e05c9011d 100644 --- a/example/warpctc/lstm.py +++ b/example/warpctc/lstm.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # pylint:skip-file import sys sys.path.insert(0, "../../python") diff --git a/example/warpctc/lstm_model.py b/example/warpctc/lstm_model.py index e9c8aa74365f..d359f1ae5a90 100644 --- a/example/warpctc/lstm_model.py +++ b/example/warpctc/lstm_model.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # pylint: disable=C0111,too-many-arguments,too-many-instance-attributes,too-many-locals,redefined-outer-name,fixme # pylint: disable=superfluous-parens, no-member, invalid-name @@ -51,4 +68,4 @@ def forward(self, input_data, new_seq=False): for key in self.states_dict.keys(): self.states_dict[key].copyto(self.executor.arg_dict[key]) prob = self.executor.outputs[0].asnumpy() - return prob \ No newline at end of file + return prob diff --git a/example/warpctc/lstm_ocr.py b/example/warpctc/lstm_ocr.py index 540c676f53e7..49df98a77236 100644 --- a/example/warpctc/lstm_ocr.py +++ b/example/warpctc/lstm_ocr.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # pylint: disable=C0111,too-many-arguments,too-many-instance-attributes,too-many-locals,redefined-outer-name,fixme # pylint: disable=superfluous-parens, no-member, invalid-name from __future__ import print_function @@ -80,8 +97,8 @@ def __iter__(self): label_all = [mx.nd.array(label)] data_names = ['data'] + init_state_names label_names = ['label'] - - + + data_batch = SimpleBatch(data_names, data_all, label_names, label_all) yield data_batch @@ -198,14 +215,14 @@ def sym_gen(seq_len): import logging head = '%(asctime)-15s %(message)s' logging.basicConfig(level=logging.DEBUG, format=head) - + print('begin fit') prefix = 'ocr' model.fit(X=data_train, eval_data=data_val, eval_metric = mx.metric.np(Accuracy), # Use the following eval_metric if your num_label >= 10, or varies in a wide range - # eval_metric = mx.metric.np(Accuracy_LCS), + # eval_metric = mx.metric.np(Accuracy_LCS), batch_end_callback=mx.callback.Speedometer(BATCH_SIZE, 50), epoch_end_callback = mx.callback.do_checkpoint(prefix, 1)) diff --git a/example/warpctc/ocr_predict.py b/example/warpctc/ocr_predict.py index a07733ef55e0..3096a664a20f 100644 --- a/example/warpctc/ocr_predict.py +++ b/example/warpctc/ocr_predict.py @@ -1,4 +1,22 @@ #!/usr/bin/env python2.7 + +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # coding=utf-8 from __future__ import print_function import sys, os diff --git a/example/warpctc/toy_ctc.py b/example/warpctc/toy_ctc.py index 46bab5776018..c7b0ccc3df3d 100644 --- a/example/warpctc/toy_ctc.py +++ b/example/warpctc/toy_ctc.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # pylint: disable=C0111,too-many-arguments,too-many-instance-attributes,too-many-locals,redefined-outer-name,fixme # pylint: disable=superfluous-parens, no-member, invalid-name from __future__ import print_function @@ -68,13 +85,13 @@ def __iter__(self): num, img = gen_rand() data.append(img) label.append(get_label(num)) - + data_all = [mx.nd.array(data)] + self.init_state_arrays label_all = [mx.nd.array(label)] data_names = ['data'] + init_state_names label_names = ['label'] - - + + data_batch = SimpleBatch(data_names, data_all, label_names, label_all) yield data_batch @@ -94,7 +111,7 @@ def ctc_label(p): continue ret.append(c2) return ret - + def Accuracy(label, pred): global BATCH_SIZE @@ -154,7 +171,7 @@ def sym_gen(seq_len): import logging head = '%(asctime)-15s %(message)s' logging.basicConfig(level=logging.DEBUG, format=head) - + print('begin fit') model.fit(X=data_train, eval_data=data_val, diff --git a/include/mxnet/base.h b/include/mxnet/base.h index 34a48e10fd49..695408380ec9 100644 --- a/include/mxnet/base.h +++ b/include/mxnet/base.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file base.h * \brief configuation of mxnet as well as basic data structure. */ @@ -38,6 +56,13 @@ #define MXNET_USE_CUDNN MSHADOW_USE_CUDNN #endif +/*! + *\brief whether to use cusolver library + */ +#ifndef MXNET_USE_CUSOLVER +#define MXNET_USE_CUSOLVER MSHADOW_USE_CUSOLVER +#endif + /*! \brief Error message for using gpu when MXNET_USE_CUDA==0 */ #define MXNET_GPU_NOT_ENABLED_ERROR "GPU is not enabled" @@ -85,9 +110,9 @@ /*! \brief major version */ #define MXNET_MAJOR 0 /*! \brief minor version */ -#define MXNET_MINOR 9 +#define MXNET_MINOR 11 /*! \brief patch version */ -#define MXNET_PATCH 5 +#define MXNET_PATCH 0 /*! \brief mxnet version */ #define MXNET_VERSION (MXNET_MAJOR*10000 + MXNET_MINOR*100 + MXNET_PATCH) /*! \brief helper for making version number */ @@ -211,6 +236,8 @@ struct Context { * The information needed in runtime for actual execution. */ struct RunContext { + /*! \brief base Context */ + Context ctx; /*! * \brief the stream of the device, can be NULL or Stream* in GPU mode */ @@ -224,6 +251,10 @@ struct RunContext { inline mshadow::Stream* get_stream() const { return static_cast*>(stream); } + /*! \brief get the base Context from RunContext */ + inline const Context& get_ctx() const { + return ctx; + } }; } // namespace mxnet diff --git a/include/mxnet/c_api.h b/include/mxnet/c_api.h index 1b112abe2ba9..2289354e8a5e 100644 --- a/include/mxnet/c_api.h +++ b/include/mxnet/c_api.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file c_api.h * \brief C API of mxnet */ @@ -48,6 +66,8 @@ typedef void *NDArrayHandle; typedef const void *FunctionHandle; /*! \brief handle to a function that takes param and creates symbol */ typedef void *AtomicSymbolCreator; +/*! \brief handle to cached operator */ +typedef void *CachedOpHandle; /*! \brief handle to a symbol that can be bind as operator */ typedef void *SymbolHandle; /*! \brief handle to a AtomicSymbol */ @@ -125,6 +145,7 @@ enum CustomOpPropCallbacks { kCustomOpPropInferType }; + typedef int (*CustomOpFBFunc)(int /*size*/, void** /*ptrs*/, int* /*tags*/, const int* /*reqs*/, const int /*is_train*/, void* /*state*/); @@ -137,12 +158,23 @@ typedef int (*CustomOpBwdDepFunc)(const int* /*out_grad*/, const int* /*in_data* const int* /*out_data*/, int* /*num_deps*/, int** /*rdeps*/, void* /*state*/); typedef int (*CustomOpCreateFunc)(const char* /*ctx*/, int /*num_inputs*/, - unsigned** /*shapes*/, int* /*ndims*/, - int* /*dtypes*/, struct MXCallbackList* /*ret*/, + unsigned** /*shapes*/, const int* /*ndims*/, + const int* /*dtypes*/, struct MXCallbackList* /*ret*/, void* /*state*/); typedef int (*CustomOpPropCreator)(const char* /*op_type*/, const int /*num_kwargs*/, - const char** /*keys*/, const char** /*values*/, - struct MXCallbackList* /*ret*/); + const char** /*keys*/, const char** /*values*/, + struct MXCallbackList* /*ret*/); + + +enum CustomFunctionCallbacks { + kCustomFunctionBackward, + kCustomFunctionDelete +}; + +typedef int (*CustomFunctionBwdFunc)(int /*num_ograds*/, int /*num_igrads*/, void** /*ptrs*/, + const int* /*reqs*/, const int /*is_train*/, + void* /*state*/); +typedef int (*CustomFunctionDelFunc)(void* /*state*/); /*! * \brief return str message of the last error @@ -390,7 +422,7 @@ MXNET_DLL int MXNDArrayGetShape(NDArrayHandle handle, const mx_uint **out_pdata); /*! * \brief get the content of the data in NDArray - * \param handle the handle to the narray + * \param handle the handle to the ndarray * \param out_pdata pointer holder to get pointer of data * \return 0 when success, -1 when failure happens */ @@ -414,6 +446,32 @@ MXNET_DLL int MXNDArrayGetDType(NDArrayHandle handle, MXNET_DLL int MXNDArrayGetContext(NDArrayHandle handle, int *out_dev_type, int *out_dev_id); +/*! + * \brief return gradient buffer attached to this NDArray + * \param handle NDArray handle + * \return 0 when success, -1 when failure happens + */ +MXNET_DLL int MXNDArrayGetGrad(NDArrayHandle handle, NDArrayHandle *out); +/*! + * \brief detach and ndarray from computation graph by clearing entry_ + * \param handle NDArray handle + * \return 0 when success, -1 when failure happens + */ +MXNET_DLL int MXNDArrayDetach(NDArrayHandle handle, NDArrayHandle *out); +/*! + * \brief set the flag for gradient array state. + * \param handle NDArray handle + * \param state the new state. + * \return 0 when success, -1 when failure happens + */ +MXNET_DLL int MXNDArraySetGradState(NDArrayHandle handle, int state); +/*! + * \brief set the flag for gradient array state. + * \param handle NDArray handle + * \param state the new state. + * \return 0 when success, -1 when failure happens + */ +MXNET_DLL int MXNDArrayGetGradState(NDArrayHandle handle, int *out); //-------------------------------- // Part 2: functions on NDArray //-------------------------------- @@ -523,6 +581,13 @@ MXNET_DLL int MXImperativeInvoke(AtomicSymbolCreator creator, int num_params, const char **param_keys, const char **param_vals); +/*! + * \brief set whether to record operator for autograd + * \param is_recording 1 when recording, 0 when not recording. + * \param prev returns the previous status before this set. + * \return 0 when success, -1 when failure happens + */ +MXNET_DLL int MXAutogradSetIsRecording(int is_recording, int* prev); /*! * \brief set whether to record operator for autograd * \param is_train 1 when training, 0 when testing @@ -530,6 +595,18 @@ MXNET_DLL int MXImperativeInvoke(AtomicSymbolCreator creator, * \return 0 when success, -1 when failure happens */ MXNET_DLL int MXAutogradSetIsTraining(int is_training, int* prev); +/*! + * \brief get whether autograd recording is on + * \param curr returns the current status. + * \return 0 when success, -1 when failure happens + */ +MXNET_DLL int MXAutogradIsRecording(bool* curr); +/*! + * \brief get whether training mode is on + * \param curr returns the current status. + * \return 0 when success, -1 when failure happens + */ +MXNET_DLL int MXAutogradIsTraining(bool* curr); /*! * \brief mark NDArrays as variables to compute gradient for autograd * \param num_var number of variable NDArrays @@ -548,6 +625,55 @@ MXNET_DLL int MXAutogradMarkVariables(mx_uint num_var, */ MXNET_DLL int MXAutogradComputeGradient(mx_uint num_output, NDArrayHandle* output_handles); +/*! + * \brief compute the gradient of outputs w.r.t variabels + * \param num_output number of output NDArray + * \param output_handles output NDArrays + * \param ograd_handles head gradient for NDArrays + * \param retain_graph whether to keep the graph after backward + * \return 0 when success, -1 when failure happens + */ +MXNET_DLL int MXAutogradBackward(mx_uint num_output, + NDArrayHandle* output_handles, + NDArrayHandle* ograd_handles, + int retain_graph); +/*! +* \brief compute the gradient of outputs w.r.t variabels +* \param num_output number of output NDArray +* \param output_handles output NDArrays +* \param ograd_handles head gradient for NDArrays +* \param retain_graph whether to keep the graph after backward +* \param is_train whether to do backward for training or inference +* \return 0 when success, -1 when failure happens +*/ +MXNET_DLL int MXAutogradBackwardEx(mx_uint num_output, + NDArrayHandle* output_handles, + NDArrayHandle* ograd_handles, + int retain_graph, + int is_train); +/* + * \brief get the graph constructed by autograd. + * \param handle ndarray handle + * \param out output symbol handle + */ +MXNET_DLL int MXAutogradGetSymbol(NDArrayHandle handle, SymbolHandle *out); +/*! + * \brief create cached operator + */ +MXNET_DLL int MXCreateCachedOp(SymbolHandle handle, + CachedOpHandle *out); +/*! + * \brief free cached operator + */ +MXNET_DLL int MXFreeCachedOp(CachedOpHandle handle); +/*! + * \brief invoke cached operator + */ +MXNET_DLL int MXInvokeCachedOp(CachedOpHandle handle, + int num_inputs, + NDArrayHandle *inputs, + int *num_outputs, + NDArrayHandle **outputs); //-------------------------------------------- // Part 3: symbolic configuration generation //-------------------------------------------- @@ -971,7 +1097,20 @@ MXNET_DLL int MXExecutorForward(ExecutorHandle handle, int is_train); MXNET_DLL int MXExecutorBackward(ExecutorHandle handle, mx_uint len, NDArrayHandle *head_grads); - +/*! + * \brief Excecutor run backward + * + * \param handle execute handle + * \param len lenth + * \param head_grads NDArray handle for heads' gradient + * \param is_train int value to indicate whether the backward pass is for evaluation + * + * \return 0 when success, -1 when failure happens + */ +MXNET_DLL int MXExecutorBackwardEx(ExecutorHandle handle, + mx_uint len, + NDArrayHandle *head_grads, + int is_train); /*! * \brief Get executor's head NDArray * @@ -1081,6 +1220,38 @@ MXNET_DLL int MXExecutorBindEX(SymbolHandle symbol_handle, NDArrayHandle *aux_states, ExecutorHandle shared_exec, ExecutorHandle *out); + +MXNET_DLL int MXExecutorSimpleBind(SymbolHandle symbol_handle, + int dev_type, + int dev_id, + const mx_uint num_g2c_keys, + const char** g2c_keys, + const int* g2c_dev_types, + const int* g2c_dev_ids, + const mx_uint provided_grad_req_list_len, + const char** provided_grad_req_names, + const char** provided_grad_req_types, + const mx_uint num_provided_arg_shapes, + const char** provided_arg_shape_names, + const mx_uint* provided_arg_shape_data, + const mx_uint* provided_arg_shape_idx, + const mx_uint num_provided_arg_dtypes, + const char** provided_arg_dtype_names, + const int* provided_arg_dtypes, + const mx_uint num_shared_arg_names, + const char** shared_arg_name_list, + int* shared_buffer_len, + const char** shared_buffer_name_list, + NDArrayHandle* shared_buffer_handle_list, + const char*** updated_shared_buffer_name_list, + NDArrayHandle** updated_shared_buffer_handle_list, + mx_uint* num_in_args, + NDArrayHandle** in_args, + NDArrayHandle** arg_grads, + mx_uint* num_aux_states, + NDArrayHandle** aux_states, + ExecutorHandle shared_exec_handle, + ExecutorHandle* out); /*! * \brief set a call back to notify the completion of operation */ @@ -1228,6 +1399,19 @@ MXNET_DLL int MXKVStoreInit(KVStoreHandle handle, const int* keys, NDArrayHandle* vals); +/*! + * \brief Init a list of (key,value) pairs in kvstore, where each key is a string + * \param handle handle to the kvstore + * \param num the number of key-value pairs + * \param keys the list of keys + * \param vals the list of values + * \return 0 when success, -1 when failure happens + */ +MXNET_DLL int MXKVStoreInitEx(KVStoreHandle handle, + mx_uint num, + const char** keys, + NDArrayHandle* vals); + /*! * \brief Push a list of (key,value) pairs to kvstore * \param handle handle to the kvstore @@ -1242,6 +1426,20 @@ MXNET_DLL int MXKVStorePush(KVStoreHandle handle, const int* keys, NDArrayHandle* vals, int priority); +/*! + * \brief Push a list of (key,value) pairs to kvstore, where each key is a string + * \param handle handle to the kvstore + * \param num the number of key-value pairs + * \param keys the list of keys + * \param vals the list of values + * \param priority the priority of the action + * \return 0 when success, -1 when failure happens + */ +MXNET_DLL int MXKVStorePushEx(KVStoreHandle handle, + mx_uint num, + const char** keys, + NDArrayHandle* vals, + int priority); /*! * \brief pull a list of (key, value) pairs from the kvstore * \param handle handle to the kvstore @@ -1256,6 +1454,20 @@ MXNET_DLL int MXKVStorePull(KVStoreHandle handle, const int* keys, NDArrayHandle* vals, int priority); +/*! + * \brief pull a list of (key, value) pairs from the kvstore, where each key is a string + * \param handle handle to the kvstore + * \param num the number of key-value pairs + * \param keys the list of keys + * \param vals the list of values + * \param priority the priority of the action + * \return 0 when success, -1 when failure happens + */ +MXNET_DLL int MXKVStorePullEx(KVStoreHandle handle, + mx_uint num, + const char** keys, + NDArrayHandle* vals, + int priority); /*! * \brief user-defined updater for the kvstore * It's this updater's responsibility to delete \a recv and \a local @@ -1492,8 +1704,23 @@ MXNET_DLL int MXRtcPush(RtcHandle handle, mx_uint num_input, mx_uint num_output, * \brief Delete a MXRtc object */ MXNET_DLL int MXRtcFree(RtcHandle handle); - +/* + * \brief register custom operators from frontend. + * \param op_type name of custom op + * \param creator + */ MXNET_DLL int MXCustomOpRegister(const char* op_type, CustomOpPropCreator creator); +/* + * \brief record custom function for backward later. + * \param num_inputs number of input NDArrays. + * \param inputs handle to input NDArrays. + * \param num_outputs number of output NDArrays. + * \param outputs handle to output NDArrays. + * \param callbacks callbacks for backward function. + */ +MXNET_DLL int MXCustomFunctionRecord(int num_inputs, NDArrayHandle *inputs, + int num_outputs, NDArrayHandle *outputs, + MXCallbackList *callbacks); #ifdef __cplusplus } diff --git a/include/mxnet/c_predict_api.h b/include/mxnet/c_predict_api.h index df60c84c7dfa..8cf153e7cae1 100644 --- a/include/mxnet/c_predict_api.h +++ b/include/mxnet/c_predict_api.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file c_predict_api.h * \brief C predict API of mxnet, contains a minimum API to run prediction. * This file is self-contained, and do not dependent on any other files. diff --git a/include/mxnet/engine.h b/include/mxnet/engine.h index 72f0bb45a63c..fc24fac06bd3 100644 --- a/include/mxnet/engine.h +++ b/include/mxnet/engine.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file engine.h * \brief Engine that schedules all the operations according to dependency. */ diff --git a/include/mxnet/executor.h b/include/mxnet/executor.h index cf71666826ab..a74d3b07b5be 100644 --- a/include/mxnet/executor.h +++ b/include/mxnet/executor.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file executor.h * \brief Symbolic executor interface of mxnet. * \author Min Lin, Bing Xu @@ -58,7 +76,7 @@ class Executor { * * \param head_grads the gradient of head nodes to be backproped. */ - virtual void Backward(const std::vector &head_grads) = 0; + virtual void Backward(const std::vector &head_grads, bool is_train = true) = 0; /*! * \brief print the execution plan info to output stream. * \param os the output stream we like to print to. @@ -69,6 +87,21 @@ class Executor { * \return array of outputs in the executor. */ virtual const std::vector &outputs() const = 0; + /*! + * \brief get input argument map, key is arg name, value is arg's NDArray. + * \return input argument map in the executor. + */ + virtual const std::unordered_map& in_arg_map() const = 0; + /*! + * \brief get input argument graident map, key is arg name, value is gradient's NDArray. + * \return input argument gradient map in the executor. + */ + virtual const std::unordered_map& arg_grad_map() const = 0; + /*! + * \brief get aux state map, key is arg name, value is aux state's NDArray. + * \return aux state map in the executor. + */ + virtual const std::unordered_map& aux_state_map() const = 0; /*! * \brief Create an operator by bind symbol with context and arguments. * If user do not want to compute the gradients of i-th argument, grad_req_type[i] can be kNullOp. @@ -91,6 +124,23 @@ class Executor { const std::vector &grad_req_type, const std::vector &aux_states, Executor* shared_exec = NULL); + + static Executor* SimpleBind(nnvm::Symbol symbol, + const Context& default_ctx, + const std::map& group2ctx, + const std::vector& in_arg_ctxes, + const std::vector& arg_grad_ctxes, + const std::vector& aux_state_ctxes, + const std::unordered_map& arg_shape_map, + const std::unordered_map& arg_dtype_map, + const std::vector& grad_req_types, + const std::unordered_set& param_names, + std::vector* in_args, + std::vector* arg_grads, + std::vector* aux_states, + std::unordered_map* + shared_data_arrays = nullptr, + Executor* shared_exec = nullptr); /*! * \brief the prototype of user-defined monitor callback */ diff --git a/include/mxnet/io.h b/include/mxnet/io.h index b4429a951920..68c1ede65ada 100644 --- a/include/mxnet/io.h +++ b/include/mxnet/io.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file io.h * \brief mxnet io data structure and data iterator */ diff --git a/include/mxnet/kvstore.h b/include/mxnet/kvstore.h index dafaf1bf9cab..d2924ecea1b5 100644 --- a/include/mxnet/kvstore.h +++ b/include/mxnet/kvstore.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file kvstore.h * \brief key-value store interface for mxnet */ @@ -48,7 +66,7 @@ class KVStore { /*! * \brief Initialize a list of key-value pair to the store. * - * One must initalize the key before \ref Push and \ref Pull, and a key + * One must initialize the key before \ref Push and \ref Pull, and a key * should be only initialized once * * It returns after data have been initialized successfully. @@ -63,6 +81,13 @@ class KVStore { */ virtual void Init(const std::vector& keys, const std::vector& values) = 0; + /*! + * \brief Initialize a list of key-value pair to the store. + * \param keys a list of unique keys in string format + * \param values a list of values + */ + virtual void Init(const std::vector& str_keys, + const std::vector& values) = 0; /*! * \brief push a list of key-value pairs into the store * @@ -102,6 +127,16 @@ class KVStore { virtual void Push(const std::vector& keys, const std::vector& values, int priority = 0) = 0; + + /*! + * \brief push a list of key-value pairs into the store + * \param keys the list of keys in string format + * \param values the list of values + * \param priority Priority of the action. + */ + virtual void Push(const std::vector& str_keys, + const std::vector& values, + int priority = 0) = 0; /*! * \brief pull a list of key-value pairs from the store * @@ -128,6 +163,16 @@ class KVStore { virtual void Pull(const std::vector& keys, const std::vector& values, int priority = 0) = 0; + /*! + * \brief pull a list of key-value pairs from the store + * \param keys the list of keys in string format + * \param values the list of buffers for the pulled data, they should be preallocated + * \param priority Priority of the action. + */ + virtual void Pull(const std::vector& str_keys, + const std::vector& values, + int priority = 0) = 0; + /** * \brief the prototype of user-defined updater diff --git a/include/mxnet/mxrtc.h b/include/mxnet/mxrtc.h index 9de59f63da2a..8d7facc5b82a 100644 --- a/include/mxnet/mxrtc.h +++ b/include/mxnet/mxrtc.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file mxrtc.h * \brief Wrapper for NVRTC * \author Junyuan Xie diff --git a/include/mxnet/ndarray.h b/include/mxnet/ndarray.h index ea38909d07f1..d7dff4098b27 100644 --- a/include/mxnet/ndarray.h +++ b/include/mxnet/ndarray.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file ndarray.h * \brief NDArray interface that handles array arithematics. */ @@ -47,6 +65,7 @@ class AGNodeEntry { } nnvm::NodeEntry nn_entry() const; + bool is_none() const; }; class AutogradRuntime; @@ -57,10 +76,10 @@ class AutogradRuntime; */ class NDArray { public: - /*! \brief default cosntructor */ + /*! \brief default constructor */ NDArray() { #if MKL_EXPERIMENTAL == 1 - Mkl_mem_ = MKLMemHolder::create(); + Mkl_mem_ = MKLMemHolder::create(); #endif } /*! @@ -73,9 +92,9 @@ class NDArray { NDArray(const TShape &shape, Context ctx, bool delay_alloc = false, int dtype = mshadow::default_type_flag) : ptr_(std::make_shared(shape.Size(), ctx, delay_alloc, dtype)), - shape_(shape), offset_(0), dtype_(dtype), entry_({nullptr, 0, 0}) { + shape_(shape), dtype_(dtype), entry_({nullptr, 0, 0}) { #if MKL_EXPERIMENTAL == 1 - Mkl_mem_ = std::make_shared(); + Mkl_mem_ = std::make_shared(); #endif } /*! @@ -86,50 +105,30 @@ class NDArray { * \param dev_id the device id this tensor sits at */ NDArray(const TBlob &data, int dev_id) - : ptr_(std::make_shared(data, dev_id)), shape_(data.shape_), offset_(0), + : ptr_(std::make_shared(data, dev_id)), shape_(data.shape_), dtype_(data.type_flag_), entry_({nullptr, 0, 0}) { #if MKL_EXPERIMENTAL == 1 - Mkl_mem_ = std::make_shared(); + Mkl_mem_ = std::make_shared(); #endif } /*! * \return the shape of current NDArray */ - inline const TShape &shape() const { + inline const TShape& shape() const { return shape_; } /*! * \return the data TBlob */ - inline TBlob data() const { + inline const TBlob& data() const { CheckAndAlloc(); - TBlob res; - MSHADOW_TYPE_SWITCH(dtype_, DType, { - res = TBlob(static_cast(ptr_->shandle.dptr) - + offset_, shape_, ptr_->shandle.ctx.dev_mask()); - }); -#if MKL_EXPERIMENTAL == 1 - res.Mkl_mem_ = Mkl_mem_; -#endif - return res; + SetTBlob(); + return tblob_; } /*! - * \return a chunk of raw data in TBlob + * \return the gradient ndarray. */ - inline TBlob raw_data(index_t offset, index_t length) const { - CheckAndAlloc(); - TBlob res; - TShape raw_shape(1); - raw_shape[0] = length; - MSHADOW_TYPE_SWITCH(dtype_, DType, { - res = TBlob(static_cast(ptr_->shandle.dptr) - + offset_ + offset, raw_shape, ptr_->shandle.ctx.dev_mask()); - }); -#if MKL_EXPERIMENTAL == 1 - res.Mkl_mem_ = Mkl_mem_; -#endif - return res; - } + NDArray grad() const; /*! * \return the context of NDArray, this function is only valid when the NDArray is not empty */ @@ -146,6 +145,10 @@ class NDArray { inline bool is_none() const { return ptr_.get() == nullptr; } + /*! \return updated grad state in entry_ */ + bool fresh_out_grad() const; + /*! \return updated grad state in entry_ */ + void set_fresh_out_grad(bool state) const; /*! * \brief Block until all the pending write operations with respect * to current NDArray are finished, and read can be performed. @@ -318,6 +321,22 @@ class NDArray { * \return NDArray in new shape */ NDArray Reshape(const TShape &shape) const; + /*! + * \brief Return a copy of this NDArray without autograd history + */ + NDArray Detach() const { + NDArray ret(*this); + ret.entry_ = autograd::AGNodeEntry{nullptr, 0, 0}; + return ret; + } + + nnvm::Symbol get_autograd_symbol() { + CHECK(!entry_.is_none()) + << "NDArray is not part of a computation graph. Did you forget to turn on recording?"; + nnvm::Symbol ret; + ret.outputs.emplace_back(entry_.nn_entry()); + return ret; + } /*! * \brief Allocate the space if it is delayed allocated. * This is an internal function used by system that normal user should not use @@ -326,7 +345,7 @@ class NDArray { ptr_->CheckAndAlloc(); } /*! - * \brief Save list of narray into the Stream.x + * \brief Save list of ndarray into the Stream.x * \param fo The stream of output. * \param data the NDArrays to be saved. * \param names the name of the NDArray, optional, can be zero length. @@ -335,7 +354,7 @@ class NDArray { const std::vector& data, const std::vector& names); /*! - * \brief Load list of narray into from the stream. + * \brief Load list of ndarray into from the stream. * \param fi The stream of the input file. * \param data the NDArrays to be loaded * \param keys the name of the NDArray, if saved in the file. @@ -368,10 +387,10 @@ class NDArray { : static_data(true), delay_alloc(false) { var = Engine::Get()->NewVariable(); - if (data.dev_mask_ == cpu::kDevMask) { + if (data.dev_mask() == cpu::kDevMask) { shandle.ctx = Context::CPU(); } else { - CHECK_EQ(data.dev_mask_, gpu::kDevMask); + CHECK_EQ(data.dev_mask(), gpu::kDevMask); shandle.ctx = Context::GPU(dev_id); } shandle.dptr = data.dptr_; @@ -405,6 +424,16 @@ class NDArray { } }; + void SetTBlob() const { + tblob_.dptr_ = static_cast(ptr_->shandle.dptr) + byte_offset_; + tblob_.shape_ = shape_; + tblob_.type_flag_ = dtype_; + tblob_.SetDLTensor(ptr_->shandle.ctx.dev_mask(), ptr_->shandle.ctx.dev_id); +#if MKL_EXPERIMENTAL == 1 + tblob_.Mkl_mem_ = Mkl_mem_; +#endif + } + #if MKL_EXPERIMENTAL == 1 std::shared_ptr Mkl_mem_; #endif @@ -412,12 +441,20 @@ class NDArray { std::shared_ptr ptr_; /*! \brief shape of current NDArray */ TShape shape_; - /*! \brief offset in chunk */ - size_t offset_; + /*! \brief byte offset in chunk */ + size_t byte_offset_ = 0; /*! \brief type of data */ int dtype_ = -1; /*! \brief node entry for autograd */ autograd::AGNodeEntry entry_; + /*! + * \brief internal TBlob + * \note When user access tblob_ by some const methods like + * NDArray::data(), the dptr in tblob_ still need to be updated + * in case that allocation happens. So we make it mutable for + * this situation. + */ + mutable TBlob tblob_; }; /*! diff --git a/include/mxnet/op_attr_types.h b/include/mxnet/op_attr_types.h index 316a90fe0841..1bcae0d29348 100644 --- a/include/mxnet/op_attr_types.h +++ b/include/mxnet/op_attr_types.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2016 by Contributors * \file op_attr_types.h * \brief Additional operator attributes * beside the ones provided by NNVM @@ -15,27 +33,174 @@ #include #include "./base.h" -#include "./operator.h" #include "./ndarray.h" +#include "./engine.h" +#include "./resource.h" namespace mxnet { using nnvm::NodeAttrs; + +/*! \brief operation request type to Forward and Backward */ +enum OpReqType { + /*! \brief no operation, do not write anything */ + kNullOp, + /*! \brief write gradient to provided space */ + kWriteTo, + /*! + * \brief perform an inplace write, + * Target shares memory with one of input arguments. + * This option only happen when + */ + kWriteInplace, + /*! \brief add to the provided space */ + kAddTo +}; + +/*! + * \brief All the possible information needed by Operator.Forward and Backward + * This is the superset of RunContext. + * We use this data structure to bookkeep everything needed by Forward and Backward. + * \sa Resource + */ +struct OpContext { + /*! \brief whether it is training phase */ + int is_train; + /*! \brief RunContext related resources */ + RunContext run_ctx; + /*! \brief the callback when operation completes, used by asynchronize ops */ + engine::CallbackOnComplete async_on_complete; + /*! \brief Resources requested by the operator */ + std::vector requested; + /*! + * \brief get mshadow stream from Context + * \return the mshadow stream + * \tparam xpu the device type of the stream + */ + template + inline mshadow::Stream* get_stream() const { + return run_ctx.get_stream(); + } +}; + +/*! \brief the execution type of the operator */ +enum class ExecType { + /*! \brief Forward/Backward are synchronize calls */ + kSync, + /*! + * \brief Forward/Backward are asynchronize, + * will call OpContext.async_on_complete when operation finishes. + */ + kAsync, + /*! \brief Run this operator on the scheduling thread without pushing to engine. */ + kLocal, + /*! + * \brief Cross device copy operation, this is a special operator + * That indicates copy across devices, the input and output can sit on different device. + * In current implementation, copy operator is specially handled by executor. + * This flag is used for special case treatment and future extension of different copy ops. + */ + kCrossDeviceCopy +}; + +/*! + * \brief Operator state. This is a pointer type, its content is mutable + * even if OpStatePtr is const. + */ +class OpStatePtr { + public: + /* \brief Create a OpStatePtr with state of type T. + * \param args Arguments passed to T's constructor. + */ + template + static OpStatePtr Create(Args&&... args) { + OpStatePtr ret; + ret.ptr_ = std::make_shared(); + ret.ptr_->var_ = Engine::Get()->NewVariable(); + ret.ptr_->state_.construct(std::forward(args)...); + + return ret; + } + /* \brief Get engine variable associated with this state */ + engine::VarHandle get_var() const { + return ptr_->var_; + } + /* \brief Get state of type T */ + template + T& get_state() const { + return dmlc::get(ptr_->state_); + } + /* \brief clear state */ + void reset() { + ptr_.reset(); + } + /* \brief Whether state is empty */ + explicit operator bool() const { + return ptr_ ? true : false; + } + + private: + /* \brief state structure */ + struct OpState { + OpState() {} + OpState(const OpState& other) = delete; + OpState& operator=(const OpState& other) = delete; + + ~OpState() { + Engine::Get()->DeleteVariable([](RunContext s) {}, Context::CPU(), var_); + } + + engine::VarHandle var_; + dmlc::any state_; + }; + /* \brief shared pointer to state */ + std::shared_ptr ptr_; +}; + /*! * \brief Create a Layer style, forward/backward operator. * This is easy to write code that contains state. + * OpStatePtr is a pointer type, it's content is mutable even if + * OpStatePtr is constant. + * * * This is not the only way to register an op execution function. * More simpler or specialized operator form can be registered * * \note Register under "FCreateLayerOp" */ -using FCreateLayerOp = std::function< - Operator* (const NodeAttrs& n, - Context ctx, - const std::vector& in_shape, - const std::vector& in_type)>; - +using FCreateOpState = std::function& in_shape, + const std::vector& in_type)>; +/*! + * \brief Execution mode of this operator. + */ +using FExecType = std::function; +/*! + * \brief Resiger a compute function for stateful operator. + * OpStatePtr is a pointer type, it's content is mutable even if + * OpStatePtr is constant. + * + * \note Register under "FStatefulCompute" and "FStatefulCompute" + */ +using FStatefulCompute = std::function& inputs, + const std::vector& req, + const std::vector& outputs)>; +/*! + * \brief Resiger a compute function for stateful operator using NDArray interface. + * OpStatePtr is a pointer type, it's content is mutable even if + * OpStatePtr is constant. + * + * \note Register under "FStatefulComputeEx" and "FStatefulComputeEx" + */ +using FStatefulComputeEx = std::function& inputs, + const std::vector& req, + const std::vector& outputs)>; /*! * \brief The resource request from the operator * diff --git a/include/mxnet/operator.h b/include/mxnet/operator.h index fe5c3de0279f..2245db0dbb93 100644 --- a/include/mxnet/operator.h +++ b/include/mxnet/operator.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file operator.h * \brief Operator interface of mxnet. * \author Naiyan Wang @@ -18,50 +36,9 @@ #include #include "./base.h" #include "./resource.h" +#include "./op_attr_types.h" namespace mxnet { -/*! \brief operation request type to Forward and Backward */ -enum OpReqType { - /*! \brief no operation, do not write anything */ - kNullOp, - /*! \brief write gradient to provided space */ - kWriteTo, - /*! - * \brief perform an inplace write, - * Target shares memory with one of input arguments. - * This option only happen when - */ - kWriteInplace, - /*! \brief add to the provided space */ - kAddTo -}; - -/*! - * \brief All the possible information needed by Operator.Forward and Backward - * This is the superset of RunContext. - * We use this data structure to bookkeep everything needed by Forward and Backward. - * \sa Resource - */ -struct OpContext { - /*! \brief whether it is training phase */ - int is_train; - /*! \brief RunContext related resources */ - RunContext run_ctx; - /*! \brief the callback when operation completes, used by asynchronize ops */ - engine::CallbackOnComplete async_on_complete; - /*! \brief Resources requested by the operator */ - std::vector requested; - /*! - * \brief get mshadow stream from Context - * \return the mshadow stream - * \tparam xpu the device type of the stream - */ - template - inline mshadow::Stream* get_stream() const { - return run_ctx.get_stream(); - } -}; - /*! * \brief Operator interface. * Operator defines basic operation unit of optimized computation graph in mxnet. @@ -76,23 +53,6 @@ struct OpContext { */ class Operator { public: - /*! \brief the execution type of the operator */ - enum ExecType { - /*! \brief Forward/Backward are synchronize calls */ - kSync, - /*! - * \brief Forward/Backward are asynchronize, - * will call OpContext.async_on_complete when operation finishes. - */ - kAsync, - /*! - * \brief Cross device copy operation, this is a special operator - * That indicates copy across devices, the input and output can sit on different device. - * In current implementation, copy operator is specially handled by executor. - * This flag is used for special case treatment and future extension of different copy ops. - */ - kCrossDeviceCopy - }; /*! \brief destructor */ virtual ~Operator() {} /*! @@ -148,9 +108,9 @@ class Operator { const std::vector &aux_states) { LOG(FATAL) << "Backward is not implemented"; } - /*! \return execution type of the operator */ - virtual ExecType exec_type() const { - return kSync; + /*! \return [Deprecated] execution type of the operator */ + virtual ExecType exec_type() const final { // NOLINT(*) exec_type has been moved to OperatorProperty + return ExecType::kSync; } }; @@ -478,6 +438,10 @@ class OperatorProperty { * \return a new constructed OperatorProperty */ static OperatorProperty *Create(const char* type_name); + /*! \return execution type of the operator */ + virtual ExecType exec_type() const { + return ExecType::kSync; + } }; /*! \brief typedef the factory function of operator property */ diff --git a/include/mxnet/operator_util.h b/include/mxnet/operator_util.h index 0f27b10368cf..92ef2ecc58f6 100644 --- a/include/mxnet/operator_util.h +++ b/include/mxnet/operator_util.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file operator_util.h * \brief Utility functions and registries to help quickly build new operators. * [Deprecated] diff --git a/include/mxnet/resource.h b/include/mxnet/resource.h index 93b8352b2617..1ca1fc6fa707 100644 --- a/include/mxnet/resource.h +++ b/include/mxnet/resource.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file resource.h * \brief Global resource allocation handling. */ diff --git a/include/mxnet/storage.h b/include/mxnet/storage.h index 1b765233947d..bfb42de8771a 100644 --- a/include/mxnet/storage.h +++ b/include/mxnet/storage.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file storage.h * \brief Storage manager across multiple devices. */ diff --git a/include/mxnet/tensor_blob.h b/include/mxnet/tensor_blob.h index e4e335666d80..18bf4fa780d9 100755 --- a/include/mxnet/tensor_blob.h +++ b/include/mxnet/tensor_blob.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2014 by Contributors * \file tensor_blob.h * \brief TBlob class that holds common representation of * arbirary dimension tensor, can be used to transformed @@ -11,6 +29,7 @@ #include #include +#include #include #include #include @@ -21,6 +40,9 @@ #endif namespace mxnet { +/* Forward declaration for friend declaration in TBlob */ +class NDArray; + /*! * \brief tensor blob class that can be used to hold tensor of any dimension, * any device and any data type, @@ -34,17 +56,12 @@ namespace mxnet { * and wait for further processing */ class TBlob { + friend class NDArray; public: /*! \brief pointer to the data */ void *dptr_; /*! \brief shape of the tensor */ TShape shape_; - /*! - * \brief storing the stride information in x dimension - */ - index_t stride_; - /*! \brief device mask of the corresponding device */ - int dev_mask_; /*! \brief type flag of the tensor blob */ int type_flag_; @@ -54,49 +71,43 @@ class TBlob { #endif /*! \brief default constructor, default copy assign will work */ TBlob(void) - : dptr_(NULL), dev_mask_(cpu::kDevMask), + : dptr_(NULL), type_flag_(mshadow::DataType::kFlag) { #if MKL_EXPERIMENTAL == 1 - Mkl_mem_ = NULL; + Mkl_mem_ = NULL; #endif + SetDLTensor(cpu::kDevMask, 0); } /*! * \brief constructor that construct TBlob from contiguous memory * \param dptr the pointer to the memory * \param shape the shape of the data * \param dev_mask the device mask, can be cpu::kDevMask or gpu::kDevMask + * \param dev_id the device id */ template - TBlob(DType *dptr, - const TShape &shape, - int dev_mask) + TBlob(DType *dptr, const TShape &shape, int dev_mask, int dev_id = -1) : dptr_(dptr), shape_(shape), - stride_(shape[shape.ndim() - 1]), - dev_mask_(dev_mask), type_flag_(mshadow::DataType::kFlag) { #if MKL_EXPERIMENTAL == 1 - Mkl_mem_ = NULL; + Mkl_mem_ = NULL; #endif + SetDLTensor(dev_mask, dev_id); } - /*! * \brief constructor that construct TBlob from contiguous memory * \param dptr the pointer to the memory * \param shape the shape of the data * \param dev_mask the device mask, can be cpu::kDevMask or gpu::kDevMask * \param type_flag the type flag. Can be one of enum mshadow::dtype + * \param dev_id the device id */ - TBlob(void *dptr, - const TShape &shape, - int dev_mask, - int type_flag) - : dptr_(dptr), shape_(shape), - stride_(shape[shape.ndim() - 1]), - dev_mask_(dev_mask), - type_flag_(type_flag) { + TBlob(void *dptr, const TShape &shape, int dev_mask, int type_flag, int dev_id = -1) + : dptr_(dptr), shape_(shape), type_flag_(type_flag) { #if MKL_EXPERIMENTAL == 1 - Mkl_mem_ = NULL; + Mkl_mem_ = NULL; #endif + SetDLTensor(dev_mask, dev_id); } /*! * \brief constructor from tensor @@ -108,9 +119,6 @@ class TBlob { template TBlob(const mshadow::Tensor &src) { // NOLINT(*) *this = src; -#if MKL_EXPERIMENTAL == 1 - Mkl_mem_ = NULL; -#endif } /*! * \brief assignment from tensor @@ -121,20 +129,21 @@ class TBlob { * \return reference of self */ template - inline TBlob - &operator=(const mshadow::Tensor &src) { + inline TBlob &operator=(const mshadow::Tensor &src) { dptr_ = src.dptr_; shape_ = src.shape_; - stride_ = src.stride_; - dev_mask_ = Device::kDevMask; type_flag_ = mshadow::DataType::kFlag; + SetDLTensor(Device::kDevMask, -1); +#if MKL_EXPERIMENTAL == 1 + Mkl_mem_ = NULL; +#endif return *this; } /*! * \return whether the tensor's memory is continuous */ inline bool CheckContiguous(void) const { - return shape_[shape_.ndim() - 1] == stride_; + return true; } /*! * \brief reshape to shape @@ -144,7 +153,7 @@ class TBlob { inline TBlob reshape(const TShape& shape) const { CHECK_EQ(this->shape_.Size(), shape.Size()) << "Shape size mismatch " << this->shape_.Size() << " v.s. " << shape.Size(); - TBlob ret(this->dptr_, shape, this->dev_mask_, this->type_flag_); + TBlob ret(this->dptr_, shape, this->dev_mask(), this->type_flag_, this->dev_id()); return ret; } /*! @@ -157,7 +166,7 @@ class TBlob { template inline mshadow::Tensor FlatTo2D( mshadow::Stream *stream = NULL) const { - CHECK(Device::kDevMask == dev_mask_) + CHECK(Device::kDevMask == this->dev_mask()) << "TBlob.get: device type do not match specified type"; CHECK(mshadow::DataType::kFlag == type_flag_) << "TBlob.get_with_shape: data type do not match specified type." @@ -168,7 +177,9 @@ class TBlob { } #endif return mshadow::Tensor(static_cast(dptr_), - shape_.FlatTo2D(), stride_, stream); + shape_.FlatTo2D(), + shape_[shape_.ndim() - 1], + stream); } /*! * \brief flatten the tensor to 1 dimension, collapse all the dimensions together. @@ -212,6 +223,22 @@ class TBlob { #endif return static_cast(dptr_); } + /*! \brief device mask of the corresponding device */ + inline int dev_mask() const { + return dltensor_.ctx.device_type; + } + /*! \brief device index of the corresponding device */ + inline int dev_id() const { + return dltensor_.ctx.device_id; + } + /*! + * \brief return the corresponding DLTensor + * \return the address of internal DLTensor + */ + inline const DLTensor& dltensor() const { + return dltensor_; + } + /*! * \brief fetch the tensor, with respect to specific dimension * if dim do not match the stored dimension, an error will be issued @@ -223,9 +250,10 @@ class TBlob { */ template inline mshadow::Tensor get(mshadow::Stream *stream = NULL) const { - CHECK(Device::kDevMask == dev_mask_) + CHECK(Device::kDevMask == this->dev_mask()) << "TBlob.get: device type do not match specified type"; - return mshadow::Tensor(dptr(), shape_.get(), stride_, stream); + return mshadow::Tensor(dptr(), + shape_.get(), shape_[shape_.ndim() - 1], stream); } /*! * \brief fetch a tensor in given shape @@ -241,7 +269,7 @@ class TBlob { inline mshadow::Tensor get_with_shape( const mshadow::Shape &shape, mshadow::Stream *stream = NULL) const { - CHECK(Device ::kDevMask == dev_mask_) + CHECK(Device::kDevMask == this->dev_mask()) << "TBlob.get: device type do not match specified type"; CHECK_EQ(this->CheckContiguous(), true) << "TBlob.get_reshape: must be contiguous"; CHECK_EQ(this->shape_.Size(), shape.Size()) @@ -281,6 +309,62 @@ class TBlob { return this->get_with_shape( this->shape_.FlatTo3D(axis_begin, axis_end), stream); } + /*! + * \brief flatten the tensor to specified number of dimensions, + * collapse the highest dimensions or pad with higher dimensions + * \param stream the possible stream target tensor should reside on + * \tparam Device which device the tensor is on + * \tparam dim desired number of dimensions of returned tensor + * \tparam DType the type of elements in the tensor + * \return tensor after flatten + */ + template + inline mshadow::Tensor FlatToKD( + mshadow::Stream *stream = NULL) const { + mshadow::Shape shape; + shape[0] = 1; + // Pad higher dimensions in case dim > ndim() + for (int i = 0; i < dim - ndim(); ++i) { + shape[i] = 1; + } + // Collapse higher dimensions in case dim < ndim() + for (int i = 0; i < ndim() - dim + 1; ++i) { + shape[0] *= shape_[i]; + } + // Preserve lower dimensions. + for (int i = std::max(0, ndim() - dim + 1); i < ndim(); ++i) { + shape[i - ndim() + dim] = shape_[i]; + } + return this->get_with_shape(shape, stream); + } + + private: + static DLDataType DTypeTransform(int type_flag) { + static std::unordered_map + MSHADOW_DTYPE_TO_DLPACK_DTYPE = { + {0, {2, 32, 1}}, // Float32 + {1, {2, 64, 1}}, // Float64 + {2, {2, 16, 1}}, // Float16 + {3, {1, 8, 1}}, // UInt8 + {4, {0, 32, 1}}, // Int32 + {5, {0, 8, 1}} // Int8 + }; + return MSHADOW_DTYPE_TO_DLPACK_DTYPE[type_flag]; + } + + inline void SetDLTensor(int dev_mask, int dev_id) { + dltensor_.data = dptr_; + dltensor_.ctx = DLContext{static_cast(dev_mask), dev_id}; + dltensor_.ndim = shape_.ndim(); + dltensor_.dtype = DTypeTransform(type_flag_); + dltensor_.shape = shape_.data(); + dltensor_.strides = NULL; + dltensor_.byte_offset = 0; + } + + private: + /*! \brief corresponding DLTensor of this TBlob */ + DLTensor dltensor_; }; } // namespace mxnet diff --git a/make/config.mk b/make/config.mk index 602a6c404737..58668fb33d8e 100644 --- a/make/config.mk +++ b/make/config.mk @@ -54,14 +54,6 @@ USE_CUDA_PATH = NONE # whether use CuDNN R3 library USE_CUDNN = 0 -# CUDA architecture setting: going with all of them. -# For CUDA < 6.0, comment the *_50 lines for compatibility. -CUDA_ARCH := -gencode arch=compute_30,code=sm_30 \ - -gencode arch=compute_35,code=sm_35 \ - -gencode arch=compute_50,code=sm_50 \ - -gencode arch=compute_50,code=compute_50 \ - -gencode arch=compute_62,code=compute_62 - # whether use cuda runtime compiling for writing kernels in native language (i.e. Python) USE_NVRTC = 0 @@ -73,7 +65,6 @@ USE_OPENCV = 1 # use openmp for parallelization USE_OPENMP = 1 - # MKL ML Library for Intel CPU/Xeon Phi # Please refer to MKL_README.md for details @@ -102,6 +93,13 @@ else USE_BLAS = atlas endif +# whether use lapack during compilation +# only effective when compiled with blas versions openblas/apple/atlas/mkl +USE_LAPACK = 1 + +# path to lapack library in case of a non-standard installation +USE_LAPACK_PATH = + # add path to intel library, you may need it for MKL, if you did not add the path # to environment variable USE_INTEL_PATH = NONE diff --git a/make/osx.mk b/make/osx.mk index a14ba3f70ff0..7823b072a2ad 100644 --- a/make/osx.mk +++ b/make/osx.mk @@ -48,13 +48,6 @@ USE_CUDA = 0 # USE_CUDA_PATH = /usr/local/cuda USE_CUDA_PATH = NONE -# CUDA architecture setting: going with all of them. -# For CUDA < 6.0, comment the *_50 lines for compatibility. -CUDA_ARCH := -gencode arch=compute_30,code=sm_30 \ - -gencode arch=compute_35,code=sm_35 \ - -gencode arch=compute_50,code=sm_50 \ - -gencode arch=compute_50,code=compute_50 - # whether use CUDNN R3 library USE_CUDNN = 0 @@ -73,6 +66,10 @@ USE_OPENMP = 0 # can be: mkl, blas, atlas, openblas USE_BLAS = apple +# whether use lapack during compilation +# only effective when compiled with blas versions openblas/apple/atlas/mkl +USE_LAPACK = 1 + # add path to intel library, you may need it for MKL, if you did not add the path # to environment variable USE_INTEL_PATH = NONE diff --git a/make/pip_linux_cpu.mk b/make/pip_linux_cpu.mk index 82ba964169ac..01bc2702ebb7 100644 --- a/make/pip_linux_cpu.mk +++ b/make/pip_linux_cpu.mk @@ -34,6 +34,13 @@ ADD_CFLAGS += -Ldeps/lib -Ideps/include # in default use atlas for linux while apple for osx USE_BLAS=openblas +# whether use lapack during compilation +# only effective when compiled with blas versions openblas/apple/atlas/mkl +USE_LAPACK = 1 + +# path to lapack library in case of a non-standard installation +USE_LAPACK_PATH = + # whether use opencv during compilation # you can disable it, however, you will not able to use # imbin iterator @@ -50,13 +57,6 @@ USE_CUDA_PATH = NONE # whether use CuDNN R3 library USE_CUDNN = 0 -# CUDA architecture setting: going with all of them. -# For CUDA < 6.0, comment the *_50 lines for compatibility. -CUDA_ARCH := -gencode arch=compute_30,code=sm_30 \ - -gencode arch=compute_35,code=sm_35 \ - -gencode arch=compute_50,code=sm_50 \ - -gencode arch=compute_50,code=compute_50 - # whether use cuda runtime compiling for writing kernels in native language (i.e. Python) USE_NVRTC = 0 diff --git a/make/readthedocs.mk b/make/readthedocs.mk index b14c4baf7482..5a33855a5e5c 100644 --- a/make/readthedocs.mk +++ b/make/readthedocs.mk @@ -32,6 +32,8 @@ USE_OPENMP = 0 # can be: mkl, blas, atlas, openblas USE_STATIC_MKL = NONE USE_BLAS = NONE +USE_LAPACK = 0 + # # add path to intel library, you may need it # for MKL, if you did not add the path to environment variable diff --git a/matlab/get_inception_model.sh b/matlab/get_inception_model.sh index aa0092deb6d8..af2479b33b83 100755 --- a/matlab/get_inception_model.sh +++ b/matlab/get_inception_model.sh @@ -1,5 +1,23 @@ #!/usr/bin/env bash +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + + MATLAB_DIR=$(cd `dirname $0`; pwd) DATA_DIR="${MATLAB_DIR}/data/" diff --git a/mshadow b/mshadow index c037b06ddd81..497eb9180b24 160000 --- a/mshadow +++ b/mshadow @@ -1 +1 @@ -Subproject commit c037b06ddd810d39322cd056650f8b1f4763dd9d +Subproject commit 497eb9180b24592b7332e7e08f2c053ec5346524 diff --git a/nnvm b/nnvm index b279286304ac..bcfbf903429d 160000 --- a/nnvm +++ b/nnvm @@ -1 +1 @@ -Subproject commit b279286304ac954098d94a2695bca599e832effb +Subproject commit bcfbf903429d086f16b19b4d202788de06e45536 diff --git a/perl-package/AI-MXNet/Changes b/perl-package/AI-MXNet/Changes index 2664d2a1c8fc..f8ecc7509737 100644 --- a/perl-package/AI-MXNet/Changes +++ b/perl-package/AI-MXNet/Changes @@ -1,5 +1,14 @@ Revision history for Perl extension AI::MXNet +1.0102 Sun Aug 6 16:55:08 PDT 2017 + - bugfixes in Image.pm, updated tests, added PearsonCorrelation metric, added Convolutional RNN modules. + +1.0101 Sun Jul 2 17:16:01 PDT 2017 + - reworked CachedOp, two new optimizers, auto module reshape, using strings to index the kvstore. + +1.01 Sat Jun 10 23:57:27 PDT 2017 + - sync with python. + 0.9507 Thu May 11 17:04:44 PDT 2017 - added AutoGrad, bugfixes. diff --git a/perl-package/AI-MXNet/MANIFEST b/perl-package/AI-MXNet/MANIFEST index 855aa0a9e883..48cb31dd6b8e 100644 --- a/perl-package/AI-MXNet/MANIFEST +++ b/perl-package/AI-MXNet/MANIFEST @@ -10,7 +10,6 @@ examples/cudnn_lstm_bucketing.pl Makefile.PL Changes META.json -t/test_autograd.t t/test_recordio.t t/test_random.t t/test_init.t @@ -32,6 +31,7 @@ t/test_executor.t t/test_infer_shape.t lib/AI/MXNet.pm lib/AI/MXNet/Random.pm +lib/AI/MXNet/CachedOp.pm lib/AI/MXNet/Context.pm lib/AI/MXNet/Contrib/AutoGrad.pm lib/AI/MXNet/Contrib/Symbol.pm diff --git a/perl-package/AI-MXNet/META.json b/perl-package/AI-MXNet/META.json index 68afafd9e4e8..692f1ddaae39 100644 --- a/perl-package/AI-MXNet/META.json +++ b/perl-package/AI-MXNet/META.json @@ -30,8 +30,8 @@ }, "runtime" : { "requires" : { - "AI::MXNetCAPI" : "0.95", - "AI::NNVMCAPI" : "0.95", + "AI::MXNetCAPI" : "1.0102", + "AI::NNVMCAPI" : "1.01", "Function::Parameters" : "1.0705", "GraphViz" : "2.14", "Mouse" : "v2.1.0", @@ -43,5 +43,5 @@ } }, "release_status" : "stable", - "version" : "0.9506" + "version" : "1.0102" } diff --git a/perl-package/AI-MXNet/META.yml b/perl-package/AI-MXNet/META.yml index 1abb0079a1ec..5b920182f159 100644 --- a/perl-package/AI-MXNet/META.yml +++ b/perl-package/AI-MXNet/META.yml @@ -17,10 +17,10 @@ no_index: - t - inc requires: - AI::MXNetCAPI: '0.95' - AI::NNVMCAPI: '0.95' + AI::MXNetCAPI: '1.0102' + AI::NNVMCAPI: '1.01' Function::Parameters: '1.0705' GraphViz: '2.14' Mouse: v2.1.0 PDL: '2.007' -version: '0.9507' +version: '1.0102' diff --git a/perl-package/AI-MXNet/Makefile.PL b/perl-package/AI-MXNet/Makefile.PL index 0f6062ec5466..2c9bda83330c 100644 --- a/perl-package/AI-MXNet/Makefile.PL +++ b/perl-package/AI-MXNet/Makefile.PL @@ -19,15 +19,15 @@ my %WriteMakefileArgs = ( "LICENSE" => "apache_2_0", "NAME" => "AI::MXNet", "PREREQ_PM" => { - "AI::MXNetCAPI" => "0.9507", - "AI::NNVMCAPI" => "0.95", + "AI::MXNetCAPI" => "1.0102", + "AI::NNVMCAPI" => "1.01", "Function::Parameters" => "1.0705", - "Mouse" => "2.1.0", + "Mouse" => "v2.1.0", "PDL" => "2.007", "GraphViz" => "2.14" }, "TEST_REQUIRES" => {}, - "VERSION" => "0.9507", + "VERSION" => "1.0101", "test" => { "TESTS" => "t/*.t" } @@ -35,10 +35,10 @@ my %WriteMakefileArgs = ( my %FallbackPrereqs = ( - "AI::MXNetCAPI" => "0.9507", - "AI::NNVMCAPI" => "0.95", + "AI::MXNetCAPI" => "1.0102", + "AI::NNVMCAPI" => "1.01", "Function::Parameters" => "1.0705", - "Mouse" => "2.1.0", + "Mouse" => "v2.1.0", "PDL" => "2.007", "GraphViz" => "2.14" ); diff --git a/perl-package/AI-MXNet/README b/perl-package/AI-MXNet/README index 9831038d54f9..86b6cf18dbac 100644 --- a/perl-package/AI-MXNet/README +++ b/perl-package/AI-MXNet/README @@ -1,5 +1,5 @@ This archive contains the distribution AI-MXNet, -version 0.9507: +version 1.0102: Perl interface to MXNet machine learning library diff --git a/perl-package/AI-MXNet/examples/calculator.pl b/perl-package/AI-MXNet/examples/calculator.pl index f41895508450..aadc7cd2641e 100755 --- a/perl-package/AI-MXNet/examples/calculator.pl +++ b/perl-package/AI-MXNet/examples/calculator.pl @@ -1,4 +1,22 @@ #!/usr/bin/perl + +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + use strict; use warnings; use AI::MXNet ('mx'); diff --git a/perl-package/AI-MXNet/examples/char_lstm.pl b/perl-package/AI-MXNet/examples/char_lstm.pl index 1b69ee1e93c6..54a9e3672f63 100755 --- a/perl-package/AI-MXNet/examples/char_lstm.pl +++ b/perl-package/AI-MXNet/examples/char_lstm.pl @@ -1,4 +1,22 @@ #!/usr/bin/perl + +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + use strict; use warnings; use PDL; diff --git a/perl-package/AI-MXNet/examples/cudnn_lstm_bucketing.pl b/perl-package/AI-MXNet/examples/cudnn_lstm_bucketing.pl index 4cfe51bfd94a..8976e6465003 100755 --- a/perl-package/AI-MXNet/examples/cudnn_lstm_bucketing.pl +++ b/perl-package/AI-MXNet/examples/cudnn_lstm_bucketing.pl @@ -1,4 +1,22 @@ #!/usr/bin/perl + +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + use strict; use warnings; use AI::MXNet qw(mx); @@ -280,4 +298,4 @@ =head1 SYNOPSIS else { $train->(); -} \ No newline at end of file +} diff --git a/perl-package/AI-MXNet/examples/get_ptb_data.sh b/perl-package/AI-MXNet/examples/get_ptb_data.sh index 1ec009aa2f99..d2641cb32b81 100755 --- a/perl-package/AI-MXNet/examples/get_ptb_data.sh +++ b/perl-package/AI-MXNet/examples/get_ptb_data.sh @@ -1,5 +1,23 @@ #!/usr/bin/env bash +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + + RNN_DIR=$(cd `dirname $0`; pwd) DATA_DIR="${RNN_DIR}/data/" diff --git a/perl-package/AI-MXNet/examples/lstm_bucketing.pl b/perl-package/AI-MXNet/examples/lstm_bucketing.pl index ffc176dccb79..e6699d79f0b1 100755 --- a/perl-package/AI-MXNet/examples/lstm_bucketing.pl +++ b/perl-package/AI-MXNet/examples/lstm_bucketing.pl @@ -1,4 +1,22 @@ #!/usr/bin/perl + +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + use strict; use warnings; use PDL; diff --git a/perl-package/AI-MXNet/examples/mnist.pl b/perl-package/AI-MXNet/examples/mnist.pl index 891b5348039c..ca452cd95444 100755 --- a/perl-package/AI-MXNet/examples/mnist.pl +++ b/perl-package/AI-MXNet/examples/mnist.pl @@ -1,4 +1,22 @@ #!/usr/bin/perl + +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + use strict; use warnings; # derived from http://mxnet.io/tutorials/python/mnist.html @@ -115,7 +133,7 @@ sub nn_fc { # Epoch[9] Validation-accuracy=0.964600 my($data) = @_; - # Flatten the data from 4-D shape (batch_size, num_channel, width, height) + # Flatten the data from 4-D shape (batch_size, num_channel, width, height) # into 2-D (batch_size, num_channel*width*height) $data = mx->sym->Flatten(data => $data); @@ -175,7 +193,7 @@ sub nn_conv { ); $model->fit( $train_iter, # training data - num_epoch => 10, # number of data passes for training + num_epoch => 10, # number of data passes for training eval_data => $val_iter, # validation data batch_end_callback => mx->callback->Speedometer($batch_size, 200), # output progress for each 200 data batches optimizer => 'adam', diff --git a/perl-package/AI-MXNet/examples/plot_network.pl b/perl-package/AI-MXNet/examples/plot_network.pl index a0bcf847af1b..fc38ef2baaab 100755 --- a/perl-package/AI-MXNet/examples/plot_network.pl +++ b/perl-package/AI-MXNet/examples/plot_network.pl @@ -1,4 +1,22 @@ #!/usr/bin/perl + +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + use strict; use warnings; use AI::MXNet qw(mx); diff --git a/perl-package/AI-MXNet/lib/AI/MXNet.pm b/perl-package/AI-MXNet/lib/AI/MXNet.pm index f8866399d611..40e84a6078e6 100644 --- a/perl-package/AI-MXNet/lib/AI/MXNet.pm +++ b/perl-package/AI-MXNet/lib/AI/MXNet.pm @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + package AI::MXNet; use v5.14.0; use strict; @@ -28,7 +45,8 @@ use AI::MXNet::RecordIO; use AI::MXNet::Image; use AI::MXNet::Contrib; use AI::MXNet::Contrib::AutoGrad; -our $VERSION = '0.9507'; +use AI::MXNet::CachedOp; +our $VERSION = '1.0102'; sub import { @@ -64,9 +82,13 @@ sub import sub callback { 'AI::MXNet::Callback' } sub img { 'AI::MXNet::Image' } sub contrib { 'AI::MXNet::Contrib' } + sub name { '$short_name' } sub AttrScope { shift; AI::MXNet::Symbol::AttrScope->new(\@_) } *AI::MXNet::Symbol::AttrScope::current = sub { \$${short_name}::AttrScope; }; \$${short_name}::AttrScope = AI::MXNet::Symbol::AttrScope->new; + sub Prefix { AI::MXNet::Symbol::Prefix->new(prefix => \$_[1]) } + *AI::MXNet::Symbol::NameManager::current = sub { \$${short_name}::NameManager; }; + \$${short_name}::NameManager = AI::MXNet::Symbol::NameManager->new; *AI::MXNet::Context::current_ctx = sub { \$${short_name}::Context; }; \$${short_name}::Context = AI::MXNet::Context->new(device_type => 'cpu', device_id => 0); 1; diff --git a/perl-package/AI-MXNet/lib/AI/MXNet/Base.pm b/perl-package/AI-MXNet/lib/AI/MXNet/Base.pm index 93859f668a9f..0c42fa9306cb 100644 --- a/perl-package/AI-MXNet/lib/AI/MXNet/Base.pm +++ b/perl-package/AI-MXNet/lib/AI/MXNet/Base.pm @@ -1,10 +1,27 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + package AI::MXNet::Base; use strict; use warnings; use PDL; use PDL::Types qw(); -use AI::MXNetCAPI 0.9506; -use AI::NNVMCAPI 0.95; +use AI::MXNetCAPI 1.0102; +use AI::NNVMCAPI 1.01; use AI::MXNet::Types; use Time::HiRes; use Carp; @@ -12,7 +29,7 @@ use Exporter; use base qw(Exporter); use List::Util qw(shuffle); -@AI::MXNet::Base::EXPORT = qw(product enumerate assert zip check_call build_param_doc +@AI::MXNet::Base::EXPORT = qw(product enumerate assert zip check_call build_param_doc pdl cat dog svd bisect_left pdl_shuffle DTYPE_STR_TO_MX DTYPE_MX_TO_STR DTYPE_MX_TO_PDL DTYPE_PDL_TO_MX DTYPE_MX_TO_PERL GRAD_REQ_MAP); @@ -239,12 +256,12 @@ sub build_param_doc $remove_dup //= 1; my %param_keys; my @param_str; - zip(sub { + zip(sub { my ($key, $type_info, $desc) = @_; return if exists $param_keys{$key} and $remove_dup; $param_keys{$key} = 1; my $ret = sprintf("%s : %s", $key, $type_info); - $ret .= "\n ".$desc if length($desc); + $ret .= "\n ".$desc if length($desc); push @param_str, $ret; }, $arg_names, $arg_types, $arg_descs diff --git a/perl-package/AI-MXNet/lib/AI/MXNet/CachedOp.pm b/perl-package/AI-MXNet/lib/AI/MXNet/CachedOp.pm new file mode 100644 index 000000000000..f3c21ed17f30 --- /dev/null +++ b/perl-package/AI-MXNet/lib/AI/MXNet/CachedOp.pm @@ -0,0 +1,106 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +package AI::MXNet::CachedOp; + +=head1 NAME + + AI::MXNet::CachedOp - A wrapper around CachedOpHandle +=cut + +use strict; +use warnings; +use AI::MXNet::Base; +use Mouse; +use overload '&{}' => sub { my $self = shift; sub { $self->call(@_) } }; + +has 'handle' => (is => 'ro', isa => 'CachedOpHandle', required => 1); +around BUILDARGS => sub { + my $orig = shift; + my $class = shift; + my ($sym) = @_; + my $handle = check_call( + AI::MXNetCAPI::CreateCachedOp( + $sym->handle + ) + ); + return $class->$orig(handle => $handle); +}; + +sub DEMOLISH +{ + check_call(AI::MXNetCAPI::FreeCachedOp(shift->handle)); +} + +sub call +{ + my $self = shift; + my @args; + my %kwargs; + if(blessed $_[0] and $_[0]->isa('AI::MXNet::NDArray')) + { + while(blessed $_[0] and $_[0]->isa('AI::MXNet::NDArray')) + { + push @args, shift(@_); + } + %kwargs = @_; + } + else + { + %kwargs = @_; + } + my $out = delete $kwargs{out}; + if(%kwargs) + { + confess( + "AI::MXNet::CachedOp::call got unexpected keyword argument(s): ". + join(', ', keys %kwargs) + ); + } + my $original_output; + if(defined $out) + { + $original_output = $out; + if(blessed($out)) + { + $out = [$out]; + } + } + else + { + $out = []; + } + my $output = check_call( + AI::MXNetCAPI::InvokeCachedOp( + $self->handle, + scalar(@args), + [map { $_->handle } @args], + [map { $_->handle } @$out] + ) + ); + return $original_output if defined $original_output; + if(@$output == 1) + { + return AI::MXNet::NDArray->new(handle => $output->[0]); + } + else + { + return [map { AI::MXNet::NDArray->new(handle => $_) } @$output]; + } +} + +1; diff --git a/perl-package/AI-MXNet/lib/AI/MXNet/Callback.pm b/perl-package/AI-MXNet/lib/AI/MXNet/Callback.pm index 04aaea06c47d..da3309700394 100644 --- a/perl-package/AI-MXNet/lib/AI/MXNet/Callback.pm +++ b/perl-package/AI-MXNet/lib/AI/MXNet/Callback.pm @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + package AI::MXNet::Callback; use strict; use warnings; @@ -92,7 +109,7 @@ extends 'AI::MXNet::Callback'; =head1 NAME - AI::MXNet::Speedometer - A callback that logs training speed + AI::MXNet::Speedometer - A callback that logs training speed =cut =head1 DESCRIPTION @@ -244,4 +261,4 @@ method LogValidationMetricsCallback() AI::MXNet::LogValidationMetricsCallback->new } -1; \ No newline at end of file +1; diff --git a/perl-package/AI-MXNet/lib/AI/MXNet/Context.pm b/perl-package/AI-MXNet/lib/AI/MXNet/Context.pm index 68628a8cc14c..2eca42436dc7 100644 --- a/perl-package/AI-MXNet/lib/AI/MXNet/Context.pm +++ b/perl-package/AI-MXNet/lib/AI/MXNet/Context.pm @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + package AI::MXNet::Context; use strict; use warnings; diff --git a/perl-package/AI-MXNet/lib/AI/MXNet/Contrib.pm b/perl-package/AI-MXNet/lib/AI/MXNet/Contrib.pm index cb6bc01008f9..a81030bdc6e0 100644 --- a/perl-package/AI-MXNet/lib/AI/MXNet/Contrib.pm +++ b/perl-package/AI-MXNet/lib/AI/MXNet/Contrib.pm @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + package AI::MXNet::Contrib; use strict; use warnings; @@ -9,4 +26,4 @@ sub symbol { 'AI::MXNet::Contrib::Symbol' } sub nd { 'AI::MXNet::Contrib::NDArray' } sub autograd { 'AI::MXNet::Contrib::AutoGrad' } -1; \ No newline at end of file +1; diff --git a/perl-package/AI-MXNet/lib/AI/MXNet/Contrib/AutoGrad.pm b/perl-package/AI-MXNet/lib/AI/MXNet/Contrib/AutoGrad.pm index 4fd910fb34c9..ff659982b813 100644 --- a/perl-package/AI-MXNet/lib/AI/MXNet/Contrib/AutoGrad.pm +++ b/perl-package/AI-MXNet/lib/AI/MXNet/Contrib/AutoGrad.pm @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + package AI::MXNet::Contrib::AutoGrad; use strict; use warnings; @@ -71,6 +88,58 @@ method mark_variables( ); } +=head2 backward + + Compute the gradients of outputs w.r.t variables. + + Parameters + ---------- + outputs: array ref of NDArray + out_grads: array ref of NDArray or undef + retain_graph: bool, defaults to false +=cut + + +method backward( + ArrayRef[AI::MXNet::NDArray] $outputs, + Maybe[ArrayRef[AI::MXNet::NDArray|Undef]] $out_grads=, + Bool $retain_graph=0 +) +{ + my @output_handles = map { $_->handle } @{ $outputs }; + if(not defined $out_grads) + { + check_call( + AI::MXNetCAPI::AutogradBackward( + scalar(@output_handles), + \@output_handles, + [], + $retain_graph + ) + ); + return; + } + + my @ograd_handles; + for my $arr (@$out_grads) + { + push @ograd_handles, (defined $arr ? $arr->handle : undef); + } + assert( + (@ograd_handles == @output_handles), + "outputs and out_grads must have the same length" + ); + + check_call( + AI::MXNetCAPI::AutogradBackward( + scalar(@output_handles), + \@output_handles, + \@ograd_handles, + $retain_graph + ) + ); +} + =head2 compute_gradient Compute the gradients of outputs w.r.t variables. @@ -87,13 +156,7 @@ method mark_variables( method compute_gradient(ArrayRef[AI::MXNet::NDArray] $outputs) { - my @output_handles = map { $_->handle } @{ $outputs }; - check_call( - AI::MXNetCAPI::AutogradComputeGradient( - scalar(@output_handles), - \@output_handles - ) - ); + __PACKAGE__->backward($outputs); } =head2 grad_and_loss @@ -164,4 +227,18 @@ method grad(CodeRef $func, Maybe[Int|ArrayRef[Int]] $argnum=) }; } -1; \ No newline at end of file +method train_section(CodeRef $sub) +{ + my $prev = __PACKAGE__->set_is_training(1); + $sub->(); + __PACKAGE__->set_is_training(0) unless $prev; +} + +method test_section(CodeRef $sub) +{ + my $prev = __PACKAGE__->set_is_training(0); + $sub->(); + __PACKAGE__->set_is_training(1) if $prev; +} + +1; diff --git a/perl-package/AI-MXNet/lib/AI/MXNet/Contrib/NDArray.pm b/perl-package/AI-MXNet/lib/AI/MXNet/Contrib/NDArray.pm index 239f1c48e81f..78aed8fd59cc 100644 --- a/perl-package/AI-MXNet/lib/AI/MXNet/Contrib/NDArray.pm +++ b/perl-package/AI-MXNet/lib/AI/MXNet/Contrib/NDArray.pm @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + package AI::MXNet::Contrib::NDArray; use strict; use warnings; @@ -10,4 +27,4 @@ sub AUTOLOAD { return AI::MXNet::NDArray->$sub(@_); } -1; \ No newline at end of file +1; diff --git a/perl-package/AI-MXNet/lib/AI/MXNet/Contrib/Symbol.pm b/perl-package/AI-MXNet/lib/AI/MXNet/Contrib/Symbol.pm index c67cdad4baa5..efe785d181f7 100644 --- a/perl-package/AI-MXNet/lib/AI/MXNet/Contrib/Symbol.pm +++ b/perl-package/AI-MXNet/lib/AI/MXNet/Contrib/Symbol.pm @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + package AI::MXNet::Contrib::Symbol; use strict; use warnings; @@ -10,4 +27,4 @@ sub AUTOLOAD { return AI::MXNet::Symbol->$sub(@_); } -1; \ No newline at end of file +1; diff --git a/perl-package/AI-MXNet/lib/AI/MXNet/Executor.pm b/perl-package/AI-MXNet/lib/AI/MXNet/Executor.pm index a2ab786a62d1..20a6f580a3db 100644 --- a/perl-package/AI-MXNet/lib/AI/MXNet/Executor.pm +++ b/perl-package/AI-MXNet/lib/AI/MXNet/Executor.pm @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + package AI::MXNet::Executor; use strict; use warnings; @@ -9,7 +26,7 @@ use AI::MXNet::Function::Parameters; has 'handle' => (is => 'ro', isa => 'ExecutorHandle', required => 1); has 'arg_arrays' => (is => 'rw', isa => 'Maybe[ArrayRef[AI::MXNet::NDArray]]'); -has 'grad_arrays' => (is => 'rw', isa => 'Maybe[ArrayRef[Undef|AI::MXNet::NDArray]]'); +has 'grad_arrays' => (is => 'rw', isa => 'Maybe[ArrayRef[Undef|AI::MXNet::NDArray]]'); has 'aux_arrays' => (is => 'rw', isa => 'Maybe[ArrayRef[AI::MXNet::NDArray]]'); has '_symbol' => (is => 'rw', init_arg => 'symbol', isa => 'AI::MXNet::Symbol'); has '_ctx' => (is => 'rw', init_arg => 'ctx', isa => 'AI::MXNet::Context' ); @@ -420,7 +437,7 @@ method copy_params_from( method reshape(HashRef[Shape] $kwargs, Int :$partial_shaping=0, Int :$allow_up_sizing=0) { my ($arg_shapes, undef, $aux_shapes) = $self->_symbol->infer_shape(%{ $kwargs }); - confess("Insufficient argument shapes provided.") + confess("Insufficient argument shapes provided.") unless defined $arg_shapes; my %new_arg_dict; my %new_grad_dict; diff --git a/perl-package/AI-MXNet/lib/AI/MXNet/Executor/Group.pm b/perl-package/AI-MXNet/lib/AI/MXNet/Executor/Group.pm index 33e54dc1e847..611c93148f25 100644 --- a/perl-package/AI-MXNet/lib/AI/MXNet/Executor/Group.pm +++ b/perl-package/AI-MXNet/lib/AI/MXNet/Executor/Group.pm @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + package AI::MXNet::Executor::Group; use strict; use warnings; @@ -36,6 +53,7 @@ func _split_input_slice($batch_size, $work_load_list) } return \@slices; } + # Load a array ref of arrays into a array ref of arrays specified by slices func _load_general($data, $targets, $major_axis) { @@ -59,20 +77,45 @@ func _load_general($data, $targets, $major_axis) my ($slice_idx, $d_dst) = @{ $d }; if($axis >= 0) { - # copy slice - my $end = $d_src->shape; - my $begin = [(0) x @{ $end }]; - $begin->[$axis] = $slice_idx->[0]; - $end->[$axis] = $slice_idx->[1]; - if($d_src->context == $d_dst->context) + my $shape = $d_src->shape; + my $do_crop = ($slice_idx->[0] != 0 or $shape->[$axis] != $slice_idx->[1]); + if($do_crop) { - $d_src->crop({ begin => $begin, end => $end, out => $d_dst }); + if($axis == 0) + { + $d_src->slice([$slice_idx->[0], $slice_idx->[1] - 1])->copyto($d_dst); + } + else + { + if($d_src->context == $d_dst->context) + { + AI::MXNet::NDArray->slice_axis( + $d_src, + { + axis => $axis, + begin => $slice_idx->[0], + end => $slice_idx->[1], + out => $d_dst + } + ); + } + else + { + my $d_dst_copy = AI::MXNet::NDArray->slice_axis( + $d_src, + { + axis => $axis, + begin => $slice_idx->[0], + end => $slice_idx->[1] + } + ); + $d_dst_copy->copyto($d_dst); + } + } } else { - # on different device, crop and then do cross device copy - my $d_dst_copy = $d_src->crop({ begin => $begin, end => $end }); - $d_dst_copy->copyto($d_dst); + $d_src->copyto($d_dst); } } else @@ -177,8 +220,8 @@ use List::Util qw(sum); shared_group : AI::MXNet::DataParallelExecutorGroup Default is undef. This is used in bucketing. When not undef, it should be a executor group corresponding to a different bucket. In other words, it will correspond to a different - symbol but with the same set of parameters (e.g. unrolled RNNs with different lengths). - In this case, many memory will be shared. + symbol with the same set of parameters (e.g. unrolled RNNs with different lengths). + In this case the memory regions of the parameters will be shared. logger : Logger Default is AI::MXNet::Logging->get_logger. fixed_param_names: Maybe[ArrayRef[Str]] @@ -523,9 +566,9 @@ method reshape( A dictionary of name to AI::MXNet::NDArray auxiliary variable mapping. =cut -method set_params(HashRef[AI::MXNet::NDArray] $arg_params, HashRef[AI::MXNet::NDArray] $aux_params) +method set_params(HashRef[AI::MXNet::NDArray] $arg_params, HashRef[AI::MXNet::NDArray] $aux_params, Bool $allow_extra=0) { - $_->copy_params_from($arg_params, $aux_params) for @{ $self->_p->execs }; + $_->copy_params_from($arg_params, $aux_params, $allow_extra) for @{ $self->_p->execs }; } =head2 get_params @@ -787,8 +830,6 @@ method update_metric(AI::MXNet::EvalMetric $eval_metric, ArrayRef[AI::MXNet::NDA }, $self->_p->execs, $self->_p->slices); } -# Internal utility function to bind the i-th executor. - method _bind_ith_exec( Int $i, ArrayRef[AI::MXNet::DataDesc] $data_shapes, @@ -804,151 +845,15 @@ method _bind_ith_exec( { %input_shapes = (%input_shapes, map { $_->name => $_->shape } @{ $label_shapes }); } - my ($arg_shapes, undef, $aux_shapes) = $self->symbol->infer_shape(%input_shapes); - confess("shape inference failed") unless defined $arg_shapes; - my %input_types = map { $_->name => $_->dtype } @{ $data_shapes }; - my ($arg_types, undef, $aux_types) = $self->symbol->infer_type(%input_types); - confess("type inference failed") unless defined $arg_types; - my $arg_arrays = []; - my $grad_arrays = $self->for_training ? {} : undef; - - #Internal helper to get a memory block or re-use by re-shaping - my $_get_or_reshape = sub { - my ($name, $shared_data_arrays, $arg_shape, $arg_type, $context, $logger) = @_; - my $arg_arr; - if(exists $shared_data_arrays->{$name}) - { - $arg_arr = $shared_data_arrays->{$name}; - if(product(@{ $arg_arr->shape }) >= product(@{ $arg_shape })) - { - # nice, we can directly re-use this data blob - confess("dtypes do not match") - unless $arg_arr->dtype eq $arg_type; - $arg_arr = $arg_arr->reshape($arg_shape); - } - else - { - $logger->warning( - 'bucketing: data "%s" has a shape (%s)' - .', which is larger than already allocated ' - .'shape (%s)' - .'. Need to re-allocate. Consider putting ' - .'default_bucket_key to' - .' be the bucket taking the largest input for better ' - .'memory sharing.', - $name, join(',', $arg_shape), join(',', $arg_arr->shape) - ); - $arg_arr = AI::MXNet::NDArray->zeros( - $arg_shape, - ctx => $context, - dtype => $arg_type - ); - # replace existing shared array because the new one is bigger - $shared_data_arrays->{ $name } = $arg_arr; - } - } - else - { - $arg_arr = AI::MXNet::NDArray->zeros( - $arg_shape, - ctx => $context, - dtype => $arg_type - ); - $shared_data_arrays->{ $name } = $arg_arr; - } - return $arg_arr; - }; - my %param_names = map { $_ => 1 } @{ $self->param_names }; - # create or borrow arguments and gradients - for my $j (0..@{ $self->_p->arg_names }-1) - { - my $name = $self->_p->arg_names->[$j]; - my $arg_arr; - if(exists $param_names{ $name }) # model parameter - { - if(not defined $shared_exec) - { - $arg_arr = AI::MXNet::NDArray->zeros( - $arg_shapes->[$j], - ctx => $context, - dtype => $arg_types->[$j] - ); - - if($self->grad_req->{$name} ne 'null') - { - my $grad_arr = AI::MXNet::NDArray->zeros( - $arg_shapes->[$j], - ctx => $context, - dtype => $arg_types->[$j] - ); - $grad_arrays->{ $name } = $grad_arr; - } - } - else - { - $arg_arr = $shared_exec->arg_dict->{ $name }; - my $arg_arr_shape = $arg_arr->shape; - my $arg_shape = $arg_shapes->[$j]; - confess "shapes do not match (@$arg_arr_shape) != (@$arg_shape)" - unless "@$arg_arr_shape" eq "@$arg_shape"; - my $arg_arr_type = $arg_arr->dtype; - my $arg_type = $arg_types->[$j]; - confess "types do not match $arg_arr_type) != $arg_type" - unless $arg_arr_type eq $arg_type; - if($self->grad_req->{ $name } ne 'null') - { - $grad_arrays->{ $name } = $shared_exec->grad_dict->{ $name }; - } - } - } - else # data or label - { - $arg_arr = $_get_or_reshape->( - $name, $shared_data_arrays, $arg_shapes->[$j], - $arg_types->[$j], $context, $self->logger - ); - if($self->grad_req->{ $name } ne 'null') - { - $grad_arrays->{ $name } = $_get_or_reshape->( - "grad of $name", $shared_data_arrays, - $arg_shapes->[$j], $arg_types->[$j], - $context, $self->logger - ); - } - } - # data might also need grad if inputs_need_grad is True - push @{ $arg_arrays }, $arg_arr; - } - # create or borrow aux variables - my $aux_arrays = []; - if(not defined $shared_exec) - { - zip(sub{ - my ($s, $t) = @_; - push @{ $aux_arrays }, AI::MXNet::NDArray->zeros($s, ctx => $context, dtype => $t); - }, $aux_shapes, $aux_types); - } - else - { - for my $j (0..@{ $shared_exec->aux_arrays }-1) - { - my $arr = $shared_exec->aux_arrays->[$j]; - my $aux_shape = $aux_shapes->[$j]; - my $arr_shape = $arr->shape; - confess("aux shape (@$aux_shape) != array shape (@$arr_shape)") - unless "@$aux_shape" eq "@$arr_shape"; - my $aux_type = $aux_types->[$j]; - my $arr_type = $arr->dtype; - confess("aux_type $aux_type != array type $arr_type") - unless $aux_type ne $arr_type; - } - @{ $aux_arrays } = @{ $shared_exec->aux_arrays }; - } - my $executor = $self->symbol->bind( - ctx => $context, args => $arg_arrays, - args_grad => $grad_arrays, aux_states => $aux_arrays, - grad_req => $self->grad_req, shared_exec => $shared_exec + my $executor = $self->symbol->simple_bind( + ctx => $context, + grad_req => $self->grad_req, + type_dict => \%input_types, + shared_arg_names => $self->param_names, + shared_exec => $shared_exec, + shared_buffer => $shared_data_arrays, + shapes => \%input_shapes ); return $executor; } @@ -999,4 +904,14 @@ method install_monitor(AI::MXNet::Monitor $mon) $mon->install($_) for @{ $self->_p->execs }; } +method shared_data_arrays() +{ + $self->_p->shared_data_arrays; +} + +method execs() +{ + $self->_p->execs; +} + 1; diff --git a/perl-package/AI-MXNet/lib/AI/MXNet/Function/Parameters.pm b/perl-package/AI-MXNet/lib/AI/MXNet/Function/Parameters.pm index 021252063c49..e4bbc90ca0a3 100644 --- a/perl-package/AI-MXNet/lib/AI/MXNet/Function/Parameters.pm +++ b/perl-package/AI-MXNet/lib/AI/MXNet/Function/Parameters.pm @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + package AI::MXNet::Function::Parameters; use strict; use warnings; @@ -32,4 +49,4 @@ sub import { }; } -1; \ No newline at end of file +1; diff --git a/perl-package/AI-MXNet/lib/AI/MXNet/IO.pm b/perl-package/AI-MXNet/lib/AI/MXNet/IO.pm index 73a672eb4a5c..7a61cd9f1f1f 100644 --- a/perl-package/AI-MXNet/lib/AI/MXNet/IO.pm +++ b/perl-package/AI-MXNet/lib/AI/MXNet/IO.pm @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + package AI::MXNet::IO; use strict; use warnings; @@ -784,7 +801,7 @@ method _init_io_module() no strict 'refs'; { *{__PACKAGE__."::$name"} = $data_iter; - } + } } } } @@ -792,4 +809,4 @@ method _init_io_module() # Initialize the io in startups __PACKAGE__->_init_io_module; -1; \ No newline at end of file +1; diff --git a/perl-package/AI-MXNet/lib/AI/MXNet/Image.pm b/perl-package/AI-MXNet/lib/AI/MXNet/Image.pm index e75a6a180b83..18ef42af5525 100644 --- a/perl-package/AI-MXNet/lib/AI/MXNet/Image.pm +++ b/perl-package/AI-MXNet/lib/AI/MXNet/Image.pm @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + package AI::MXNet::Image; use strict; use warnings; @@ -7,7 +24,7 @@ use AI::MXNet::Function::Parameters; =head1 NAME - AI::MXNet:Image - Read invidual image files and perform augmentations. + AI::MXNet:Image - Read individual image files and perform augmentations. =cut =head2 imdecode @@ -747,7 +764,7 @@ sub BUILD { chomp($line); my @line = split(/\t/, $line); - my $label = AI::MXNet::NDArray->array([@line[1..@line-1]]); + my $label = AI::MXNet::NDArray->array([@line[1..@line-2]]); my $key = $line[0]; $imglist{$key} = [$label, $line[-1]]; push @imgkeys, $key; @@ -821,6 +838,10 @@ sub BUILD { $self->aug_list(AI::MXNet::Image->CreateAugmenter(data_shape => $self->data_shape, %{ $self->kwargs//{} })); } + else + { + $self->aug_list([]); + } $self->cur(0); $self->reset(); } @@ -860,7 +881,7 @@ method next_sample() } else { - my ($label, $fname) = $self->imglist->{$idx}; + my ($label, $fname) = @{ $self->imglist->{$idx} }; if(not defined $self->imgrec) { open(F, $self->path_root . "/$fname") or confess("can't open $fname $!"); @@ -913,4 +934,4 @@ method next() return AI::MXNet::DataBatch->new(data=>[$batch_data], label=>[$batch_label], pad => $batch_size-$i); } -1; \ No newline at end of file +1; diff --git a/perl-package/AI-MXNet/lib/AI/MXNet/Initializer.pm b/perl-package/AI-MXNet/lib/AI/MXNet/Initializer.pm index e5e57b82ed5d..182327dfccfe 100644 --- a/perl-package/AI-MXNet/lib/AI/MXNet/Initializer.pm +++ b/perl-package/AI-MXNet/lib/AI/MXNet/Initializer.pm @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + package AI::MXNet::InitDesc; use Mouse; use AI::MXNet::Function::Parameters; @@ -15,8 +32,8 @@ use AI::MXNet::Function::Parameters; attrs : hash ref of str to str attributes of this variable taken from AI::MXNet::Symbol->attr_dict =cut -has 'name' => (is => 'ro', isa => 'Str', required => 1); -has 'attrs' => (is => 'rw', isa => 'HashRef[Str]', lazy => 1, default => sub { +{} }); +has 'name' => (is => 'ro', isa => 'Str', required => 1); +has 'attrs' => (is => 'rw', isa => 'HashRef[Str]', lazy => 1, default => sub { +{} }); use overload '""' => sub { shift->name }; around BUILDARGS => sub { my $orig = shift; @@ -42,6 +59,15 @@ use overload "&{}" => sub { my $self = shift; sub { $self->call(@_) } }, }, fallback => 1; has 'kwargs' => (is => 'rw', init_arg => undef, isa => 'HashRef'); +has '_verbose' => (is => 'rw', isa => 'Bool', lazy => 1, default => 0); +has '_print_func' => (is => 'rw', isa => 'CodeRef', lazy => 1, + default => sub { + return sub { + my $x = shift; + return ($x->norm/sqrt($x->size))->asscalar; + }; + } +); =head1 NAME @@ -52,6 +78,34 @@ has 'kwargs' => (is => 'rw', init_arg => undef, isa => 'HashRef'); Register an initializer class to the AI::MXNet::Initializer factory. =cut +=head2 set_verbosity + + Switch on/off verbose mode + + Parameters + ---------- + $verbose : bool + switch on/off verbose mode + $print_func : CodeRef + A function that computes statistics of initialized arrays. + Takes an AI::MXNet::NDArray and returns a scalar. Defaults to mean + absolute value |x|/size(x) +=cut + +method set_verbosity(Bool $verbose=0, CodeRef $print_func=) +{ + $self->_verbose($verbose); + $self->_print_func($print_func) if defined $print_func; +} + +method _verbose_print($desc, $init, $arr) +{ + if($self->_verbose and defined $self->_print_func) + { + AI::MXNet::Logging->info('Initialized %s as %s: %s', $desc, $init, $self->_print_func->($arr)); + } +} + my %init_registry; method get_init_registry() { @@ -67,7 +121,7 @@ method register() { my $existing = $init_registry{ $name }; warn( - "WARNING: New initializer $self.$name" + "WARNING: New initializer $self.$name" ."is overriding existing initializer $existing.$name" ); } @@ -99,6 +153,7 @@ method call(Str|AI::MXNet::InitDesc $desc, AI::MXNet::NDArray $arr) { my ($klass, $kwargs) = @{ decode_json($init) }; $self->get_init_registry->{ lc $klass }->new(%{ $kwargs })->_init_weight("$desc", $arr); + $self->_verbose_print($desc, $init, $arr); } else { @@ -107,6 +162,7 @@ method call(Str|AI::MXNet::InitDesc $desc, AI::MXNet::NDArray $arr) { my $method = "_init_$1"; $self->$method($desc, $arr); + $self->_verbose_print($desc, $1, $arr); } else { @@ -690,7 +746,7 @@ extends 'AI::MXNet::Initializer'; Parameters ---------- init : Initializer - intializer applied to unpacked weights. + initializer applied to unpacked weights. All parameters below must be exactly the same as ones passed to the FusedRNNCell constructor. diff --git a/perl-package/AI-MXNet/lib/AI/MXNet/KVStore.pm b/perl-package/AI-MXNet/lib/AI/MXNet/KVStore.pm index 9f36cebc0fb7..eff57a31dc53 100644 --- a/perl-package/AI-MXNet/lib/AI/MXNet/KVStore.pm +++ b/perl-package/AI-MXNet/lib/AI/MXNet/KVStore.pm @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + package AI::MXNet::KVStore; use strict; use warnings; @@ -13,7 +30,7 @@ use AI::MXNet::Function::Parameters; AI::MXNet::KVStore - Key value store interface of MXNet. -=head1 DESCRIPTION +=head1 DESCRIPTION Key value store interface of MXNet for parameter synchronization, over multiple devices. =cut @@ -36,7 +53,7 @@ sub DEMOLISH Parameters ---------- - key : int or an array ref of int + key : str or an array ref of str The keys. value : NDArray or an array ref of NDArray objects The values. @@ -59,13 +76,13 @@ sub DEMOLISH =cut method init( - Int|ArrayRef[Int] $key, + Str|ArrayRef[Str] $key, AI::MXNet::NDArray|ArrayRef[AI::MXNet::NDArray]|ArrayRef[ArrayRef[AI::MXNet::NDArray]] $value ) { my ($keys, $vals) = _key_value($key, $value); check_call( - AI::MXNetCAPI::KVStoreInit( + AI::MXNetCAPI::KVStoreInitEx( $self->handle, scalar(@{ $keys }), $keys, $vals ) ); @@ -83,7 +100,7 @@ method init( Parameters ---------- - key : int or array ref of int + key : str or array ref of str value : NDArray or array ref of NDArray or array ref of array refs of NDArray priority : int, optional The priority of the push operation. @@ -127,14 +144,14 @@ method init( =cut method push( - Int|ArrayRef[Int] $key, + Str|ArrayRef[Str] $key, AI::MXNet::NDArray|ArrayRef[AI::MXNet::NDArray]|ArrayRef[ArrayRef[AI::MXNet::NDArray]] $value, Int :$priority=0 ) { my ($keys, $vals) = _key_value($key, $value); check_call( - AI::MXNetCAPI::KVStorePush( + AI::MXNetCAPI::KVStorePushEx( $self->handle, scalar(@{ $keys }), $keys, $vals, $priority ) ); @@ -154,7 +171,7 @@ method push( Parameters ---------- - key : int or array ref of int + key : str or array ref of str Keys out: NDArray or array ref of NDArray or array ref of array refs of NDArray According values @@ -197,14 +214,14 @@ method push( =cut method pull( - Int|ArrayRef[Int] $key, + Str|ArrayRef[Str] $key, AI::MXNet::NDArray|ArrayRef[AI::MXNet::NDArray]|ArrayRef[ArrayRef[AI::MXNet::NDArray]] :$out, Int :$priority=0 ) { my ($keys, $vals) = _key_value($key, $out); check_call( - AI::MXNetCAPI::KVStorePull( + AI::MXNetCAPI::KVStorePullEx( $self->handle, scalar(@{ $keys }), $keys, $vals, $priority ) ); diff --git a/perl-package/AI-MXNet/lib/AI/MXNet/KVStoreServer.pm b/perl-package/AI-MXNet/lib/AI/MXNet/KVStoreServer.pm index 6b018afaf6e5..4c274b92c71f 100644 --- a/perl-package/AI-MXNet/lib/AI/MXNet/KVStoreServer.pm +++ b/perl-package/AI-MXNet/lib/AI/MXNet/KVStoreServer.pm @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + package AI::MXNet::KVStoreServer; use strict; use warnings; @@ -30,7 +47,7 @@ has 'init_logging' => (is => 'rw', isa => 'Int', default => 0); # return the server controller method _controller() { - return sub { + return sub { my ($cmd_id, $cmd_body) = @_; if (not $self->init_logging) { diff --git a/perl-package/AI-MXNet/lib/AI/MXNet/LRScheduler.pm b/perl-package/AI-MXNet/lib/AI/MXNet/LRScheduler.pm index a9ffb37d6a69..27420f45167d 100644 --- a/perl-package/AI-MXNet/lib/AI/MXNet/LRScheduler.pm +++ b/perl-package/AI-MXNet/lib/AI/MXNet/LRScheduler.pm @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + package AI::MXNet::LRScheduler; use strict; use warnings; @@ -173,4 +190,4 @@ method call(Int $num_update) return $self->base_lr; } -1; \ No newline at end of file +1; diff --git a/perl-package/AI-MXNet/lib/AI/MXNet/Logging.pm b/perl-package/AI-MXNet/lib/AI/MXNet/Logging.pm index d6d3744ef37f..f3039cc09bfd 100644 --- a/perl-package/AI-MXNet/lib/AI/MXNet/Logging.pm +++ b/perl-package/AI-MXNet/lib/AI/MXNet/Logging.pm @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + package AI::MXNet::Logging; ## TODO use Mouse; diff --git a/perl-package/AI-MXNet/lib/AI/MXNet/Metric.pm b/perl-package/AI-MXNet/lib/AI/MXNet/Metric.pm index fbb93b006a6f..c3a3183432d5 100644 --- a/perl-package/AI-MXNet/lib/AI/MXNet/Metric.pm +++ b/perl-package/AI-MXNet/lib/AI/MXNet/Metric.pm @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + package AI::MXNet::Metric; use strict; use warnings; @@ -484,7 +501,7 @@ method update(ArrayRef[AI::MXNet::NDArray] $labels, ArrayRef[AI::MXNet::NDArray] my $label_shape = $label->shape->at(0); my $pred_shape = $pred->shape->at(-1); confess( - "Size of label $label_shape and + "Size of label $label_shape and .first dimension of pred $pred_shape do not match" ) unless $label_shape == $pred_shape; my $prob = $pred->index($label); @@ -493,6 +510,55 @@ method update(ArrayRef[AI::MXNet::NDArray] $labels, ArrayRef[AI::MXNet::NDArray] }, $labels, $preds); } +package AI::MXNet::PearsonCorrelation; +use Mouse; +use AI::MXNet::Base; +extends 'AI::MXNet::EvalMetric'; +has '+name' => (default => 'pearson-correlation'); + +=head1 NAME + + AI::MXNet::PearsonCorrelation +=cut + +=head1 DESCRIPTION + + Computes Pearson correlation. + + Parameters + ---------- + name : str + Name of this metric instance for display. + + Examples + -------- + >>> $predicts = [mx->nd->array([[0.3, 0.7], [0, 1.], [0.4, 0.6]])] + >>> $labels = [mx->nd->array([[1, 0], [0, 1], [0, 1]])] + >>> $pr = mx->metric->PearsonCorrelation() + >>> $pr->update($labels, $predicts) + >>> print pr->get() + ('pearson-correlation', '0.421637061887229') +=cut + +method update(ArrayRef[AI::MXNet::NDArray] $labels, ArrayRef[AI::MXNet::NDArray] $preds) +{ + AI::MXNet::Metric::check_label_shapes($labels, $preds); + zip(sub { + my ($label, $pred) = @_; + AI::MXNet::Metric::check_label_shapes($label, $pred); + $label = $label->aspdl->flat; + $pred = $pred->aspdl->flat; + my ($label_mean, $label_stdv) = ($label->stats)[0, 6]; + my ($pred_mean, $pred_stdv) = ($pred->stats)[0, 6]; + $self->sum_metric( + $self->sum_metric + + + ((($label-$label_mean)*($pred-$pred_mean))->sum/$label->nelem)/(($label_stdv*$pred_stdv)->at(0)) + ); + $self->num_inst($self->num_inst + 1); + }, $labels, $preds); +} + =head1 DESCRIPTION Custom evaluation metric that takes a sub ref. @@ -557,6 +623,7 @@ my %metrics = qw/ top_k_accuracy AI::MXNet::TopKAccuracy Perplexity AI::MXNet::Perplexity perplexity AI::MXNet::Perplexity + pearsonr AI::MXNet::PearsonCorrelation /; method create(Metric|ArrayRef[Metric] $metric, %kwargs) @@ -599,4 +666,4 @@ method create(Metric|ArrayRef[Metric] $metric, %kwargs) } } -1; \ No newline at end of file +1; diff --git a/perl-package/AI-MXNet/lib/AI/MXNet/Module.pm b/perl-package/AI-MXNet/lib/AI/MXNet/Module.pm index 2c5a2a5fc424..3e4d938bf4e9 100644 --- a/perl-package/AI-MXNet/lib/AI/MXNet/Module.pm +++ b/perl-package/AI-MXNet/lib/AI/MXNet/Module.pm @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + ## TODO ## this class is here because of https://github.com/gfx/p5-Mouse/pull/67 ## once 2.4.7 version of Mouse in Ubuntu for affected Perl version @@ -18,6 +35,7 @@ package AI::MXNet::Module; use AI::MXNet::Base; use AI::MXNet::Function::Parameters; use List::Util qw(max); +use Data::Dumper (); use Mouse; func _create_kvstore( @@ -71,10 +89,11 @@ func _initialize_kvstore( { enumerate(sub{ my ($idx, $param_on_devs) = @_; - $kvstore->init($idx, $arg_params->{ $param_names->[$idx] }); + my $name = $param_names->[$idx]; + $kvstore->init($name, $arg_params->{ $name }); if($update_on_kvstore) { - $kvstore->pull($idx, out => $param_on_devs, priority => -$idx); + $kvstore->pull($name, out => $param_on_devs, priority => -$idx); } }, $param_arrays); } @@ -82,7 +101,8 @@ func _initialize_kvstore( func _update_params_on_kvstore( ArrayRef[AI::MXNet::NDArray]|ArrayRef[ArrayRef[AI::MXNet::NDArray]] $param_arrays, ArrayRef[AI::MXNet::NDArray]|ArrayRef[ArrayRef[AI::MXNet::NDArray]] $grad_arrays, - AI::MXNet::KVStore $kvstore + AI::MXNet::KVStore $kvstore, + ArrayRef[Str] $param_names ) { enumerate(sub{ @@ -91,10 +111,11 @@ func _update_params_on_kvstore( { return; } + my $name = $param_names->[$index]; # push gradient, priority is negative index - $kvstore->push($index, $grad_list, priority => -$index); + $kvstore->push($name, $grad_list, priority => -$index); # pull back the weights - $kvstore->pull($index, out => $arg_list, priority => -$index); + $kvstore->pull($name, out => $arg_list, priority => -$index); }, $param_arrays, $grad_arrays); } @@ -103,7 +124,8 @@ func _update_params( ArrayRef[ArrayRef[AI::MXNet::NDArray]] $grad_arrays, AI::MXNet::Updater $updater, Int $num_device, - Maybe[AI::MXNet::KVStore] $kvstore= + Maybe[AI::MXNet::KVStore] $kvstore=, + Maybe[ArrayRef[Str]] $param_names= ) { enumerate(sub{ @@ -114,16 +136,17 @@ func _update_params( } if($kvstore) { + my $name = $param_names->[$index]; # push gradient, priority is negative index - $kvstore->push($index, $grad_list, priority => -$index); + $kvstore->push($name, $grad_list, priority => -$index); # pull back the sum gradients, to the same locations. - $kvstore->pull($index, out => $grad_list, priority => -$index); + $kvstore->pull($name, out => $grad_list, priority => -$index); } enumerate(sub { my ($k, $w, $g) = @_; # faked an index here, to make optimizer create diff # state for the same index but on diff devs, TODO(mli) - # use a better solution latter + # use a better solution later &{$updater}($index*$num_device+$k, $g, $w); }, $arg_list, $grad_list); }, $param_arrays, $grad_arrays); @@ -167,7 +190,7 @@ has 'state_names' => (is => 'rw', isa => 'Maybe[ArrayRef[Str]]'); has 'logger' => (is => 'ro', default => sub { AI::MXNet::Logging->get_logger }); has '_p' => (is => 'rw', init_arg => undef); has 'context' => ( - is => 'ro', + is => 'ro', isa => 'AI::MXNet::Context|ArrayRef[AI::MXNet::Context]', default => sub { AI::MXNet::Context->cpu } ); @@ -399,7 +422,8 @@ method init_params( Maybe[HashRef[AI::MXNet::NDArray]] :$arg_params=, Maybe[HashRef[AI::MXNet::NDArray]] :$aux_params=, Bool :$allow_missing=0, - Bool :$force_init=0 + Bool :$force_init=0, + Bool :$allow_extra=0 ) { if($self->params_initialized and not $force_init) @@ -467,21 +491,23 @@ method init_params( $self->_p->_params_dirty(0); # copy the initialized parameters to devices - $self->_p->_exec_group->set_params($self->_p->_arg_params, $self->_p->_aux_params); + $self->_p->_exec_group->set_params($self->_p->_arg_params, $self->_p->_aux_params, $allow_extra); } method set_params( HashRef[AI::MXNet::NDArray] $arg_params, HashRef[AI::MXNet::NDArray] $aux_params, Bool :$allow_missing=0, - Bool :$force_init=1 + Bool :$force_init=1, + Bool :$allow_extra=0 ) { if(not $allow_missing) { $self->init_params( arg_params => $arg_params, aux_params => $aux_params, - allow_missing => $allow_missing, force_init => $force_init + allow_missing => $allow_missing, force_init => $force_init, + allow_extra => $allow_extra ); return; } @@ -494,7 +520,7 @@ method set_params( ); return; } - $self->_p->_exec_group->set_params($arg_params, $aux_params); + $self->_p->_exec_group->set_params($arg_params, $aux_params, $allow_extra); $self->_p->_params_dirty(1); $self->params_initialized(1); } @@ -770,6 +796,43 @@ method forward( ) { assert($self->binded and $self->params_initialized); + + my @curr_data_shapes = map { $_->shape } @{ $self->data_shapes }; + my @new_data_shapes = map { $_->shape } @{ $data_batch->data }; + if(Data::Dumper->Dump(\@curr_data_shapes) ne Data::Dumper->Dump(\@new_data_shapes)) + { + my $new_dshape; + if($data_batch->can('provide_data') and $data_batch->provide_data) + { + $new_dshape = $data_batch->provide_data; + } + else + { + $new_dshape = []; + zip(sub { + my ($i, $shape) = @_; + push @{ $new_dshape }, AI::MXNet::DataDesc->new( + $i->name, $shape, $i->dtype, $i->layout + ); + }, $self->data_shapes, \@new_data_shapes); + } + my $new_lshape; + if($data_batch->can('provide_label') and $data_batch->provide_label) + { + $new_lshape = $data_batch->provide_label; + } + elsif($data_batch->can('label') and $data_batch->label) + { + $new_lshape = []; + zip(sub { + my ($i, $j) = @_; + push @{ $new_lshape }, AI::MXNet::DataDesc->new( + $i->name, $j->shape, $i->dtype, $i->layout + ); + }, $self->label_shapes, $data_batch->label); + } + $self->reshape(data_shapes => $new_dshape, label_shapes => $new_lshape); + } $self->_p->_exec_group->forward($data_batch, $is_train); } @@ -788,7 +851,8 @@ method update() _update_params_on_kvstore( $self->_p->_exec_group->_p->param_arrays, $self->_p->_exec_group->_p->grad_arrays, - $self->_p->_kvstore + $self->_p->_kvstore, + $self->_p->_exec_group->param_names ); } else @@ -798,7 +862,8 @@ method update() $self->_p->_exec_group->_p->grad_arrays, $self->_p->_updater, scalar(@{ $self->_p->_context}), - $self->_p->_kvstore + $self->_p->_kvstore, + $self->_p->_exec_group->param_names ); } } @@ -896,4 +961,4 @@ method _kvstore() $self->_p->_kvstore; } -1; \ No newline at end of file +1; diff --git a/perl-package/AI-MXNet/lib/AI/MXNet/Module/Base.pm b/perl-package/AI-MXNet/lib/AI/MXNet/Module/Base.pm index 44df735a15ee..7a9e3de090db 100644 --- a/perl-package/AI-MXNet/lib/AI/MXNet/Module/Base.pm +++ b/perl-package/AI-MXNet/lib/AI/MXNet/Module/Base.pm @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + package AI::MXNet::BatchEndParam; use Mouse; use AI::MXNet::Function::Parameters; @@ -677,6 +694,10 @@ method get_params() { confess("NotImplemented") } called to fill those missing params. :$force_init=0 : Bool If true, will force re-initialize even if already initialized. + :$allow_extra=0 : Boolean, optional + Whether allow extra parameters that are not needed by symbol. + If this is True, no error will be thrown when arg_params or aux_params + contain extra parameters that is not needed by the executor. =cut method init_params( @@ -684,7 +705,8 @@ method init_params( Maybe[HashRef[AI::MXNet::NDArray]] :$arg_params=, Maybe[HashRef[AI::MXNet::NDArray]] :$aux_params=, Bool :$allow_missing=0, - Bool :$force_init=0 + Bool :$force_init=0, + Bool :$allow_extra=0 ) { confess("NotImplemented"); @@ -705,13 +727,18 @@ method init_params( called to fill those missing params. :$force_init=0 : Bool If true, will force re-initialize even if already initialized. + :$allow_extra=0 : Bool + Whether allow extra parameters that are not needed by symbol. + If this is True, no error will be thrown when arg_params or aux_params + contain extra parameters that is not needed by the executor. =cut method set_params( Maybe[HashRef[AI::MXNet::NDArray]] $arg_params=, Maybe[HashRef[AI::MXNet::NDArray]] $aux_params=, Bool :$allow_missing=0, - Bool :$force_init=0 + Bool :$force_init=0, + Bool :$allow_extra=0 ) { $self->init_params( @@ -719,7 +746,8 @@ method set_params( arg_params => $arg_params, aux_params => $aux_params, allow_missing => $allow_missing, - force_init => $force_init + force_init => $force_init, + allow_extra => $allow_extra ); } @@ -865,7 +893,11 @@ method prepare(AI::MXNet::DataBatch $data_batch){} =head2 forward - Forward computation. + Forward computation. It supports data batches with different shapes, such as + different batch sizes or different image sizes. + If reshaping of data batch relates to modification of symbol or module, such as + changing image layout ordering or switching from training to predicting, module + rebinding is required. Parameters ---------- diff --git a/perl-package/AI-MXNet/lib/AI/MXNet/Module/Bucketing.pm b/perl-package/AI-MXNet/lib/AI/MXNet/Module/Bucketing.pm index 30bdc4378abb..531f41d58a3a 100644 --- a/perl-package/AI-MXNet/lib/AI/MXNet/Module/Bucketing.pm +++ b/perl-package/AI-MXNet/lib/AI/MXNet/Module/Bucketing.pm @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + package AI::MXNet::Module::Bucketing; use Mouse; use AI::MXNet::Function::Parameters; @@ -210,14 +227,16 @@ method set_params( HashRef[AI::MXNet::NDArray] $arg_params, HashRef[AI::MXNet::NDArray] $aux_params, Bool $allow_missing=0, - Bool $force_init=1 + Bool $force_init=1, + Bool $allow_extra=0 ) { if(not $allow_missing) { $self->init_params( arg_params => $arg_params, aux_params => $aux_params, - allow_missing => $allow_missing, force_init => $force_init + allow_missing => $allow_missing, force_init => $force_init, + allow_extra => $allow_extra ); return; } @@ -232,7 +251,8 @@ method set_params( $self->_curr_module->set_params( $arg_params, $aux_params, allow_missing => $allow_missing, - force_init => $force_init + force_init => $force_init, + allow_extra => $allow_extra ); # because we didn't update self._arg_params, they are dirty now. $self->_params_dirty(1); @@ -244,7 +264,8 @@ method init_params( Maybe[HashRef[AI::MXNet::NDArray]] :$arg_params=, Maybe[HashRef[AI::MXNet::NDArray]] :$aux_params=, Bool :$allow_missing=0, - Bool :$force_init=0 + Bool :$force_init=0, + Bool :$allow_extra=0 ) { return if($self->params_initialized and not $force_init); @@ -254,7 +275,8 @@ method init_params( arg_params => $arg_params, aux_params => $aux_params, allow_missing => $allow_missing, - force_init => $force_init + force_init => $force_init, + allow_extra => $allow_extra ); $self->_params_dirty(0); $self->params_initialized(1); diff --git a/perl-package/AI-MXNet/lib/AI/MXNet/Monitor.pm b/perl-package/AI-MXNet/lib/AI/MXNet/Monitor.pm index 7ac989c6e27f..993461713cb6 100644 --- a/perl-package/AI-MXNet/lib/AI/MXNet/Monitor.pm +++ b/perl-package/AI-MXNet/lib/AI/MXNet/Monitor.pm @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + package AI::MXNet::Monitor; use Mouse; use AI::MXNet::Function::Parameters; @@ -153,7 +170,7 @@ method toc() my $s = ''; for my $v (@{ $v_list }) { - confess("the argument must be NDArray") + confess("the argument must be NDArray") unless blessed($v) and $v->isa('AI::MXNet::NDArray'); if($v->size == 1) { diff --git a/perl-package/AI-MXNet/lib/AI/MXNet/NDArray.pm b/perl-package/AI-MXNet/lib/AI/MXNet/NDArray.pm index 2871a62226e5..1f58a74e2bba 100644 --- a/perl-package/AI-MXNet/lib/AI/MXNet/NDArray.pm +++ b/perl-package/AI-MXNet/lib/AI/MXNet/NDArray.pm @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + package AI::MXNet::NDArray; =head1 NAME @@ -12,7 +29,7 @@ use AI::MXNet::NDArray::Slice; use AI::MXNet::Context; use Mouse; use AI::MXNet::Function::Parameters; -use overload +use overload '""' => \&stringify, '+' => \&add, '+=' => \&iadd, @@ -22,6 +39,8 @@ use overload '*=' => \&imultiply, '/' => \÷, '/=' => \&idivide, + '%' => \&modulo, + '%=' => \&imodulo, '**' => \&power, '==' => \&equal, '!=' => \¬_equal, @@ -66,7 +85,7 @@ method at(Index @indices) my $isize = @indices; confess("Dimensions size $dsize < indexes size $isize") if $dsize < $isize; - confess("Dimensions size $dsize = indexes size $isize, + confess("Dimensions size $dsize = indexes size $isize, ndarray only supports either ->at on dimension 0 or full crop") if $isize > 1 and $dsize != $isize; @@ -76,7 +95,7 @@ method at(Index @indices) confess("Dimension $i mismatch Idx: $idx >= Dim Size: $dim_size") if $idx >= $dim_size or ($idx + $dim_size) < 0; ++$i; - }, \@indices, $shape); + }, \@indices, $shape); $i = 0; for my $v (@indices) { @@ -179,7 +198,7 @@ method _sync_copyfrom(ArrayRef|PDL|PDL::Matrix $source_array) my $convert_func = $pdl_type->convertfunc; $source_array = $source_array->$convert_func; } - $source_array = pdl($pdl_type, [@{ $source_array->unpdl } ? $source_array->unpdl->[0] : 0 ]) + $source_array = pdl($pdl_type, [@{ $source_array->unpdl } ? $source_array->unpdl->[0] : 0 ]) unless @{ $source_array->shape->unpdl }; my $pdl_shape = $source_array->shape->unpdl; my $pdl_shape_str = join(',', ref($source_array) eq 'PDL' ? reverse @{ $pdl_shape } : @{ $pdl_shape }); @@ -220,7 +239,7 @@ method aspdl() my $pdl = PDL->new_from_specification($pdl_type, reverse @{ $self->shape }); my $perl_pack_type = DTYPE_MX_TO_PERL->{$dtype}; my $buf = pack("$perl_pack_type*", (0)x$self->size); - check_call(AI::MXNetCAPI::NDArraySyncCopyToCPU($self->handle, $buf, $self->size)); + check_call(AI::MXNetCAPI::NDArraySyncCopyToCPU($self->handle, $buf, $self->size)); ## special handling for float16 if($perl_pack_type eq 'S') { @@ -251,7 +270,7 @@ method asmpdl() my $pdl = PDL::Matrix->new_from_specification($pdl_type, @{ $self->shape }); my $perl_pack_type = DTYPE_MX_TO_PERL->{$dtype}; my $buf = pack("$perl_pack_type*", (0)x$self->size); - check_call(AI::MXNetCAPI::NDArraySyncCopyToCPU($self->handle, $buf, $self->size)); + check_call(AI::MXNetCAPI::NDArraySyncCopyToCPU($self->handle, $buf, $self->size)); ## special handling for float16 if($perl_pack_type eq 'S') { @@ -275,7 +294,7 @@ method asmpdl() Finishing index of slice. =cut -method _slice ( +method _slice ( Index $start, Index $stop ) @@ -392,7 +411,7 @@ method moveaxis(Int $source, Int $dest) =head2 broadcast_to - Broadcasting the current NDArray into the given shape. + Broadcasting the current NDArray into the given shape. Parameters --------- @@ -402,7 +421,7 @@ method moveaxis(Int $source, Int $dest) method broadcast_to(Shape $shape) { my $cur_shape = $self->shape; - my $err_str = "operands could not be broadcast together with remapped shapes" + my $err_str = "operands could not be broadcast together with remapped shapes" ."[original->remapped]: [@$cur_shape] and requested shape [@$shape]"; if(@$shape < @$cur_shape) { @@ -492,7 +511,7 @@ method context() Returns ------- - a data type string ('float32', 'float64', 'float16', 'uint8', 'int32') + a data type string ('float32', 'float64', 'float16', 'uint8', 'int32') representing the data type of the ndarray. 'float32' is the default dtype for the ndarray class. =cut @@ -705,7 +724,7 @@ method stringify($other=, $reverse=) method iadd(AI::MXNet::NDArray|Num $other, $reverse=) { confess('trying to add to a readonly NDArray') unless $self->writable; - return ref $other + return ref $other ? __PACKAGE__->broadcast_add($self, $other, { out => $self }) : __PACKAGE__->_plus_scalar($self, $other, { out => $self }) } @@ -750,9 +769,9 @@ method multiply(AI::MXNet::NDArray|Num $other, $reverse=) method imultiply(AI::MXNet::NDArray|Num $other, $reverse=) { confess('trying to add to a readonly NDArray') unless $self->writable; - return ref $other - ? __PACKAGE__->broadcast_mul($self, $other, { out => $self }) - : __PACKAGE__->_mul_scalar($self, $other, { out => $self }) + return ref $other + ? __PACKAGE__->broadcast_mul($self, $other, { out => $self }) + : __PACKAGE__->_mul_scalar($self, $other, { out => $self }) } method divide(AI::MXNet::NDArray|Num $other, $reverse=) @@ -768,9 +787,9 @@ method divide(AI::MXNet::NDArray|Num $other, $reverse=) method idivide(AI::MXNet::NDArray|Num $other, $reverse=) { confess('trying to add to a readonly NDArray') unless $self->writable; - return ref $other - ? __PACKAGE__->broadcast_div($self, $other, { out => $self }) - : __PACKAGE__->_div_scalar($self, $other, { out => $self }) + return ref $other + ? __PACKAGE__->broadcast_div($self, $other, { out => $self }) + : __PACKAGE__->_div_scalar($self, $other, { out => $self }) } method power(AI::MXNet::NDArray|Num $other, $reverse=) @@ -864,6 +883,24 @@ method true_divide(AI::MXNet::NDArray|Num $other, $reverse=) return $self->divide($other, $reverse); } +method modulo(AI::MXNet::NDArray|Num $other, $reverse=) +{ + return _ufunc_helper( + $self, + $other, + qw/broadcast_mod _mod_scalar _rmod_scalar/, + $reverse + ); +} + +method imodulo(AI::MXNet::NDArray|Num $other, $reverse=) +{ + confess('trying to modulo to a readonly NDArray') unless $self->writable; + return ref $other + ? __PACKAGE__->broadcast_mod($self, $other, { out => $self }) + : __PACKAGE__->_mod_scalar($self, $other, { out => $self }) +} + =head2 empty Creates an empty uninitialized NDArray, with the specified shape. @@ -918,9 +955,14 @@ method empty(Shape $shape, AI::MXNet::Context :$ctx=AI::MXNet::Context->current_ The created NDArray. =cut -method zeros(Shape $shape, AI::MXNet::Context :$ctx=AI::MXNet::Context->current_ctx, Dtype :$dtype='float32') +method zeros( + Shape $shape, + AI::MXNet::Context :$ctx=AI::MXNet::Context->current_ctx, + Dtype :$dtype='float32', + Maybe[AI::MXNet::NDArray] :$out= +) { - return __PACKAGE__->_zeros({ shape => $shape, ctx => "$ctx", dtype => $dtype }); + return __PACKAGE__->_zeros({ shape => $shape, ctx => "$ctx", dtype => $dtype, ($out ? (out => $out) : ()) }); } =head2 ones @@ -944,9 +986,14 @@ method zeros(Shape $shape, AI::MXNet::Context :$ctx=AI::MXNet::Context->current_ The created NDArray. =cut -method ones(Shape $shape, AI::MXNet::Context :$ctx=AI::MXNet::Context->current_ctx, Dtype :$dtype='float32') +method ones( + Shape $shape, + AI::MXNet::Context :$ctx=AI::MXNet::Context->current_ctx, + Dtype :$dtype='float32', + Maybe[AI::MXNet::NDArray] :$out= +) { - return __PACKAGE__->_ones({ shape => $shape, ctx => "$ctx", dtype => $dtype }); + return __PACKAGE__->_ones({ shape => $shape, ctx => "$ctx", dtype => $dtype, ($out ? (out => $out) : ()) }); } =head2 full @@ -973,9 +1020,13 @@ method ones(Shape $shape, AI::MXNet::Context :$ctx=AI::MXNet::Context->current_c The created NDArray. =cut -method full(Shape $shape, Num $val, AI::MXNet::Context :$ctx=AI::MXNet::Context->current_ctx, Dtype :$dtype='float32') +method full( + Shape $shape, Num $val, + AI::MXNet::Context :$ctx=AI::MXNet::Context->current_ctx, + Dtype :$dtype='float32', Maybe[AI::MXNet::NDArray] :$out= +) { - return __PACKAGE__->_set_value({ src => $val, out => __PACKAGE__->empty($shape, ctx => $ctx, dtype => $dtype) }); + return __PACKAGE__->_set_value({ src => $val, out => $out ? $out : __PACKAGE__->empty($shape, ctx => $ctx, dtype => $dtype) }); } =head2 array @@ -984,7 +1035,7 @@ method full(Shape $shape, Num $val, AI::MXNet::Context :$ctx=AI::MXNet::Context- Parameters ---------- - $source_array : PDL, PDL::Matrix, Array ref in PDL::pdl format + $source_array : AI::MXNet::NDArray PDL, PDL::Matrix, Array ref in PDL::pdl format Source data to create NDArray from. :$ctx : AI::MXNet::Context, optional @@ -999,8 +1050,14 @@ method full(Shape $shape, Num $val, AI::MXNet::Context :$ctx=AI::MXNet::Context- The created NDArray. =cut -method array(PDL|PDL::Matrix|ArrayRef $source_array, AI::MXNet::Context :$ctx=AI::MXNet::Context->current_ctx, Dtype :$dtype='float32') +method array(PDL|PDL::Matrix|ArrayRef|AI::MXNet::NDArray $source_array, AI::MXNet::Context :$ctx=AI::MXNet::Context->current_ctx, Dtype :$dtype='float32') { + if(blessed $source_array and $source_array->isa('AI::MXNet::NDArray')) + { + my $arr = __PACKAGE__->empty($source_array->shape, ctx => $ctx, dtype => $dtype); + $arr .= $source_array; + return $arr; + } my $pdl_type = PDL::Type->new(DTYPE_MX_TO_PDL->{ $dtype }); if(not blessed($source_array)) { @@ -1054,11 +1111,11 @@ method concatenate(ArrayRef[AI::MXNet::NDArray] $arrays, Index :$axis=0, :$alway $shape_axis += $arr->shape->[$axis]; my $arr_shape_rest1 = [@{ $arr->shape }[0..($axis-1)]]; my $arr_shape_rest2 = [@{ $arr->shape }[($axis+1)..(@{ $arr->shape }-1)]]; - confess("first array $arrays->[0] and $i array $arr do not match") + confess("first array $arrays->[0] and $i array $arr do not match") unless join(',',@$arr_shape_rest1) eq join(',',@$shape_rest1); - confess("first array $arrays->[0] and $i array $arr do not match") + confess("first array $arrays->[0] and $i array $arr do not match") unless join(',',@$arr_shape_rest2) eq join(',',@$shape_rest2); - confess("first array $arrays->[0] and $i array $arr dtypes do not match") + confess("first array $arrays->[0] and $i array $arr dtypes do not match") unless join(',',@$arr_shape_rest2) eq join(',',@$shape_rest2); $i++; } @@ -1078,8 +1135,8 @@ method concatenate(ArrayRef[AI::MXNet::NDArray] $arrays, Index :$axis=0, :$alway $begin->[$axis] = $idx; $end->[$axis] = $idx+$arr->shape->[$axis]; __PACKAGE__->_crop_assign( - $ret, $arr, - { + $ret, $arr, + { out => $ret, begin => $begin, end => $end @@ -1307,6 +1364,59 @@ method waitall() check_call(AI::MXNetCAPI::NDArrayWaitAll()); } +=head2 _fresh_grad + + Parameters: + ---------- + Maybe[Bool] $state= + + Whether this array's corresponding gradient array + (registered via `autograd->mark_variables`) has been + updated by `autograd->backward` since last reset. + + `_fresh_grad` need to be manually set to False + after consuming gradient (usually after updating this + array). +=cut + +method _fresh_grad(Maybe[Bool] $state=) +{ + if(defined $state) + { + check_call(AI::MXNetCAPI::NDArraySetGradState($self->handle, $state)); + return $state; + } + else + { + return scalar(check_call(AI::MXNetCAPI::NDArrayGetGradState($self->handle))); + } +} + +=head2 detach + + Returns a new NDArray, detached from the current graph. +=cut + +method detach() +{ + my $handle = check_call(AI::MXNetCAPI::NDArrayDetach($self->handle)); + return __PACKAGE__->new(handle => $handle); +} + +method backward(Maybe[AI::MXNet::NDArray] $out_grad=, Bool $retain_graph=0) +{ + check_call( + AI::MXNetCAPI::AutogradBackward( + 1, + [$self->handle], + [defined $out_grad ? $out_grad->handle : undef], + $retain_graph + ) + ) +} + +method CachedOp(@args) { AI::MXNet::CachedOp->new(@args) } + my $lvalue_methods = join "\n", map {"use attributes 'AI::MXNet::NDArray', \\&AI::MXNet::NDArray::$_, 'lvalue';"} qw/at slice aspdl asmpdl reshape copy sever T astype as_in_context copyto empty zero ones full array/; diff --git a/perl-package/AI-MXNet/lib/AI/MXNet/NDArray/Base.pm b/perl-package/AI-MXNet/lib/AI/MXNet/NDArray/Base.pm index c962f0849733..b51436157a82 100644 --- a/perl-package/AI-MXNet/lib/AI/MXNet/NDArray/Base.pm +++ b/perl-package/AI-MXNet/lib/AI/MXNet/NDArray/Base.pm @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + package AI::MXNet::NDArray::Base; use strict; use warnings; @@ -100,7 +117,7 @@ func _make_ndarray_function($handle, $func_name) } for my $key (keys %kwargs) { - $kwargs{ $key } = "(" .join(", ", @{ $kwargs{ $key } }) .")" + $kwargs{ $key } = "(" .join(", ", @{ $kwargs{ $key } }) .")" if ref $kwargs{ $key } eq 'ARRAY'; } my $out = check_call(AI::MXNetCAPI::ImperativeInvoke( @@ -140,6 +157,7 @@ method _init_ndarray_module() } } + __PACKAGE__->_init_ndarray_module; 1; diff --git a/perl-package/AI-MXNet/lib/AI/MXNet/NDArray/Doc.pm b/perl-package/AI-MXNet/lib/AI/MXNet/NDArray/Doc.pm index a1a7812ca9a5..fc44812f2cff 100644 --- a/perl-package/AI-MXNet/lib/AI/MXNet/NDArray/Doc.pm +++ b/perl-package/AI-MXNet/lib/AI/MXNet/NDArray/Doc.pm @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + package AI::MXNet::NDArray::Doc; use strict; use warnings; diff --git a/perl-package/AI-MXNet/lib/AI/MXNet/NDArray/Slice.pm b/perl-package/AI-MXNet/lib/AI/MXNet/NDArray/Slice.pm index a52f8eef7c1d..40312ebaa24f 100644 --- a/perl-package/AI-MXNet/lib/AI/MXNet/NDArray/Slice.pm +++ b/perl-package/AI-MXNet/lib/AI/MXNet/NDArray/Slice.pm @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + package AI::MXNet::NDArray::Slice; use strict; use warnings; @@ -13,7 +30,7 @@ use AI::MXNet::Function::Parameters; has parent => (is => 'ro', isa => 'AI::MXNet::NDArray', required => 1); has begin => (is => 'ro', isa => 'Shape', required => 1); has end => (is => 'ro', isa => 'Shape', required => 1); -use overload +use overload '.=' => \&set, '=' => sub { $_[0] }, '""' => \¬supported, @@ -37,10 +54,10 @@ method set(AcceptableInput $value, $reverse=) { confess("set value must be defined") unless defined $value; confess("${\ $self->parent } is not writable") unless $self->parent->writable; - my $shape = []; + my $shape = []; zip( sub { my ($begin, $end) = @_; push @$shape, ($end-$begin); }, - $self->begin, + $self->begin, $self->end ); if(ref $value) @@ -58,12 +75,12 @@ method set(AcceptableInput $value, $reverse=) $value = AI::MXNet::NDArray->array($value, ctx => $self->parent->context); } confess("value $value does not match slice dim sizes [@$shape]") - if @{$value->shape} != @$shape; + if @{$value->shape} != @$shape; zip( - sub { - my ($dsize, $vdsize) = @_; - confess("Slice [@$shape] != $value given as value") - if $dsize != $vdsize; + sub { + my ($dsize, $vdsize) = @_; + confess("Slice [@$shape] != $value given as value") + if $dsize != $vdsize; }, $shape, $value->shape diff --git a/perl-package/AI-MXNet/lib/AI/MXNet/Optimizer.pm b/perl-package/AI-MXNet/lib/AI/MXNet/Optimizer.pm index 8b60db6c071b..c6f682253833 100644 --- a/perl-package/AI-MXNet/lib/AI/MXNet/Optimizer.pm +++ b/perl-package/AI-MXNet/lib/AI/MXNet/Optimizer.pm @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + package AI::MXNet::Optimizer; use strict; use warnings; @@ -33,7 +50,7 @@ method register() { my $existing = $opt_registry{ $name }; warn( - "WARNING: New optimizer $self.$name" + "WARNING: New optimizer $self.$name" ."is overriding existing optimizer $existing.$name" ); } @@ -258,8 +275,15 @@ method _get_wd(Index $index) clip_gradient : float, optional clip gradient in range [-clip_gradient, clip_gradient] - param_idx2name : dict of string/int to float, optional + param_idx2name : hash of string/int to float, optional special treat weight decay in parameter ends with bias, gamma, and beta + + multi_precision: bool, optional + Flag to control the internal precision of the optimizer. + False results in using the same precision as the weights (default), + True makes internal 32-bit copy of the weights and applies gradients + in 32-bit precision even if actual weights used in the model have lower precision. + Turning this on can improve convergence and accuracy when training with float16. =cut package AI::MXNet::SGD; @@ -268,6 +292,7 @@ extends 'AI::MXNet::Optimizer'; has 'kwargs' => (is => "rw", isa => "HashRef[Num]"); has 'momentum' => (is => "rw", isa => "Num", default => 0); +has 'multi_precision' => (is => "ro", isa => "Bool", default => 0); sub BUILD { @@ -285,52 +310,79 @@ sub BUILD method create_state(Index $index, AI::MXNet::NDArray $weight) { - if($self->momentum == 0) + my $momentum; + my $weight_master_copy; + if($self->multi_precision and $weight->dtype eq 'float16') { - return undef; + my $weight_master_copy = AI::MXNet::NDArray->array($weight, ctx => $weight->context, dtype => 'float32'); + if($self->momentum != 0) + { + $momentum = AI::MXNet::NDArray->zeros($weight->shape, ctx => $weight->context, dtype => 'float32'); + } + return [$momentum, $weight_master_copy]; } - else + if($weight->dtype eq 'float16' and not $self->multi_precision) { - return AI::MXNet::NDArray->zeros( - $weight->shape, ctx => $weight->context, dtype => $weight->dtype + AI::MXNet::Logging->warning( + "Accumulating with float16 in optimizer can lead to ". + "poor accuracy or slow convergence. ". + "Consider using multi_precision=True option of the ". + "SGD optimizer" ); } + if($self->momentum != 0) + { + $momentum = AI::MXNet::NDArray->zeros($weight->shape, ctx => $weight->context, dtype => $weight->dtype); + } + return $momentum; } method update( Index $index, AI::MXNet::NDArray $weight, AI::MXNet::NDArray $grad, - Maybe[AI::MXNet::NDArray] $state + Maybe[AI::MXNet::NDArray|ArrayRef[Maybe[AI::MXNet::NDArray]]] $state ) { my $lr = $self->_get_lr($index); my $wd = $self->_get_wd($index); $self->_update_count($index); - if($state) + my $kwargs = { + out => $weight, + lr => $lr, + wd => $wd, + %{ $self->kwargs } + }; + my $use_multi_precision = ref($state) eq 'ARRAY'; + if(not $use_multi_precision) { - AI::MXNet::NDArray->sgd_mom_update( - $weight, $grad, $state, - { - out => $weight, - lr => $lr, - wd => $wd, - %{ $self->kwargs } - } - ); + if(defined $state) + { + AI::MXNet::NDArray->sgd_mom_update( + $weight, $grad, $state, $kwargs + ); + } + else + { + AI::MXNet::NDArray->sgd_update( + $weight, $grad, $kwargs + ); + } } else { - AI::MXNet::NDArray->sgd_update( - $weight, - $grad, - { - out => $weight, - lr => $lr, - wd => $wd, - %{ $self->kwargs } - } - ); + if(defined $state->[0]) + { + AI::MXNet::NDArray->mp_sgd_mom_update( + $weight, $grad, $state->[0], $state->[1], $kwargs + ); + } + else + { + AI::MXNet::NDArray->mp_sgd_update( + $weight, $grad, $state->[1], $kwargs + ); + } } } @@ -470,7 +522,7 @@ method update( if($self->clip_gradient) { $grad = AI::MXNet::NDArray->clip( - $grad, + $grad, -$self->clip_gradient, $self->clip_gradient ); @@ -531,7 +583,7 @@ method create_state(Index $index, AI::MXNet::NDArray $weight) } method update( - Index $index, + Index $index, AI::MXNet::NDArray $weight, AI::MXNet::NDArray $grad, AI::MXNet::NDArray|Undef $state @@ -643,7 +695,7 @@ method create_state(Index $index, AI::MXNet::NDArray $weight) } method update( - Index $index, + Index $index, AI::MXNet::NDArray $weight, AI::MXNet::NDArray $grad, ArrayRef[AI::MXNet::NDArray] $state @@ -713,7 +765,7 @@ has '+learning_rate' => (default => 0.05); method create_state(Index $index, AI::MXNet::NDArray $weight) { return AI::MXNet::NDArray->zeros( - $weight->shape, + $weight->shape, ctx => $weight->context ); # history } @@ -990,7 +1042,7 @@ extends 'AI::MXNet::Optimizer'; method create_state(Index $index, AI::MXNet::NDArray $weight) { return AI::MXNet::NDArray->zeros( - $weight->shape, + $weight->shape, ctx => $weight->context ); } @@ -1081,6 +1133,184 @@ method update( (($self->beta + $n->sqrt) / $lr + $wd) * ($dn->abs > $self->lamda1); } +__PACKAGE__->register; + +package AI::MXNet::Adamax; + +=head1 NAME + + AI::MXNet::Adamax +=cut + +=head1 DESCRIPTION + + It is a variant of Adam based on the infinity norm + available at http://arxiv.org/abs/1412.6980 Section 7. + + This optimizer accepts the following parameters in addition to those accepted + AI::MXNet::Optimizer. + + Parameters + ---------- + beta1 : float, optional + Exponential decay rate for the first moment estimates. + beta2 : float, optional + Exponential decay rate for the second moment estimates. +=cut + +use Mouse; +extends 'AI::MXNet::Optimizer'; +has '+learning_rate' => (default => 0.002); +has 'beta1' => (is => "ro", isa => "Num", default => 0.9); +has 'beta2' => (is => "ro", isa => "Num", default => 0.999); + +method create_state(Index $index, AI::MXNet::NDArray $weight) +{ + return [ + AI::MXNet::NDArray->zeros( + $weight->shape, + ctx => $weight->context, + dtype => $weight->dtype + ), # mean + AI::MXNet::NDArray->zeros( + $weight->shape, + ctx => $weight->context, + dtype => $weight->dtype + ) # variance + ]; +} + +method update( + Index $index, + AI::MXNet::NDArray $weight, + AI::MXNet::NDArray $grad, + ArrayRef[AI::MXNet::NDArray] $state +) +{ + my $wd = $self->_get_wd($index); + my $lr = $self->_get_lr($index); + $self->_update_count($index); + my $t = $self->_index_update_count->{$index}; + $lr /= (1 - $self->beta1**$t); + + $grad = $grad * $self->rescale_grad + $wd * $weight; + if($self->clip_gradient) + { + $grad = AI::MXNet::NDArray->clip( + $grad, + -$self->clip_gradient, + $self->clip_gradient + ); + } + + # update m_t and u_t + my($m_t, $u_t) = @{ $state }; + $m_t .= $self->beta1 * $m_t + (1 - $self->beta1) * $grad; + $u_t .= AI::MXNet::NDArray->maximum($self->beta2 * $u_t, $grad->abs); + + # update weight + $weight -= $lr * $m_t / $u_t; +} + +__PACKAGE__->register; + +package AI::MXNet::Nadam; + +=head1 NAME + + AI::MXNet::Nadam +=cut + +=head1 DESCRIPTION + + The Nesterov Adam optimizer. + + Much like Adam is essentially RMSprop with momentum, + Nadam is Adam RMSprop with Nesterov momentum available + at http://cs229.stanford.edu/proj2015/054_report.pdf. + + This optimizer accepts the following parameters in addition to those accepted + AI::MXNet::Optimizer. + + Parameters + ---------- + beta1 : float, optional + Exponential decay rate for the first moment estimates. + beta2 : float, optional + Exponential decay rate for the second moment estimates. + epsilon : float, optional + Small value to avoid division by 0. + schedule_decay : float, optional + Exponential decay rate for the momentum schedule +=cut + +use Mouse; +extends 'AI::MXNet::Optimizer'; +has '+learning_rate' => (default => 0.001); +has 'beta1' => (is => "ro", isa => "Num", default => 0.9); +has 'beta2' => (is => "ro", isa => "Num", default => 0.999); +has 'epsilon' => (is => "ro", isa => "Num", default => 1e-8); +has 'schedule_decay' => (is => "ro", isa => "Num", default => 0.004); +has 'm_schedule' => (is => "rw", default => 1, init_arg => undef); + +method create_state(Index $index, AI::MXNet::NDArray $weight) +{ + return [ + AI::MXNet::NDArray->zeros( + $weight->shape, + ctx => $weight->context, + dtype => $weight->dtype + ), # mean + AI::MXNet::NDArray->zeros( + $weight->shape, + ctx => $weight->context, + dtype => $weight->dtype + ) # variance + ]; +} + +method update( + Index $index, + AI::MXNet::NDArray $weight, + AI::MXNet::NDArray $grad, + ArrayRef[AI::MXNet::NDArray] $state +) +{ + my $wd = $self->_get_wd($index); + my $lr = $self->_get_lr($index); + $self->_update_count($index); + my $t = $self->_index_update_count->{$index}; + $grad = $grad * $self->rescale_grad + $wd * $weight; + if($self->clip_gradient) + { + $grad = AI::MXNet::NDArray->clip( + $grad, + -$self->clip_gradient, + $self->clip_gradient + ); + } + # warming momentum schedule + my $momentum_t = $self->beta1 * (1 - 0.5 * (0.96**($t * $self->schedule_decay))); + my $momentum_t_1 = $self->beta1 * (1 - 0.5 * (0.96**(($t + 1) * $self->schedule_decay))); + $self->m_schedule = $self->m_schedule * $momentum_t; + my $m_schedule_next = $self->m_schedule * $momentum_t_1; + + # update m_t and v_t + my ($m_t, $v_t) = @{ $state }; + $m_t .= $self->beta1 * $m_t + (1 - $self->beta1) * $grad; + $v_t .= $self->beta2 * $v_t + (1 - $self->beta2) * $grad * $grad; + + my $grad_prime = $grad / (1 - $self->m_schedule); + my $m_t_prime = $m_t / (1 - $m_schedule_next); + my $v_t_prime = $v_t / (1 - $self->beta2**$t); + my $m_t_bar = (1 - $momentum_t) * $grad_prime + $momentum_t_1 * $m_t_prime; + + # update weight + $weight -= $lr * $m_t_bar / (sqrt($v_t_prime) + $self->epsilon); +} + +__PACKAGE__->register; + # updater for kvstore package AI::MXNet::Updater; use Mouse; @@ -1088,22 +1318,44 @@ use Storable qw(thaw freeze); use overload "&{}" => sub { my $self = shift; sub { $self->call(@_) } }, fallback => 1; -has "optimizer" => (is => "rw", isa => "AI::MXNet::Optimizer"); -has "states" => (is => "rw", isa => "HashRef", default => sub { +{} }); +has "optimizer" => (is => "rw", isa => "AI::MXNet::Optimizer"); +has "states" => (is => "rw", isa => "HashRef", default => sub { +{} }); +has "states_synced" => (is => "rw", isa => "HashRef", default => sub { +{} }); method call(Index $index, AI::MXNet::NDArray $grad, AI::MXNet::NDArray $weight) { if(not exists $self->states->{ $index }) { $self->states->{ $index } = $self->optimizer->create_state($index, $weight); + $self->states_synced->{ $index } = 1; + } + elsif(not $self->states_synced->{ $index }) + { + $self->states->{ $index } = $self->sync_state_context($self->states->{ $index }, $weight->context); + $self->states_synced->{ $index } = 1; } $self->optimizer->update($index, $weight, $grad, $self->states->{ $index }); } *slice = *call; +method sync_state_context(Maybe[AI::MXNet::NDArray|ArrayRef[AI::MXNet::NDArray]] $state, AI::MXNet::Context $context) +{ + if(blessed $state) + { + return $state->as_in_context($context); + } + elsif(ref $state) + { + return [map { $self->sync_state_context($_, $context) } @{ $state }]; + } + return $state; +} + method set_states($states) { - $self->states(thaw($states)); + my $thawed_states = thaw($states); + $self->states($thawed_states); + %{ $self->states_synced } = map { $_ => 0 } keys %{ $thawed_states }; } method get_states() @@ -1113,10 +1365,9 @@ method get_states() package AI::MXNet::Optimizer; - method get_updater(AI::MXNet::Optimizer $optimizer) { return AI::MXNet::Updater->new(optimizer => $optimizer); } -1; \ No newline at end of file +1; diff --git a/perl-package/AI-MXNet/lib/AI/MXNet/Profiler.pm b/perl-package/AI-MXNet/lib/AI/MXNet/Profiler.pm index 6398fcb3e432..47d7a0ddf716 100644 --- a/perl-package/AI-MXNet/lib/AI/MXNet/Profiler.pm +++ b/perl-package/AI-MXNet/lib/AI/MXNet/Profiler.pm @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + package AI::MXNet::Profiler; use strict; use warnings; diff --git a/perl-package/AI-MXNet/lib/AI/MXNet/RNN.pm b/perl-package/AI-MXNet/lib/AI/MXNet/RNN.pm index cb3db9740868..07e72a755723 100644 --- a/perl-package/AI-MXNet/lib/AI/MXNet/RNN.pm +++ b/perl-package/AI-MXNet/lib/AI/MXNet/RNN.pm @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + package AI::MXNet::RNN; use strict; use warnings; @@ -149,6 +166,10 @@ method SequentialRNNCell(@args) { AI::MXNet::RNN::SequentialCell->new(@args) } method BidirectionalCell(@args) { AI::MXNet::RNN::BidirectionalCell->new(@args) } method DropoutCell(@args) { AI::MXNet::RNN::DropoutCell->new(@args) } method ZoneoutCell(@args) { AI::MXNet::RNN::ZoneoutCell->new(@args) } +method ConvRNNCell(@args) { AI::MXNet::RNN::ConvCell->new(@args) } +method ConvLSTMCell(@args) { AI::MXNet::RNN::ConvLSTMCell->new(@args) } +method ConvGRUCell(@args) { AI::MXNet::RNN::ConvGRUCell->new(@args) } +method ResidualCell(@args) { AI::MXNet::RNN::ResidualCell->new(@args) } method encode_sentences(@args) { AI::MXNet::RNN::IO->encode_sentences(@args) } method BucketSentenceIter(@args) { diff --git a/perl-package/AI-MXNet/lib/AI/MXNet/RNN/Cell.pm b/perl-package/AI-MXNet/lib/AI/MXNet/RNN/Cell.pm index cea1af7cb9cc..08c3094aa9c7 100644 --- a/perl-package/AI-MXNet/lib/AI/MXNet/RNN/Cell.pm +++ b/perl-package/AI-MXNet/lib/AI/MXNet/RNN/Cell.pm @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + package AI::MXNet::RNN::Params; use Mouse; use AI::MXNet::Function::Parameters; @@ -560,7 +577,7 @@ use Mouse; use AI::MXNet::Base; extends 'AI::MXNet::RNN::Cell'; -=head1 NAME +=head1 NAME AI::MXNet::RNN::LSTMCell =cut @@ -749,7 +766,7 @@ has '_dropout' => (is => 'ro', isa => 'Num', init_arg => 'dropout', has '_get_next_state' => (is => 'ro', isa => 'Bool', init_arg => 'get_next_state', default => 0); has '_bidirectional' => (is => 'ro', isa => 'Bool', init_arg => 'bidirectional', default => 0); has 'forget_bias' => (is => 'ro', isa => 'Num', default => 1); -has 'initializer' => (is => 'rw', isa => 'Maybe[AI::MXNet::Initializer]'); +has 'initializer' => (is => 'rw', isa => 'Maybe[Initializer]'); has '_mode' => ( is => 'ro', isa => enum([qw/rnn_relu rnn_tanh lstm gru/]), @@ -981,8 +998,8 @@ method unroll( name => $self->_prefix.'rnn', %states ); - my $outputs; + my %attr = (__layout__ => 'LNC'); if(not $self->_get_next_state) { ($outputs, $states) = ($rnn, []); @@ -990,11 +1007,14 @@ method unroll( elsif($self->_mode eq 'lstm') { my @rnn = @{ $rnn }; + $rnn[1]->_set_attr(%attr); + $rnn[2]->_set_attr(%attr); ($outputs, $states) = ($rnn[0], [$rnn[1], $rnn[2]]); } else { my @rnn = @{ $rnn }; + $rnn[1]->_set_attr(%attr); ($outputs, $states) = ($rnn[0], [$rnn[1]]); } if(defined $merge_outputs and not $merge_outputs) @@ -1257,6 +1277,18 @@ sub BUILD { my ($self, $original_arguments) = @_; $self->_override_cell_params(defined $original_arguments->{params}); + if($self->_override_cell_params) + { + assert( + ($self->l_cell->_own_params and $self->r_cell->_own_params), + "Either specify params for BidirectionalCell ". + "or child cells, not both." + ); + %{ $self->l_cell->params->_params } = (%{ $self->l_cell->params->_params }, %{ $self->params->_params }); + %{ $self->r_cell->params->_params } = (%{ $self->r_cell->params->_params }, %{ $self->params->_params }); + } + %{ $self->params->_params } = (%{ $self->params->_params }, %{ $self->l_cell->params->_params }); + %{ $self->params->_params } = (%{ $self->params->_params }, %{ $self->r_cell->params->_params }); $self->_cells([$self->l_cell, $self->r_cell]); } @@ -1397,6 +1429,309 @@ method unroll( return($outputs, $states); } +package AI::MXNet::RNN::ConvCell::Base; +use Mouse; +use AI::MXNet::Base; +extends 'AI::MXNet::RNN::Cell::Base'; + +=head1 NAME + + AI::MXNet::RNN::Conv::Base +=cut + +=head1 DESCRIPTION + + Abstract base class for Convolutional RNN cells + +=cut + +has '_h2h_kernel' => (is => 'ro', isa => 'Shape', init_arg => 'h2h_kernel'); +has '_h2h_dilate' => (is => 'ro', isa => 'Shape', init_arg => 'h2h_dilate'); +has '_h2h_pad' => (is => 'rw', isa => 'Shape', init_arg => undef); +has '_i2h_kernel' => (is => 'ro', isa => 'Shape', init_arg => 'i2h_kernel'); +has '_i2h_stride' => (is => 'ro', isa => 'Shape', init_arg => 'i2h_stride'); +has '_i2h_dilate' => (is => 'ro', isa => 'Shape', init_arg => 'i2h_dilate'); +has '_i2h_pad' => (is => 'ro', isa => 'Shape', init_arg => 'i2h_pad'); +has '_num_hidden' => (is => 'ro', isa => 'DimSize', init_arg => 'num_hidden'); +has '_input_shape' => (is => 'ro', isa => 'Shape', init_arg => 'input_shape'); +has '_conv_layout' => (is => 'ro', isa => 'Str', init_arg => 'conv_layout', default => 'NCHW'); +has '_activation' => (is => 'ro', init_arg => 'activation'); +has '_state_shape' => (is => 'rw', init_arg => undef); +has [qw/i2h_weight_initializer h2h_weight_initializer + i2h_bias_initializer h2h_bias_initializer/] => (is => 'rw', isa => 'Maybe[Initializer]'); + +sub BUILD +{ + my $self = shift; + assert ( + ($self->_h2h_kernel->[0] % 2 == 1 and $self->_h2h_kernel->[1] % 2 == 1), + "Only support odd numbers, got h2h_kernel= (@{[ $self->_h2h_kernel ]})" + ); + $self->_h2h_pad([ + int($self->_h2h_dilate->[0] * ($self->_h2h_kernel->[0] - 1) / 2), + int($self->_h2h_dilate->[1] * ($self->_h2h_kernel->[1] - 1) / 2) + ]); + # Infer state shape + my $data = AI::MXNet::Symbol->Variable('data'); + my $state_shape = AI::MXNet::Symbol->Convolution( + data => $data, + num_filter => $self->_num_hidden, + kernel => $self->_i2h_kernel, + stride => $self->_i2h_stride, + pad => $self->_i2h_pad, + dilate => $self->_i2h_dilate, + layout => $self->_conv_layout + ); + $state_shape = ($state_shape->infer_shape(data=>$self->_input_shape))[1]->[0]; + $state_shape->[0] = 0; + $self->_state_shape($state_shape); +} + +method state_info() +{ + return [ + { shape => $self->_state_shape, __layout__ => $self->_conv_layout }, + { shape => $self->_state_shape, __layout__ => $self->_conv_layout } + ]; +} + +method call($inputs, $states) +{ + confess("AI::MXNet::RNN::ConvCell::Base is abstract class for convolutional RNN"); +} + +package AI::MXNet::RNN::ConvCell; +use Mouse; +extends 'AI::MXNet::RNN::ConvCell::Base'; + +=head1 NAME + + AI::MXNet::RNN::ConvCell +=cut + +=head1 DESCRIPTION + + Convolutional RNN cells + + Parameters + ---------- + input_shape : array ref of int + Shape of input in single timestep. + num_hidden : int + Number of units in output symbol. + h2h_kernel : array ref of int, default (3, 3) + Kernel of Convolution operator in state-to-state transitions. + h2h_dilate : array ref of int, default (1, 1) + Dilation of Convolution operator in state-to-state transitions. + i2h_kernel : array ref of int, default (3, 3) + Kernel of Convolution operator in input-to-state transitions. + i2h_stride : array ref of int, default (1, 1) + Stride of Convolution operator in input-to-state transitions. + i2h_pad : array ref of int, default (1, 1) + Pad of Convolution operator in input-to-state transitions. + i2h_dilate : array ref of int, default (1, 1) + Dilation of Convolution operator in input-to-state transitions. + activation : str or Symbol, + default functools.partial(symbol.LeakyReLU, act_type='leaky', slope=0.2) + Type of activation function. + prefix : str, default 'ConvRNN_' + Prefix for name of layers (and name of weight if params is None). + params : RNNParams, default None + Container for weight sharing between cells. Created if None. + conv_layout : str, , default 'NCHW' + Layout of ConvolutionOp +=cut + +has '+_h2h_kernel' => (default => sub { [3, 3] }); +has '+_h2h_dilate' => (default => sub { [1, 1] }); +has '+_i2h_kernel' => (default => sub { [3, 3] }); +has '+_i2h_stride' => (default => sub { [1, 1] }); +has '+_i2h_dilate' => (default => sub { [1, 1] }); +has '+_i2h_pad' => (default => sub { [1, 1] }); +has '+_prefix' => (default => 'ConvRNN_'); +has '+_activation' => (default => sub { sub { AI::MXNet::Symbol->LeakyReLU(@_, act_type => 'leaky', slope => 0.2) } }); +has '+i2h_bias_initializer' => (default => 'zeros'); +has '+h2h_bias_initializer' => (default => 'zeros'); +has 'forget_bias' => (is => 'ro', isa => 'Num'); +has [qw/_iW _iB + _hW _hB/] => (is => 'rw', init_arg => undef); + + +sub BUILD +{ + my $self = shift; + $self->_iW($self->_params->get('i2h_weight', init => $self->i2h_weight_initializer)); + $self->_hW($self->_params->get('h2h_weight', init => $self->h2h_weight_initializer)); + $self->_iB( + $self->params->get( + 'i2h_bias', + (defined($self->forget_bias and not defined $self->i2h_bias_initializer) + ? (init => AI::MXNet::LSTMBias->new(forget_bias => $self->forget_bias)) + : (init => $self->i2h_bias_initializer) + ) + ) + ); + $self->_hB($self->_params->get('h2h_bias', init => $self->h2h_bias_initializer)); +} + +method _num_gates() +{ + scalar(@{ $self->_gate_names() }); +} + +method _gate_names() +{ + return [''] +} + +method _conv_forward($inputs, $states, $name) +{ + my $i2h = AI::MXNet::Symbol->Convolution( + name => "${name}i2h", + data => $inputs, + num_filter => $self->_num_hidden*$self->_num_gates(), + kernel => $self->_i2h_kernel, + stride => $self->_i2h_stride, + pad => $self->_i2h_pad, + dilate => $self->_i2h_dilate, + weight => $self->_iW, + bias => $self->_iB + ); + my $h2h = AI::MXNet::Symbol->Convolution( + name => "${name}h2h", + data => @{ $states }[0], + num_filter => $self->_num_hidden*$self->_num_gates(), + kernel => $self->_h2h_kernel, + stride => [1, 1], + pad => $self->_h2h_pad, + dilate => $self->_h2h_dilate, + weight => $self->_hW, + bias => $self->_hB + ); + return ($i2h, $h2h); +} + +method call(AI::MXNet::Symbol $inputs, AI::MXNet::Symbol|ArrayRef[AI::MXNet::Symbol] $states) +{ + $self->_counter($self->_counter + 1); + my $name = sprintf('%st%d_', $self->_prefix, $self->_counter); + my ($i2h, $h2h) = $self->_conv_forward($inputs, $states, $name); + my $output = $self->_get_activation($i2h + $h2h, $self->_activation, name => "${name}out"); + return ($output, [$output]); +} + +package AI::MXNet::RNN::ConvLSTMCell; +use Mouse; +extends 'AI::MXNet::RNN::ConvCell'; +has '+forget_bias' => (default => 1); +has '+_prefix' => (default => 'ConvLSTM_'); + +=head1 NAME + + AI::MXNet::RNN::ConvLSTMCell +=cut + +=head1 DESCRIPTION + + Convolutional LSTM network cell. + + Reference: + Xingjian et al. NIPS2015 +=cut + +method _gate_names() +{ + return ['_i', '_f', '_c', '_o']; +} + +method call(AI::MXNet::Symbol $inputs, AI::MXNet::Symbol|ArrayRef[AI::MXNet::Symbol] $states) +{ + $self->_counter($self->_counter + 1); + my $name = sprintf('%st%d_', $self->_prefix, $self->_counter); + my ($i2h, $h2h) = $self->_conv_forward($inputs, $states, $name); + my $gates = $i2h + $h2h; + my @slice_gates = @{ AI::MXNet::Symbol->SliceChannel( + $gates, + num_outputs => 4, + axis => index($self->_conv_layout, 'C'), + name => "${name}slice" + ) }; + my $in_gate = AI::MXNet::Symbol->Activation( + $slice_gates[0], + act_type => "sigmoid", + name => "${name}i" + ); + my $forget_gate = AI::MXNet::Symbol->Activation( + $slice_gates[1], + act_type => "sigmoid", + name => "${name}f" + ); + my $in_transform = $self->_get_activation( + $slice_gates[2], + $self->_activation, + name => "${name}c" + ); + my $out_gate = AI::MXNet::Symbol->Activation( + $slice_gates[3], + act_type => "sigmoid", + name => "${name}o" + ); + my $next_c = AI::MXNet::Symbol->_plus( + $forget_gate * @{$states}[1], + $in_gate * $in_transform, + name => "${name}state" + ); + my $next_h = AI::MXNet::Symbol->_mul( + $out_gate, $self->_get_activation($next_c, $self->_activation), + name => "${name}out" + ); + return ($next_h, [$next_h, $next_c]); +} + +package AI::MXNet::RNN::ConvGRUCell; +use Mouse; +extends 'AI::MXNet::RNN::ConvCell'; +has '+_prefix' => (default => 'ConvGRU_'); + +=head1 NAME + + AI::MXNet::RNN::ConvGRUCell +=cut + +=head1 DESCRIPTION + + Convolutional GRU network cell. +=cut + +method _gate_names() +{ + return ['_r', '_z', '_o']; +} + +method call(AI::MXNet::Symbol $inputs, AI::MXNet::Symbol|ArrayRef[AI::MXNet::Symbol] $states) +{ + $self->_counter($self->_counter + 1); + my $name = sprintf('%st%d_', $self->_prefix, $self->_counter); + my ($i2h, $h2h) = $self->_conv_forward($inputs, $states, $name); + my ($i2h_r, $i2h_z, $h2h_r, $h2h_z); + ($i2h_r, $i2h_z, $i2h) = @{ AI::MXNet::Symbol->SliceChannel($i2h, num_outputs => 3, name => "${name}_i2h_slice") }; + ($h2h_r, $h2h_z, $h2h) = @{ AI::MXNet::Symbol->SliceChannel($h2h, num_outputs => 3, name => "${name}_h2h_slice") }; + my $reset_gate = AI::MXNet::Symbol->Activation( + $i2h_r + $h2h_r, act_type => "sigmoid", + name => "${name}_r_act" + ); + my $update_gate = AI::MXNet::Symbol->Activation( + $i2h_z + $h2h_z, act_type => "sigmoid", + name => "${name}_z_act" + ); + my $next_h_tmp = $self->_get_activation($i2h + $reset_gate * $h2h, $self->_activation, name => "${name}_h_act"); + my $next_h = AI::MXNet::Symbol->_plus( + (1 - $update_gate) * $next_h_tmp, $update_gate * @{$states}[0], + name => "${name}out" + ); + return ($next_h, [$next_h]); +} + package AI::MXNet::RNN::ModifierCell; use Mouse; use AI::MXNet::Base; @@ -1519,7 +1854,7 @@ has 'prev_output' => (is => 'rw', init_arg => undef); =head1 DESCRIPTION - Apply Zoneout on base cell + Apply Zoneout on base cell. =cut sub BUILD @@ -1555,14 +1890,13 @@ method call(AI::MXNet::Symbol $inputs, SymbolOrArrayOfSymbols $states) my $mask = sub { my ($p, $like) = @_; AI::MXNet::Symbol->Dropout( - AI::MXNet::Symbol->_identity_with_attr_like_rhs( - AI::MXNet::Symbol->ones(shape => [0, 0]), + AI::MXNet::Symbol->ones_like( $like ), p => $p ); }; - my $prev_output = $self->prev_output || AI::MXNet::Symbol->zeros(shape => [0, 0]); + my $prev_output = $self->prev_output // AI::MXNet::Symbol->zeros(shape => [0, 0]); my $output = $p_outputs != 0 ? AI::MXNet::Symbol->where( &{$mask}($p_outputs, $next_output), @@ -1586,4 +1920,106 @@ method call(AI::MXNet::Symbol $inputs, SymbolOrArrayOfSymbols $states) return ($output, @states ? \@states : $next_states); } +package AI::MXNet::RNN::ResidualCell; +use Mouse; +use AI::MXNet::Base; +extends 'AI::MXNet::RNN::ModifierCell'; + +=head1 NAME + + AI::MXNet::RNN::ResidualCell +=cut + +=head1 DESCRIPTION + + Adds residual connection as described in Wu et al, 2016 + (https://arxiv.org/abs/1609.08144). + Output of the cell is output of the base cell plus input. +=cut + +method call(AI::MXNet::Symbol $inputs, SymbolOrArrayOfSymbols $states) +{ + my $output; + ($output, $states) = &{$self->base_cell}($inputs, $states); + $output = AI::MXNet::Symbol->elemwise_add($output, $inputs, name => $output->name.'_plus_residual'); + return ($output, $states) +} + +method unroll( + Int $length, + Maybe[AI::MXNet::Symbol|ArrayRef[AI::MXNet::Symbol]] :$inputs=, + Maybe[AI::MXNet::Symbol|ArrayRef[AI::MXNet::Symbol]] :$begin_state=, + Str :$input_prefix='', + Str :$layout='NTC', + Maybe[Bool] :$merge_outputs= +) +{ + $self->reset; + $self->base_cell->_modified(0); + my ($outputs, $states) = $self->base_cell->unroll($length, inputs=>$inputs, begin_state=>$begin_state, + layout=>$layout, merge_outputs=>$merge_outputs); + $self->base_cell->_modified(1); + $merge_outputs //= (blessed($outputs) and $outputs->isa('AI::MXNet::Symbol')); + ($inputs) = _normalize_sequence($length, $inputs, $layout, $merge_outputs); + if($merge_outputs) + { + $outputs = AI::MXNet::Symbol->elemwise_add($outputs, $inputs, name => $outputs->name . "_plus_residual"); + } + else + { + my @temp; + zip(sub { + my ($output_sym, $input_sym) = @_; + push @temp, AI::MXNet::Symbol->elemwise_add($output_sym, $input_sym, + name=>$output_sym->name."_plus_residual"); + }, [@{ $outputs }], [@{ $inputs }]); + $outputs = \@temp; + } + return ($outputs, $states); +} + +func _normalize_sequence($length, $inputs, $layout, $merge, $in_layout=) +{ + assert((defined $inputs), + "unroll(inputs=>undef) has been deprecated. ". + "Please create input variables outside unroll." + ); + + my $axis = index($layout, 'T'); + my $in_axis = defined $in_layout ? index($in_layout, 'T') : $axis; + if(blessed($inputs)) + { + if(not $merge) + { + assert( + (@{ $inputs->list_outputs() } == 1), + "unroll doesn't allow grouped symbol as input. Please " + ."convert to list first or let unroll handle splitting" + ); + $inputs = [ @{ AI::MXNet::Symbol->split( + $inputs, + axis => $in_axis, + num_outputs => $length, + squeeze_axis => 1 + ) }]; + } + } + else + { + assert(not defined $length or @$inputs == $length); + if($merge) + { + $inputs = [map { AI::MXNet::Symbol->expand_dims($_, axis=>$axis) } @{ $inputs }]; + $inputs = AI::MXNet::Symbol->Concat(@{ $inputs }, dim=>$axis); + $in_axis = $axis; + } + } + + if(blessed($inputs) and $axis != $in_axis) + { + $inputs = AI::MXNet::Symbol->swapaxes($inputs, dim0=>$axis, dim1=>$in_axis); + } + return ($inputs, $axis); +} + 1; diff --git a/perl-package/AI-MXNet/lib/AI/MXNet/RNN/IO.pm b/perl-package/AI-MXNet/lib/AI/MXNet/RNN/IO.pm index 065daded84cf..be3bdbd373cb 100644 --- a/perl-package/AI-MXNet/lib/AI/MXNet/RNN/IO.pm +++ b/perl-package/AI-MXNet/lib/AI/MXNet/RNN/IO.pm @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + package AI::MXNet::RNN::IO; use strict; use warnings; @@ -137,7 +154,7 @@ has 'invalid_label' => (is => 'ro', isa => 'Int', default => -1); has 'data_name' => (is => 'ro', isa => 'Str', default => 'data'); has 'label_name' => (is => 'ro', isa => 'Str', default => 'softmax_label'); has 'dtype' => (is => 'ro', isa => 'Dtype', default => 'float32'); -has 'layout' => (is => 'ro', isa => 'Str', default => 'NTC'); +has 'layout' => (is => 'ro', isa => 'Str', default => 'NT'); has 'buckets' => (is => 'rw', isa => 'Maybe[ArrayRef[Int]]'); has [qw/data nddata ndlabel major_axis default_bucket_key @@ -204,14 +221,16 @@ sub BUILD AI::MXNet::DataDesc->new( name => $self->data_name, shape => $shape, - dtype => $self->dtype + dtype => $self->dtype, + layout => $self->layout ) ]); $self->provide_label([ AI::MXNet::DataDesc->new( name => $self->label_name, shape => $shape, - dtype => $self->dtype + dtype => $self->dtype, + layout => $self->layout ) ]); $self->idx([]); @@ -272,17 +291,19 @@ method next() AI::MXNet::DataDesc->new( name => $self->data_name, shape => $data->shape, - dtype => $self->dtype + dtype => $self->dtype, + layout => $self->layout ) ], provide_label => [ AI::MXNet::DataDesc->new( name => $self->label_name, shape => $label->shape, - dtype => $self->dtype + dtype => $self->dtype, + layout => $self->layout ) ], ); } -1; \ No newline at end of file +1; diff --git a/perl-package/AI-MXNet/lib/AI/MXNet/Random.pm b/perl-package/AI-MXNet/lib/AI/MXNet/Random.pm index dd17523e3cbf..9ca013c62348 100644 --- a/perl-package/AI-MXNet/lib/AI/MXNet/Random.pm +++ b/perl-package/AI-MXNet/lib/AI/MXNet/Random.pm @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + package AI::MXNet::Random; use strict; use warnings; @@ -59,4 +76,4 @@ for my $method ( } } -1; \ No newline at end of file +1; diff --git a/perl-package/AI-MXNet/lib/AI/MXNet/RecordIO.pm b/perl-package/AI-MXNet/lib/AI/MXNet/RecordIO.pm index f0833bf77e5e..2027a901ec10 100644 --- a/perl-package/AI-MXNet/lib/AI/MXNet/RecordIO.pm +++ b/perl-package/AI-MXNet/lib/AI/MXNet/RecordIO.pm @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + package AI::MXNet::RecordIO; use strict; use warnings; @@ -24,7 +41,7 @@ use Mouse; has 'uri' => (is => 'ro', isa => 'Str', required => 1); has 'flag' => (is => 'ro', isa => enum([qw/r w/]), required => 1); has 'handle' => (is => 'rw', isa => 'RecordIOHandle'); -has [qw/writable +has [qw/writable is_open/] => (is => 'rw', isa => 'Bool'); sub BUILD @@ -336,4 +353,4 @@ method write_idx(Int $idx, Str $buf) push @{ $self->keys }, $idx; } -1; \ No newline at end of file +1; diff --git a/perl-package/AI-MXNet/lib/AI/MXNet/Rtc.pm b/perl-package/AI-MXNet/lib/AI/MXNet/Rtc.pm index 92edcaf2b8c0..09dc66200322 100644 --- a/perl-package/AI-MXNet/lib/AI/MXNet/Rtc.pm +++ b/perl-package/AI-MXNet/lib/AI/MXNet/Rtc.pm @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + package AI::MXNet::Rtc; use strict; use warnings; @@ -124,4 +141,4 @@ method push( ); } -1; \ No newline at end of file +1; diff --git a/perl-package/AI-MXNet/lib/AI/MXNet/Symbol.pm b/perl-package/AI-MXNet/lib/AI/MXNet/Symbol.pm index f4f5f0de3efb..eed6e93f568b 100644 --- a/perl-package/AI-MXNet/lib/AI/MXNet/Symbol.pm +++ b/perl-package/AI-MXNet/lib/AI/MXNet/Symbol.pm @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + package AI::MXNet::Symbol; =head1 NAME @@ -20,6 +37,7 @@ use overload '/' => \÷, '/=' => \&idivide, '**' => \&power, + '%' => \&mod, '==' => \&equal, '!=' => \¬_equal, '>' => \&greater, @@ -169,6 +187,16 @@ method true_divide(AI::MXNet::Symbol|Num $other, $reverse=) return $self->divide($other, $reverse); } +method mod(AI::MXNet::Symbol|Num $other, $reverse=) +{ + return _ufunc_helper( + $self, + $other, + qw/_Mod _ModScalar _RModScalar/, + $reverse + ); +} + method maximum(AI::MXNet::Symbol|Num $other) { return _ufunc_helper( @@ -329,7 +357,7 @@ method attr_dict() method _set_attr(Str @args) { - my %kwargs = @args; + my %kwargs = @args; while(my ($key, $val) = each(%kwargs)) { check_call( @@ -429,6 +457,25 @@ method list_auxiliary_states() } +=head2 list_inputs + + Lists all arguments and auxiliary states of this Symbol. + + Returns + ------- + inputs : array ref of str + List of all inputs. + + Examples + -------- + >>> my $bn = mx->sym->BatchNorm(name=>'bn'); +=cut + +method list_inputs() +{ + return scalar(check_call(AI::NNVMCAPI::SymbolListInputNames($self->handle, 0))); +} + =head2 infer_type Infer the type of outputs and arguments of given known types of arguments. @@ -462,7 +509,7 @@ method list_auxiliary_states() method infer_type(Str|Undef @args) { - my ($positional_arguments, $kwargs, $kwargs_order) = _parse_arguments("Dtype", @args); + my ($positional_arguments, $kwargs, $kwargs_order) = _parse_arguments("Dtype", @args); my $sdata = []; my $keys = []; if(@$positional_arguments) @@ -680,7 +727,7 @@ method _get_ndarray_inputs( my ($arg_handles, $arg_arrays) = ([], []); if(ref $args eq 'ARRAY') { - confess("Length of $arg_key do not match number of arguments") + confess("Length of $arg_key do not match number of arguments") unless @$args == @$arg_names; @{ $arg_handles } = map { $_->handle } @{ $args }; $arg_arrays = $args; @@ -732,6 +779,19 @@ method _get_ndarray_inputs( :$shapes : hash ref of str->Shape Input shape map, name->shape + :$shared_arg_names : Maybe[ArrayRef[Str]] + The argument names whose 'NDArray' of shared_exec can be reused for initializing + the current executor. + + :$shared_exec : Maybe[AI::MXNet::Executor] + The executor whose arg_arrays, arg_arrays, grad_arrays, and aux_arrays can be + reused for initializing the current executor. + + :$shared_buffer : Maybe[HashRef[AI::MXNet::NDArray]] + The dict mapping argument names to the `NDArray` that can be reused for initializing + the current executor. This buffer will be checked for reuse if one argument name + of the current executor is not found in `shared_arg_names`. + Returns ------- $executor : AI::MXNet::Executor @@ -739,115 +799,161 @@ method _get_ndarray_inputs( =cut method simple_bind( - AI::MXNet::Context :$ctx=AI::MXNet::Context->current_ctx, - Maybe[HashRef[Shape]] :$shapes=, - Str|HashRef[Str] :$grad_req='write', - Maybe[HashRef[Dtype]] :$type_dict=, - Maybe[HashRef[AI::MXNet::Context]] :$group2ctx= + AI::MXNet::Context :$ctx=AI::MXNet::Context->current_ctx, + GradReq|ArrayRef[GradReq]|HashRef[GradReq] :$grad_req='write', + Maybe[HashRef[Shape]] :$shapes=, + Maybe[HashRef[Dtype]] :$type_dict=, + Maybe[HashRef[AI::MXNet::Context]] :$group2ctx=, + Maybe[ArrayRef[Str]] :$shared_arg_names=, + Maybe[AI::MXNet::Executor] :$shared_exec=, + Maybe[HashRef[AI::MXNet::NDArray]] :$shared_buffer= ) { - $shapes //= {}; - if(not defined $type_dict) + my $num_provided_arg_types; + my @provided_arg_type_names; + my @provided_arg_type_data; + if(defined $type_dict) { - $type_dict = {}; - my $attrs = $self->attr_dict; - for my $k (@{ $self->list_arguments }) + while(my ($k, $v) = each %{ $type_dict }) { - if(not exists $attrs->{$k} or not exists $attrs->{$k}{__dtype__}) - { - $type_dict->{ $k } = 'float32'; - } + push @provided_arg_type_names, $k; + push @provided_arg_type_data, DTYPE_STR_TO_MX->{$v}; } + $num_provided_arg_types = @provided_arg_type_names; } - my @keys = keys %$shapes; - my @shape_input; - my @type_input; - for my $k (@keys) + my @provided_arg_shape_data; + # argument shape index in sdata, + # e.g. [sdata[indptr[0]], sdata[indptr[1]]) is the shape of the first arg + my @provided_arg_shape_idx = (0); + my @provided_arg_shape_names; + while(my ($k, $v) = each %{ $shapes//{} }) { - push @shape_input, ($k => $shapes->{$k}); - push @type_input, ($k => $type_dict->{$k}) + push @provided_arg_shape_names, $k; + push @provided_arg_shape_data, @{ $v }; + push @provided_arg_shape_idx, scalar(@provided_arg_shape_data); } - my ($arg_shapes, undef, $aux_shapes) = $self->infer_shape(@shape_input); - my ($arg_types, undef, $aux_types) = $self->infer_type(@type_input); - confess("Input node is not complete") - unless $arg_shapes and $arg_types; + $num_provided_arg_types = @provided_arg_type_names; - my ($arg_ctx, $aux_ctx) = ([], []); - if(defined $group2ctx) + my $provided_req_type_list_len = 0; + my @provided_grad_req_types; + my @provided_grad_req_names; + if(defined $grad_req) { - my $attr_dict = $self->attr_dict(); - for my $name (@{ $self->list_arguments() }) + if(not ref $grad_req) { - if( - exists $attr_dict->{ $name } - and - exists $attr_dict->{ $name }{ __ctx_group__ } - and - $group2ctx->{ $attr_dict->{ $name }{ __ctx_group__ } } - ) - { - push @{ $arg_ctx }, $group2ctx->{ $attr_dict->{ $name }{ __ctx_group__ } }; - } - else - { - push @{ $arg_ctx }, $ctx; - } + push @provided_grad_req_types, $grad_req; } - for my $name (@{ $self->list_auxiliary_states() }) + elsif(ref $grad_req eq 'ARRAY') { - if( - exists $attr_dict->{ $name } - and - exists $attr_dict->{ $name }{ __ctx_group__ } - and - $group2ctx->{ $attr_dict->{ $name }{ __ctx_group__ } } - ) - { - push @{ $aux_ctx }, $group2ctx->{ $attr_dict->{ $name }{ __ctx_group__ } }; - } - else + assert((@{ $grad_req } != 0), 'grad_req in simple_bind cannot be an empty list'); + @provided_grad_req_types = @{ $grad_req }; + $provided_req_type_list_len = @provided_grad_req_types; + } + elsif(ref $grad_req eq 'HASH') + { + assert((keys %{ $grad_req } != 0), 'grad_req in simple_bind cannot be an empty hash'); + while(my ($k, $v) = each %{ $grad_req }) { - push @{ $aux_ctx }, $ctx; + push @provided_grad_req_names, $k; + push @provided_grad_req_types, $v; } + $provided_req_type_list_len = @provided_grad_req_types; } } - else + my $num_ctx_map_keys = 0; + my @ctx_map_keys; + my @ctx_map_dev_types; + my @ctx_map_dev_ids; + if(defined $group2ctx) { - @{ $arg_ctx } = (($ctx) x @{ $arg_shapes }); - @{ $aux_ctx } = (($ctx) x @{ $aux_shapes }); + while(my ($k, $v) = each %{ $group2ctx }) + { + push @ctx_map_keys, $k; + push @ctx_map_dev_types, $v->device_type_id; + push @ctx_map_dev_ids, $v->device_id; + } + $num_ctx_map_keys = @ctx_map_keys; } - my @arg_ndarrays; - for (my $i = 0; $i < @{ $arg_types }; $i++) + + my @shared_arg_name_list; + if(defined $shared_arg_names) { - push @arg_ndarrays, AI::MXNet::NDArray->zeros( - $arg_shapes->[$i], ctx => $arg_ctx->[$i], dtype => $arg_types->[$i] - ); + @shared_arg_name_list = @{ $shared_arg_names }; } - my $grad_ndarrays; - if($grad_req ne 'null') + my %shared_data; + if(defined $shared_buffer) { - my $names = $self->list_arguments; - for (my $i = 0; $i < @{ $arg_types }; $i++) + while(my ($k, $v) = each %{ $shared_buffer }) { - if(not ref $grad_req eq 'HASH' or not ($grad_req->{ $names->[$i] }//'') eq 'null') - { - $grad_ndarrays->{ $names->[$i] } = AI::MXNet::NDArray->zeros( - $arg_shapes->[$i], ctx => $arg_ctx->[$i], dtype => $arg_types->[$i] - ); - } + $shared_data{$k} = $v->handle; } } - my @aux_ndarrays; - for (my $i = 0; $i < @{ $aux_types }; $i++) + my $shared_exec_handle = defined $shared_exec ? $shared_exec->handle : undef; + my ( + $updated_shared_data, + $in_arg_handles, + $arg_grad_handles, + $aux_state_handles, + $exe_handle + ); + eval { + ($updated_shared_data, $in_arg_handles, $arg_grad_handles, $aux_state_handles, $exe_handle) + = + check_call( + AI::MXNetCAPI::ExecutorSimpleBind( + $self->handle, + $ctx->device_type_id, + $ctx->device_id, + $num_ctx_map_keys, + \@ctx_map_keys, + \@ctx_map_dev_types, + \@ctx_map_dev_ids, + $provided_req_type_list_len, + \@provided_grad_req_names, + \@provided_grad_req_types, + scalar(@provided_arg_shape_names), + \@provided_arg_shape_names, + \@provided_arg_shape_data, + \@provided_arg_shape_idx, + $num_provided_arg_types, + \@provided_arg_type_names, + \@provided_arg_type_data, + scalar(@shared_arg_name_list), + \@shared_arg_name_list, + defined $shared_buffer ? \%shared_data : undef, + $shared_exec_handle + ) + ); + }; + if($@) { - push @aux_ndarrays, AI::MXNet::NDArray->zeros( - $aux_shapes->[$i], ctx => $aux_ctx->[$i], dtype => $aux_types->[$i] + confess( + "simple_bind failed: Error: $@; Arguments: ". + Data::Dumper->new( + [$shapes//{}] + )->Purity(1)->Deepcopy(1)->Terse(1)->Dump ); } - my $executor = $self->bind( - ctx => $ctx, args => \@arg_ndarrays, args_grad => $grad_ndarrays, - grad_req => $grad_req, aux_states => \@aux_ndarrays, group2ctx => $group2ctx + if(defined $shared_buffer) + { + while(my ($k, $v) = each %{ $updated_shared_data }) + { + $shared_buffer->{$k} = AI::MXNet::NDArray->new(handle => $v); + } + } + my @arg_arrays = map { AI::MXNet::NDArray->new(handle => $_) } @{ $in_arg_handles }; + my @grad_arrays = map { defined $_ ? AI::MXNet::NDArray->new(handle => $_) : undef } @{ $arg_grad_handles }; + my @aux_arrays = map { AI::MXNet::NDArray->new(handle => $_) } @{ $aux_state_handles }; + my $executor = AI::MXNet::Executor->new( + handle => $exe_handle, + symbol => $self, + ctx => $ctx, + grad_req => $grad_req, + group2ctx => $group2ctx ); + $executor->arg_arrays(\@arg_arrays); + $executor->grad_arrays(\@grad_arrays); + $executor->aux_arrays(\@aux_arrays); return $executor; } @@ -1126,7 +1232,7 @@ method Variable( Maybe[Num] :$lr_mult=, Maybe[Num] :$wd_mult=, Maybe[Dtype] :$dtype=, - Maybe[AI::MXNet::Initializer] :$init=, + Maybe[Initializer] :$init=, HashRef[Str] :$kwargs={}, Maybe[Str] :$__layout__= ) @@ -1288,6 +1394,7 @@ method arange(Index :$start=0, Index :$stop=, Num :$step=1.0, Index :$repeat=1, }); } + sub _parse_arguments { my $type = shift; diff --git a/perl-package/AI-MXNet/lib/AI/MXNet/Symbol/AttrScope.pm b/perl-package/AI-MXNet/lib/AI/MXNet/Symbol/AttrScope.pm index 017168439d7b..c728ed1b6ce8 100644 --- a/perl-package/AI-MXNet/lib/AI/MXNet/Symbol/AttrScope.pm +++ b/perl-package/AI-MXNet/lib/AI/MXNet/Symbol/AttrScope.pm @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + package AI::MXNet::Symbol::AttrScope; use strict; use warnings; diff --git a/perl-package/AI-MXNet/lib/AI/MXNet/Symbol/Base.pm b/perl-package/AI-MXNet/lib/AI/MXNet/Symbol/Base.pm index 95b634024135..4282f124a34b 100644 --- a/perl-package/AI-MXNet/lib/AI/MXNet/Symbol/Base.pm +++ b/perl-package/AI-MXNet/lib/AI/MXNet/Symbol/Base.pm @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + package AI::MXNet::Symbol::Base; use strict; use warnings; @@ -68,7 +85,7 @@ sub _compose # Create an atomic symbol function by handle and funciton name func _make_atomic_symbol_function($handle, $name) { - my ($real_name, $desc, $arg_names, + my ($real_name, $desc, $arg_names, $arg_types, $arg_descs, $key_var_num_args, $ret_type) = @{ check_call(AI::MXNetCAPI::SymbolGetAtomicSymbolInfo($handle)) }; $ret_type //= ''; @@ -76,7 +93,7 @@ func _make_atomic_symbol_function($handle, $name) my $doc_str = build_doc($func_name, $desc, $arg_names, - $arg_types, + $arg_types, $arg_descs, $key_var_num_args, $ret_type @@ -162,11 +179,12 @@ method _init_symbol_module() no strict 'refs'; { *{__PACKAGE__."::$name"} = $function; - } + } } } } + __PACKAGE__->_init_symbol_module; 1; diff --git a/perl-package/AI-MXNet/lib/AI/MXNet/Symbol/Doc.pm b/perl-package/AI-MXNet/lib/AI/MXNet/Symbol/Doc.pm index 2485f21fe3a0..1d9a2c1288ea 100644 --- a/perl-package/AI-MXNet/lib/AI/MXNet/Symbol/Doc.pm +++ b/perl-package/AI-MXNet/lib/AI/MXNet/Symbol/Doc.pm @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + package AI::MXNet::Symbol::Doc; use strict; use warnings; diff --git a/perl-package/AI-MXNet/lib/AI/MXNet/Symbol/NameManager.pm b/perl-package/AI-MXNet/lib/AI/MXNet/Symbol/NameManager.pm index 4791bc8b74a8..109949c79078 100644 --- a/perl-package/AI-MXNet/lib/AI/MXNet/Symbol/NameManager.pm +++ b/perl-package/AI-MXNet/lib/AI/MXNet/Symbol/NameManager.pm @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + package AI::MXNet::Symbol::NameManager; use strict; use warnings; @@ -44,7 +61,7 @@ our $current; A canonical name for the symbol. =cut -method get(Str|Undef $name, Str $hint) +method get(Maybe[Str] $name, Str $hint) { return $name if $name; if(not exists $self->counter->{ $hint }) @@ -84,7 +101,7 @@ has prefix => ( required => 1 ); -method get(Str $name, Str $hint) +method get(Maybe[Str] $name, Str $hint) { $name = $self->SUPER::get($name, $hint); return $self->prefix . $name; diff --git a/perl-package/AI-MXNet/lib/AI/MXNet/TestUtils.pm b/perl-package/AI-MXNet/lib/AI/MXNet/TestUtils.pm index d43d8eb09884..ea918c0cddf3 100644 --- a/perl-package/AI-MXNet/lib/AI/MXNet/TestUtils.pm +++ b/perl-package/AI-MXNet/lib/AI/MXNet/TestUtils.pm @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + package AI::MXNet::TestUtils; use strict; use warnings; @@ -9,7 +26,7 @@ use Exporter; use base qw(Exporter); @AI::MXNet::TestUtils::EXPORT_OK = qw(same reldiff almost_equal GetMNIST_ubyte GetCifar10 pdl_maximum pdl_minimum mlp2 conv - check_consistency zip assert enumerate); + check_consistency zip assert enumerate same_array dies_like); use constant default_numerical_threshold => 1e-6; =head1 NAME @@ -352,4 +369,51 @@ sub assert unless $input; } -1; \ No newline at end of file +=head2 same_array + + Check whether two NDArrays sharing the same memory block + + Parameters + ---------- + + array1 : NDArray + First NDArray to be checked + array2 : NDArray + Second NDArray to be checked + + Returns + ------- + bool + Whether two NDArrays share the same memory +=cut + +func same_array( + AI::MXNet::NDArray $array1, + AI::MXNet::NDArray $array2 +) +{ + $array1 += 1; + if(not same($array1->aspdl, $array2->aspdl)) + { + $array1 -= 1; + return 0 + } + $array1 -= 1; + return same($array1->aspdl, $array2->aspdl); +} + +func dies_like($code, $regexp) +{ + eval { $code->() }; + if($@ =~ $regexp) + { + return 1; + } + else + { + warn $@; + return 0; + } +} + +1; diff --git a/perl-package/AI-MXNet/lib/AI/MXNet/Types.pm b/perl-package/AI-MXNet/lib/AI/MXNet/Types.pm index 424591eb65a0..b4ec7e9018b3 100644 --- a/perl-package/AI-MXNet/lib/AI/MXNet/Types.pm +++ b/perl-package/AI-MXNet/lib/AI/MXNet/Types.pm @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + package AI::MXNet::Types; use strict; use warnings; @@ -17,6 +34,7 @@ class_type 'AI::MXNet::Callback'; class_type 'AI::MXNet::EvalMetric'; class_type 'AI::MXNet::DataParallelExecutorGroup'; class_type 'AI::MXNet::Optimizer'; +class_type 'AI::MXNet::Initializer'; class_type 'AI::MXNet::InitDesc'; class_type 'AI::MXNet::IRHeader'; subtype "AcceptableInput" => as "Num|PDL|PDL::Matrix|AI::MXNet::NDArray|AI::MXNet::NDArray::Slice|ArrayRef"; @@ -38,6 +56,7 @@ subtype "NameShape" => as "ArrayRef" => where { subtype "Callback" => as "CodeRef|ArrayRef[Coderef]|AI::MXNet::Callback|ArrayRef[AI::MXNet::Callback]"; subtype "EvalMetric" => as "AI::MXNet::EvalMetric|Str|CodeRef"; subtype "Optimizer" => as "AI::MXNet::Optimizer|Str"; -subtype "Activation" => as "AI::MXNet::Symbol|Str"; +subtype "Initializer" => as "AI::MXNet::Initializer|Str"; +subtype "Activation" => as "AI::MXNet::Symbol|Str|CodeRef"; subtype "SymbolOrArrayOfSymbols" => as "AI::MXNet::Symbol|ArrayRef[AI::MXNet::Symbol]"; subtype "NameShapeOrDataDesc" => as "NameShape|AI::MXNet::DataDesc"; diff --git a/perl-package/AI-MXNet/lib/AI/MXNet/Util/Printable.pm b/perl-package/AI-MXNet/lib/AI/MXNet/Util/Printable.pm index 4e8f8051ae10..1ae6c2d26c96 100644 --- a/perl-package/AI-MXNet/lib/AI/MXNet/Util/Printable.pm +++ b/perl-package/AI-MXNet/lib/AI/MXNet/Util/Printable.pm @@ -1,5 +1,22 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + package AI::MXNet::Util::Printable; use strict; use warnings; use Data::Dumper qw(); -use overload '""' => sub { print Data::Dumper->new([shift])->Purity(1)->Deepcopy(1)->Terse(1)->Dump }; \ No newline at end of file +use overload '""' => sub { print Data::Dumper->new([shift])->Purity(1)->Deepcopy(1)->Terse(1)->Dump }; diff --git a/perl-package/AI-MXNet/lib/AI/MXNet/Visualization.pm b/perl-package/AI-MXNet/lib/AI/MXNet/Visualization.pm index 9e90c69d9529..e28cd654722d 100644 --- a/perl-package/AI-MXNet/lib/AI/MXNet/Visualization.pm +++ b/perl-package/AI-MXNet/lib/AI/MXNet/Visualization.pm @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + package AI::MXNet::Visualization; use strict; use warnings; @@ -37,7 +54,7 @@ use JSON::PP; my $softmax = mx->symbol->SoftmaxOutput(data => $fc2, name => 'softmax'); ## creates the image file working directory - mx->viz->plot_network($softmax, save_format => 'png')->render("network.png"); + mx->viz->plot_network($softmax, save_format => 'png')->render("network.png"); =head1 DESCRIPTION @@ -291,7 +308,7 @@ method plot_network( my $label = $name; if($op eq 'null') { - if($name =~ /(?:_weight|_bias)$/) + if($name =~ /(?:_weight|_bias|_beta|_gamma|_moving_var|_moving_mean)$/) { if($hide_weights) { @@ -354,6 +371,7 @@ method plot_network( } $dot->graph->add_node($name, label => $label, %attr); }; + # add edges for my $node (@{ $nodes }) { @@ -378,6 +396,13 @@ method plot_network( { my $key = $input_name; $key .= '_output' if $input_node->{op} ne 'null'; + if($input_node->{op} ne 'null' and exists $input_node->{attr}) + { + if(ref $input_node->{attr} eq 'HASH' and exists $input_node->{attr}{num_outputs}) + { + $key .= ($input_node->{attr}{num_outputs} - 1); + } + } my $end = @{ $shape_dict{$key} }; $attr{label} = join('x', @{ $shape_dict{$key} }[1..$end-1]); } @@ -408,4 +433,4 @@ method render($output=) return $self->graph->$method($output); } -1; \ No newline at end of file +1; diff --git a/perl-package/AI-MXNet/t/test_autograd.t b/perl-package/AI-MXNet/t/test_autograd.t deleted file mode 100644 index 60cfd3b8bf98..000000000000 --- a/perl-package/AI-MXNet/t/test_autograd.t +++ /dev/null @@ -1,96 +0,0 @@ -use strict; -use warnings; -use AI::MXNet qw(mx); -use AI::MXNet::TestUtils qw(same zip); -use Test::More tests => 31; - -sub autograd_assert -{ - my ($args, $kwargs) = @_; - my $func = $kwargs->{func}; - my $grad_f = $kwargs->{grad_func}; - my $argnum = $kwargs->{argnum}; - - my $grad_func = mx->contrib->autograd->grad_and_loss($func, $argnum); - my ($grad_vals, $output) = $grad_func->(@$args); - my $res = $func->(@$args); - ok(same($output->aspdl, $res->aspdl)); - my $grad_res = &{$grad_f}(@$args); - is(scalar(@$grad_vals), scalar(@$grad_res)); - zip(sub { - ok(same($_[0]->aspdl, $_[1]->aspdl)); - }, $grad_vals, $grad_res); -} - -sub test_unary_func -{ - my $x = mx->nd->uniform({ shape=>[4, 5] }); - my $f_exp = sub { $_[0]->exp }; - my $f_exp_grad = sub { [$_[0]->exp] }; - autograd_assert([$x], { func=>$f_exp, grad_func=>$f_exp_grad }); - my $f_half = sub { $_[0]/2 }; - my $f_half_grad = sub { [mx->nd->ones($_[0]->shape) * 0.5] }; - autograd_assert([$x], { func=>$f_half, grad_func=>$f_half_grad }); - my $f_square = sub { $_[0]**2 }; - my $f_square_grad = sub { [2*$_[0]] }; - autograd_assert([$x],{ func=>$f_square, grad_func=>$f_square_grad }); -} - -test_unary_func(); - -sub test_binary_func -{ - my $x = mx->nd->uniform({ shape=>[4, 5] }); - my $y = mx->nd->uniform({ shape=>[4, 5] }); - my $f_add = sub { $_[0] + $_[1] }; - my $f_add_grad = sub { [mx->nd->ones($_[0]->shape), mx->nd->ones($_[1]->shape)] }; - autograd_assert([$x, $y], { func=>$f_add, grad_func=>$f_add_grad }); - my $f_mul = sub { $_[0] * $_[1] }; - my $f_mul_grad = sub { [$_[1], $_[0]] }; - autograd_assert([$x, $y], { func=>$f_mul, grad_func=>$f_mul_grad }); - my $f_compose = sub { $_[0] + $_[0]*$_[1] }; - my $f_compose_grad = sub { [mx->nd->ones($_[0]->shape) + $_[1], $_[0]] }; - autograd_assert([$x, $y], { func=>$f_compose, grad_func=>$f_compose_grad }); -} - -test_binary_func(); - -sub test_argnum -{ - - my $f_with_mode = sub { my ($a, $b, $mode) = @_; - if($mode) - { - return $a+$b; - } - else - { - return $a*$b - } - }; - - my $a = mx->nd->uniform({ shape=>[3, 2] }); - my $b = mx->nd->uniform({ shape=>[3, 2] }); - my $f_add_grad = sub { [mx->nd->ones($_[0]->shape), mx->nd->ones($_[1]->shape)] }; - my $f_mul_grad = sub { [$_[1], $_[0]] }; - autograd_assert([$a, $b, 1], - { argnum=>[0, 1], func=>$f_with_mode, grad_func=>$f_add_grad }); - autograd_assert([$a, $b, 0], - { argnum=>[0, 1], func=>$f_with_mode, grad_func=>$f_mul_grad }); -} - -test_argnum(); - -sub test_training -{ - my $x = mx->nd->ones([10, 10]); - mx->contrib->autograd->set_is_training(1); - my $y = mx->nd->Dropout($x, { p=>0.5 }); - ok(not ($y->aspdl== $x->aspdl)->all); - mx->contrib->autograd->set_is_training(0); - $y = mx->nd->Dropout($x, { p=>0.5 }); - ok(($y->aspdl== $x->aspdl)->all); -} - -test_training(); - diff --git a/perl-package/AI-MXNet/t/test_executor.t b/perl-package/AI-MXNet/t/test_executor.t index d6439b61aee6..026f1f13454a 100644 --- a/perl-package/AI-MXNet/t/test_executor.t +++ b/perl-package/AI-MXNet/t/test_executor.t @@ -151,12 +151,10 @@ sub test_reshape { my $x = mx->sym->Variable('x'); my $y = mx->sym->FullyConnected($x, num_hidden=>4); - - my $exe = $y->simple_bind(ctx => mx->cpu(), shapes => { x=>[5,4] }); + my $exe = $y->simple_bind(ctx => mx->cpu(), shapes => { x=>[5,4] }, grad_req=>'null'); $exe->arg_arrays->[0] .= 1; $exe->arg_arrays->[1] .= mx->nd->ones([4,4]); $exe->arg_arrays->[2] .= 0; - my $new_exe = $exe->reshape({ x=>[3,4] }); $new_exe->forward(0); # test sub exec forward diff --git a/perl-package/AI-MXNet/t/test_model_parallel.t b/perl-package/AI-MXNet/t/test_model_parallel.t index e20b208029b5..6a8aba7aab06 100644 --- a/perl-package/AI-MXNet/t/test_model_parallel.t +++ b/perl-package/AI-MXNet/t/test_model_parallel.t @@ -1,47 +1,59 @@ use strict; use warnings; -use Test::More tests => 3; +use Test::More tests => 4; use AI::MXNet qw(mx); use AI::MXNet::TestUtils qw(reldiff); use AI::MXNet::Base; sub test_chain { + my $ctx1 = mx->cpu(0); + my $ctx2 = mx->cpu(1); my $n = 2; my $data1 = mx->sym->Variable('data1'); my $data2 = mx->sym->Variable('data2'); + my $data3 = mx->sym->Variable('data2'); my $net; { local($mx::AttrScope) = mx->AttrScope(ctx_group=>'dev1'); $net = $data1 + $data2; $net = $net * 3; } - { local($mx::AttrScope) = mx->AttrScope(ctx_group=>'dev2'); - $net = $net + $data1; + $net = $net + $data3; } - my $arr; - my $arr_grad; + + my $arr = []; + my $arr_grad = []; my $shape = [4, 5]; { - local($mx::Context) = mx->Context(mx->cpu(0)); - $arr = [map { mx->nd->empty($shape) } 0..$n-1]; - $arr_grad = [map { mx->nd->empty($shape) } 0..$n-1]; + local($mx::Context) = $ctx1; + for (0..$n-1) + { + push @$arr, mx->nd->empty($shape); + push @$arr_grad, mx->nd->empty($shape); + } + } + { + local($mx::Context) = $ctx2; + push @$arr, mx->nd->empty($shape); + push @$arr_grad, mx->nd->empty($shape); } my $exec1 = $net->bind( - ctx => mx->cpu(), + ctx => $ctx1, args => $arr, args_grad => $arr_grad, - group2ctx => { dev1 => mx->cpu(0), dev2 => mx->cpu(1) } + group2ctx => { dev1 => $ctx1, dev2 => $ctx2 } ); $arr->[0] .= 1; $arr->[1] .= 2; - my $arr2 = [map { $_->copyto(mx->cpu()) } @$arr]; - my $arr_grad2 = [map { $_->copyto(mx->cpu()) } @$arr_grad]; + $arr->[2] .= 3; + my $arr2 = [map { $_->copyto($ctx1) } @$arr]; + my $arr_grad2 = [map { $_->copyto($ctx1) } @$arr_grad]; my $exec2 = $net->bind( - ctx => mx->cpu(), + ctx => $ctx1, args => $arr2, args_grad => $arr_grad2 ); @@ -49,10 +61,10 @@ sub test_chain $exec1->forward(1); $exec2->forward(1); ok(reldiff($exec1->outputs->[0]->aspdl, $exec2->outputs->[0]->aspdl) < 1e-6); - my $out_grad = mx->nd->empty($shape, ctx => mx->cpu(1)); + my $out_grad = mx->nd->empty($shape, ctx => $ctx1); $out_grad .= 1; $exec1->backward([$out_grad]); - $exec2->backward([$out_grad->copyto(mx->cpu())]); + $exec2->backward([$out_grad->copyto($ctx1)]); zip(sub { my ($a, $b) = @_; ok(reldiff($a->aspdl, $b->aspdl) < 1e-6); diff --git a/perl-package/AI-MXNet/t/test_module.t b/perl-package/AI-MXNet/t/test_module.t index 89228c949aa8..4d19a8e7d5df 100644 --- a/perl-package/AI-MXNet/t/test_module.t +++ b/perl-package/AI-MXNet/t/test_module.t @@ -1,9 +1,10 @@ use strict; use warnings; -use Test::More tests => 23; +use Test::More tests => 257; use AI::MXNet qw(mx); use AI::MXNet::Base; -use AI::MXNet::TestUtils qw(almost_equal enumerate); +use AI::MXNet::TestUtils qw(almost_equal enumerate same_array dies_like); +use Data::Dumper; sub test_module_layout { @@ -332,6 +333,283 @@ sub test_module_input_grads ok(($c_grad == 3)->all); } +sub test_executor_group +{ + my $get_rnn_sym = sub { my ($num_layers, $num_words, $num_hidden, $num_embed, $seq_len) = @_; + my $stack = mx->rnn->SequentialRNNCell(); + for my $i (0..$num_layers-1) + { + $stack->add(mx->rnn->LSTMCell(num_hidden=>$num_hidden, prefix=>"lstm_l${i}_")); + } + my $data = mx->sym->Variable('data'); + my $label = mx->sym->Variable('softmax_label'); + my $embed = mx->sym->Embedding(data=>$data, input_dim=>$num_words, + output_dim=>$num_embed, name=>'embed'); + + $stack->reset(); + my ($outputs, $states) = $stack->unroll($seq_len, inputs=>$embed, merge_outputs=>1); + + my $pred = mx->sym->Reshape($outputs, shape=>[-1, $num_hidden]); + $pred = mx->sym->FullyConnected(data=>$pred, num_hidden=>$num_words, name=>'pred'); + + $label = mx->sym->Reshape($label, shape=>[-1]); + $pred = mx->sym->SoftmaxOutput(data=>$pred, label=>$label, name=>'softmax'); + return $pred; + }; + + my $test_shared_exec_group = sub { my ($exec_grp_shared, $exec_grp_created, $shared_arg_names, $extra_args) = @_; + # Test shared data arrays + for my $i (0..@{ $exec_grp_shared->execs }-1) + { + # test same shared_data_arrays for two exec groups + my $shared_data_array1 = $exec_grp_shared->shared_data_arrays->[$i]; + my $shared_data_array2 = $exec_grp_created->shared_data_arrays->[$i]; + if(defined $extra_args) + { + ok(keys(%$shared_data_array1) == @$extra_args); + } + ok(keys(%$shared_data_array1) == keys(%$shared_data_array2)); + while(my ($k, $v) = each %{ $shared_data_array1 }) + { + if(defined $extra_args) + { + ok(grep { $_ eq $k } @$extra_args); + } + ok(exists $shared_data_array2->{$k}); + ok(same_array($v, $shared_data_array2->{$k})); + } + # Test shared argument arrays and gradient arrays + my $exec_shared = $exec_grp_shared->execs->[$i]; + my $exec_created = $exec_grp_created->execs->[$i]; + if(defined $shared_arg_names) + { + # test shared arguments + for my $arg_name (@$shared_arg_names) + { + ok(exists $exec_created->arg_dict->{$arg_name}); + ok(same_array($exec_shared->arg_dict->{$arg_name}, $exec_created->arg_dict->{$arg_name})); + } + # test shared argument gradients + for my $arg_name (@$shared_arg_names) + { + ok(exists $exec_created->grad_dict->{$arg_name}); + ok(same_array($exec_shared->grad_dict->{$arg_name}, $exec_created->grad_dict->{$arg_name})); + } + } + my $grad_req = $exec_grp_shared->grad_req; + while(my ($arg_name, $grad) = each %{ $grad_req }) + { + ok($grad eq $exec_grp_created->grad_req->{$arg_name}); + } + } + }; + my $contexts = [mx->cpu(0), mx->cpu(1)]; + my $workload = [(1) x scalar(@$contexts)]; + my $batch_size = 32; + my $max_bucket_size = 80; + my $num_words = 1000; + my $num_hidden = 100; + my $num_embed = 200; + my $data_shapes = [['data', [$batch_size, $max_bucket_size]]]; + my $label_shapes = [['softmax_label', [$batch_size, $max_bucket_size]]]; + + # generate an rnn sym with #layers=5 + my $sym = $get_rnn_sym->(3, $num_words, $num_hidden, + $num_embed, $max_bucket_size); + my $arg_names1 = $sym->list_arguments(); + my $input_names = ['data', 'softmax_label']; + my $shared_arg_names = [grep { !/^(?:data|softmax_label)$/ } @$arg_names1]; + my $exec_group1 = AI::MXNet::DataParallelExecutorGroup->new( + symbol=>$sym, contexts=>$contexts, + workload=>$workload, data_shapes=>$data_shapes, + label_shapes=>$label_shapes, param_names=>$shared_arg_names, + for_training=>1, inputs_need_grad=>0 + ); + # shared_data_arrays should only have input "data" and "softmax_label" arrays + for my $i (0..@{$contexts}-1) + { + ok(keys(%{$exec_group1->shared_data_arrays->[$i]}) == @$input_names); + for my $name (@$input_names) + { + ok(exists $exec_group1->shared_data_arrays->[$i]->{$name}); + } + } + # generate an rnn sym with #layers=5 + $sym = $get_rnn_sym->(5, $num_words, $num_hidden, + $num_embed, $max_bucket_size); + my $arg_names2 = $sym->list_arguments(); + my $exec_group2 = AI::MXNet::DataParallelExecutorGroup->new(symbol=>$sym, contexts=>$contexts, + workload=>$workload, data_shapes=>$data_shapes, + label_shapes=>$label_shapes, param_names=>$shared_arg_names, + for_training=>1, inputs_need_grad=>0, + shared_group=>$exec_group1); + my %shared_arg_names = map { $_ => 1 } @$shared_arg_names; + my $extra_args = [grep { not exists $shared_arg_names{$_} } @$arg_names2]; + $test_shared_exec_group->( + $exec_group1, $exec_group2, + $shared_arg_names, $extra_args + ); +} + +sub test_module_set_params +{ + # data iter + mx->random->seed(11); + my $data = mx->nd->array([[0.05, .10]]); + my $label = mx->nd->array([[.01, 0.99]]); + my $train_data = mx->io->NDArrayIter(data => $data, label => $label, batch_size => 1); + + # symbols + my $x = mx->symbol->Variable('data'); + $x = mx->symbol->FullyConnected(name=>'fc_0', data=>$x, num_hidden=>2); + $x = mx->symbol->Activation(name=>"act_0", data=>$x, act_type=>'sigmoid'); + $x = mx->symbol->FullyConnected(name=>'fc_1', data=>$x, num_hidden=>2); + $x = mx->symbol->Activation(name=>"act_1", data=>$x, act_type=>'sigmoid'); + $x = mx->symbol->LinearRegressionOutput(data=>$x, name=>'softmax', grad_scale=>2); + + # create module + my $mod = mx->mod->Module($x, context=>[mx->cpu()]); + $mod->bind(data_shapes => $train_data->provide_data, label_shapes=>$train_data->provide_label, + for_training=>1); + + my $arg_params_correct = {fc_0_weight => mx->nd->array([[.15, .20], [.25, .30]]), + fc_0_bias => mx->nd->array([.35, .35]), + fc_1_weight => mx->nd->array([[.40, .45], [.50, .55]]), + fc_1_bias => mx->nd->array([.60, .60])}; + + my $arg_params_missing = {fc_0_weight => mx->nd->array([[.15, .20], [.25, .30]]), + fc_0_bias => mx->nd->array([.35, .35]), + fc_1_weight => mx->nd->array([[.40, .45], [.50, .55]])}; + + my $arg_params_extra = {fc_0_weight => mx->nd->array([[.15, .20], [.25, .30]]), + fc_0_bias => mx->nd->array([.35, .35]), + fc_1_weight=> mx->nd->array([[.40, .45], [.50, .55]]), + fc_1_bias => mx->nd->array([.60, .60]), + fc_2_weight => mx->nd->array([.60, .60])}; + + my $arg_params_missing_extra = {fc_3_weight => mx->nd->array([.60, .60])}; + + # test regular set_params + $mod->set_params($arg_params_correct, {}, force_init=>1); + + # test allow missing + $mod->set_params($arg_params_missing, {}, allow_missing=>1, force_init=>1); + ok(dies_like(sub { $mod->set_params($arg_params_missing, {}, force_init=>1, allow_missing=>0); }, qr/fc_/)); + + # test allow extra + $mod->set_params($arg_params_extra, {}, force_init=>1, allow_missing=>1, allow_extra=>1); + ok(dies_like(sub { $mod->set_params($arg_params_extra, {}, force_init=>1, allow_missing=>1, allow_extra=>0); }, qr/fc_/)); + + # test allow missing + extra, this will throw a runtime error + ok(dies_like(sub { $mod->set_params($arg_params_missing_extra, {}, force_init=>1, allow_missing=>1, allow_extra=>0); }, qr/fc_/)); +} + +sub test_forward_reshape +{ + my $num_class = 10; + my $data1 = mx->sym->Variable('data1'); + my $data2 = mx->sym->Variable('data2'); + my $conv1 = mx->sym->Convolution(data=>$data1, kernel=>[2, 2], num_filter=>2, stride=>[2, 2]); + my $conv2 = mx->sym->Convolution(data=>$data2, kernel=>[3, 3], num_filter=>3, stride=>[1, 1]); + my $pooling1 = mx->sym->Pooling(data=>$conv1, kernel=>[2, 2], stride=>[1, 1], pool_type=>"avg"); + my $pooling2 = mx->sym->Pooling(data=>$conv2, kernel=>[2, 2], stride=>[1, 1], pool_type=>"max"); + my $flatten1 = mx->sym->flatten(data=>$pooling1); + my $flatten2 = mx->sym->flatten(data=>$pooling2); + my $sum = mx->sym->sum(data=>$flatten1, axis=>1) + mx->sym->sum(data=>$flatten2, axis=>1); + my $fc = mx->sym->FullyConnected(data=>$sum, num_hidden=>$num_class); + my $sym = mx->sym->SoftmaxOutput(data=>$fc, name=>'softmax'); + + my $dshape1 = [10, 3, 64, 64]; + my $dshape2 = [10, 3, 32, 32]; + my $lshape = [10]; + + my $mod = mx->mod->Module(symbol=>$sym, data_names=>['data1', 'data2'], + label_names=>['softmax_label']); + $mod->bind(data_shapes=>[['data1', $dshape1], ['data2', $dshape2]], + label_shapes=>[['softmax_label', $lshape]]); + $mod->init_params(); + $mod->init_optimizer(optimizer_params=>{learning_rate => 0.01}); + + # Train with original data shapes + my $data_batch = mx->io->DataBatch(data=>[mx->nd->random_uniform(0, 9, $dshape1), + mx->nd->random_uniform(5, 15, $dshape2)], + label=>[mx->nd->ones($lshape)]); + $mod->forward($data_batch); + is_deeply($mod->get_outputs->[0]->shape, [$lshape->[0], $num_class]); + $mod->backward(); + $mod->update(); + + # Train with different batch size + $dshape1 = [3, 3, 64, 64]; + $dshape2 = [3, 3, 32, 32]; + $lshape = [3]; + $data_batch = mx->io->DataBatch(data=>[mx->nd->random_uniform(0, 9, $dshape1), + mx->nd->random_uniform(5, 15, $dshape2)], + label=>[mx->nd->ones($lshape)]); + $mod->forward($data_batch); + is_deeply($mod->get_outputs->[0]->shape, [$lshape->[0], $num_class]); + $mod->backward(); + $mod->update(); + + $dshape1 = [20, 3, 64, 64]; + $dshape2 = [20, 3, 32, 32]; + $lshape = [20]; + $data_batch = mx->io->DataBatch(data=>[mx->nd->random_uniform(3, 5, $dshape1), + mx->nd->random_uniform(10, 25, $dshape2)], + label=>[mx->nd->ones($lshape)]); + $mod->forward($data_batch); + is_deeply($mod->get_outputs->[0]->shape, [$lshape->[0], $num_class]); + $mod->backward(); + $mod->update(); + + #Train with both different batch size and data shapes + $dshape1 = [20, 3, 120, 120]; + $dshape2 = [20, 3, 32, 64]; + $lshape = [20]; + $data_batch = mx->io->DataBatch(data=>[mx->nd->random_uniform(0, 9, $dshape1), + mx->nd->random_uniform(5, 15, $dshape2)], + label=>[mx->nd->ones($lshape)]); + $mod->forward($data_batch); + is_deeply($mod->get_outputs->[0]->shape, [$lshape->[0], $num_class]); + $mod->backward(); + $mod->update(); + + $dshape1 = [5, 3, 28, 40]; + $dshape2 = [5, 3, 24, 16]; + $lshape = [5]; + $data_batch = mx->io->DataBatch(data=>[mx->nd->random_uniform(0, 9, $dshape1), + mx->nd->random_uniform(15, 25, $dshape2)], + label=>[mx->nd->ones($lshape)]); + $mod->forward($data_batch); + is_deeply($mod->get_outputs->[0]->shape, [$lshape->[0], $num_class]); + $mod->backward(); + $mod->update(); + + #Test score + my $dataset_shape1 = [30, 3, 30, 30]; + my $dataset_shape2 = [30, 3, 20, 40]; + my $labelset_shape = [30]; + + my $eval_dataiter = mx->io->NDArrayIter(data=>[mx->nd->random_uniform(0, 9, $dataset_shape1), + mx->nd->random_uniform(15, 25, $dataset_shape2)], + label=>[mx->nd->ones($labelset_shape)], + batch_size=>5); + ok(keys %{ $mod->score($eval_dataiter, 'acc') } == 1); + + #Test prediction + $dshape1 = [1, 3, 30, 30]; + $dshape2 = [1, 3, 20, 40]; + $dataset_shape1 = [10, 3, 30, 30]; + $dataset_shape2 = [10, 3, 20, 40]; + + my $pred_dataiter = mx->io->NDArrayIter(data=>[mx->nd->random_uniform(0, 9, $dataset_shape1), + mx->nd->random_uniform(15, 25, $dataset_shape2)]); + $mod->bind(data_shapes=>[['data1', $dshape1], ['data2', $dshape2]], + for_training=>0, force_rebind=>1); + is_deeply($mod->predict($pred_dataiter)->shape, [10, $num_class]); + +} + test_module_input_grads(); test_module_dtype(); test_monitor(); @@ -340,3 +618,6 @@ test_module_layout(); test_module_states(); test_module_reshape(); test_save_load(); +test_executor_group(); +test_module_set_params(); +test_forward_reshape(); \ No newline at end of file diff --git a/perl-package/AI-MXNet/t/test_ndarray.t b/perl-package/AI-MXNet/t/test_ndarray.t index 55350b70125a..4faf464d3b56 100644 --- a/perl-package/AI-MXNet/t/test_ndarray.t +++ b/perl-package/AI-MXNet/t/test_ndarray.t @@ -1,5 +1,8 @@ +use strict; +use warnings; use AI::MXNet qw(mx); -use Test::More tests => 5; +use AI::MXNet::TestUtils qw(almost_equal); +use Test::More tests => 10; sub test_ndarray_reshape { @@ -33,5 +36,38 @@ sub test_moveaxis is_deeply($X->moveaxis(2, 0)->shape, [3, 2, 2]); } + +sub test_output +{ + my $shape = [2,2]; + my $ones = mx->nd->ones($shape); + my $zeros = mx->nd->zeros($shape); + my $out = mx->nd->zeros($shape); + mx->nd->ones($shape, out=>$out); + ok(almost_equal($out->aspdl, $ones->aspdl)); + mx->nd->zeros($shape, out=>$out); + ok(almost_equal($out->aspdl, $zeros->aspdl)); + mx->nd->full($shape, 2, out=>$out); + ok(almost_equal($out->aspdl, $ones->aspdl * 2)); +} + +sub test_cached +{ + my $sym = mx->sym->Convolution(kernel=>[3, 3], num_filter=>10) + 2; + my $op = mx->nd->CachedOp($sym); + my $data = mx->nd->ones([3, 4, 10, 10]); + my $weight = mx->nd->ones([10, 4, 3, 3]); + my $bias = mx->nd->ones([10]); + my $o1 = &{$op}($data, $weight, $bias); + $bias .= 2; + my $o2 = &{$op}($data, $weight, $bias); + ok(almost_equal($o2->aspdl, $o1->aspdl+1)); + $o2 .= 0; + &{$op}($data, $weight, $bias, out=>$o2); + ok(almost_equal($o2->aspdl, $o1->aspdl+1)); +} + test_ndarray_reshape(); -test_moveaxis(); \ No newline at end of file +test_moveaxis(); +test_output(); +test_cached(); diff --git a/perl-package/AI-MXNet/t/test_optimizers.t b/perl-package/AI-MXNet/t/test_optimizers.t index a92a78846ed6..52ff3072d9eb 100644 --- a/perl-package/AI-MXNet/t/test_optimizers.t +++ b/perl-package/AI-MXNet/t/test_optimizers.t @@ -192,12 +192,31 @@ use Mouse; extends 'AI::MXNet::Optimizer'; has '+learning_rate' => (default => 0.01); has 'momentum' => (is => "ro", isa => "Num", default => 0); +has 'multi_precision' => (is => 'ro', isa => 'Bool', default => 0); # Create additional optimizer state: momentum method create_state(Index $index, AI::MXNet::NDArray $weight) { - return undef if $self->momentum == 0; - return mx->nd->zeros($weight->shape, ctx => $weight->context, dtype => $weight->dtype); + my $momentum; + my $weight_master_copy; + my $do_multi_precision = ($self->multi_precision and $weight->dtype eq 'float16'); + if($do_multi_precision) + { + if($self->momentum != 0) + { + $momentum = mx->nd->zeros($weight->shape, ctx => $weight->context, dtype=>'float32'); + } + $weight_master_copy = mx->nd->array($weight, ctx=>$weight->context, dtype=>'float32'); + return [$momentum, $weight_master_copy]; + } + else + { + if($self->momentum != 0) + { + $momentum = mx->nd->zeros($weight->shape, ctx => $weight->context, dtype => $weight->dtype); + } + } + return $momentum; } method update($index, $weight, $grad, $state) @@ -205,48 +224,90 @@ method update($index, $weight, $grad, $state) my $lr = $self->_get_lr($index); my $wd = $self->_get_wd($index); $self->_update_count($index); - if($self->momentum == 0) + my $use_multi_precision = ref($state) eq 'ARRAY'; + + if(not $use_multi_precision) { - if(defined $self->clip_gradient) + if($self->momentum == 0) { - $weight .= ((1 - $lr*$wd)*$weight - - $lr * mx->nd->clip($grad*$self->rescale_grad, -$self->clip_gradient, $self->clip_gradient) - ); + if(defined $self->clip_gradient) + { + $weight .= ((1 - $lr*$wd)*$weight - + $lr * mx->nd->clip($grad*$self->rescale_grad, -$self->clip_gradient, $self->clip_gradient) + ); + } + else + { + $weight .= (1 - $lr*$wd)*$weight - $lr*$self->rescale_grad*$grad; + } } else { - $weight .= (1 - $lr*$wd)*$weight - $lr*$self->rescale_grad*$grad; + my $mom = $state; + if(defined $self->clip_gradient) + { + $mom .= ($self->momentum*$mom - $lr*$wd*$weight - + $lr * mx->nd->clip($grad*$self->rescale_grad, -$self->clip_gradient, $self->clip_gradient) + ); + $weight += $mom; + } + else + { + $mom .= $self->momentum*$mom - $lr*$wd*$weight - $lr*$self->rescale_grad*$grad; + $weight += $mom; + } } } else { - my $mom = $state; - if(defined $self->clip_gradient) + my $grad32 = mx->nd->array($grad, ctx=>$grad->context, dtype=>'float32'); + my $mom = $state->[0]; + my $weight32 = $state->[1]; + if($self->momentum == 0) { - $mom .= ($self->momentum*$mom - $lr*$wd*$weight - - $lr * mx->nd->clip($grad*$self->rescale_grad, -$self->clip_gradient, $self->clip_gradient) - ); - $weight += $mom; + if(defined $self->clip_gradient) + { + $weight32 .= ((1 - $lr*$wd)*$weight32 - + $lr * mx->nd->clip($grad32*$self->rescale_grad, -$self->clip_gradient, $self->clip_gradient) + ); + } + else + { + $weight32 .= (1 - $lr*$wd)*$weight32 - $lr*$self->rescale_grad*$grad32; + } } else { - $mom .= $self->momentum*$mom - $lr*$wd*$weight - $lr*$self->rescale_grad*$grad; - $weight += $mom; + if(defined $self->clip_gradient) + { + $mom .= ($self->momentum*$mom - $lr*$wd*$weight32 - + $lr * mx->nd->clip($grad32*$self->rescale_grad, -$self->clip_gradient, $self->clip_gradient) + ); + $weight32 += $mom; + } + else + { + $mom .= $self->momentum*$mom - $lr*$wd*$weight32 - $lr*$self->rescale_grad*$grad32; + $weight32 += $mom; + } } + my $tmp = $weight32->astype($weight->dtype); + $tmp->copyto($weight); } } + package main; -use Test::More tests => 190; +use Test::More tests => 1314; use AI::MXNet::Base; use PDL::NiceSlice; use AI::MXNet::TestUtils qw(same reldiff almost_equal); use AI::MXNet::Function::Parameters; -func compare_optimizer($opt1, $opt2, $shape) +func compare_optimizer($opt1, $opt2, $shape, $dtype) { - my $w1 = mx->random->uniform({shape => $shape}); - my $g1 = mx->random->uniform({shape => $shape}); + my $w1 = mx->random->uniform({shape => $shape, dtype=>$dtype}); + my $g1 = mx->random->uniform({shape => $shape, dtype=>$dtype}); my $w2 = $w1->copyto(mx->cpu()); my $g2 = $g1->copyto(mx->cpu()); @@ -256,7 +317,7 @@ func compare_optimizer($opt1, $opt2, $shape) zip( sub { my ($s1, $s2) = @_; - ok(same($s1->aspdl, $s2->aspdl)) + ok(same($s1->aspdl, $s2->aspdl)) if defined $s1 and defined $s2; }, ref $state1 eq 'ARRAY' ? $state1 : [$state1], ref $state2 eq 'ARRAY' ? $state2 : [$state2] ) if defined $state1 and defined $state2; @@ -266,7 +327,7 @@ func compare_optimizer($opt1, $opt2, $shape) zip( sub { my ($s1, $s2) = @_; - ok(reldiff($s1->aspdl, $s2->aspdl) < 1e-5) + ok(reldiff($s1->aspdl, $s2->aspdl) < 1e-5) if defined $s1 and defined $s2; }, ref $state1 eq 'ARRAY' ? $state1 : [$state1], ref $state2 eq 'ARRAY' ? $state2 : [$state2] ) if defined $state1 and defined $state2; @@ -285,7 +346,7 @@ func test_adam() {'rescale_grad'=> 0.1}); for my $kwarg (@kwargs) { - compare_optimizer($opt1->new(%$kwarg), $opt2->new(wd => 0.9, %$kwarg), $shape); + compare_optimizer($opt1->new(%$kwarg), $opt2->new(wd => 0.9, %$kwarg), $shape, 'float32'); } } @@ -324,7 +385,7 @@ func test_rms() {rescale_grad => 0.8, wd => 0.05, centered => 1, clip_weights => 0.01}); for my $kwarg (@kwargs) { - compare_optimizer($opt1->new(%$kwarg), $opt2->new(%$kwarg), $shape); + compare_optimizer($opt1->new(%$kwarg), $opt2->new(%$kwarg), $shape, 'float32'); } } @@ -335,25 +396,40 @@ sub test_sgd my $opt1 = 'PerlSGD'; my $opt2 = mx->optimizer->SGD; my $shape = [3, 4, 5]; - my @kwargs = ( - {}, - {momentum => 0.9}, - {clip_gradient => 0.5}, - {clip_gradient => 0.4, rescale_grad => 0.14}, - {rescale_grad => 0.8}, - {clip_gradient => 0.5, wd => 0.07}, - {clip_gradient => 0.4, rescale_grad => 0.14, wd => 0.03}, - {rescale_grad => 0.8, wd => 0.05}, - {clip_gradient => 0.5, momentum => 0.9}, - {clip_gradient => 0.4, rescale_grad => 0.14, momentum => 0.9}, - {rescale_grad => 0.8, momentum => 0.9}, - {clip_gradient => 0.5, wd => 0.07, momentum => 0.9}, - {clip_gradient => 0.4, rescale_grad => 0.14, wd => 0.03, momentum => 0.9}, - {rescale_grad => 0.8, wd => 0.05, momentum => 0.9} - ); - for my $kwarg (@kwargs) + my @mom_options = ({}, {momentum => 0.9}); + my @cg_options = ({}, {clip_gradient => 0.4}, {clip_gradient => 0.5}); + my @rg_options = ({}, {rescale_grad => 0.14}, {rescale_grad => 0.8}); + my @wd_options = ({}, {wd => 0.03}, {wd => 0.05}, {wd => 0.07}); + my @mp_options = ({}, {multi_precision => 0}, {multi_precision => 1}); + for my $dtype(qw/float16 float32 float64/) { - compare_optimizer($opt1->new(%$kwarg), $opt2->new(%$kwarg), $shape); + for my $mom_option (@mom_options) + { + for my $cg_option (@cg_options) + { + for my $rg_option (@rg_options) + { + for my $wd_option (@wd_options) + { + for my $mp_option (@mp_options) + { + my %kwarg; + %kwarg = (%kwarg, %$mom_option); + %kwarg = (%kwarg, %$cg_option); + %kwarg = (%kwarg, %$rg_option); + %kwarg = (%kwarg, %$wd_option); + %kwarg = (%kwarg, %$mp_option); + next if ( + $dtype eq 'float16' + and + (not exists $kwarg{multi_precision} or not $kwarg{multi_precision}) + ); + compare_optimizer($opt1->new(%kwarg), $opt2->new(%kwarg), $shape, $dtype); + } + } + } + } + } } } @@ -392,4 +468,3 @@ test_adam(); test_rms(); test_sgd(); test_lr_wd_mult(); - diff --git a/perl-package/AI-MXNet/t/test_random.t b/perl-package/AI-MXNet/t/test_random.t index 7d7ef192fd0b..82175948efc4 100644 --- a/perl-package/AI-MXNet/t/test_random.t +++ b/perl-package/AI-MXNet/t/test_random.t @@ -1,6 +1,6 @@ use strict; use warnings; -use Test::More tests => 11; +use Test::More tests => 8; use AI::MXNet qw(mx); use AI::MXNet::TestUtils qw(same); @@ -44,17 +44,6 @@ sub check_symbolic_random my $un2 = ($yexec->outputs->[0] - $x)->copyto($dev); ok(same($un1->aspdl, $un2->aspdl)); ok(abs($un1->aspdl->avg - ($a+$b)/2) < 0.1); - - $Y = mx->sym->normal(loc=>$mu, scale=>$sigma, shape=>$shape); - $yexec = $Y->simple_bind(ctx => $dev); - mx->random->seed(128); - $yexec->forward; - my $ret1 = $yexec->outputs->[0]->copyto($dev); - mx->random->seed(128); - my $ret2 = mx->random->normal($mu, $sigma, $shape); - ok(same($ret1->aspdl, $ret2->aspdl)); - ok(abs($ret1->aspdl->avg - $mu) < 0.1); - ok(abs(($ret1->aspdl->stats)[6] - $sigma) < 0.1); } sub test_random diff --git a/perl-package/AI-MXNet/t/test_rnn.t b/perl-package/AI-MXNet/t/test_rnn.t index d314298c1eb9..76242c0f48c6 100644 --- a/perl-package/AI-MXNet/t/test_rnn.t +++ b/perl-package/AI-MXNet/t/test_rnn.t @@ -1,8 +1,9 @@ use strict; use warnings; use AI::MXNet qw(mx); +use AI::MXNet::TestUtils qw(same); use PDL; -use Test::More tests => 37; +use Test::More tests => 54; sub test_rnn { @@ -60,12 +61,89 @@ sub test_gru is_deeply($outs, [[10, 100], [10, 100], [10, 100]]); } +sub test_residual +{ + my $cell = mx->rnn->ResidualCell(mx->rnn->GRUCell(50, prefix=>'rnn_')); + my $inputs = [map { mx->sym->Variable("rnn_t${_}_data") } 0..1]; + my ($outputs)= $cell->unroll(2, inputs => $inputs); + $outputs = mx->sym->Group($outputs); + is_deeply( + [sort keys %{ $cell->params->_params }], + ['rnn_h2h_bias', 'rnn_h2h_weight', 'rnn_i2h_bias', 'rnn_i2h_weight'] + ); + is_deeply( + $outputs->list_outputs, + ['rnn_t0_out_plus_residual_output', 'rnn_t1_out_plus_residual_output'] + ); + + my (undef, $outs) = $outputs->infer_shape(rnn_t0_data=>[10, 50], rnn_t1_data=>[10, 50]); + is_deeply($outs, [[10, 50], [10, 50]]); + $outputs = $outputs->eval(args => { + rnn_t0_data=>mx->nd->ones([10, 50]), + rnn_t1_data=>mx->nd->ones([10, 50]), + rnn_i2h_weight=>mx->nd->zeros([150, 50]), + rnn_i2h_bias=>mx->nd->zeros([150]), + rnn_h2h_weight=>mx->nd->zeros([150, 50]), + rnn_h2h_bias=>mx->nd->zeros([150]) + }); + my $expected_outputs = mx->nd->ones([10, 50])->aspdl; + same(@{$outputs}[0]->aspdl, $expected_outputs); + same(@{$outputs}[1]->aspdl, $expected_outputs); +} + +sub test_residual_bidirectional +{ + my $cell = mx->rnn->ResidualCell( + mx->rnn->BidirectionalCell( + mx->rnn->GRUCell(25, prefix=>'rnn_l_'), + mx->rnn->GRUCell(25, prefix=>'rnn_r_') + ) + ); + my $inputs = [map { mx->sym->Variable("rnn_t${_}_data") } 0..1]; + my ($outputs) = $cell->unroll(2, inputs => $inputs, merge_outputs=>0); + $outputs = mx->sym->Group($outputs); + is_deeply( + [sort keys %{ $cell->params->_params }], + ['rnn_l_h2h_bias', 'rnn_l_h2h_weight', 'rnn_l_i2h_bias', 'rnn_l_i2h_weight', + 'rnn_r_h2h_bias', 'rnn_r_h2h_weight', 'rnn_r_i2h_bias', 'rnn_r_i2h_weight'] + ); + is_deeply( + $outputs->list_outputs, + ['bi_t0_plus_residual_output', 'bi_t1_plus_residual_output'] + ); + + my (undef, $outs) = $outputs->infer_shape(rnn_t0_data=>[10, 50], rnn_t1_data=>[10, 50]); + is_deeply($outs, [[10, 50], [10, 50]]); + $outputs = $outputs->eval(args => { + rnn_t0_data=>mx->nd->ones([10, 50])+5, + rnn_t1_data=>mx->nd->ones([10, 50])+5, + rnn_l_i2h_weight=>mx->nd->zeros([75, 50]), + rnn_l_i2h_bias=>mx->nd->zeros([75]), + rnn_l_h2h_weight=>mx->nd->zeros([75, 25]), + rnn_l_h2h_bias=>mx->nd->zeros([75]), + rnn_r_i2h_weight=>mx->nd->zeros([75, 50]), + rnn_r_i2h_bias=>mx->nd->zeros([75]), + rnn_r_h2h_weight=>mx->nd->zeros([75, 25]), + rnn_r_h2h_bias=>mx->nd->zeros([75]) + }); + my $expected_outputs = (mx->nd->ones([10, 50])+5)->aspdl; + ok(same(@{$outputs}[0]->aspdl, $expected_outputs)); + ok(same(@{$outputs}[1]->aspdl, $expected_outputs)); +} + sub test_stack { my $cell = mx->rnn->SequentialRNNCell(); for my $i (0..4) { - $cell->add(mx->rnn->LSTMCell(100, prefix=>"rnn_stack${i}_")); + if($i == 1) + { + $cell->add(mx->rnn->ResidualCell(mx->rnn->LSTMCell(100, prefix=>"rnn_stack${i}_"))); + } + else + { + $cell->add(mx->rnn->LSTMCell(100, prefix=>"rnn_stack${i}_")); + } } my ($outputs) = $cell->unroll(3, input_prefix=>'rnn_'); $outputs = mx->sym->Group($outputs); @@ -123,11 +201,73 @@ sub test_zoneout is_deeply($outs, [[10, 100], [10, 100], [10, 100]]); } +sub test_convrnn +{ + my $cell = mx->rnn->ConvRNNCell(input_shape => [1, 3, 16, 10], num_hidden=>10, + h2h_kernel=>[3, 3], h2h_dilate=>[1, 1], + i2h_kernel=>[3, 3], i2h_stride=>[1, 1], + i2h_pad=>[1, 1], i2h_dilate=>[1, 1], + prefix=>'rnn_'); + my $inputs = [map { mx->sym->Variable("rnn_t${_}_data") } 0..2]; + my ($outputs) = $cell->unroll(3, inputs => $inputs); + $outputs = mx->sym->Group($outputs); + is_deeply( + [sort keys %{ $cell->params->_params }], + ['rnn_h2h_bias', 'rnn_h2h_weight', 'rnn_i2h_bias', 'rnn_i2h_weight'] + ); + is_deeply($outputs->list_outputs(), ['rnn_t0_out_output', 'rnn_t1_out_output', 'rnn_t2_out_output']); + my (undef, $outs) = $outputs->infer_shape(rnn_t0_data=>[1, 3, 16, 10], rnn_t1_data=>[1, 3, 16, 10], rnn_t2_data=>[1, 3, 16, 10]); + is_deeply($outs, [[1, 10, 16, 10], [1, 10, 16, 10], [1, 10, 16, 10]]); +} + +sub test_convlstm +{ + my $cell = mx->rnn->ConvLSTMCell(input_shape => [1, 3, 16, 10], num_hidden=>10, + h2h_kernel=>[3, 3], h2h_dilate=>[1, 1], + i2h_kernel=>[3, 3], i2h_stride=>[1, 1], + i2h_pad=>[1, 1], i2h_dilate=>[1, 1], + prefix=>'rnn_', forget_bias => 1); + my $inputs = [map { mx->sym->Variable("rnn_t${_}_data") } 0..2]; + my ($outputs) = $cell->unroll(3, inputs => $inputs); + $outputs = mx->sym->Group($outputs); + is_deeply( + [sort keys %{ $cell->params->_params }], + ['rnn_h2h_bias', 'rnn_h2h_weight', 'rnn_i2h_bias', 'rnn_i2h_weight'] + ); + is_deeply($outputs->list_outputs(), ['rnn_t0_out_output', 'rnn_t1_out_output', 'rnn_t2_out_output']); + my (undef, $outs) = $outputs->infer_shape(rnn_t0_data=>[1, 3, 16, 10], rnn_t1_data=>[1, 3, 16, 10], rnn_t2_data=>[1, 3, 16, 10]); + is_deeply($outs, [[1, 10, 16, 10], [1, 10, 16, 10], [1, 10, 16, 10]]); +} + +sub test_convgru +{ + my $cell = mx->rnn->ConvGRUCell(input_shape => [1, 3, 16, 10], num_hidden=>10, + h2h_kernel=>[3, 3], h2h_dilate=>[1, 1], + i2h_kernel=>[3, 3], i2h_stride=>[1, 1], + i2h_pad=>[1, 1], i2h_dilate=>[1, 1], + prefix=>'rnn_', forget_bias => 1); + my $inputs = [map { mx->sym->Variable("rnn_t${_}_data") } 0..2]; + my ($outputs) = $cell->unroll(3, inputs => $inputs); + $outputs = mx->sym->Group($outputs); + is_deeply( + [sort keys %{ $cell->params->_params }], + ['rnn_h2h_bias', 'rnn_h2h_weight', 'rnn_i2h_bias', 'rnn_i2h_weight'] + ); + is_deeply($outputs->list_outputs(), ['rnn_t0_out_output', 'rnn_t1_out_output', 'rnn_t2_out_output']); + my (undef, $outs) = $outputs->infer_shape(rnn_t0_data=>[1, 3, 16, 10], rnn_t1_data=>[1, 3, 16, 10], rnn_t2_data=>[1, 3, 16, 10]); + is_deeply($outs, [[1, 10, 16, 10], [1, 10, 16, 10], [1, 10, 16, 10]]); +} + test_rnn(); test_lstm(); test_lstm_forget_bias(); test_gru(); +test_residual(); +test_residual_bidirectional(); test_stack(); test_bidirectional(); test_unfuse(); test_zoneout(); +test_convrnn(); +test_convlstm(); +test_convgru(); diff --git a/perl-package/AI-MXNet/t/test_symbol.t b/perl-package/AI-MXNet/t/test_symbol.t index d6d79eaf30df..f21a14cf78ff 100644 --- a/perl-package/AI-MXNet/t/test_symbol.t +++ b/perl-package/AI-MXNet/t/test_symbol.t @@ -409,4 +409,4 @@ __DATA__ ], "arg_nodes": [0, 1, 2, 5, 6, 9, 10, 12, 13, 15], "heads": [[16, 0]] -} \ No newline at end of file +} diff --git a/perl-package/AI-MXNetCAPI/Changes b/perl-package/AI-MXNetCAPI/Changes index 0cd407a2f8dc..1a6356c0333d 100644 --- a/perl-package/AI-MXNetCAPI/Changes +++ b/perl-package/AI-MXNetCAPI/Changes @@ -1,5 +1,14 @@ Revision history for Perl extension AI::MXNetCAPI +1.0102 Sun Aug 6 16:55:08 PDT 2017 + - updated autograd calls. + +1.0101 Sun Jul 2 17:16:01 PDT 2017 + - refactored CachedOp, using strings to index the kvstore. + +1.01 Sat Jun 10 23:57:27 PDT 2017 + - sync with python. + 0.9507 Thu May 11 17:04:44 PDT 2017 - Added Autograd. diff --git a/perl-package/AI-MXNetCAPI/META.json b/perl-package/AI-MXNetCAPI/META.json index 137c542d47ac..a6d65fd2d73a 100644 --- a/perl-package/AI-MXNetCAPI/META.json +++ b/perl-package/AI-MXNetCAPI/META.json @@ -37,5 +37,5 @@ } }, "release_status" : "stable", - "version" : "0.9507" + "version" : "1.0102" } diff --git a/perl-package/AI-MXNetCAPI/META.yml b/perl-package/AI-MXNetCAPI/META.yml index 8191978fe68a..0e3bb53c475c 100644 --- a/perl-package/AI-MXNetCAPI/META.yml +++ b/perl-package/AI-MXNetCAPI/META.yml @@ -19,4 +19,4 @@ no_index: - inc requires: Test::More: '0' -version: '0.9507' +version: '1.0102' diff --git a/perl-package/AI-MXNetCAPI/README b/perl-package/AI-MXNetCAPI/README index 2ddc00e7dfe5..5c531463e83b 100644 --- a/perl-package/AI-MXNetCAPI/README +++ b/perl-package/AI-MXNetCAPI/README @@ -1,4 +1,4 @@ -AI-MXNetCAPI version 0.9507 +AI-MXNetCAPI version 1.0102 ===================== Swig interface to MXNet c api. diff --git a/perl-package/AI-MXNetCAPI/lib/AI/MXNetCAPI.pm b/perl-package/AI-MXNetCAPI/lib/AI/MXNetCAPI.pm index 5e3f44562b08..0a93d71916f8 100644 --- a/perl-package/AI-MXNetCAPI/lib/AI/MXNetCAPI.pm +++ b/perl-package/AI-MXNetCAPI/lib/AI/MXNetCAPI.pm @@ -1,7 +1,24 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + package AI::MXNetCAPI; use base qw(DynaLoader); bootstrap AI::MXNetCAPI; -our $VERSION = '0.9507'; +our $VERSION = '1.0102'; 1; __END__ diff --git a/perl-package/AI-MXNetCAPI/mxnet.i b/perl-package/AI-MXNetCAPI/mxnet.i index 4b8afacfde1a..fd1a471bcf16 100644 --- a/perl-package/AI-MXNetCAPI/mxnet.i +++ b/perl-package/AI-MXNetCAPI/mxnet.i @@ -104,7 +104,7 @@ static void ExecutorMonitor_callback(const char* name, NDArrayHandle handle, voi } } -%} +%} %init %{ /* These SWIG_TypeClientData() calls might break in the future, but @@ -119,6 +119,7 @@ static void ExecutorMonitor_callback(const char* name, NDArrayHandle handle, voi SWIG_TypeClientData(SWIGTYPE_p_MXKVStore, (void *)"KVStoreHandle"); SWIG_TypeClientData(SWIGTYPE_p_MXRecordIO, (void *)"RecordIOHandle"); SWIG_TypeClientData(SWIGTYPE_p_MXRtc, (void *)"RtcHandle"); + SWIG_TypeClientData(SWIGTYPE_p_MXCachedOp, (void *)"CachedOpHandle"); %} /*! \brief manually define unsigned int */ @@ -130,7 +131,7 @@ typedef float mx_float; // these typedefs are mainly used for readablity reasons /*! \brief handle to NDArray */ typedef MXNDArray *NDArrayHandle; -/*! \brief handle to a mxnet narray function that changes NDArray */ +/*! \brief handle to a mxnet ndarray function that changes NDArray */ typedef MXFunction *FunctionHandle; /*! \brief handle to a function that takes param and creates symbol */ typedef MXAtomicSymbolCreator *AtomicSymbolCreator; @@ -150,6 +151,8 @@ typedef MXKVStore *KVStoreHandle; typedef MXRecordIO *RecordIOHandle; /*! \brief handle to MXRtc*/ typedef MXRtc *RtcHandle; +/*! \brief handle to cached operator */ +typedef MXCachedOp *CachedOpHandle; typedef void (*ExecutorMonitorCallback)(const char*, NDArrayHandle, @@ -234,6 +237,9 @@ int MXSetProfilerState(int state); /*! \brief Save profile and stop profiler */ int MXDumpProfile(); +/*! \brief Set the number of OMP threads to use */ +int MXSetNumOMPThreads(int thread_num); + //------------------------------------- // Part 1: NDArray creation and deletion //------------------------------------- @@ -252,7 +258,7 @@ int MXNDArrayCreateNone(NDArrayHandle *out); * \param dev_type device type, specify device we want to take * \param dev_id the device id of the specific device * \param delay_alloc whether to delay allocation until - * the narray is first mutated + * the ndarray is first mutated * \param out the returning handle * \return 0 when success, -1 when failure happens */ @@ -270,7 +276,7 @@ int MXNDArrayCreate(const mx_uint *in, * \param dev_type device type, specify device we want to take * \param dev_id the device id of the specific device * \param delay_alloc whether to delay allocation until - * the narray is first mutated + * the ndarray is first mutated * \param dtype data type of created array * \param out the returning handle * \return 0 when success, -1 when failure happens @@ -303,7 +309,7 @@ int MXNDArraySaveRawBytes(NDArrayHandle handle, size_t *out_size, const char **out_array); /*! - * \brief Save list of narray into the file. + * \brief Save list of ndarray into the file. * \param fname name of the file. * \param num_args number of arguments to save. * \param args the array of NDArrayHandles to be saved. @@ -315,10 +321,10 @@ int MXNDArraySave(const char* fname, NDArrayHandle* in, const char** in); /*! - * \brief Load list of narray from the file. + * \brief Load list of ndarray from the file. * \param fname name of the file. - * \param out_size number of narray loaded. - * \param out_arr head of the returning narray handles. + * \param out_size number of ndarray loaded. + * \param out_arr head of the returning ndarray handles. * \param out_name_size size of output name arrray. * \param out_names the names of returning NDArrays, can be NULL * \return 0 when success, -1 when failure happens @@ -377,7 +383,7 @@ int MXNDArrayWaitToWrite(NDArrayHandle handle); */ int MXNDArrayWaitAll(); /*! - * \brief free the narray handle + * \brief free the ndarray handle * \param handle the handle to be freed * \return 0 when success, -1 when failure happens */ @@ -406,7 +412,7 @@ int MXNDArrayAt(NDArrayHandle handle, NDArrayHandle *out); /*! * \brief Reshape the NDArray. - * \param handle the handle to the narray + * \param handle the handle to the ndarray * \param ndim number of dimensions of new shape * \param dims new shape * \param out the NDArrayHandle of reshaped NDArray @@ -418,7 +424,7 @@ int MXNDArrayReshape(NDArrayHandle handle, NDArrayHandle *out); /*! * \brief get the shape of the array - * \param handle the handle to the narray + * \param handle the handle to the ndarray * \param out_dim the output dimension * \param out_pdata pointer holder to get data pointer of the shape * \return 0 when success, -1 when failure happens @@ -428,7 +434,7 @@ int MXNDArrayGetShape(NDArrayHandle handle, const mx_uint **out_pdata); /*! * \brief get the content of the data in NDArray - * \param handle the handle to the narray + * \param handle the handle to the ndarray * \param out_pdata pointer holder to get pointer of data * \return 0 when success, -1 when failure happens */ @@ -436,7 +442,7 @@ int MXNDArrayGetData(NDArrayHandle handle, void **out_pdata); /*! * \brief get the type of the data in NDArray - * \param handle the handle to the narray + * \param handle the handle to the ndarray * \param out_dtype pointer holder to get type of data * \return 0 when success, -1 when failure happens */ @@ -444,7 +450,7 @@ int MXNDArrayGetDType(NDArrayHandle handle, int *out); /*! * \brief get the context of the NDArray - * \param handle the handle to the narray + * \param handle the handle to the ndarray * \param out_dev_type the output device type * \param out_dev_id the output device id * \return 0 when success, -1 when failure happens @@ -452,6 +458,35 @@ int MXNDArrayGetDType(NDArrayHandle handle, int MXNDArrayGetContext(NDArrayHandle handle, int *out, int *out); +/*! + * \brief return gradient buffer attached to this NDArray + * \param handle NDArray handle + * \return 0 when success, -1 when failure happens + */ +int MXNDArrayGetGrad(NDArrayHandle handle, NDArrayHandle *out); + +/*! + * \brief detach and ndarray from computation graph by clearing entry_ + * \param handle NDArray handle + * \return 0 when success, -1 when failure happens + */ +int MXNDArrayDetach(NDArrayHandle handle, NDArrayHandle *out); + +/*! + * \brief set the flag for gradient array state. + * \param handle NDArray handle + * \param state the new state. + * \return 0 when success, -1 when failure happens + */ +int MXNDArraySetGradState(NDArrayHandle handle, int state); + +/*! + * \brief set the flag for gradient array state. + * \param handle NDArray handle + * \param state the new state. + * \return 0 when success, -1 when failure happens + */ +int MXNDArrayGetGradState(NDArrayHandle handle, int *out); //-------------------------------- // Part 2: functions on NDArray @@ -587,7 +622,36 @@ int MXAutogradMarkVariables(mx_uint num_var, */ int MXAutogradComputeGradient(mx_uint num_output, NDArrayHandle* in); +/*! + * \brief compute the gradient of outputs w.r.t variabels + * \param num_output number of output NDArray + * \param output_handles output NDArrays + * \param ograd_handles head gradient for NDArrays + * \param retain_graph whether to keep the graph after backward + * \return 0 when success, -1 when failure happens + */ +int MXAutogradBackward(mx_uint num_output, + NDArrayHandle* in, + NDArrayHandle* in, + int retain_graph); + /*! + * \brief create cached operator + */ +int MXCreateCachedOp(SymbolHandle handle, + CachedOpHandle *out); + /*! + * \brief free cached operator + */ +int MXFreeCachedOp(CachedOpHandle handle); + /*! + * \brief invoke cached operator + */ +int MXInvokeCachedOp(CachedOpHandle handle, + int num_inputs, + NDArrayHandle *in, + int *out_size, + NDArrayHandle **out_array); //-------------------------------------------- // Part 3: symbolic configuration generation //-------------------------------------------- @@ -1016,8 +1080,8 @@ int MXExecutorBackward(ExecutorHandle handle, * \brief Get executor's head NDArray * * \param handle executor handle - * \param out_size output narray vector size - * \param out out put narray handles + * \param out_size output ndarray vector size + * \param out out put ndarray handles * \return 0 when success, -1 when failure happens */ int MXExecutorOutputs(ExecutorHandle handle, @@ -1121,6 +1185,45 @@ int MXExecutorBindEX(SymbolHandle symbol_handle, NDArrayHandle *in, ExecutorHandle shared_exec, ExecutorHandle *out); + +int MXExecutorSimpleBind(SymbolHandle symbol_handle, + int dev_type, + int dev_id, + const mx_uint num_g2c_keys, + const char** in, // g2c_keys, + const int* in, // g2c_dev_types, + const int* in, // g2c_dev_ids, + const mx_uint provided_grad_req_list_len, + const char** in, // provided_grad_req_names, + const char** in, // provided_grad_req_types, + const mx_uint num_provided_arg_shapes, + const char** in, // provided_arg_shape_names, + const mx_uint* in, // provided_arg_shape_data, + const mx_uint* in, // provided_arg_shape_idx, + const mx_uint num_provided_arg_dtypes, + const char** in, // provided_arg_dtype_names, + const int* in, // provided_arg_dtypes, + const mx_uint num_shared_arg_names, + const char** in, // shared_arg_name_list, +//------------ + int* shared_buffer_len, + const char** shared_buffer_name_list, + NDArrayHandle* shared_buffer_handle_list, + const char*** updated_shared_buffer_name_list, + NDArrayHandle** updated_shared_buffer_handle_list, +//------------------ + + mx_uint* num_in_args, + NDArrayHandle** in_args, + NDArrayHandle** arg_grads, +//----------------- + mx_uint* num_aux_states, + NDArrayHandle** aux_states, +//---------- + ExecutorHandle shared_exec_handle, + ExecutorHandle* out +); + /*! * \brief set a call back to notify the completion of operation */ @@ -1255,21 +1358,21 @@ int MXKVStoreCreate(const char *type, * \return 0 when success, -1 when failure happens */ int MXKVStoreFree(KVStoreHandle handle); + /*! - * \brief Init a list of (key,value) pairs in kvstore + * \brief Init a list of (key,value) pairs in kvstore, where each key is a string * \param handle handle to the kvstore * \param num the number of key-value pairs * \param keys the list of keys * \param vals the list of values * \return 0 when success, -1 when failure happens */ -int MXKVStoreInit(KVStoreHandle handle, - mx_uint num, - const int* in, - NDArrayHandle* in); - -/*! - * \brief Push a list of (key,value) pairs to kvstore +int MXKVStoreInitEx(KVStoreHandle handle, + mx_uint num, + const char** in, + NDArrayHandle* in); + /*! + * \brief Push a list of (key,value) pairs to kvstore, where each key is a string * \param handle handle to the kvstore * \param num the number of key-value pairs * \param keys the list of keys @@ -1277,13 +1380,13 @@ int MXKVStoreInit(KVStoreHandle handle, * \param priority the priority of the action * \return 0 when success, -1 when failure happens */ -int MXKVStorePush(KVStoreHandle handle, - mx_uint num, - const int* in, - NDArrayHandle* in, - int priority); -/*! - * \brief pull a list of (key, value) pairs from the kvstore +int MXKVStorePushEx(KVStoreHandle handle, + mx_uint num, + const char** in, + NDArrayHandle* in, + int priority); + /*! + * \brief pull a list of (key, value) pairs from the kvstore, where each key is a string * \param handle handle to the kvstore * \param num the number of key-value pairs * \param keys the list of keys @@ -1291,11 +1394,11 @@ int MXKVStorePush(KVStoreHandle handle, * \param priority the priority of the action * \return 0 when success, -1 when failure happens */ -int MXKVStorePull(KVStoreHandle handle, - mx_uint num, - const int* in, - NDArrayHandle* in, - int priority); +int MXKVStorePullEx(KVStoreHandle handle, + mx_uint num, + const char** in, + NDArrayHandle* in, + int priority); /*! * \brief user-defined updater for the kvstore * It's this updater's responsibility to delete \a recv and \a local diff --git a/perl-package/AI-MXNetCAPI/mxnet_typemaps.i b/perl-package/AI-MXNetCAPI/mxnet_typemaps.i index 8e035f1b4973..640215fd7792 100644 --- a/perl-package/AI-MXNetCAPI/mxnet_typemaps.i +++ b/perl-package/AI-MXNetCAPI/mxnet_typemaps.i @@ -15,13 +15,13 @@ { $1 = (char **) safemalloc((len)*sizeof(char *)); for (i = 0; i < len; i++) { - tv = av_fetch(tempav, i, 0); + tv = av_fetch(tempav, i, 0); $1[i] = (char *) SvPV(*tv,len2); } } else { - $1 = NULL; + $1 = NULL; } } %typemap(freearg) (const char** in), (char** in) { @@ -47,13 +47,18 @@ { $1 = (char **)safemalloc(hash_len*sizeof(char *)); $2 = (char **)safemalloc(hash_len*sizeof(char *)); - while ((val = hv_iternextsv(temphv, &key, &len))) + while ((val = hv_iternextsv(temphv, &key, &len))) { $1[i] = key; $2[i] = SvPV(val, len2); ++i; } } + else + { + $1 = NULL; + $2 = NULL; + } } %typemap(freearg) (const char **keys, const char **vals), (char **keys, char **vals) { @@ -197,6 +202,10 @@ $1[i] = (mx_uint)SvIV(*tv); } } + else + { + $1 = NULL; + } } %typemap(freearg) (const mx_uint *in), (mx_uint *in) { @@ -215,11 +224,19 @@ croak("Argument $argnum is not an array."); tempav = (AV*)SvRV($input); av_len = av_top_index(tempav) + 1; - $1 = (int *)safemalloc(av_len*sizeof(int)); - for (i = 0; i < av_len; i++) { - tv = av_fetch(tempav, i, 0); - $1[i] = (int)SvIV(*tv); + if(av_len) + { + $1 = (int *)safemalloc(av_len*sizeof(int)); + for (i = 0; i < av_len; i++) { + tv = av_fetch(tempav, i, 0); + $1[i] = (int)SvIV(*tv); + } } + else + { + $1 = NULL; + } + } %typemap(freearg) (const int *in), (int *in) { @@ -250,6 +267,10 @@ } } } + else + { + $1 = NULL; + } } %typemap(freearg) (NDArrayHandle* in), (SymbolHandle* in) { Safefree($1); @@ -270,15 +291,20 @@ { $1 = (mx_float *)safemalloc(len*sizeof(mx_float)); for (i = 0; i < len; i++) { - tv = av_fetch(tempav, i, 0); + tv = av_fetch(tempav, i, 0); $1[i] = (mx_float)SvNV(*tv); } } + else + { + $1 = NULL; + } } %typemap(freearg) (mx_float *in) { Safefree($1); } + %typemap(in,numinputs=0) (NDArrayHandle *out) (NDArrayHandle temp), (FunctionHandle* out) (FunctionHandle temp), (SymbolHandle *out) (SymbolHandle temp), @@ -286,16 +312,16 @@ (DataIterHandle *out) (ExecutorHandle temp), (KVStoreHandle *out) (KVStoreHandle temp), (RecordIOHandle *out) (RecordIOHandle temp), - (RtcHandle *out) (RtcHandle temp) - + (RtcHandle *out) (RtcHandle temp), + (CachedOpHandle *out) (CachedOpHandle temp) { $1 = &temp; } -%typemap(argout) (NDArrayHandle *out), (FunctionHandle* out), (SymbolHandle *out), (ExecutorHandle *out), (DataIterHandle *out), - (KVStoreHandle *out), (RecordIOHandle *out), (RtcHandle *out) (RtcHandle temp) +%typemap(argout) (NDArrayHandle *out), (FunctionHandle* out), (SymbolHandle *out), (ExecutorHandle *out), (DataIterHandle *out), + (KVStoreHandle *out), (RecordIOHandle *out), (RtcHandle *out) (RtcHandle temp), (CachedOpHandle *out) (CachedOpHandle temp) { if(!result) - { + { $result = SWIG_NewPointerObj(SWIG_as_voidptr(*$1), $*1_descriptor, 0); argvi++; } } @@ -520,7 +546,7 @@ SWIG_exception_fail(SWIG_ArgError(res), "in method '" "$symname" "', argument " "$argnum"" of type '" "NDArray""'"); } } - } + } temp = av_len; $1 = &temp; $2 = &temp_array; @@ -660,7 +686,7 @@ for (i = 0; i < *$3 ; i++) { av_push(names, newSVpv((*$4)[i],0)); av_push(types, newSVpv((*$5)[i],0)); - av_push(descs, newSVpv((*$6)[i],0)); + av_push(descs, newSVpv((*$6)[i],0)); } } av_push(container, newRV_noinc((SV*)names)); @@ -695,9 +721,10 @@ (mx_uint *aux_shape_size, const mx_uint **aux_shape_ndim, const mx_uint ***aux_shape_data) (mx_uint temp1, mx_uint *temp2, mx_uint **temp3) { - $1 = &temp1; + $1 = &temp1; $2 = &temp2; - $3 = &temp3; + $3 = &temp3; + *$1 = 0; } %typemap(argout) (mx_uint *in_shape_size, const mx_uint **in_shape_ndim, const mx_uint ***in_shape_data), @@ -733,13 +760,14 @@ (mx_uint *aux_type_size, const int **aux_type_data) (mx_uint temp1, int *temp2) { - $1 = &temp1; + $1 = &temp1; $2 = &temp2; + *$1 = 0; } %typemap(argout) (mx_uint *in_type_size, const int **in_type_data), - (mx_uint *out_type_size, const int **out_type_data), - (mx_uint *aux_type_size, const int **aux_type_data) + (mx_uint *out_type_size, const int **out_type_data), + (mx_uint *aux_type_size, const int **aux_type_data) { if(!result && *arg11) @@ -757,6 +785,155 @@ } } +%typemap(in,numinputs=0) (mx_uint* num_in_args, + NDArrayHandle** in_args, + NDArrayHandle** arg_grads) + (mx_uint temp1, + NDArrayHandle* temp2, + NDArrayHandle* temp3) +{ + $1 = &temp1; + $2 = &temp2; + $3 = &temp3; + *$1 = 0; +} + +%typemap(argout) (mx_uint* num_in_args, + NDArrayHandle** in_args, + NDArrayHandle** arg_grads) +{ + if(!result) + { + AV *container1 = newAV(); + AV *container2 = newAV(); + for (int i = 0; i < *$1 ; i++) + { + av_push(container1, SvREFCNT_inc(SWIG_NewPointerObj(SWIG_as_voidptr((*$2)[i]), SWIGTYPE_p_MXNDArray, 0))); + av_push(container2, (*$3)[i] ? SvREFCNT_inc(SWIG_NewPointerObj(SWIG_as_voidptr((*$3)[i]), SWIGTYPE_p_MXNDArray, 0)) : newSV(0)); + } + $result = newRV_noinc((SV*)container1); + sv_2mortal($result); + argvi++; + $result = newRV_noinc((SV*)container2); + sv_2mortal($result); + argvi++; + } +} + +%typemap(in,numinputs=0) (mx_uint* num_aux_states, + NDArrayHandle** aux_states) + (mx_uint temp1, + NDArrayHandle* temp2) +{ + $1 = &temp1; + $2 = &temp2; + *$1 = 0; +} + +%typemap(argout) (mx_uint* num_aux_states, + NDArrayHandle** aux_states) +{ + if(!result) + { + AV *container = newAV(); + for (int i = 0; i < *$1 ; i++) + { + av_push(container, SvREFCNT_inc(SWIG_NewPointerObj(SWIG_as_voidptr((*$2)[i]), SWIGTYPE_p_MXNDArray, 0))); + } + $result = newRV_noinc((SV*)container); + sv_2mortal($result); + argvi++; + } +} + +%typemap(in) (int* shared_buffer_len, + const char** shared_buffer_name_list, + NDArrayHandle* shared_buffer_handle_list, + const char*** updated_shared_buffer_name_list, + NDArrayHandle** updated_shared_buffer_handle_list) + (int temp1, + char* temp2, + NDArrayHandle temp3, + char** temp4, + NDArrayHandle* temp5) +{ + HV *temphv; + char *key; + SV *val; + I32 len; + int res; + int i = 0; + int hash_len; + $1 = &temp1; + $2 = &temp2; + $3 = &temp3; + $4 = &temp4; + $5 = &temp5; + if (!SvROK($input)) + { + *$1 = -1; + $2 = NULL; + $3 = NULL; + } + else + { + if (SvTYPE(SvRV($input)) != SVt_PVHV) + croak("Argument $argnum is not a hash."); + temphv = (HV*)SvRV($input); + *$1 = hv_iterinit(temphv); + if(*$1) + { + $2 = (char**)safemalloc((*$1)*sizeof(char*)); + $3 = (void**)safemalloc((*$1)*sizeof(void*)); + while ((val = hv_iternextsv(temphv, &key, &len))) + { + $2[i] = key; + res = SWIG_ConvertPtr(val,SWIG_as_voidptrptr(&($3[i])), 0, 0); + if (!SWIG_IsOK(res)) { + SWIG_exception_fail(SWIG_ArgError(res), "in method '" "$symname" "', argument " "$argnum"" of type '" "NDArray""'"); + } + i++; + } + } + else + { + $2 = NULL; + $3 = NULL; + } + } +} + +%typemap(freearg) (int* shared_buffer_len, + const char** shared_buffer_name_list, + NDArrayHandle* shared_buffer_handle_list, + const char*** updated_shared_buffer_name_list, + NDArrayHandle** updated_shared_buffer_handle_list) +{ + Safefree($2); + Safefree($3); +} + +%typemap(argout) (int* shared_buffer_len, + const char** shared_buffer_name_list, + NDArrayHandle* shared_buffer_handle_list, + const char*** updated_shared_buffer_name_list, + NDArrayHandle** updated_shared_buffer_handle_list) + +{ + if(!result) + { + HV* hash = newHV(); + for(int j = 0; j < *$1; j++) + { + hv_store(hash, (*$4)[j], strlen((*$4)[j]), SvREFCNT_inc(SWIG_NewPointerObj(SWIG_as_voidptr((*$5)[j]), SWIGTYPE_p_MXNDArray, 0)), 0); + } + $result = newRV_noinc((SV*)hash); + sv_2mortal($result); + argvi++; + } +} + + %typemap(in) (uint32_t x) { union fbits u; diff --git a/perl-package/AI-NNVMCAPI/Changes b/perl-package/AI-NNVMCAPI/Changes index 6539ee0e4ef9..09395184e3c6 100644 --- a/perl-package/AI-NNVMCAPI/Changes +++ b/perl-package/AI-NNVMCAPI/Changes @@ -1,5 +1,8 @@ Revision history for Perl extension AI::NNVMCAPI. +1.01 Sat Jun 10 23:57:27 PDT 2017 + - sync with python. + 0.95 Sun Mar 26 17:42:02 PDT 2017 - visible on http://mxnet.io diff --git a/perl-package/AI-NNVMCAPI/META.json b/perl-package/AI-NNVMCAPI/META.json index 7c0329d4c359..42247c6b98ff 100644 --- a/perl-package/AI-NNVMCAPI/META.json +++ b/perl-package/AI-NNVMCAPI/META.json @@ -37,5 +37,5 @@ } }, "release_status" : "stable", - "version" : "0.95" + "version" : "1.01" } diff --git a/perl-package/AI-NNVMCAPI/META.yml b/perl-package/AI-NNVMCAPI/META.yml index d43e8ee22389..6d48cc7b8578 100644 --- a/perl-package/AI-NNVMCAPI/META.yml +++ b/perl-package/AI-NNVMCAPI/META.yml @@ -19,4 +19,4 @@ no_index: - inc requires: Test::More: '0' -version: '0.95' +version: '1.01' diff --git a/perl-package/AI-NNVMCAPI/README b/perl-package/AI-NNVMCAPI/README index fa7870d301ee..50579140de82 100644 --- a/perl-package/AI-NNVMCAPI/README +++ b/perl-package/AI-NNVMCAPI/README @@ -1,4 +1,4 @@ -AI-NNVMCAPI version 0.95 +AI-NNVMCAPI version 1.01 ===================== Swig interface to MXNet c api. diff --git a/perl-package/AI-NNVMCAPI/lib/AI/NNVMCAPI.pm b/perl-package/AI-NNVMCAPI/lib/AI/NNVMCAPI.pm index 577f7dad2faf..134d922b4d8d 100644 --- a/perl-package/AI-NNVMCAPI/lib/AI/NNVMCAPI.pm +++ b/perl-package/AI-NNVMCAPI/lib/AI/NNVMCAPI.pm @@ -1,7 +1,24 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + package AI::NNVMCAPI; use base qw(DynaLoader); bootstrap AI::NNVMCAPI; -our $VERSION = '0.95'; +our $VERSION = '1.01'; 1; __END__ diff --git a/perl-package/test.sh b/perl-package/test.sh new file mode 100755 index 000000000000..c8509c141920 --- /dev/null +++ b/perl-package/test.sh @@ -0,0 +1,32 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +MXNET_HOME=${PWD} +export LD_LIBRARY_PATH=${MXNET_HOME}/lib +export PERL5LIB=${MXNET_HOME}/perl5/lib/perl5 + +cd ${MXNET_HOME}/perl-package/AI-MXNetCAPI/ +perl Makefile.PL INSTALL_BASE=${MXNET_HOME}/perl5 +make install || exit -1 + +cd ${MXNET_HOME}/perl-package/AI-NNVMCAPI/ +perl Makefile.PL INSTALL_BASE=${MXNET_HOME}/perl5 +make install || exit -1 + +cd ${MXNET_HOME}/perl-package/AI-MXNet/ +perl Makefile.PL INSTALL_BASE=${MXNET_HOME}/perl5 +make test || exit -1 diff --git a/plugin/caffe/caffe_blob.cc b/plugin/caffe/caffe_blob.cc index c6d5156ffbc4..697efbfa99f2 100644 --- a/plugin/caffe/caffe_blob.cc +++ b/plugin/caffe/caffe_blob.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2016 by Contributors * \file caffe_blob.cc * \brief Implementations of SetDataGradToBlob given various device/dimension * \author Haoran Wang diff --git a/plugin/caffe/caffe_blob.h b/plugin/caffe/caffe_blob.h index 3037031ad991..666d269fdae1 100644 --- a/plugin/caffe/caffe_blob.h +++ b/plugin/caffe/caffe_blob.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2016 by Contributors * \file caffe_blob.h * \brief conversion between tensor and caffeBlob * \author Haoran Wang diff --git a/plugin/caffe/caffe_common.cc b/plugin/caffe/caffe_common.cc index 722b19138f79..53513a17d6c8 100644 --- a/plugin/caffe/caffe_common.cc +++ b/plugin/caffe/caffe_common.cc @@ -1,8 +1,26 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2016 by Contributors * \file caffe_common.h * \brief Common functions for caffeOp and caffeLoss symbols - * \author Haoran Wang + * \author Haoran Wang */ #include #include diff --git a/plugin/caffe/caffe_common.h b/plugin/caffe/caffe_common.h index 6ee3c26202b9..8565d9e2e27c 100644 --- a/plugin/caffe/caffe_common.h +++ b/plugin/caffe/caffe_common.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2016 by Contributors * \file caffe_common.h * \brief Common functions for caffeOp and caffeLoss symbols * \author Haoran Wang diff --git a/plugin/caffe/caffe_data_iter.cc b/plugin/caffe/caffe_data_iter.cc index ecf776270a91..2682298b4b3d 100644 --- a/plugin/caffe/caffe_data_iter.cc +++ b/plugin/caffe/caffe_data_iter.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file caffe_data_iter.cc * \brief register mnist iterator */ diff --git a/plugin/caffe/caffe_fieldentry.h b/plugin/caffe/caffe_fieldentry.h index a020cf9d7e77..47d246f4439f 100644 --- a/plugin/caffe/caffe_fieldentry.h +++ b/plugin/caffe/caffe_fieldentry.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2016 by Contributors * \file caffe_fieldentry.h * \brief Implement FieldEntry * \author Haoran Wang diff --git a/plugin/caffe/caffe_loss-inl.h b/plugin/caffe/caffe_loss-inl.h index 038ee1458bcd..37bfcf06be4a 100644 --- a/plugin/caffe/caffe_loss-inl.h +++ b/plugin/caffe/caffe_loss-inl.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2016 by Contributors * \file caffe_loss-inl.h * \brief Caffe Operator * \author Haoran Wang diff --git a/plugin/caffe/caffe_loss.cc b/plugin/caffe/caffe_loss.cc index a51f12602991..ce697d6c8ff9 100644 --- a/plugin/caffe/caffe_loss.cc +++ b/plugin/caffe/caffe_loss.cc @@ -1,8 +1,26 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2016 by Contributors * \file caffe_loss.cc - * \brief caffe loss - * \author Haoran Wang + * \brief caffe loss + * \author Haoran Wang */ #include "./caffe_loss-inl.h" diff --git a/plugin/caffe/caffe_loss.cu b/plugin/caffe/caffe_loss.cu index 55489cafc316..2002cf2c661d 100644 --- a/plugin/caffe/caffe_loss.cu +++ b/plugin/caffe/caffe_loss.cu @@ -1,8 +1,26 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2016 by Contributors * \file caffe_loss_gpu.cc - * \brief caffe loss - * \author Haoran Wang + * \brief caffe loss + * \author Haoran Wang */ #include "./caffe_loss-inl.h" diff --git a/plugin/caffe/caffe_op-inl.h b/plugin/caffe/caffe_op-inl.h index 1950865b76c3..43b9b5a091af 100644 --- a/plugin/caffe/caffe_op-inl.h +++ b/plugin/caffe/caffe_op-inl.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2016 by Contributors * \file caffe_op-inl.h * \brief Caffe Operator * \author Haoran Wang diff --git a/plugin/caffe/caffe_op.cc b/plugin/caffe/caffe_op.cc index 90cb4da44b0f..5198ccaac7c9 100644 --- a/plugin/caffe/caffe_op.cc +++ b/plugin/caffe/caffe_op.cc @@ -1,8 +1,26 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2016 by Contributors * \file caffe_op.cc * \brief caffe operator - * \author Haoran Wang + * \author Haoran Wang */ #include "./caffe_op-inl.h" namespace mxnet { diff --git a/plugin/caffe/caffe_op.cu b/plugin/caffe/caffe_op.cu index c52f2b69fb63..be6c20a1084f 100644 --- a/plugin/caffe/caffe_op.cu +++ b/plugin/caffe/caffe_op.cu @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2016 by Contributors * \file caffe_operator_gpu.cc * \brief caffe operator * \author Haoran Wang diff --git a/plugin/caffe/caffe_stream.cc b/plugin/caffe/caffe_stream.cc index 99202bf9c09c..03badda65ca2 100644 --- a/plugin/caffe/caffe_stream.cc +++ b/plugin/caffe/caffe_stream.cc @@ -1,8 +1,26 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2016 by Contributors * \file caffe_stream.cc * \brief define stream opertors >> and << - * \author Haoran Wang + * \author Haoran Wang */ #include"caffe_stream.h" diff --git a/plugin/caffe/caffe_stream.h b/plugin/caffe/caffe_stream.h index de9edb84feb4..b9a08d028f38 100644 --- a/plugin/caffe/caffe_stream.h +++ b/plugin/caffe/caffe_stream.h @@ -1,8 +1,26 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2016 by Contributors * \file caffe_stream.h * \brief define stream opertors >> and << - * \author Haoran Wang + * \author Haoran Wang */ #ifndef PLUGIN_CAFFE_CAFFE_STREAM_H_ #define PLUGIN_CAFFE_CAFFE_STREAM_H_ diff --git a/plugin/opencv/__init__.py b/plugin/opencv/__init__.py index 072575177e41..bcf6d1ebc969 100644 --- a/plugin/opencv/__init__.py +++ b/plugin/opencv/__init__.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # coding: utf-8 # pylint: disable=wildcard-import diff --git a/plugin/opencv/cv_api.cc b/plugin/opencv/cv_api.cc index 78bec01548d4..b0bcbbce203e 100644 --- a/plugin/opencv/cv_api.cc +++ b/plugin/opencv/cv_api.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2016 by Contributors * \file cv_api.h * \brief C API for opencv * \author Junyuan Xie diff --git a/plugin/opencv/cv_api.h b/plugin/opencv/cv_api.h index fc224d0e1d05..e04357bf30b7 100644 --- a/plugin/opencv/cv_api.h +++ b/plugin/opencv/cv_api.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2016 by Contributors * \file cv_api.h * \brief C API for opencv * \author Junyuan Xie diff --git a/plugin/opencv/opencv.py b/plugin/opencv/opencv.py index 43b73b615cc8..52138af00660 100644 --- a/plugin/opencv/opencv.py +++ b/plugin/opencv/opencv.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # coding: utf-8 # pylint: disable=too-many-arguments,no-member,invalid-name diff --git a/plugin/sframe/iter_sframe.cc b/plugin/sframe/iter_sframe.cc index d91c0ded1d88..2a987e2b10f2 100644 --- a/plugin/sframe/iter_sframe.cc +++ b/plugin/sframe/iter_sframe.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file iter_sframe_image.cc * \brief * \author Bing Xu diff --git a/plugin/torch/torch_base.cc b/plugin/torch/torch_base.cc index af102d672f9e..89f832ccdfae 100644 --- a/plugin/torch/torch_base.cc +++ b/plugin/torch/torch_base.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2016 by Contributors * \file torch_base.cc * \brief torch_state * \author Junyuan Xie diff --git a/plugin/torch/torch_base.h b/plugin/torch/torch_base.h index 0a7d22f8fc05..3aaaa2f13902 100644 --- a/plugin/torch/torch_base.h +++ b/plugin/torch/torch_base.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file torch_base.h * \brief Torch interface. * \author Junyuan Xie @@ -125,11 +143,11 @@ class TorchTensor { } static const char* TensorType(TBlob data) { - return TensorType(data.dev_mask_); + return TensorType(data.dev_mask()); } static const char* ModuleType(TBlob data) { - return TensorType(data.dev_mask_); + return TensorType(data.dev_mask()); } static THGeneralTensor TBlobToTHTensor(TorchState* torchState, TBlob data) { @@ -140,7 +158,7 @@ class TorchTensor { THLongStorage_set(thshape, i, data.shape_[i]); } CHECK_EQ(data.type_flag_, mshadow::kFloat32) << "Torch Interface only support float32"; - switch (data.dev_mask_) { + switch (data.dev_mask()) { case cpu::kDevMask: { THFloatStorage* storage = THFloatStorage_newWithData(static_cast(data.dptr_), size); @@ -191,7 +209,7 @@ class TorchTensor { static void SetInternal(TorchState* torchState, THGeneralTensor tensor, const TBlob& blob) { size_t size = blob.Size(); - switch (blob.dev_mask_) { + switch (blob.dev_mask()) { case cpu::kDevMask: { THFloatStorage* storage = THFloatStorage_newWithData(static_cast(blob.dptr_), size); @@ -216,7 +234,7 @@ class TorchTensor { } #endif default: - LOG(FATAL) << "Unknown device type " << blob.dev_mask_; + LOG(FATAL) << "Unknown device type " << blob.dev_mask(); } } @@ -249,7 +267,7 @@ class TorchTensor { static void CopyIfDifferent(TorchState* torchState, TBlob dst, THGeneralTensor th_dst) { lua_State* L = torchState->L; if (luaT_isudata(L, -1, TorchTensor::TensorType(cpu::kDevMask))) { - CHECK_EQ(dst.dev_mask_, cpu::kDevMask) << "Device type mismatch."; + CHECK_EQ(dst.dev_mask(), cpu::kDevMask) << "Device type mismatch."; THFloatTensor* src = static_cast( luaT_toudata(L, -1, TorchTensor::TensorType(cpu::kDevMask))); if (src->storage != static_cast(th_dst)->storage) { @@ -257,7 +275,7 @@ class TorchTensor { } #if MXNET_USE_CUDA } else if (luaT_isudata(L, -1, TorchTensor::TensorType(gpu::kDevMask))) { - CHECK_EQ(dst.dev_mask_, gpu::kDevMask) << "Device type mismatch."; + CHECK_EQ(dst.dev_mask(), gpu::kDevMask) << "Device type mismatch."; THCudaTensor* src = static_cast( luaT_toudata(L, -1, TorchTensor::TensorType(gpu::kDevMask))); if (src->storage != static_cast(th_dst)->storage) { diff --git a/plugin/torch/torch_criterion-inl.h b/plugin/torch/torch_criterion-inl.h index 174ebf2dd9dd..7f592f156226 100644 --- a/plugin/torch/torch_criterion-inl.h +++ b/plugin/torch/torch_criterion-inl.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file torch_module-inl.h * \brief torch module operator * \author Min Lin diff --git a/plugin/torch/torch_criterion.cc b/plugin/torch/torch_criterion.cc index a54be46a936d..bdfb2f42e61a 100644 --- a/plugin/torch/torch_criterion.cc +++ b/plugin/torch/torch_criterion.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file activation.cc * \brief activation op * \author Junyuan Xie diff --git a/plugin/torch/torch_criterion.cu b/plugin/torch/torch_criterion.cu index 57730a0bd88b..68c519c7c9f1 100644 --- a/plugin/torch/torch_criterion.cu +++ b/plugin/torch/torch_criterion.cu @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file activation.cc * \brief activation op * \author Bing Xu diff --git a/plugin/torch/torch_function.cc b/plugin/torch/torch_function.cc index b47ab56f68d2..a1c5ff578da7 100644 --- a/plugin/torch/torch_function.cc +++ b/plugin/torch/torch_function.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2016 by Contributors * \file torch_base.cc * \brief torch_state * \author Junyuan Xie diff --git a/plugin/torch/torch_function.h b/plugin/torch/torch_function.h index 0151d5aa9925..8fb2ccfde454 100644 --- a/plugin/torch/torch_function.h +++ b/plugin/torch/torch_function.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file torch_function.h * \brief Torch interface. * \author Junyuan Xie diff --git a/plugin/torch/torch_module-inl.h b/plugin/torch/torch_module-inl.h index 31138fdcf16b..15b569fbbeef 100644 --- a/plugin/torch/torch_module-inl.h +++ b/plugin/torch/torch_module-inl.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file torch_module-inl.h * \brief torch module operator * \author Min Lin diff --git a/plugin/torch/torch_module.cc b/plugin/torch/torch_module.cc index 81dc481c6632..658669fb419c 100644 --- a/plugin/torch/torch_module.cc +++ b/plugin/torch/torch_module.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file activation.cc * \brief activation op * \author Bing Xu diff --git a/plugin/torch/torch_module.cu b/plugin/torch/torch_module.cu index a298a23ae37e..caf9eb19911a 100644 --- a/plugin/torch/torch_module.cu +++ b/plugin/torch/torch_module.cu @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file activation.cc * \brief activation op * \author Bing Xu diff --git a/plugin/warpctc/warpctc-inl.h b/plugin/warpctc/warpctc-inl.h index a5b570b76cf9..d492656b2f15 100644 --- a/plugin/warpctc/warpctc-inl.h +++ b/plugin/warpctc/warpctc-inl.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file warpctc-inl.h * \brief warpctc operator * \author Liang Xiang @@ -121,16 +139,16 @@ class WarpCTCOp : public Operator { TBlob label = in_data[warpctc_enum::kLabel]; CHECK_EQ(data.shape_.ndim(), 2) << "input data shape should be 2 (t*n, p)"; ctcOptions info; //please updated to latest baidu/warp-ctc NOLINT(*) - if (data.dev_mask_ == cpu::kDevMask) { + if (data.dev_mask() == cpu::kDevMask) { info.loc = CTC_CPU; info.num_threads = 1; - } else if (data.dev_mask_ == gpu::kDevMask) { + } else if (data.dev_mask() == gpu::kDevMask) { #if MXNET_USE_CUDA info.loc = CTC_GPU; info.stream = ctx.get_stream()->stream_; } else { #endif - LOG(FATAL) << "Unknown device type " << data.dev_mask_; + LOG(FATAL) << "Unknown device type " << data.dev_mask(); } info.blank_label = 0; @@ -149,7 +167,7 @@ class WarpCTCOp : public Operator { int* flat_labels = static_cast(label.dptr_); int* cpu_raw_labels = flat_labels; float* grads = static_cast(in_grad[warpctc_enum::kData].dptr_); - if (data.dev_mask_ == gpu::kDevMask) { + if (data.dev_mask() == gpu::kDevMask) { #if MXNET_USE_CUDA cpu_raw_labels = reinterpret_cast(malloc(sizeof(int) * label.Size())); cuda_status = cudaMemcpyAsync(cpu_raw_labels, flat_labels, @@ -193,9 +211,9 @@ class WarpCTCOp : public Operator { info), "Error: compute_ctc_loss"); - if (data.dev_mask_ == cpu::kDevMask) { + if (data.dev_mask() == cpu::kDevMask) { free(cpu_labels); - } else if (data.dev_mask_ == gpu::kDevMask) { + } else if (data.dev_mask() == gpu::kDevMask) { #if MXNET_USE_CUDA free(cpu_raw_labels); free(cpu_labels); diff --git a/plugin/warpctc/warpctc.cc b/plugin/warpctc/warpctc.cc index db88a3316c7e..0ff61be758c7 100644 --- a/plugin/warpctc/warpctc.cc +++ b/plugin/warpctc/warpctc.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file warpctc.cc * \brief warpctc op * \author Liang Xiang diff --git a/plugin/warpctc/warpctc.cu b/plugin/warpctc/warpctc.cu index 186c4d0c18f4..7562a12a3c9d 100644 --- a/plugin/warpctc/warpctc.cu +++ b/plugin/warpctc/warpctc.cu @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file warpctc.cc * \brief warpctc op * \author Liang Xiang diff --git a/prepare_mkl.sh b/prepare_mkl.sh index ac799b5cd74c..9769731f5396 100755 --- a/prepare_mkl.sh +++ b/prepare_mkl.sh @@ -1,4 +1,22 @@ #!/bin/bash + +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # set -ex # # All modification made by Intel Corporation: © 2016 Intel Corporation @@ -57,10 +75,10 @@ MXNET_ROOT=`dirname $0` USE_MKLML=0 # NOTE: if you update the following line, please also update the dockerfile at # tests/ci_build/Dockerfile.mkl -VERSION_MATCH=20170210 -ARCHIVE_BASENAME=mklml_lnx_2017.0.2.20170209.tgz +VERSION_MATCH=20170425 +ARCHIVE_BASENAME=mklml_lnx_2018.0.20170425.tgz MKL_CONTENT_DIR=`echo $ARCHIVE_BASENAME | rev | cut -d "." -f 2- | rev` -MKLURL="https://github.com/dmlc/web-data/raw/master/mxnet/mklml-release/$ARCHIVE_BASENAME" +MKLURL="https://github.com/01org/mkl-dnn/releases/download/v0.7/$ARCHIVE_BASENAME" # there are diffrent MKL lib to be used for GCC and for ICC reg='^[0-9]+$' VERSION_LINE=`GetVersionName $MKLROOT` diff --git a/python/mxnet/__init__.py b/python/mxnet/__init__.py index 5e27cd5b70d5..3c3ce76a9284 100644 --- a/python/mxnet/__init__.py +++ b/python/mxnet/__init__.py @@ -1,4 +1,22 @@ #!/usr/bin/env python + +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # coding: utf-8 """MXNet: a concise, fast and flexible framework for deep learning.""" from __future__ import absolute_import @@ -23,6 +41,7 @@ from . import random from . import optimizer from . import model +from . import notebook from . import initializer # use mx.init as short for mx.initializer from . import initializer as init @@ -59,4 +78,6 @@ from . import rnn +from . import gluon + __version__ = base.__version__ diff --git a/python/mxnet/_ctypes/__init__.py b/python/mxnet/_ctypes/__init__.py index 2708cc5c1367..a9433ed06670 100644 --- a/python/mxnet/_ctypes/__init__.py +++ b/python/mxnet/_ctypes/__init__.py @@ -1 +1,18 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + "ctypes module" diff --git a/python/mxnet/_ctypes/ndarray.py b/python/mxnet/_ctypes/ndarray.py index 786b134befa6..5a50f80498ec 100644 --- a/python/mxnet/_ctypes/ndarray.py +++ b/python/mxnet/_ctypes/ndarray.py @@ -1,7 +1,24 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # coding: utf-8 # pylint: disable=invalid-name, protected-access, too-many-arguments # pylint: disable=global-statement, unused-import -"""Symbolic configuration API.""" +"""NDArray configuration API.""" from __future__ import absolute_import as _abs import ctypes @@ -10,11 +27,10 @@ from ..base import _LIB from ..base import c_array, py_str, c_str, mx_uint, _Null -from ..base import NDArrayHandle, OpHandle +from ..base import NDArrayHandle, OpHandle, CachedOpHandle from ..base import check_call from ..ndarray_doc import _build_doc -_ndarray_cls = None class NDArrayBase(object): """Base data structure for ndarray""" @@ -40,123 +56,16 @@ def __reduce__(self): return (_ndarray_cls, (None,), self.__getstate__()) -# pylint: disable=too-many-locals, invalid-name -def _make_ndarray_function(handle, name): - """Create a NDArray function from the FunctionHandle.""" - real_name = ctypes.c_char_p() - desc = ctypes.c_char_p() - num_args = mx_uint() - arg_names = ctypes.POINTER(ctypes.c_char_p)() - arg_types = ctypes.POINTER(ctypes.c_char_p)() - arg_descs = ctypes.POINTER(ctypes.c_char_p)() - key_var_num_args = ctypes.c_char_p() - ret_type = ctypes.c_char_p() - - check_call(_LIB.MXSymbolGetAtomicSymbolInfo( - handle, ctypes.byref(real_name), ctypes.byref(desc), - ctypes.byref(num_args), - ctypes.byref(arg_names), - ctypes.byref(arg_types), - ctypes.byref(arg_descs), - ctypes.byref(key_var_num_args), - ctypes.byref(ret_type))) - narg = int(num_args.value) - arg_names = [py_str(arg_names[i]) for i in range(narg)] - arg_types = [py_str(arg_types[i]) for i in range(narg)] - func_name = name - key_var_num_args = py_str(key_var_num_args.value) - ret_type = py_str(ret_type.value) if ret_type.value is not None else '' - doc_str = _build_doc(func_name, - py_str(desc.value), - arg_names, - arg_types, - [py_str(arg_descs[i]) for i in range(narg)], - key_var_num_args, - ret_type) - - dtype_name = None - arr_name = None - ndsignature = [] - signature = [] - ndarg_names = [] - kwarg_names = [] - for i in range(narg): - name, atype = arg_names[i], arg_types[i] - if name == 'dtype': - dtype_name = name - signature.append('%s=_Null'%name) - elif atype.startswith('NDArray') or atype.startswith('Symbol'): - assert not arr_name, \ - "Op can only have one argument with variable " \ - "size and it must be the last argument." - if atype.endswith('[]'): - ndsignature.append('*%s'%name) - arr_name = name - else: - ndsignature.append('%s=None'%name) - ndarg_names.append(name) - else: - signature.append('%s=_Null'%name) - kwarg_names.append(name) - #signature.append('is_train=False') - signature.append('out=None') - signature.append('name=None') - signature.append('**kwargs') - signature = ndsignature + signature - - code = [] - if arr_name: - code.append(""" -def %s(*%s, **kwargs):"""%(func_name, arr_name)) - code.append(""" - ndargs = [] - for i in {}: - assert isinstance(i, NDArrayBase), \\ - "Positional arguments must have NDArray type, " \\ - "but got %s"%str(type(i)) - ndargs.append(i.handle)""".format(arr_name)) - if dtype_name is not None: - code.append(""" - if '%s' in kwargs: - kwargs['%s'] = np.dtype(kwargs['%s']).name"""%( - dtype_name, dtype_name, dtype_name)) - code.append(""" - try: - kwargs.pop('name') - except: - pass - out = kwargs.pop('out', None) - keys = list(kwargs.keys()) - vals = [str(i) for i in kwargs.values()]""") - else: - code.append(""" -def %s(%s): - ndargs = [] - keys = list(kwargs.keys()) - vals = [str(i) for i in kwargs.values()]"""%(func_name, ', '.join(signature))) - # NDArray args - for name in ndarg_names: - code.append(""" - if {name} is not None: - assert isinstance({name}, NDArrayBase), \\ - "Argument {name} must have NDArray type, but got %s"%str(type({name})) - ndargs.append({name}.handle)""".format(name=name)) - # kwargs - for name in kwarg_names: - code.append(""" - if %s is not _Null: - keys.append('%s') - vals.append(str(%s))"""%(name, name, name)) - # dtype - if dtype_name is not None: - code.append(""" - if %s is not _Null: - keys.append('%s') - vals.append(np.dtype(%s).name)"""%(dtype_name, dtype_name, dtype_name)) - - # output - code.append(""" - global handle +_ndarray_cls = None + +def _set_ndarray_class(cls): + """Set the symbolic class to be cls""" + global _ndarray_cls + _ndarray_cls = cls + + +def _imperative_invoke(handle, ndargs, keys, vals, out): + """ctypes implementation of imperative invoke wrapper""" if out is not None: original_output = out if isinstance(out, NDArrayBase): @@ -170,14 +79,15 @@ def %s(%s): num_output = ctypes.c_int(0) check_call(_LIB.MXImperativeInvoke( - ctypes.c_void_p(%d), + ctypes.c_void_p(handle), ctypes.c_int(len(ndargs)), - c_array(NDArrayHandle, ndargs), + c_array(NDArrayHandle, [arr.handle for arr in ndargs]), ctypes.byref(num_output), ctypes.byref(output_vars), ctypes.c_int(len(keys)), c_array(ctypes.c_char_p, [c_str(key) for key in keys]), - c_array(ctypes.c_char_p, [c_str(val) for val in vals]))) + c_array(ctypes.c_char_p, [c_str(str(val)) for val in vals]))) + if original_output is not None: return original_output if num_output.value == 1: @@ -185,48 +95,50 @@ def %s(%s): else: return [_ndarray_cls(ctypes.cast(output_vars[i], NDArrayHandle)) for i in range(num_output.value)] -"""%handle.value) - - local = {} - exec(''.join(code), None, local) # pylint: disable=exec-used - ndarray_function = local[func_name] - ndarray_function.__name__ = func_name - ndarray_function.__doc__ = doc_str - ndarray_function.__module__ = 'mxnet.ndarray' - return ndarray_function -def _set_ndarray_class(cls): - """Set the symbolic class to be cls""" - global _ndarray_cls - _ndarray_cls = cls - +class CachedOp(object): + """Cached operator handle.""" + __slots__ = ["handle"] + def __init__(self, sym): + self.handle = CachedOpHandle() + check_call(_LIB.MXCreateCachedOp( + sym.handle, + ctypes.byref(self.handle))) -# pylint: enable=too-many-locals, invalid-name -def _init_ndarray_module(ndarray_class, root_namespace): - """List and add all the ndarray functions to current module.""" - _set_ndarray_class(ndarray_class) - plist = ctypes.POINTER(ctypes.c_char_p)() - size = ctypes.c_uint() - - check_call(_LIB.MXListAllOpNames(ctypes.byref(size), - ctypes.byref(plist))) - op_names = [] - for i in range(size.value): - op_names.append(py_str(plist[i])) - - module_obj = _sys.modules["%s.ndarray" % root_namespace] - module_internal = _sys.modules["%s._ndarray_internal" % root_namespace] - module_contrib = _sys.modules["%s.contrib.ndarray" % root_namespace] - for name in op_names: - hdl = OpHandle() - check_call(_LIB.NNGetOpHandle(c_str(name), ctypes.byref(hdl))) - function = _make_ndarray_function(hdl, name) - if function.__name__.startswith('_contrib_'): - function.__name__ = function.__name__[9:] - function.__module__ = 'mxnet.contrib.ndarray' - setattr(module_contrib, function.__name__, function) - elif function.__name__.startswith('_'): - setattr(module_internal, function.__name__, function) + def __del__(self): + check_call(_LIB.MXFreeCachedOp(self.handle)) + + def __call__(self, *args, **kwargs): + """ctypes implementation of imperative invoke wrapper""" + out = kwargs.pop('out', None) + if out is not None: + original_output = out + if isinstance(out, NDArrayBase): + out = (out,) + num_output = ctypes.c_int(len(out)) + output_vars = c_array(NDArrayHandle, [i.handle for i in out]) + output_vars = ctypes.cast(output_vars, ctypes.POINTER(NDArrayHandle)) + else: + original_output = None + output_vars = ctypes.POINTER(NDArrayHandle)() + num_output = ctypes.c_int(0) + if kwargs: + raise TypeError( + "CachedOp.__call__ got unexpected keyword argument(s): " + \ + ', '.join(kwargs.keys())) + + check_call(_LIB.MXInvokeCachedOp( + self.handle, + ctypes.c_int(len(args)), + c_array(NDArrayHandle, [arr.handle for arr in args]), + ctypes.byref(num_output), + ctypes.byref(output_vars))) + + if original_output is not None: + return original_output + if num_output.value == 1: + return _ndarray_cls(ctypes.cast(output_vars[0], NDArrayHandle)) else: - setattr(module_obj, function.__name__, function) + return [_ndarray_cls(ctypes.cast(output_vars[i], NDArrayHandle)) + for i in range(num_output.value)] diff --git a/python/mxnet/_ctypes/symbol.py b/python/mxnet/_ctypes/symbol.py index 00d935d4b0be..3ec2ddcdc548 100644 --- a/python/mxnet/_ctypes/symbol.py +++ b/python/mxnet/_ctypes/symbol.py @@ -1,18 +1,30 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # coding: utf-8 # pylint: disable=invalid-name, protected-access, too-many-arguments, global-statement """Symbolic configuration API.""" from __future__ import absolute_import as _abs import ctypes -import sys -import numpy as _numpy from ..base import _LIB -from ..base import c_array, c_str, mx_uint, py_str -from ..base import SymbolHandle, OpHandle +from ..base import c_array, c_str, mx_uint +from ..base import SymbolHandle from ..base import check_call -from ..symbol_doc import _build_doc -from ..name import NameManager -from ..attribute import AttrScope _symbol_cls = None @@ -67,7 +79,7 @@ def _compose(self, *args, **kwargs): num_args = len(args) + len(kwargs) if len(kwargs) != 0: - keys = c_array(ctypes.c_char_p, [c_str(key) for key in kwargs.keys()]) + keys = c_array(ctypes.c_char_p, [c_str(key) for key in kwargs]) args = c_array(SymbolHandle, [s.handle for s in kwargs.values()]) else: keys = None @@ -84,7 +96,7 @@ def _set_attr(self, **kwargs): The attributes to set """ keys = c_array(ctypes.c_char_p, - [c_str(key) for key in kwargs.keys()]) + [c_str(key) for key in kwargs]) vals = c_array(ctypes.c_char_p, [c_str(str(val)) for val in kwargs.values()]) num_args = mx_uint(len(kwargs)) @@ -105,122 +117,24 @@ def _set_symbol_class(cls): _symbol_cls = cls -def _make_atomic_symbol_function(handle, name): - """Create an atomic symbol function by handle and funciton name.""" - real_name = ctypes.c_char_p() - desc = ctypes.c_char_p() - num_args = mx_uint() - arg_names = ctypes.POINTER(ctypes.c_char_p)() - arg_types = ctypes.POINTER(ctypes.c_char_p)() - arg_descs = ctypes.POINTER(ctypes.c_char_p)() - key_var_num_args = ctypes.c_char_p() - ret_type = ctypes.c_char_p() - - check_call(_LIB.MXSymbolGetAtomicSymbolInfo( - handle, ctypes.byref(real_name), ctypes.byref(desc), - ctypes.byref(num_args), - ctypes.byref(arg_names), - ctypes.byref(arg_types), - ctypes.byref(arg_descs), - ctypes.byref(key_var_num_args), - ctypes.byref(ret_type))) - narg = int(num_args.value) - func_name = name - key_var_num_args = py_str(key_var_num_args.value) - ret_type = py_str(ret_type.value) if ret_type.value is not None else '' - doc_str = _build_doc(func_name, - py_str(desc.value), - [py_str(arg_names[i]) for i in range(narg)], - [py_str(arg_types[i]) for i in range(narg)], - [py_str(arg_descs[i]) for i in range(narg)], - key_var_num_args, - ret_type) - - def creator(*args, **kwargs): - """Activation Operator of Neural Net. - The parameters listed below can be passed in as keyword arguments. - - Parameters - ---------- - name : string, required. - Name of the resulting symbol. - - Returns - ------- - symbol: Symbol - the resulting symbol - """ - param_keys = [] - param_vals = [] - symbol_kwargs = {} - - attr = kwargs.pop('attr', None) - kwargs.update(AttrScope.current.get(attr)) - name = kwargs.pop('name', None) - if 'dtype' in kwargs: - kwargs['dtype'] = _numpy.dtype(kwargs['dtype']).name - - if key_var_num_args and key_var_num_args not in kwargs: - param_keys.append(c_str(key_var_num_args)) - param_vals.append(c_str(str(len(args)))) - - for k, v in kwargs.items(): - if isinstance(v, SymbolBase): - symbol_kwargs[k] = v - else: - param_keys.append(c_str(k)) - param_vals.append(c_str(str(v))) - # create atomic symbol - param_keys = c_array(ctypes.c_char_p, param_keys) - param_vals = c_array(ctypes.c_char_p, param_vals) - sym_handle = SymbolHandle() - check_call(_LIB.MXSymbolCreateAtomicSymbol( - handle, - mx_uint(len(param_keys)), - param_keys, param_vals, - ctypes.byref(sym_handle))) - - if len(args) != 0 and len(symbol_kwargs) != 0: - raise TypeError( - '%s can only accept input' - 'Symbols either as positional or keyword arguments, not both' % func_name) - s = _symbol_cls(sym_handle) - - hint = func_name.lower() - name = NameManager.current.get(name, hint) - s._compose(*args, name=name, **symbol_kwargs) - return s - - creator.__name__ = func_name - creator.__doc__ = doc_str - creator.__module__ = 'mxnet.symbol' - return creator - - -def _init_symbol_module(symbol_class, root_namespace): - """List and add all the atomic symbol functions to current module.""" - _set_symbol_class(symbol_class) - plist = ctypes.POINTER(ctypes.c_char_p)() - size = ctypes.c_uint() - - check_call(_LIB.MXListAllOpNames(ctypes.byref(size), - ctypes.byref(plist))) - op_names = [] - for i in range(size.value): - op_names.append(py_str(plist[i])) - - module_obj = sys.modules["%s.symbol" % root_namespace] - module_internal = sys.modules["%s._symbol_internal" % root_namespace] - module_contrib = sys.modules["%s.contrib.symbol" % root_namespace] - for name in op_names: - hdl = OpHandle() - check_call(_LIB.NNGetOpHandle(c_str(name), ctypes.byref(hdl))) - function = _make_atomic_symbol_function(hdl, name) - if function.__name__.startswith('_contrib_'): - function.__name__ = function.__name__[9:] - function.__module__ = 'mxnet.contrib.symbol' - setattr(module_contrib, function.__name__, function) - elif function.__name__.startswith('_'): - setattr(module_internal, function.__name__, function) - else: - setattr(module_obj, function.__name__, function) +def _symbol_creator(handle, args, kwargs, keys, vals, name): + sym_handle = SymbolHandle() + check_call(_LIB.MXSymbolCreateAtomicSymbol( + ctypes.c_void_p(handle), + mx_uint(len(keys)), + c_array(ctypes.c_char_p, [c_str(i) for i in keys]), + c_array(ctypes.c_char_p, [c_str(str(i)) for i in vals]), + ctypes.byref(sym_handle))) + + if args and kwargs: + raise TypeError( + 'Operators with variable length input can only accept input' + 'Symbols either as positional or keyword arguments, not both') + s = _symbol_cls(sym_handle) + if args: + s._compose(*args, name=name) + elif kwargs: + s._compose(name=name, **kwargs) + else: + s._compose(name=name) + return s diff --git a/python/mxnet/_cy2/__init__.py b/python/mxnet/_cy2/__init__.py index 910cbe2e586b..1961cd9ff613 100644 --- a/python/mxnet/_cy2/__init__.py +++ b/python/mxnet/_cy2/__init__.py @@ -1 +1,18 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + """Namespace for cython generated modules for python2""" diff --git a/python/mxnet/_cy3/__init__.py b/python/mxnet/_cy3/__init__.py index e89f266a0535..44dcca5ac003 100644 --- a/python/mxnet/_cy3/__init__.py +++ b/python/mxnet/_cy3/__init__.py @@ -1 +1,18 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + """Namespace for cython generated modules for python3""" diff --git a/python/mxnet/_ndarray_internal.py b/python/mxnet/_ndarray_internal.py index 52ec16df4a8a..8f151f1b5b64 100644 --- a/python/mxnet/_ndarray_internal.py +++ b/python/mxnet/_ndarray_internal.py @@ -1 +1,18 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + """NDArray namespace used to register internal functions.""" diff --git a/python/mxnet/_symbol_internal.py b/python/mxnet/_symbol_internal.py index 58a8e4b2658a..cd6ae41c2a19 100644 --- a/python/mxnet/_symbol_internal.py +++ b/python/mxnet/_symbol_internal.py @@ -1 +1,18 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + """Symbol namespace used to register internal functions.""" diff --git a/python/mxnet/attribute.py b/python/mxnet/attribute.py index b8604024efa4..15d38f81f2e3 100644 --- a/python/mxnet/attribute.py +++ b/python/mxnet/attribute.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # coding: utf-8 """Attribute scoping support for symbolic API.""" from __future__ import absolute_import diff --git a/python/mxnet/autograd.py b/python/mxnet/autograd.py new file mode 100644 index 000000000000..292bcc2308fc --- /dev/null +++ b/python/mxnet/autograd.py @@ -0,0 +1,430 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +# coding: utf-8 +"""Autograd for NDArray.""" +from __future__ import absolute_import +from __future__ import division + +from threading import Lock +import traceback +import ctypes +from ctypes import c_int, c_void_p, CFUNCTYPE, POINTER, cast +from .base import _LIB, check_call, string_types +from .base import mx_uint, NDArrayHandle, c_array, MXCallbackList, SymbolHandle +from .ndarray import NDArray +from .symbol import _GRAD_REQ_MAP, Symbol + + +def set_recording(is_recording): #pylint: disable=redefined-outer-name + """Set status to recording/not recording. When recording, graph will be constructed + for gradient computation. + + Parameters + ---------- + is_recording: bool + + Returns + ------- + previous state before this set. + """ + prev = ctypes.c_int() + check_call(_LIB.MXAutogradSetIsRecording( + ctypes.c_int(is_recording), ctypes.byref(prev))) + return bool(prev.value) + +def set_training(train_mode): #pylint: disable=redefined-outer-name + """Set status to training/predicting. This affects ctx.is_train in operator + running context. For example, Dropout will drop inputs randomly when + train_mode=True while simply passing through if train_mode=False. + + Parameters + ---------- + train_mode: bool + + Returns + ------- + previous state before this set. + """ + prev = ctypes.c_int() + check_call(_LIB.MXAutogradSetIsTraining( + ctypes.c_int(train_mode), ctypes.byref(prev))) + return bool(prev.value) + +def is_recording(): + """Get status on recording/not recording. + + Returns + ------- + Current state of recording. + """ + curr = ctypes.c_bool() + check_call(_LIB.MXAutogradIsRecording(ctypes.byref(curr))) + return curr.value + +def is_training(): + """Get status on training/predicting. + + Returns + ------- + Current state of training/predicting. + """ + curr = ctypes.c_bool() + check_call(_LIB.MXAutogradIsTraining(ctypes.byref(curr))) + return curr.value + + +class _RecordingStateScope(object): + """Scope for managing training state. + + Example:: + + with _RecordingStateScope(True, True): + y = model(x) + backward([y]) + + """ + def __init__(self, is_record, train_mode): #pylint: disable=redefined-outer-name + self._enter_is_record = is_record + self._enter_train_mode = train_mode + self._prev_is_record = None + self._prev_train_mode = None + + def __enter__(self): + if self._enter_is_record is not None: + self._prev_is_record = set_recording(self._enter_is_record) + if self._enter_train_mode is not None: + self._prev_train_mode = set_training(self._enter_train_mode) + + def __exit__(self, ptype, value, trace): + if self._enter_is_record is not None and self._prev_is_record != self._enter_is_record: + set_recording(self._prev_is_record) + if self._enter_train_mode is not None and self._prev_train_mode != self._enter_train_mode: + set_training(self._prev_train_mode) + + +def record(train_mode=True): #pylint: disable=redefined-outer-name + """Returns an autograd recording scope context to be used in 'with' statement + and captures code that needs gradients to be calculated. + + .. note:: When forwarding with train_mode=False, the corresponding backward + should also use train_mode=False, otherwise gradient is undefined. + + Example:: + + with autograd.record(): + y = model(x) + backward([y]) + metric.update(...) + optim.step(...) + + Parameters + ---------- + train_mode: bool, default True + Whether the forward pass is in training or predicting mode. This controls the behavior + of some layers such as Dropout, BatchNorm. + """ + return _RecordingStateScope(True, train_mode) + + +def pause(train_mode=False): #pylint: disable=redefined-outer-name + """Returns a scope context to be used in 'with' statement for codes that do not need + gradients to be calculated. + + Example:: + + with autograd.record(): + y = model(x) + backward([y]) + with autograd.pause(): + # testing, IO, gradient updates... + + Parameters + ---------- + train_mode: bool, default False + Whether to do forward for training or predicting. + """ + return _RecordingStateScope(False, train_mode) + + +def train_mode(): + """Returns a scope context to be used in 'with' statement + in which forward pass behavior is set to training mode, + without changing the recording states. + + Example:: + + y = model(x) + with autograd.train_mode(): + y = dropout(y) + + """ + return _RecordingStateScope(None, True) + + +def predict_mode(): + """Returns a scope context to be used in 'with' statement + in which forward pass behavior is set to inference mode, + without changing the recording states. + + Example:: + + with autograd.record(): + y = model(x) + with autograd.predict_mode(): + y = sampling(y) + backward([y]) + """ + return _RecordingStateScope(None, False) + + +def mark_variables(variables, gradients, grad_reqs='write'): + """Mark NDArrays as variables to compute gradient for autograd. + + Parameters + ---------- + variables: NDArray or list of NDArray + gradients: NDArray or list of NDArray + grad_reqs: str or list of str + """ + if isinstance(variables, NDArray): + assert isinstance(gradients, NDArray) + variables = [variables] + gradients = [gradients] + + variable_handles = [] + gradient_handles = [] + for var, gradvar in zip(variables, gradients): + variable_handles.append(var.handle) + gradient_handles.append(gradvar.handle) + if isinstance(grad_reqs, string_types): + grad_reqs = [_GRAD_REQ_MAP[grad_reqs]]*len(variables) + else: + grad_reqs = [_GRAD_REQ_MAP[i] for i in grad_reqs] + + check_call(_LIB.MXAutogradMarkVariables( + len(variable_handles), + c_array(NDArrayHandle, variable_handles), + c_array(mx_uint, grad_reqs), + c_array(NDArrayHandle, gradient_handles))) + + +def backward(heads, head_grads=None, retain_graph=False, train_mode=True): #pylint: disable=redefined-outer-name + """Compute the gradients of heads w.r.t previously marked variables. + + Parameters + ---------- + heads: NDArray or list of NDArray + Output NDArray(s) + head_grads: NDArray or list of NDArray or None + Gradients with respect to heads. + train_mode: bool, optional + Whether to do backward for training or predicting. + """ + if isinstance(heads, NDArray): + assert head_grads is None or isinstance(head_grads, NDArray) + heads = [heads] + head_grads = [head_grads] if head_grads is not None else None + + output_handles = [] + for arr in heads: + output_handles.append(arr.handle) + + if head_grads is None: + check_call(_LIB.MXAutogradBackwardEx( + len(output_handles), + c_array(NDArrayHandle, output_handles), + ctypes.c_void_p(0), + ctypes.c_int(retain_graph), + ctypes.c_int(train_mode))) + return + + ograd_handles = [] + for arr in head_grads: + if arr is not None: + ograd_handles.append(arr.handle) + else: + ograd_handles.append(NDArrayHandle(0)) + assert len(ograd_handles) == len(output_handles), \ + "heads and head_grads must have the same length" + + check_call(_LIB.MXAutogradBackwardEx( + len(output_handles), + c_array(NDArrayHandle, output_handles), + c_array(NDArrayHandle, ograd_handles), + ctypes.c_int(retain_graph), + ctypes.c_int(train_mode))) + + +def get_symbol(x): + """Retrieve recorded computation history as `Symbol`. + + Parameters + ---------- + x : NDArray + Array representing the head of computation graph. + + Returns + ------- + Symbol + The retrieved Symbol. + """ + hdl = SymbolHandle() + check_call(_LIB.MXAutogradGetSymbol(x.handle, ctypes.byref(hdl))) + return Symbol(hdl) + + +class Function(object): + """User-defined differentiable function. + + Function allows defining both forward and backward computation for + custom operators. During gradient computation, the used-defined + backward function will be used instead of the default chain-rule. + You can also cast to numpy array and back for some operations in + forward and backward. + + For example, a stable sigmoid function can be defined as:: + + class sigmoid(Function): + def forward(self, x): + y = 1 / (1 + mx.nd.exp(-x)) + self.save_for_backward(y) + return y + + def backward(self, dy): + # backward takes as many inputs as forward's return value, + # and returns as many NDArrays as forward's arguments. + y, = self.saved_tensors + return y * (1-y) + """ + _bwd_functype = CFUNCTYPE(c_int, c_int, c_int, POINTER(c_void_p), + POINTER(c_int), c_int, c_void_p) + _del_functype = CFUNCTYPE(c_int, c_void_p) + class _Registry(object): + """CustomOp registry.""" + def __init__(self): + self.ref_holder = {} + self.counter = 0 + self.lock = Lock() + + def inc(self): + """Get index for new entry.""" + self.lock.acquire() + cur = self.counter + self.counter += 1 + self.lock.release() + return cur + + _registry = _Registry() + + def __init__(self): + self._used = False + self.saved_tensors = () + + def save_for_backward(self, *args): + self.saved_tensors = args + + def __call__(self, *inputs): + assert not self._used, \ + "Each Function instance can only be called once. "\ + "Please create another instance." + self._used = True + + prev_recording = set_recording(False) + outputs = self.forward(*inputs) + set_recording(prev_recording) + + if not prev_recording: + return outputs + + ret_outputs = outputs + if isinstance(outputs, NDArray): + outputs = (outputs,) + + key = Function._registry.inc() + + def backward_entry(num_ograds, num_igrads, ptrs, reqs, is_train, _): + """entry point for backward.""" + # pylint: disable=W0613 + try: + output_grads = [NDArray(ctypes.cast(i, NDArrayHandle), writable=False) \ + for i in ptrs[:num_ograds]] + input_grads = [NDArray(ctypes.cast(i, NDArrayHandle), writable=True) \ + for i in ptrs[num_ograds:num_ograds+num_igrads]] + reqs = [reqs[i] for i in range(num_igrads)] + rets = self.backward(*output_grads) + if isinstance(rets, NDArray): + rets = (rets,) + assert len(rets) == len(input_grads), \ + "%s.backward must return exactly the same number " \ + "of NDArrays as the number of NDArrays arguments to forward." \ + "Expecting %d got %d"%(self.__class__.name, len(input_grads), len(rets)) + for igrad, ret, req in zip(input_grads, rets, reqs): + assert isinstance(ret, NDArray), \ + "autograd.Function.backward must return NDArrays, not %s"%type(ret) + if req == 0: # null + return + elif req == 1 or req == 2: # write or inplace + igrad[:] = ret + elif req == 'add': + igrad[:] += ret + except Exception: # pylint: disable=broad-except + print('Error in Function.backward: %s' % traceback.format_exc()) + return False + return True + + def delete_entry(_): + """C Callback for CustomFunction::delete""" + try: + del Function._registry.ref_holder[key] + except Exception: # pylint: disable=broad-except + print('Error in autograd.Function.delete: %s' % traceback.format_exc()) + return False + return True + + input_handles = [x.handle for x in inputs] + output_handles = [x.handle for x in outputs] + callbacks = [Function._bwd_functype(backward_entry), + Function._del_functype(delete_entry)] + callbacks = [cast(i, CFUNCTYPE(c_int)) for i in callbacks] + context = MXCallbackList(c_int(len(callbacks)), + cast(c_array(CFUNCTYPE(c_int), callbacks), + POINTER(CFUNCTYPE(c_int))), + cast(c_array(c_void_p, [None]*len(callbacks)), + POINTER(c_void_p))) + check_call(_LIB.MXCustomFunctionRecord( + c_int(len(inputs)), + c_array(NDArrayHandle, input_handles), + c_int(len(outputs)), + c_array(NDArrayHandle, output_handles), + ctypes.byref(context))) + + Function._registry.ref_holder[key] = context + + return ret_outputs + + def forward(self, *inputs): + """Forward computation.""" + raise NotImplementedError + + def backward(self, *output_grads): + """Backward computation. + + Takes as many inputs as forward's outputs, + and returns as many NDArrays as forward's inputs. + """ + raise NotImplementedError diff --git a/python/mxnet/base.py b/python/mxnet/base.py index 83d06e5b9bb6..aad0580e7d07 100644 --- a/python/mxnet/base.py +++ b/python/mxnet/base.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # coding: utf-8 # pylint: disable=invalid-name, no-member """ctypes library of mxnet and helper functions.""" @@ -18,13 +35,15 @@ #---------------------------- if sys.version_info[0] == 3: string_types = str, - numeric_types = (float, int, np.float32, np.int32) + numeric_types = (float, int, np.generic) + integer_types = int # this function is needed for python3 # to convert ctypes.char_p .value back to python str py_str = lambda x: x.decode('utf-8') else: string_types = basestring, - numeric_types = (float, int, long, np.float32, np.int32) + numeric_types = (float, int, long, np.generic) + integer_types = (int, long) py_str = lambda x: x class _NullType(object): @@ -38,10 +57,35 @@ class MXNetError(Exception): """Error that will be throwed by all mxnet functions.""" pass +class NotImplementedForSymbol(MXNetError): + def __init__(self, function, alias, *args): + super(NotImplementedForSymbol, self).__init__() + self.function = function.__name__ + self.alias = alias + self.args = [str(type(a)) for a in args] + def __str__(self): + msg = 'Function {}'.format(self.function) + if self.alias: + msg += ' (namely operator "{}")'.format(self.alias) + if self.args: + msg += ' with arguments ({})'.format(', '.join(self.args)) + msg += ' is not implemented for Symbol and only available in NDArray.' + return msg + + +class MXCallbackList(ctypes.Structure): + """Structure that holds Callback information. Passed to CustomOpProp.""" + _fields_ = [ + ('num_callbacks', ctypes.c_int), + ('callbacks', ctypes.POINTER(ctypes.CFUNCTYPE(ctypes.c_int))), + ('contexts', ctypes.POINTER(ctypes.c_void_p)) + ] + + def _load_lib(): - """Load libary by searching possible path.""" + """Load library by searching possible path.""" lib_path = libinfo.find_lib_path() - lib = ctypes.CDLL(lib_path[0], ctypes.RTLD_GLOBAL) + lib = ctypes.CDLL(lib_path[0], ctypes.RTLD_LOCAL) # DMatrix functions lib.MXGetLastError.restype = ctypes.c_char_p return lib @@ -59,6 +103,7 @@ def _load_lib(): NDArrayHandle = ctypes.c_void_p FunctionHandle = ctypes.c_void_p OpHandle = ctypes.c_void_p +CachedOpHandle = ctypes.c_void_p SymbolHandle = ctypes.c_void_p ExecutorHandle = ctypes.c_void_p DataIterCreatorHandle = ctypes.c_void_p @@ -282,3 +327,21 @@ def _add_fileline(obj): _add_fileline(obj.__func__) if inspect.isclass(obj) and incursive: add_fileline_to_docstring(obj, False) + +def _as_list(obj): + """A utility function that converts the argument to a list if it is not already. + + Parameters + ---------- + obj : object + + Returns + ------- + If `obj` is a list or tuple, return it. Otherwise, return `[obj]` as a + single-element list. + + """ + if isinstance(obj, (list, tuple)): + return obj + else: + return [obj] diff --git a/python/mxnet/callback.py b/python/mxnet/callback.py index b585ce82b525..8c9f64a95144 100644 --- a/python/mxnet/callback.py +++ b/python/mxnet/callback.py @@ -1,10 +1,26 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # coding: utf-8 """Callback functions that can be used to track various status during epoch.""" from __future__ import absolute_import import logging import math -import sys import time from .model import save_checkpoint @@ -158,14 +174,21 @@ def __call__(self, param): class ProgressBar(object): - """Show a progress bar. + """Displays a progress bar, indicating the percentage of batches processed within each epoch. Parameters ---------- total: int - total batch size + total number of batches per epoch length: int - length or progress bar + number of chars to define maximum length of progress bar + + Examples + -------- + >>> progress_bar = mx.callback.ProgressBar(total=2) + >>> mod.fit(data, num_epoch=5, batch_end_callback=progress_bar) + [========--------] 50.0% + [================] 100.0% """ def __init__(self, total, length=80): self.bar_len = length @@ -177,7 +200,7 @@ def __call__(self, param): filled_len = int(round(self.bar_len * count / float(self.total))) percents = math.ceil(100.0 * count / float(self.total)) prog_bar = '=' * filled_len + '-' * (self.bar_len - filled_len) - sys.stdout.write('[%s] %s%s\r' % (prog_bar, percents, '%')) + logging.info('[%s] %s%s\r', prog_bar, percents, '%') class LogValidationMetricsCallback(object): diff --git a/python/mxnet/context.py b/python/mxnet/context.py index 9822a6d86708..9798b480d235 100644 --- a/python/mxnet/context.py +++ b/python/mxnet/context.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # coding: utf-8 """Context management API of mxnet.""" from __future__ import absolute_import diff --git a/python/mxnet/contrib/__init__.py b/python/mxnet/contrib/__init__.py index c46fa2a24c7f..2730bc43863d 100644 --- a/python/mxnet/contrib/__init__.py +++ b/python/mxnet/contrib/__init__.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # coding: utf-8 """Experimental contributions""" diff --git a/python/mxnet/contrib/autograd.py b/python/mxnet/contrib/autograd.py index 40ab289c8f4c..c7fb6e17803a 100644 --- a/python/mxnet/contrib/autograd.py +++ b/python/mxnet/contrib/autograd.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # coding: utf-8 """Autograd for NDArray.""" from __future__ import absolute_import @@ -28,6 +45,8 @@ def set_is_training(is_train): prev = ctypes.c_int() check_call(_LIB.MXAutogradSetIsTraining( ctypes.c_int(is_train), ctypes.byref(prev))) + check_call(_LIB.MXAutogradSetIsRecording( + ctypes.c_int(is_train), ctypes.byref(prev))) return bool(prev.value) @@ -104,24 +123,48 @@ def mark_variables(variables, gradients, grad_reqs='write'): c_array(mx_uint, grad_reqs), c_array(NDArrayHandle, gradient_handles))) -def compute_gradient(outputs): + +def backward(outputs, out_grads=None, retain_graph=False): """Compute the gradients of outputs w.r.t variables. Parameters ---------- outputs: list of NDArray - - Returns - ------- - gradients: list of NDArray + out_grads: list of NDArray or None """ + assert isinstance(outputs, (list, tuple)), \ + "outputs must be a list or tuple of NDArrays" output_handles = [] for arr in outputs: output_handles.append(arr.handle) - check_call(_LIB.MXAutogradComputeGradient( + if out_grads is None: + check_call(_LIB.MXAutogradBackward( + len(output_handles), + c_array(NDArrayHandle, output_handles), + ctypes.c_void_p(0), + ctypes.c_int(retain_graph))) + return + + ograd_handles = [] + for arr in out_grads: + if arr is not None: + ograd_handles.append(arr.handle) + else: + ograd_handles.append(NDArrayHandle(0)) + assert len(ograd_handles) == len(output_handles), \ + "outputs and out_grads must have the same length" + + check_call(_LIB.MXAutogradBackward( len(output_handles), - c_array(NDArrayHandle, output_handles))) + c_array(NDArrayHandle, output_handles), + c_array(NDArrayHandle, ograd_handles), + ctypes.c_int(retain_graph))) + + +def compute_gradient(outputs): + """Deprecated. Please use backward""" + backward(outputs) def grad_and_loss(func, argnum=None): diff --git a/python/mxnet/contrib/ndarray.py b/python/mxnet/contrib/ndarray.py index cf1815c99434..3c86fe7ba3fb 100644 --- a/python/mxnet/contrib/ndarray.py +++ b/python/mxnet/contrib/ndarray.py @@ -1,2 +1,19 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # coding: utf-8 """NDArray namespace used to register contrib functions""" diff --git a/python/mxnet/contrib/symbol.py b/python/mxnet/contrib/symbol.py index 81c5ce889331..1d5334595f27 100644 --- a/python/mxnet/contrib/symbol.py +++ b/python/mxnet/contrib/symbol.py @@ -1,2 +1,19 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # coding: utf-8 """Symbol namespace used to register contrib functions""" diff --git a/python/mxnet/contrib/tensorboard.py b/python/mxnet/contrib/tensorboard.py index 5bcc3440842c..2bb766e7d69b 100644 --- a/python/mxnet/contrib/tensorboard.py +++ b/python/mxnet/contrib/tensorboard.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # coding: utf-8 """TensorBoard functions that can be used to log various status during epoch.""" from __future__ import absolute_import @@ -28,8 +45,8 @@ class LogMetricsCallback(object): >>> evaluation_log = 'logs/eval' >>> # in this case, each training and evaluation metric pairs has same name, >>> # you can add a prefix to make it separate. - >>> batch_end_callbacks = [mx.tensorboard.LogMetricsCallback(training_log)] - >>> eval_end_callbacks = [mx.tensorboard.LogMetricsCallback(evaluation_log)] + >>> batch_end_callbacks = [mx.contrib.tensorboard.LogMetricsCallback(training_log)] + >>> eval_end_callbacks = [mx.contrib.tensorboard.LogMetricsCallback(evaluation_log)] >>> # run >>> model.fit(train, >>> ... diff --git a/python/mxnet/cython/base.pyi b/python/mxnet/cython/base.pyi index 9df5985839e5..d73e1a7d0194 100644 --- a/python/mxnet/cython/base.pyi +++ b/python/mxnet/cython/base.pyi @@ -7,6 +7,7 @@ from cpython.version cimport PY_MAJOR_VERSION ctypedef void* SymbolHandle ctypedef void* NDArrayHandle ctypedef void* OpHandle +ctypedef void* CachedOpHandle ctypedef unsigned nn_uint cdef py_str(const char* x): @@ -15,7 +16,6 @@ cdef py_str(const char* x): else: return x.decode("utf-8") - cdef c_str(pystr): """Create ctypes char * from a python string Parameters @@ -99,3 +99,11 @@ cdef extern from "mxnet/c_api.h": const char **param_keys, const char **param_vals); int MXNDArrayFree(NDArrayHandle handle); + int MXCreateCachedOp(SymbolHandle handle, + CachedOpHandle *out); + int MXFreeCachedOp(CachedOpHandle handle); + int MXInvokeCachedOp(CachedOpHandle handle, + int num_inputs, + NDArrayHandle *inputs, + int *num_outputs, + NDArrayHandle **outputs); diff --git a/python/mxnet/cython/ndarray.pyx b/python/mxnet/cython/ndarray.pyx index 62ba21edff44..a861ae661b45 100644 --- a/python/mxnet/cython/ndarray.pyx +++ b/python/mxnet/cython/ndarray.pyx @@ -4,6 +4,7 @@ import sys as _sys import ctypes as _ctypes import numpy as np from ..ndarray_doc import _build_doc +from libc.stdint cimport uint32_t, int64_t include "./base.pyi" @@ -47,7 +48,7 @@ cdef class NDArrayBase: _ndarray_cls = NDArrayBase -cdef _set_ndarray_class(cls): +def _set_ndarray_class(cls): global _ndarray_cls _ndarray_cls = cls @@ -59,100 +60,56 @@ cdef NewArray(NDArrayHandle handle): (nd).cwritable = True return nd -cdef _make_ndarray_function(OpHandle handle, string name): - """Create a NDArray function from the FunctionHandle.""" - cdef const char *real_name - cdef const char *desc - cdef nn_uint num_args - cdef const char** arg_names - cdef const char** arg_types - cdef const char** arg_descs - cdef const char* return_type - cdef const char* key_var_num_args - - CALL(MXSymbolGetAtomicSymbolInfo( - handle, &real_name, &desc, - &num_args, &arg_names, - &arg_types, &arg_descs, - &key_var_num_args, &return_type)) - func_name = py_str(name.c_str()) - - key_vargs = py_str(key_var_num_args) - num_args = int(num_args) - doc_str = _build_doc(func_name, - py_str(desc), - [py_str(arg_names[i]) for i in range(num_args)], - [py_str(arg_types[i]) for i in range(num_args)], - [py_str(arg_descs[i]) for i in range(num_args)], - key_vargs, - py_str(return_type) if return_type != NULL else '') - func_hint = func_name.lower() - - arguments = [] - for i in range(num_args): - dtype = py_str(arg_types[i]) - if not (dtype.startswith('NDArray') or dtype.startswith('Symbol')): - arguments.append(py_str(arg_names[i])) - - num_param_args = len(arguments) - - # Definition of internal functions. - def generic_ndarray_function(*args, **kwargs): - """Invoke this function by passing in parameters - - Parameters - ---------- - *args - Positional arguments of input scalars and NDArray - out : NDArray or tuple of NDArray, optional - Output NDArray, used to hold the output result. - - Returns - ------- - out : NDArray - The result NDArray(tuple) of result of computation. - """ - cdef vector[string] sparam_keys - cdef vector[string] sparam_vals - cdef vector[NDArrayHandle] nd_args + +cdef class CachedOp: + """Cached operator handle.""" + cdef CachedOpHandle chandle + + cdef _set_handle(self, handle): + cdef unsigned long long ptr + if handle is None: + self.chandle = NULL + else: + ptr = handle.value + self.chandle = (ptr) + + property handle: + def __get__(self): + if self.chandle == NULL: + return None + else: + return _ctypes.cast(self.chandle, _ctypes.c_void_p) + def __set__(self, value): + self._set_handle(value) + + def __init__(self, sym): + cdef unsigned long long ptr = sym.handle.value + CALL(MXCreateCachedOp( + (ptr), + &self.chandle)) + + def __del__(self): + CALL(MXFreeCachedOp(self.chandle)) + + def __call__(self, *args, out=None): + """ctypes implementation of imperative invoke wrapper""" + cdef vector[NDArrayHandle] ndvars cdef vector[NDArrayHandle] output_vars cdef NDArrayHandle* p_output_vars cdef NDArrayHandle ret_handle - cdef int pos_param_arg cdef int num_output - pos_param_arg = 0 - - for v in args: - if isinstance(v, NDArrayBase): - nd_args.push_back((v).chandle) - else: - if pos_param_arg >= num_param_args: - raise ValueError("Too many positional arguments") - if arguments[pos_param_arg] == 'dtype': - sparam_vals.push_back(c_str(np.dtype(v).name)) - else: - sparam_vals.push_back(c_str(str(v))) - sparam_keys.push_back(c_str(arguments[pos_param_arg])) - pos_param_arg = pos_param_arg + 1 + for i in args: + ndvars.push_back((i).chandle) original_output = None - for k, v in kwargs.items(): - if k == "out": - original_output = v - if isinstance(v, NDArrayBase): - output_vars.push_back((v).chandle) - else: - for item in v: - if not isinstance(item, NDArrayBase): - raise ValueError("out need to be of type NDArray") - output_vars.push_back((v).chandle) - elif k == 'dtype': - sparam_vals.push_back(c_str(np.dtype(v).name)) - sparam_keys.push_back(c_str(k)) + if out is not None: + original_output = out + if isinstance(out, NDArrayBase): + output_vars.push_back((out).chandle) else: - sparam_vals.push_back(c_str(str(v))) - sparam_keys.push_back(c_str(k)) + for i in out: + output_vars.push_back((i).chandle) num_output = output_vars.size() if output_vars.size() == 0: @@ -161,52 +118,72 @@ cdef _make_ndarray_function(OpHandle handle, string name): else: p_output_vars = &output_vars[0] - cdef vector[const char*] param_keys = SVec2Ptr(sparam_keys) - cdef vector[const char*] param_vals = SVec2Ptr(sparam_vals) - - CALL(MXImperativeInvoke( - handle, - nd_args.size(), - &nd_args[0] if nd_args.size() != 0 else NULL, + CALL(MXInvokeCachedOp( + (self).chandle, + len(args), + &ndvars[0] if ndvars.size() != 0 else NULL, &num_output, - &p_output_vars, - param_keys.size(), - CBeginPtr(param_keys), - CBeginPtr(param_vals))) + &p_output_vars)) if original_output is not None: return original_output - if num_output == 1: return NewArray(p_output_vars[0]) else: return tuple(NewArray(p_output_vars[i]) for i in range(num_output)) - # End of function declaration - generic_ndarray_function.__name__ = func_name - generic_ndarray_function.__doc__ = doc_str - generic_ndarray_function.__module__ = 'mxnet.ndarray' - return generic_ndarray_function - - -def _init_ndarray_module(nd_class, root_namespace): - """List and add all the atomic symbol functions to current module.""" - cdef const char** op_name_ptrs - cdef nn_uint size - cdef vector[string] op_names - cdef OpHandle handle - - _set_ndarray_class(nd_class) - CALL(MXListAllOpNames(&size, &op_name_ptrs)) - for i in range(size): - op_names.push_back(string(op_name_ptrs[i])) - - module_obj = _sys.modules["%s.ndarray" % root_namespace] - module_internal = _sys.modules["%s._ndarray_internal" % root_namespace] - for i in range(op_names.size()): - CALL(NNGetOpHandle(op_names[i].c_str(), &handle)) - function = _make_ndarray_function(handle, op_names[i]) - if function.__name__.startswith('_'): - setattr(module_internal, function.__name__, function) + +def _imperative_invoke(handle, ndargs, keys, vals, out): + """cython implementation of imperative invoke wrapper""" + cdef unsigned long long ihandle = handle + cdef OpHandle chandle = ihandle + cdef vector[string] ckeys + cdef vector[string] cvals + cdef vector[NDArrayHandle] ndvars + cdef vector[NDArrayHandle] output_vars + cdef NDArrayHandle* p_output_vars + cdef NDArrayHandle ret_handle + cdef int num_output + + for i in ndargs: + ndvars.push_back((i).chandle) + for i in keys: + ckeys.push_back(c_str(i)) + for i in vals: + cvals.push_back(c_str(str(i))) + + original_output = None + if out is not None: + original_output = out + if isinstance(out, NDArrayBase): + output_vars.push_back((out).chandle) else: - setattr(module_obj, function.__name__, function) + for i in out: + output_vars.push_back((i).chandle) + + num_output = output_vars.size() + if output_vars.size() == 0: + output_vars.resize(1) + p_output_vars = NULL + else: + p_output_vars = &output_vars[0] + + cdef vector[const char*] param_keys = SVec2Ptr(ckeys) + cdef vector[const char*] param_vals = SVec2Ptr(cvals) + + CALL(MXImperativeInvoke( + chandle, + ndvars.size(), + &ndvars[0] if ndvars.size() != 0 else NULL, + &num_output, + &p_output_vars, + param_keys.size(), + CBeginPtr(param_keys), + CBeginPtr(param_vals))) + + if original_output is not None: + return original_output + if num_output == 1: + return NewArray(p_output_vars[0]) + else: + return tuple(NewArray(p_output_vars[i]) for i in range(num_output)) diff --git a/python/mxnet/cython/symbol.pyx b/python/mxnet/cython/symbol.pyx index 0750212a8bb6..aea0aa9f4809 100644 --- a/python/mxnet/cython/symbol.pyx +++ b/python/mxnet/cython/symbol.pyx @@ -68,7 +68,7 @@ cdef SymbolSetAttr(SymbolHandle handle, dict kwargs): _symbol_cls = SymbolBase -cdef _set_symbol_class(cls): +def _set_symbol_class(cls): global _symbol_cls _symbol_cls = cls @@ -78,124 +78,52 @@ cdef NewSymbol(SymbolHandle handle): (sym).chandle = handle return sym -cdef _make_atomic_symbol_function(OpHandle handle, string name): - """Create an atomic symbol function by handle and funciton name.""" - cdef const char *real_name - cdef const char *desc - cdef nn_uint num_args - cdef const char** arg_names - cdef const char** arg_types - cdef const char** arg_descs - cdef const char* return_type - cdef const char* key_var_num_args - - CALL(MXSymbolGetAtomicSymbolInfo( - handle, &real_name, &desc, - &num_args, &arg_names, - &arg_types, &arg_descs, - &key_var_num_args, &return_type)) - func_name = py_str(name.c_str()) - - key_vargs = py_str(key_var_num_args) - num_args = int(num_args) - doc_str = _build_doc(func_name, - py_str(desc), - [py_str(arg_names[i]) for i in range(num_args)], - [py_str(arg_types[i]) for i in range(num_args)], - [py_str(arg_descs[i]) for i in range(num_args)], - key_vargs, - py_str(return_type) if return_type != NULL else '') - - func_hint = func_name.lower() - - def creator(*args, **kwargs): - cdef vector[string] sparam_keys - cdef vector[string] sparam_vals - cdef vector[SymbolHandle] symbol_args - cdef vector[string] ssymbol_keys - cdef SymbolHandle ret_handle - attr = kwargs.pop("attr", None) - kwargs.update(AttrScope.current.get(attr)) - name = kwargs.pop("name", None) - - if key_vargs: - if key_vargs not in kwargs: - sparam_keys.push_back(c_str(key_vargs)) - sparam_vals.push_back(c_str(str(len(args)))) - - if len(kwargs) != 0: - for k, v in kwargs.items(): - if isinstance(v, SymbolBase): - ssymbol_keys.push_back(c_str(k)) - symbol_args.push_back((v).chandle) - elif k == 'dtype': - sparam_keys.push_back(c_str(k)) - sparam_vals.push_back(c_str(_numpy.dtype(v).name)) - else: - sparam_keys.push_back(c_str(k)) - sparam_vals.push_back(c_str(str(v))) - - if len(args) != 0: - if symbol_args.size() != 0: - raise TypeError("compose only accept input Symbols\ - either as positional or keyword arguments, not both") - for v in args: - if not isinstance(v, SymbolBase): - raise TypeError('Compose expect `Symbol` as arguments') - symbol_args.push_back((v).chandle) - - cdef vector[const char*] param_keys = SVec2Ptr(sparam_keys) - cdef vector[const char*] param_vals = SVec2Ptr(sparam_vals) - cdef vector[const char*] symbol_keys = SVec2Ptr(ssymbol_keys) - - CALL(MXSymbolCreateAtomicSymbol( - handle, - param_keys.size(), - CBeginPtr(param_keys), - CBeginPtr(param_vals), - &ret_handle)) - num_args = (symbol_args.size()) - - name = NameManager.current.get(name, func_hint) - - cdef const char* c_name = NULL - - if name: - name = c_str(name) - c_name = name - - CALL(NNSymbolCompose( - ret_handle, - c_name, - num_args, - &symbol_keys[0] if symbol_keys.size() != 0 else NULL, - &symbol_args[0] if symbol_args.size() != 0 else NULL)) - return NewSymbol(ret_handle) - - creator.__name__ = func_name - creator.__doc__ = doc_str - creator.__module__ = 'mxnet.symbol' - return creator - - -def _init_symbol_module(symbol_class, root_namespace): - """List and add all the atomic symbol functions to current module.""" - cdef const char** op_name_ptrs - cdef nn_uint size - cdef vector[string] op_names - cdef OpHandle handle - - _set_symbol_class(symbol_class) - CALL(MXListAllOpNames(&size, &op_name_ptrs)) - for i in range(size): - op_names.push_back(string(op_name_ptrs[i])) - - module_obj = _sys.modules["%s.symbol" % root_namespace] - module_internal = _sys.modules["%s._symbol_internal" % root_namespace] - for i in range(op_names.size()): - CALL(NNGetOpHandle(op_names[i].c_str(), &handle)) - function = _make_atomic_symbol_function(handle, op_names[i]) - if function.__name__.startswith('_'): - setattr(module_internal, function.__name__, function) - else: - setattr(module_obj, function.__name__, function) + +def _symbol_creator(handle, args, kwargs, keys, vals, name): + cdef unsigned long long ihandle = handle + cdef OpHandle chandle = ihandle + cdef vector[string] ckeys + cdef vector[string] cvals + cdef vector[string] sym_keys + cdef vector[SymbolHandle] sym_args + cdef SymbolHandle ret_handle + cdef string cname = c_str(name) + + for i in keys: + ckeys.push_back(c_str(i)) + for i in vals: + cvals.push_back(c_str(str(i))) + + cdef vector[const char*] param_keys = SVec2Ptr(ckeys) + cdef vector[const char*] param_vals = SVec2Ptr(cvals) + + CALL(MXSymbolCreateAtomicSymbol( + chandle, + param_keys.size(), + CBeginPtr(param_keys), + CBeginPtr(param_vals), + &ret_handle)) + + if args and kwargs: + raise TypeError( + 'Operators with variable length input can only accept input' + 'Symbols either as positional or keyword arguments, not both') + + if args: + for i in args: + sym_args.push_back((i).chandle) + elif kwargs: + for k, v in kwargs.items(): + sym_keys.push_back(c_str(k)) + sym_args.push_back((v).chandle) + + cdef vector[const char*] csym_keys = SVec2Ptr(sym_keys) + + CALL(NNSymbolCompose( + ret_handle, + cname.c_str(), + sym_args.size(), + &csym_keys[0] if csym_keys.size() != 0 else NULL, + &sym_args[0] if sym_args.size() != 0 else NULL)) + + return NewSymbol(ret_handle) diff --git a/python/mxnet/executor.py b/python/mxnet/executor.py index 6b9aab2de6f1..baff834bb33a 100644 --- a/python/mxnet/executor.py +++ b/python/mxnet/executor.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # coding: utf-8 # pylint: disable=invalid-name, protected-access, too-many-locals, too-many-arguments """Symbolic Executor component of MXNet.""" @@ -5,7 +22,6 @@ import ctypes import copy -import warnings import numpy as np from .base import _LIB from .base import mx_uint, NDArrayHandle, ExecutorHandle @@ -61,7 +77,6 @@ def __init__(self, handle, symbol, ctx, grad_req, group2ctx): self._aux_dict = None self._output_dict = None self._monitor_callback = None - self._output_dirty = False self._ctx = copy.deepcopy(ctx) self._grad_req = copy.deepcopy(grad_req) self._group2ctx = copy.deepcopy(group2ctx) @@ -99,8 +114,7 @@ def forward(self, is_train=False, **kwargs): ---------- is_train: bool, optional Whether this forward is for evaluation purpose. If True, - a backward call is expected to follow. Otherwise following - backward is invalid. + a backward call is expected to follow. **kwargs Additional specification of input arguments. @@ -132,15 +146,9 @@ def forward(self, is_train=False, **kwargs): self.handle, ctypes.c_int(int(is_train)))) - if self._output_dirty: - warnings.warn( - "Calling forward the second time after forward(is_train=True) " - "without calling backward first. Is this intended?", stacklevel=2) - self._output_dirty = is_train - return self.outputs - def backward(self, out_grads=None): + def backward(self, out_grads=None, is_train=True): """Do backward pass to get the gradient of arguments. Parameters @@ -149,6 +157,11 @@ def backward(self, out_grads=None): Gradient on the outputs to be propagated back. This parameter is only needed when bind is called on outputs that are not a loss function. + is_train : bool, default True + Whether this backward is for training or inference. Note that in rare + cases you want to call backward with is_train=False to get gradient + during inference. + Examples -------- @@ -211,16 +224,11 @@ def backward(self, out_grads=None): if not isinstance(obj, NDArray): raise TypeError("inputs must be NDArray") ndarray = c_array(NDArrayHandle, [item.handle for item in out_grads]) - check_call(_LIB.MXExecutorBackward( + check_call(_LIB.MXExecutorBackwardEx( self.handle, mx_uint(len(out_grads)), - ndarray)) - - if not self._output_dirty: - warnings.warn( - "Calling backward without calling forward(is_train=True) " - "first. Behavior is undefined.", stacklevel=2) - self._output_dirty = False + ndarray, + ctypes.c_int(is_train))) def set_monitor_callback(self, callback): """Install callback for monitor. diff --git a/python/mxnet/executor_manager.py b/python/mxnet/executor_manager.py index 4361d75cd4bd..33c6c9762717 100644 --- a/python/mxnet/executor_manager.py +++ b/python/mxnet/executor_manager.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # coding: utf-8 # pylint: disable=invalid-name, protected-access, too-many-locals, too-many-arguments, too-many-statements """Executor manager.""" @@ -30,7 +47,7 @@ def _split_input_slice(batch_size, work_load_list): Raises ------ ValueError - If there are two many splits such that some slice can be empty. + In case of too many splits, leading to some empty slices. """ total_work_load = sum(work_load_list) batch_num_list = [round(work_load * batch_size / total_work_load) @@ -44,7 +61,7 @@ def _split_input_slice(batch_size, work_load_list): begin = int(min((end, batch_size))) end = int(min((begin + batch_num, batch_size))) if begin >= end: - raise ValueError('Too many slices such that some splits are empty') + raise ValueError('Too many slices. Some splits are empty.') slices.append(slice(begin, end)) return slices diff --git a/python/mxnet/gluon/.gitignore b/python/mxnet/gluon/.gitignore new file mode 100644 index 000000000000..8436a89ff416 --- /dev/null +++ b/python/mxnet/gluon/.gitignore @@ -0,0 +1 @@ +!data diff --git a/python/mxnet/gluon/__init__.py b/python/mxnet/gluon/__init__.py new file mode 100644 index 000000000000..089340efcd2c --- /dev/null +++ b/python/mxnet/gluon/__init__.py @@ -0,0 +1,38 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +# coding: utf-8 +# pylint: disable=wildcard-import +"""Neural network module.""" + +from .parameter import * + +from .block import * + +from . import nn + +from . import rnn + +from .trainer import * + +from . import loss + +from . import utils + +from . import data + +from . import model_zoo diff --git a/python/mxnet/gluon/block.py b/python/mxnet/gluon/block.py new file mode 100644 index 000000000000..74a9058e98e0 --- /dev/null +++ b/python/mxnet/gluon/block.py @@ -0,0 +1,510 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +# coding: utf-8 +# pylint: disable= arguments-differ +"""Base container class for all neural network models.""" + +import copy + +from .. import symbol, ndarray, initializer +from ..symbol import Symbol +from ..ndarray import NDArray +from .. import name as _name +from .parameter import Parameter, ParameterDict, DeferredInitializationError +from .utils import _indent + + +class _BlockScope(object): + """Scope for collecting child `Block`s.""" + _current = None + + def __init__(self, block): + self._block = block + self._counter = {} + self._old_scope = None + self._name_scope = None + + @staticmethod + def create(prefix, params, hint): + """Creates prefix and params for new `Block`.""" + current = _BlockScope._current + if current is None: + if prefix is None: + prefix = _name.NameManager.current.get(None, hint) + '_' + if params is None: + params = ParameterDict(prefix) + else: + params = ParameterDict(params.prefix, params) + return prefix, params + + if prefix is None: + count = current._counter.get(hint, 0) + prefix = '%s%d_'%(hint, count) + current._counter[hint] = count + 1 + if params is None: + parent = current._block.params + params = ParameterDict(parent.prefix+prefix, parent._shared) + else: + params = ParameterDict(params.prefix, params) + return current._block.prefix+prefix, params + + def __enter__(self): + self._old_scope = _BlockScope._current + _BlockScope._current = self + self._name_scope = _name.Prefix(self._block.prefix) + self._name_scope.__enter__() + return self + + def __exit__(self, ptype, value, trace): + self._name_scope.__exit__(ptype, value, trace) + self._name_scope = None + _BlockScope._current = self._old_scope + + +def _flatten(args): + if isinstance(args, NDArray): + return [args], int(0) + if isinstance(args, Symbol): + length = len(args.list_outputs()) + length = length if length > 1 else 0 + return [args], int(length) + + assert isinstance(args, (list, tuple)), \ + "HybridBlock input must be (nested) list of Symbol or NDArray, " \ + "but got %s of type %s"%(str(args), str(type(args))) + flat = [] + fmts = [] + for i in args: + arg, fmt = _flatten(i) + flat.extend(arg) + fmts.append(fmt) + return flat, fmts + + +def _regroup(args, fmt): + if isinstance(fmt, int): + if fmt == 0: + return args[0], args[1:] + return args[:fmt], args[fmt:] + + assert isinstance(args, (list, tuple)), \ + "HybridBlock output must be (nested) list of Symbol or NDArray, " \ + "but got %s of type %s"%(str(args), str(type(args))) + ret = [] + for i in fmt: + res, args = _regroup(args, i) + ret.append(res) + return ret, args + + +class Block(object): + """Base class for all neural network layers and models. Your models should + subclass this class. + + `Block` can be nested recursively in a tree structure. You can create and + assign child `Block` as regular attributes:: + + from mxnet.gluon import Block, nn + from mxnet import ndarray as F + + class Model(Block): + def __init__(self, **kwargs): + super(Model, self).__init__(**kwargs) + # use name_scope to give child Blocks appropriate names. + # It also allows sharing Parameters between Blocks recursively. + with self.name_scope(): + self.dense0 = nn.Dense(20) + self.dense1 = nn.Dense(20) + + def forward(self, x): + x = F.relu(self.dense0(x)) + return F.relu(self.dense1(x)) + + model = Model() + model.initialize(ctx=mx.cpu(0)) + model(F.zeros((10, 10), ctx=mx.cpu(0))) + + + Child `Block` assigned this way will be registered and `collect_params` + will collect their Parameters recursively. + + Parameters + ---------- + prefix : str + Prefix acts like a name space. It will be prepended to the names of all + Parameters and child `Block`s in this `Block`'s `name_scope`. Prefix + should be unique within one model to prevent name collisions. + params : ParameterDict or None + `ParameterDict` for sharing weights with the new `Block`. For example, + if you want `dense1` to share `dense0`'s weights, you can do:: + + dense0 = nn.Dense(20) + dense1 = nn.Dense(20, params=dense0.collect_params()) + """ + def __init__(self, prefix=None, params=None): + self._prefix, self._params = _BlockScope.create(prefix, params, self._alias()) + self._name = self._prefix[:-1] if self._prefix.endswith('_') else self._prefix + self._scope = _BlockScope(self) + self._children = [] + + def __repr__(self): + s = '{name}(\n{modstr}\n)' + modstr = '\n'.join([' ({key}): {block}'.format(key=key, + block=_indent(block.__repr__(), 2)) + for key, block in self.__dict__.items() if isinstance(block, Block)]) + return s.format(name=self.__class__.__name__, + modstr=modstr) + + def __setattr__(self, name, value): + """Registers parameters.""" + super(Block, self).__setattr__(name, value) + if isinstance(value, Block): + self.register_child(value) + + def _alias(self): + return self.__class__.__name__.lower() + + @property + def prefix(self): + """Prefix of this `Block`.""" + return self._prefix + + @property + def name(self): + """Name of this `Block`, without '_' in the end.""" + return self._name + + def name_scope(self): + """Returns a name space object managing a child `Block` and parameter + names. Should be used within a `with` statement:: + + with self.name_scope(): + self.dense = nn.Dense(20) + """ + return self._scope + + @property + def params(self): + """Returns this `Block`'s parameter dictionary (does not include its + children's parameters).""" + return self._params + + def collect_params(self): + """Returns a `ParameterDict` containing this `Block` and all of its + children's Parameters.""" + ret = ParameterDict(self._params.prefix) + ret.update(self.params) + for cld in self._children: + ret.update(cld.collect_params()) + return ret + + def save_params(self, filename): + """Save parameters to file. + + filename : str + Path to file. + """ + self.collect_params().save(filename, strip_prefix=self.prefix) + + def load_params(self, filename, ctx, allow_missing=False, + ignore_extra=False): + """Load parameters from file. + + filename : str + Path to parameter file. + ctx : Context or list of Context + Context(s) initialize loaded parameters on. + allow_missing : bool, default False + Whether to silently skip loading parameters not represents in the file. + ignore_extra : bool, default False + Whether to silently ignore parameters from the file that are not + present in this Block. + """ + self.collect_params().load(filename, ctx, allow_missing, ignore_extra, + self.prefix) + + + def register_child(self, block): + """Registers block as a child of self. `Block`s assigned to self as + attributes will be registered automatically.""" + self._children.append(block) + + def initialize(self, init=initializer.Uniform(), ctx=None, verbose=False): + """Initializes `Parameter`s of this `Block` and its children. + + Equivalent to `block.collect_params().initialize(...)` + """ + self.collect_params().initialize(init, ctx, verbose) + + def hybridize(self, active=True): + """Activates or deactivates `HybridBlock`s recursively. Has no effect on + non-hybrid children. + + Parameters + ---------- + active : bool, default True + Whether to turn hybrid on or off. + """ + for cld in self._children: + cld.hybridize(active) + + def __call__(self, *args): + """Calls forward. Only accepts positional arguments.""" + return self.forward(*args) + + def forward(self, *args): + """Overrides to implement forward computation using `NDArray`. Only + accepts positional arguments. + + Parameters + ---------- + *args : list of NDArray + Input tensors. + """ + # pylint: disable= invalid-name + raise NotImplementedError + + +class HybridBlock(Block): + """`HybridBlock` supports forwarding with both Symbol and NDArray. + + Forward computation in `HybridBlock` must be static to work with `Symbol`s, + i.e. you cannot call `.asnumpy()`, `.shape`, `.dtype`, etc on tensors. + Also, you cannot use branching or loop logic that bases on non-constant + expressions like random numbers or intermediate results, since they change + the graph structure for each iteration. + + Before activating with `hybridize()`, `HybridBlock` works just like normal + `Block`. After activation, `HybridBlock` will create a symbolic graph + representing the forward computation and cache it. On subsequent forwards, + the cached graph will be used instead of `hybrid_forward`. + + Refer `Hybrid tutorial `_ to see + the end-to-end usage. + """ + def __init__(self, prefix=None, params=None): + super(HybridBlock, self).__init__(prefix=prefix, params=params) + self._reg_params = {} + self._cached_graph = () + self._cached_op = None + self._cached_params = None + self._out_format = None + self._in_format = None + self._active = False + + def __setattr__(self, name, value): + """Registers parameters.""" + super(HybridBlock, self).__setattr__(name, value) + if isinstance(value, Parameter): + assert name not in self._reg_params or \ + not isinstance(self._reg_params[name], Parameter), \ + "Overriding Parameter attribute %s is not allowed. " \ + "Please pass in Parameters by specifying `params` at " \ + "Block construction instead." + self._reg_params[name] = value + + def register_child(self, block): + if not isinstance(block, HybridBlock): + raise ValueError( + "Children of HybridBlock must also be HybridBlock, " \ + "but %s has type %s. If you are using Sequential, " \ + "please try HybridSequential instead"%( + str(block), str(type(block)))) + super(HybridBlock, self).register_child(block) + + def hybridize(self, active=True): + self._active = active + super(HybridBlock, self).hybridize(active) + + def _get_graph(self, *args): + if not self._cached_graph: + args, self._in_format = _flatten(args) + inputs = [symbol.var('input_%d'%i) for i in range(len(args))] + grouped_inputs = _regroup(inputs, self._in_format)[0] + + params = {i: j.var() for i, j in self._reg_params.items()} + with self.name_scope(): + out = self.hybrid_forward(symbol, *grouped_inputs, **params) # pylint: disable=no-value-for-parameter + out, self._out_format = _flatten(out) + + self._cached_graph = inputs, symbol.Group(out) + + return self._cached_graph + + def infer_shape(self, *args): + """Infers shape of Parameters from inputs.""" + inputs, out = self._get_graph(*args) + args, _ = _flatten(args) + arg_shapes, _, aux_shapes = out.infer_shape( + **{i.name: j.shape for i, j in zip(inputs, args)}) + sdict = {i: j for i, j in zip(out.list_arguments(), arg_shapes)} + sdict.update({name : shape for name, shape in \ + zip(out.list_auxiliary_states(), aux_shapes)}) + for i in self.collect_params().values(): + i.shape = sdict[i.name] + + def _build_cache(self, *args): + inputs, out = self._get_graph(*args) + self._cached_op = ndarray.CachedOp(out) + + params = dict(self.collect_params().items()) + self._cached_params = [params.get(name, None) for name in out.list_inputs()] + assert len(params) + len(self._cached_graph[0]) == len(out.list_inputs()), \ + "Wrong number of inputs." + + name2pos = {var.name: i for i, var in enumerate(inputs)} + self._in_idx = [(i, name2pos[name]) for i, name in enumerate(out.list_inputs()) + if name not in params] + + def _call_cached_op(self, *args): + if self._cached_op is None: + self._build_cache(*args) + + try: + cargs = [i.data() if i else None for i in self._cached_params] + except DeferredInitializationError: + self.infer_shape(*args) + for i in self._cached_params: + if i is not None: + i._finish_deferred_init() + cargs = [i.data() if i else None for i in self._cached_params] + + args, fmt = _flatten(args) + assert fmt == self._in_format, "Invalid input format" + for i, j in self._in_idx: + cargs[i] = args[j] + out = self._cached_op(*cargs) + if isinstance(out, NDArray): + out = [out] + return _regroup(out, self._out_format)[0] + + def forward(self, x, *args): + """Defines the forward computation. Arguments can be either + `NDArray` or `Symbol`.""" + if isinstance(x, NDArray): + with x.context as ctx: + if self._active: + return self._call_cached_op(x, *args) + try: + params = {i: j.data(ctx) for i, j in self._reg_params.items()} + except DeferredInitializationError: + self.infer_shape(x, *args) + for i in self.collect_params().values(): + i._finish_deferred_init() + params = {i: j.data(ctx) for i, j in self._reg_params.items()} + return self.hybrid_forward(ndarray, x, *args, **params) + + assert isinstance(x, Symbol), \ + "HybridBlock requires the first argument to forward be either " \ + "Symbol or NDArray, but got %s"%type(x) + params = {i: j.var() for i, j in self._reg_params.items()} + with self.name_scope(): + return self.hybrid_forward(symbol, x, *args, **params) + + def hybrid_forward(self, F, x, *args, **kwargs): + """Overrides to construct symbolic graph for this `Block`. + + Parameters + ---------- + x : Symbol or NDArray + The first input tensor. + *args : list of Symbol or list of NDArray + Additional input tensors. + """ + # pylint: disable= invalid-name + raise NotImplementedError + + +class SymbolBlock(HybridBlock): + """Construct block from symbol. This is useful for using pre-trained models + as feature extractors. For example, you may want to extract get the output + from fc2 layer in AlexNet. + + Parameters + ---------- + outputs : Symbol or list of Symbol + The desired output for SymbolBlock. + inputs : Symbol or list of Symbol + The Variables in output's argument that should be used as inputs. + params : ParameterDict + Parameter dictionary for arguments and auxililary states of outputs + that are not inputs. + + Examples + -------- + >>> # To extract the feature from fc1 and fc2 layers of AlexNet: + >>> alexnet = gluon.model_zoo.vision.alexnet(pretrained=True, ctx=mx.cpu(), + prefix='model_') + >>> inputs = mx.sym.var('data') + >>> out = alexnet(inputs) + >>> internals = out.get_internals() + >>> print(internals.list_outputs()) + ['data', ..., 'model_dense0_relu_fwd_output', ..., 'model_dense1_relu_fwd_output', ...] + >>> outputs = [internals['model_dense0_relu_fwd_output'], + internals['model_dense1_relu_fwd_output']] + >>> # Create SymbolBlock that shares parameters with alexnet + >>> feat_model = gluon.SymbolBlock(outputs, inputs, params=alexnet.collect_params()) + >>> x = mx.nd.random_normal(shape=(16, 3, 224, 224)) + >>> print(feat_model(x)) + """ + def __init__(self, outputs, inputs, params=None): + super(SymbolBlock, self).__init__(prefix=None, params=None) + self._prefix = '' + self._params = ParameterDict('', params) + if isinstance(inputs, symbol.Symbol) and len(inputs.list_outputs()) == 1: + inputs = [inputs] + if isinstance(outputs, symbol.Symbol) and len(outputs.list_outputs()) == 1: + outputs = [outputs] + + syms, self._in_format = _flatten(inputs) + out, self._out_format = _flatten(outputs) + out = symbol.Group(out) + + input_names = set() + for i in syms: + assert len(i.get_internals().list_outputs()) == 1, \ + "Input symbols must be variable, but %s is an output of operators"%str(i) + input_names.add(i.name) + + for i in out.list_arguments(): + if i not in input_names: + self.params.get(i, allow_deferred_init=True) + + for i in out.list_auxiliary_states(): + if i not in input_names: + self.params.get(i, grad_req='null', allow_deferred_init=True) + + self._cached_graph = syms, out + self._build_cache() + + def forward(self, x, *args): + if isinstance(x, NDArray): + with x.context: + return self._call_cached_op(x, *args) + + assert isinstance(x, Symbol), \ + "HybridBlock requires the first argument to forward be either " \ + "Symbol or NDArray, but got %s"%type(x) + args, in_fmt = _flatten([x] + list(args)) + assert in_fmt == self._in_format, "Invalid input format" + ret = copy.copy(self._cached_graph[1]) + ret._compose(**{k.name: v for k, v in zip(self._cached_graph[0], args)}) + return _regroup(ret, self._out_format)[0] + + def hybrid_forward(self, F, x, *args, **kwargs): + raise NotImplementedError diff --git a/python/mxnet/gluon/data/__init__.py b/python/mxnet/gluon/data/__init__.py new file mode 100644 index 000000000000..23ae3e9b3be6 --- /dev/null +++ b/python/mxnet/gluon/data/__init__.py @@ -0,0 +1,28 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +# coding: utf-8 +# pylint: disable=wildcard-import +"""Dataset utilities.""" + +from .dataset import * + +from .sampler import * + +from .dataloader import * + +from . import vision diff --git a/python/mxnet/gluon/data/dataloader.py b/python/mxnet/gluon/data/dataloader.py new file mode 100644 index 000000000000..772209a6f2aa --- /dev/null +++ b/python/mxnet/gluon/data/dataloader.py @@ -0,0 +1,95 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +# coding: utf-8 +# pylint: disable= +"""Dataset generator.""" + +import numpy as np + +from . import sampler as _sampler +from ... import nd + + +def _batchify(data): + """Collate data into batch.""" + if isinstance(data[0], nd.NDArray): + return nd.stack(*data) + elif isinstance(data[0], tuple): + data = zip(*data) + return [_batchify(i) for i in data] + else: + data = np.asarray(data) + return nd.array(data, dtype=data.dtype) + + +class DataLoader(object): + """Loads data from a dataset and returns mini-batches of data. + + Parameters + ---------- + dataset : Dataset + Source dataset. Note that numpy and mxnet arrays can be directly used + as a Dataset. + batch_size : int + Size of mini-batch. + shuffle : bool + Whether to shuffle the samples. + sampler : Sampler + The sampler to use. Either specify sampler or shuffle, not both. + last_batch : {'keep', 'discard', 'rollover'} + How to handle the last batch if batch_size does not evenly divide + `len(dataset)`. + + keep - A batch with less samples than previous batches is returned. + discard - The last batch is discarded if its incomplete. + rollover - The remaining samples are rolled over to the next epoch. + batch_sampler : Sampler + A sampler that returns mini-batches. Do not specify batch_size, + shuffle, sampler, and last_batch if batch_sampler is specified. + """ + def __init__(self, dataset, batch_size=None, shuffle=False, sampler=None, + last_batch=None, batch_sampler=None): + self._dataset = dataset + + if batch_sampler is None: + if batch_size is None: + raise ValueError("batch_size must be specified unless " \ + "batch_sampler is specified") + if sampler is None: + if shuffle: + sampler = _sampler.RandomSampler(len(dataset)) + else: + sampler = _sampler.SequentialSampler(len(dataset)) + elif shuffle: + raise ValueError("shuffle must not be specified if sampler is specified") + + batch_sampler = _sampler.BatchSampler( + sampler, batch_size, last_batch if last_batch else 'keep') + elif batch_size is not None or shuffle or sampler is not None or \ + last_batch is not None: + raise ValueError("batch_size, shuffle, sampler and last_batch must " \ + "not be specified if batch_sampler is specified.") + + self._batch_sampler = batch_sampler + + def __iter__(self): + for batch in self._batch_sampler: + yield _batchify([self._dataset[idx] for idx in batch]) + + def __len__(self): + return len(self._batch_sampler) diff --git a/python/mxnet/gluon/data/dataset.py b/python/mxnet/gluon/data/dataset.py new file mode 100644 index 000000000000..2fa20ccc522f --- /dev/null +++ b/python/mxnet/gluon/data/dataset.py @@ -0,0 +1,85 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +# coding: utf-8 +# pylint: disable= +"""Dataset container.""" +import os + +from ... import recordio, ndarray + +class Dataset(object): + """Abstract dataset class. All datasets should have this interface. + + Subclasses need to override `__getitem__`, which returns the i-th + element, and `__len__`, which returns the total number elements. + + .. note:: An mxnet or numpy array can be directly used as a dataset. + """ + def __getitem__(self, idx): + raise NotImplementedError + + def __len__(self): + raise NotImplementedError + + +class ArrayDataset(Dataset): + """A dataset with a data array and a label array. + + The i-th sample is `(data[i], lable[i])`. + + Parameters + ---------- + data : array-like object + The data array. Can be mxnet or numpy array. + label : array-like object + The label array. Can be mxnet or numpy array. + """ + def __init__(self, data, label): + assert len(data) == len(label) + self._data = data + if isinstance(label, ndarray.NDArray) and len(label.shape) == 1: + self._label = label.asnumpy() + else: + self._label = label + + def __getitem__(self, idx): + return self._data[idx], self._label[idx] + + def __len__(self): + return len(self._data) + + +class RecordFileDataset(Dataset): + """A dataset wrapping over a RecordIO (.rec) file. + + Each sample is a string representing the raw content of an record. + + Parameters + ---------- + filename : str + Path to rec file. + """ + def __init__(self, filename): + idx_file = os.path.splitext(filename)[0] + '.idx' + self._record = recordio.MXIndexedRecordIO(idx_file, filename, 'r') + + def __getitem__(self, idx): + return self._record.read_idx(idx) + + def __len__(self): + return len(self._record.keys) diff --git a/python/mxnet/gluon/data/sampler.py b/python/mxnet/gluon/data/sampler.py new file mode 100644 index 000000000000..80f115e0333f --- /dev/null +++ b/python/mxnet/gluon/data/sampler.py @@ -0,0 +1,137 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +# coding: utf-8 +# pylint: disable= +"""Dataset sampler.""" + +import random + +class Sampler(object): + """Base class for samplers. + + All samplers should subclass `Sampler` and define `__iter__` and `__len__` + methods. + """ + def __iter__(self): + raise NotImplementedError + + def __len__(self): + raise NotImplementedError + + +class SequentialSampler(Sampler): + """Samples elements from [0, length) sequentially. + + Parameters + ---------- + length : int + Length of the sequence. + """ + def __init__(self, length): + self._length = length + + def __iter__(self): + return iter(range(self._length)) + + def __len__(self): + return self._length + + +class RandomSampler(Sampler): + """Samples elements from [0, length) randomly without replacement. + + Parameters + ---------- + length : int + Length of the sequence. + """ + def __init__(self, length): + self._length = length + + def __iter__(self): + indices = list(range(self._length)) + random.shuffle(indices) + return iter(indices) + + def __len__(self): + return self._length + + +class BatchSampler(Sampler): + """Wraps over another `Sampler` and return mini-batches of samples. + + Parameters + ---------- + sampler : Sampler + The source Sampler. + batch_size : int + Size of mini-batch. + last_batch : {'keep', 'discard', 'rollover'} + Specifies how the last batch is handled if batch_size does not evenly + divide sequence length. + + If 'keep', the last batch will be returned directly, but will contain + less element than `batch_size` requires. + + If 'discard', the last batch will be discarded. + + If 'rollover', the remaining elements will be rolled over to the next + iteration. + + Examples + -------- + >>> sampler = gluon.data.SequentialSampler(10) + >>> batch_sampler = gluon.data.BatchSampler(sampler, 3, 'keep') + >>> list(batch_sampler) + [[0, 1, 2], [3, 4, 5], [6, 7, 8], [9]] + """ + def __init__(self, sampler, batch_size, last_batch='keep'): + self._sampler = sampler + self._batch_size = batch_size + self._last_batch = last_batch + self._prev = [] + + def __iter__(self): + batch, self._prev = self._prev, [] + for i in self._sampler: + batch.append(i) + if len(batch) == self._batch_size: + yield batch + batch = [] + if batch: + if self._last_batch == 'keep': + yield batch + elif self._last_batch == 'discard': + return + elif self._last_batch == 'rollover': + self._prev = batch + else: + raise ValueError( + "last_batch must be one of 'keep', 'discard', or 'rollover', " \ + "but got %s"%self._last_batch) + + def __len__(self): + if self._last_batch == 'keep': + return (len(self._sampler) + self._batch_size - 1) // self._batch_size + if self._last_batch == 'discard': + return len(self._sampler) // self._batch_size + if self._last_batch == 'rollover': + return (len(self._prev) + len(self._sampler)) // self._batch_size + raise ValueError( + "last_batch must be one of 'keep', 'discard', or 'rollover', " \ + "but got %s"%self._last_batch) diff --git a/python/mxnet/gluon/data/vision.py b/python/mxnet/gluon/data/vision.py new file mode 100644 index 000000000000..b63624508124 --- /dev/null +++ b/python/mxnet/gluon/data/vision.py @@ -0,0 +1,261 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +# coding: utf-8 +# pylint: disable= +"""Dataset container.""" + +import os +import gzip +import tarfile +import struct +import warnings +import numpy as np + +from . import dataset +from ..utils import download, check_sha1 +from ... import nd, image, recordio + + +class _DownloadedDataset(dataset.Dataset): + """Base class for MNIST, cifar10, etc.""" + def __init__(self, root, train, transform): + self._root = os.path.expanduser(root) + self._train = train + self._transform = transform + self._data = None + self._label = None + + self._get_data() + + def __getitem__(self, idx): + if self._transform is not None: + return self._transform(self._data[idx], self._label[idx]) + return self._data[idx], self._label[idx] + + def __len__(self): + return len(self._label) + + def _get_data(self): + raise NotImplementedError + + +class MNIST(_DownloadedDataset): + """MNIST handwritten digits dataset from `http://yann.lecun.com/exdb/mnist`_. + + Each sample is an image (in 3D NDArray) with shape (28, 28, 1). + + Parameters + ---------- + root : str + Path to temp folder for storing data. + train : bool + Whether to load the training or testing set. + transform : function + A user defined callback that transforms each instance. For example:: + + transform=lambda data, label: (data.astype(np.float32)/255, label) + """ + def __init__(self, root='~/.mxnet/datasets/', train=True, + transform=None): + super(MNIST, self).__init__(root, train, transform) + + def _get_data(self): + if not os.path.isdir(self._root): + os.makedirs(self._root) + url = 'http://data.mxnet.io/data/mnist/' + if self._train: + data_file = download(url+'train-images-idx3-ubyte.gz', self._root, + sha1_hash='6c95f4b05d2bf285e1bfb0e7960c31bd3b3f8a7d') + label_file = download(url+'train-labels-idx1-ubyte.gz', self._root, + sha1_hash='2a80914081dc54586dbdf242f9805a6b8d2a15fc') + else: + data_file = download(url+'t10k-images-idx3-ubyte.gz', self._root, + sha1_hash='c3a25af1f52dad7f726cce8cacb138654b760d48') + label_file = download(url+'t10k-labels-idx1-ubyte.gz', self._root, + sha1_hash='763e7fa3757d93b0cdec073cef058b2004252c17') + + with gzip.open(label_file, 'rb') as fin: + struct.unpack(">II", fin.read(8)) + label = np.fromstring(fin.read(), dtype=np.uint8).astype(np.int32) + + with gzip.open(data_file, 'rb') as fin: + struct.unpack(">IIII", fin.read(16)) + data = np.fromstring(fin.read(), dtype=np.uint8) + data = data.reshape(len(label), 28, 28, 1) + + self._data = [nd.array(x, dtype=x.dtype) for x in data] + self._label = label + + +class CIFAR10(_DownloadedDataset): + """CIFAR10 image classification dataset from `https://www.cs.toronto.edu/~kriz/cifar.html`_. + + Each sample is an image (in 3D NDArray) with shape (32, 32, 1). + + Parameters + ---------- + root : str + Path to temp folder for storing data. + train : bool + Whether to load the training or testing set. + transform : function + A user defined callback that transforms each instance. For example:: + + transform=lambda data, label: (data.astype(np.float32)/255, label) + """ + def __init__(self, root='~/.mxnet/datasets/', train=True, + transform=None): + self._file_hashes = {'data_batch_1.bin': 'aadd24acce27caa71bf4b10992e9e7b2d74c2540', + 'data_batch_2.bin': 'c0ba65cce70568cd57b4e03e9ac8d2a5367c1795', + 'data_batch_3.bin': '1dd00a74ab1d17a6e7d73e185b69dbf31242f295', + 'data_batch_4.bin': 'aab85764eb3584312d3c7f65fd2fd016e36a258e', + 'data_batch_5.bin': '26e2849e66a845b7f1e4614ae70f4889ae604628', + 'test_batch.bin': '67eb016db431130d61cd03c7ad570b013799c88c'} + super(CIFAR10, self).__init__(root, train, transform) + + def _read_batch(self, filename): + with open(filename, 'rb') as fin: + data = np.fromstring(fin.read(), dtype=np.uint8).reshape(-1, 3072+1) + + return data[:, 1:].reshape(-1, 3, 32, 32).transpose(0, 2, 3, 1), \ + data[:, 0].astype(np.int32) + + def _get_data(self): + if not os.path.isdir(self._root): + os.makedirs(self._root) + + file_paths = [(name, os.path.join(self._root, 'cifar-10-batches-bin/', name)) + for name in self._file_hashes] + if any(not os.path.exists(path) or not check_sha1(path, self._file_hashes[name]) + for name, path in file_paths): + url = 'https://www.cs.toronto.edu/~kriz/cifar-10-binary.tar.gz' + filename = download(url, self._root, + sha1_hash='e8aa088b9774a44ad217101d2e2569f823d2d491') + + with tarfile.open(filename) as tar: + tar.extractall(self._root) + + if self._train: + filename = os.path.join(self._root, 'cifar-10-batches-bin/data_batch_%d.bin') + data, label = zip(*[self._read_batch(filename%i) for i in range(1, 6)]) + data = np.concatenate(data) + label = np.concatenate(label) + else: + filename = os.path.join(self._root, 'cifar-10-batches-bin/test_batch.bin') + data, label = self._read_batch(filename) + + self._data = [nd.array(x, dtype=x.dtype) for x in data] + self._label = label + + +class ImageRecordDataset(dataset.RecordFileDataset): + """A dataset wrapping over a RecordIO file containing images. + + Each sample is an image and its corresponding label. + + Parameters + ---------- + filename : str + Path to rec file. + flag : {0, 1}, default 1 + If 0, always convert images to greyscale. + + If 1, always convert images to colored (RGB). + transform : function + A user defined callback that transforms each instance. For example:: + + transform=lambda data, label: (data.astype(np.float32)/255, label) + """ + def __init__(self, filename, flag=1, transform=None): + super(ImageRecordDataset, self).__init__(filename) + self._flag = flag + self._transform = transform + + def __getitem__(self, idx): + record = super(ImageRecordDataset, self).__getitem__(idx) + header, img = recordio.unpack(record) + if self._transform is not None: + return self._transform(image.imdecode(img, self._flag), header.label) + return image.imdecode(img, self._flag), header.label + + +class ImageFolderDataset(dataset.Dataset): + """A dataset for loading image files stored in a folder structure like:: + + root/car/0001.jpg + root/car/xxxa.jpg + root/car/yyyb.jpg + root/bus/123.jpg + root/bus/023.jpg + root/bus/wwww.jpg + + Parameters + ---------- + root : str + Path to root directory. + flag : {0, 1}, default 1 + If 0, always convert loaded images to greyscale (1 channel). + If 1, always convert loaded images to colored (3 channels). + transform : callable + A function that takes data and label and transforms them:: + + transform = lambda data, label: (data.astype(np.float32)/255, label) + + Attributes + ---------- + synsets : list + List of class names. `synsets[i]` is the name for the integer label `i` + items : list of tuples + List of all images in (filename, label) pairs. + """ + def __init__(self, root, flag=1, transform=None): + self._root = os.path.expanduser(root) + self._flag = flag + self._transform = transform + self._exts = ['.jpg', '.jpeg', '.png'] + self._list_iamges(self._root) + + def _list_iamges(self, root): + self.synsets = [] + self.items = [] + + for folder in sorted(os.listdir(root)): + path = os.path.join(root, folder) + if not os.path.isdir(path): + warnings.warn('Ignoring %s, which is not a directory.'%path, stacklevel=3) + continue + label = len(self.synsets) + self.synsets.append(folder) + for filename in sorted(os.listdir(path)): + filename = os.path.join(path, filename) + ext = os.path.splitext(filename)[1] + if ext.lower() not in self._exts: + warnings.warn('Ignoring %s of type %s. Only support %s'%( + filename, ext, ', '.join(self._exts))) + continue + self.items.append((filename, label)) + + def __getitem__(self, idx): + img = image.imread(self.items[idx][0], self._flag) + label = self.items[idx][1] + if self._transform is not None: + return self._transform(img, label) + return img, label + + def __len__(self): + return len(self.items) diff --git a/python/mxnet/gluon/loss.py b/python/mxnet/gluon/loss.py new file mode 100644 index 000000000000..583910590868 --- /dev/null +++ b/python/mxnet/gluon/loss.py @@ -0,0 +1,297 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +# coding: utf-8 +# pylint: disable=arguments-differ +""" losses for training neural networks """ +from __future__ import absolute_import + +from .. import ndarray +from ..base import numeric_types +from .block import HybridBlock + +def _apply_weighting(F, loss, weight=None, sample_weight=None): + """Apply weighting to loss. + + Parameters + ---------- + loss : Symbol + The loss to be weighted. + weight : float or None + Global scalar weight for loss. + sample_weight : Symbol or None + Per sample weighting. Must be broadcastable to + the same shape as loss. For example, if loss has + shape (64, 10) and you want to weight each sample + in the batch separately, `sample_weight` should have + shape (64, 1). + + Returns + ------- + loss : Symbol + Weighted loss + """ + if sample_weight is not None: + loss = F.broadcast_mul(loss, sample_weight) + + if weight is not None: + assert isinstance(weight, numeric_types), "weight must be a number" + loss = loss * weight + + return loss + +def _reshape_label_as_output(F, output, label): + # for symbolic output.shape is not available so we reshape + # to empty shape and let it be inferred from output's shape + # via the '-' operator later. + return label.reshape(output.shape) if F is ndarray else label.reshape(()) + +class Loss(HybridBlock): + """Base class for loss. + + Parameters + ---------- + weight : float or None + Global scalar weight for loss. + batch_axis : int, default 0 + The axis that represents mini-batch. + """ + def __init__(self, weight, batch_axis, **kwargs): + super(Loss, self).__init__(**kwargs) + self._weight = weight + self._batch_axis = batch_axis + + def __repr__(self): + s = '{name}(batch_axis={_batch_axis}, w={_weight})' + return s.format(name=self.__class__.__name__, **self.__dict__) + + def hybrid_forward(self, F, x, *args, **kwargs): + """Overrides to construct symbolic graph for this `Block`. + + Parameters + ---------- + x : Symbol or NDArray + The first input tensor. + *args : list of Symbol or list of NDArray + Additional input tensors. + """ + # pylint: disable= invalid-name + raise NotImplementedError + + +class L2Loss(Loss): + """Calculates the mean squared error between output and label: + + .. math:: + L = \\frac{1}{2}\\sum_i \\Vert {output}_i - {label}_i \\Vert^2. + + Output and label can have arbitrary shape as long as they have the same + number of elements. + + Parameters + ---------- + weight : float or None + Global scalar weight for loss. + sample_weight : Symbol or None + Per sample weighting. Must be broadcastable to + the same shape as loss. For example, if loss has + shape (64, 10) and you want to weight each sample + in the batch, `sample_weight` should have shape (64, 1). + batch_axis : int, default 0 + The axis that represents mini-batch. + """ + def __init__(self, weight=1., batch_axis=0, **kwargs): + super(L2Loss, self).__init__(weight, batch_axis, **kwargs) + + def hybrid_forward(self, F, output, label, sample_weight=None): + label = _reshape_label_as_output(F, output, label) + loss = F.square(output - label) + loss = _apply_weighting(F, loss, self._weight/2, sample_weight) + return F.mean(loss, axis=self._batch_axis, exclude=True) + + +class L1Loss(Loss): + """Calculates the mean absolute error between output and label: + + .. math:: + L = \\frac{1}{2}\\sum_i \\vert {output}_i - {label}_i \\vert. + + Output and label must have the same shape. + + Parameters + ---------- + weight : float or None + Global scalar weight for loss. + sample_weight : Symbol or None + Per sample weighting. Must be broadcastable to + the same shape as loss. For example, if loss has + shape (64, 10) and you want to weight each sample + in the batch, `sample_weight` should have shape (64, 1). + batch_axis : int, default 0 + The axis that represents mini-batch. + """ + def __init__(self, weight=None, batch_axis=0, **kwargs): + super(L1Loss, self).__init__(weight, batch_axis, **kwargs) + + def hybrid_forward(self, F, output, label, sample_weight=None): + label = _reshape_label_as_output(F, output, label) + loss = F.abs(output - label) + loss = _apply_weighting(F, loss, self._weight, sample_weight) + return F.mean(loss, axis=self._batch_axis, exclude=True) + + +class SigmoidBinaryCrossEntropyLoss(Loss): + r"""The cross-entropy loss for binary classification. (alias: SigmoidBCELoss) + + BCE loss is useful when training logistic regression. + + .. math:: + loss(o, t) = - 1/n \sum_i (t[i] * log(o[i]) + (1 - t[i]) * log(1 - o[i])) + + + Parameters + ---------- + from_sigmoid : bool, default is `False` + Whether the input is from the output of sigmoid. Set this to false will make + the loss calculate sigmoid and then BCE, which is more numerically stable through + log-sum-exp trick. + weight : float or None + Global scalar weight for loss. + sample_weight : Symbol or None + Per sample weighting. Must be broadcastable to + the same shape as loss. For example, if loss has + shape (64, 10) and you want to weight each sample + in the batch, `sample_weight` should have shape (64, 1). + batch_axis : int, default 0 + The axis that represents mini-batch. + """ + def __init__(self, from_sigmoid=False, weight=None, batch_axis=0, **kwargs): + super(SigmoidBinaryCrossEntropyLoss, self).__init__(weight, batch_axis, **kwargs) + self._from_sigmoid = from_sigmoid + + def hybrid_forward(self, F, output, label, sample_weight=None): + label = _reshape_label_as_output(F, output, label) + if not self._from_sigmoid: + max_val = F.maximum(-output, 0) + loss = output - output*label + max_val + F.log(F.exp(-max_val)+F.exp(-output-max_val)) + else: + loss = -(F.log(output+1e-8)*label + F.log(1.-output+1e-8)*(1.-label)) + loss = _apply_weighting(F, loss, self._weight, sample_weight) + return F.mean(loss, axis=self._batch_axis, exclude=True) + +SigmoidBCELoss = SigmoidBinaryCrossEntropyLoss + + +class SoftmaxCrossEntropyLoss(Loss): + """Computes the softmax cross entropy loss. (alias: SoftmaxCELoss) + + If `sparse_label` is `True`, label should contain integer category indicators: + + .. math:: + p = {softmax}({output}) + + L = -\\sum_i {log}(p_{i,{label}_i}) + + Label's shape should be output's shape without the `axis` dimension. i.e. for + `output.shape` = (1,2,3,4) and axis = 2, `label.shape` should be (1,2,4). + + If `sparse_label` is `False`, label should contain probability distribution + with the same shape as output: + + .. math:: + p = {softmax}({output}) + + L = -\\sum_i \\sum_j {label}_j {log}(p_{ij}) + + Parameters + ---------- + axis : int, default -1 + The axis to sum over when computing softmax and entropy. + sparse_label : bool, default True + Whether label is an integer array instead of probability distribution. + from_logits : bool, default False + Whether input is a log probability (usually from log_softmax) instead + of unnormalized numbers. + weight : float or None + Global scalar weight for loss. + sample_weight : Symbol or None + Per sample weighting. Must be broadcastable to + the same shape as loss. For example, if loss has + shape (64, 10) and you want to weight each sample + in the batch, `sample_weight` should have shape (64, 1). + batch_axis : int, default 0 + The axis that represents mini-batch. + """ + def __init__(self, axis=-1, sparse_label=True, from_logits=False, weight=None, + batch_axis=0, **kwargs): + super(SoftmaxCrossEntropyLoss, self).__init__(weight, batch_axis, **kwargs) + self._axis = axis + self._sparse_label = sparse_label + self._from_logits = from_logits + + def hybrid_forward(self, F, output, label, sample_weight=None): + if not self._from_logits: + output = F.log_softmax(output) + if self._sparse_label: + loss = -F.pick(output, label, axis=self._axis, keepdims=True) + else: + loss = -F.sum(output*label, axis=self._axis, keepdims=True) + loss = _apply_weighting(F, loss, self._weight, sample_weight) + return F.mean(loss, axis=self._batch_axis, exclude=True) + +SoftmaxCELoss = SoftmaxCrossEntropyLoss + + +class KLDivLoss(Loss): + """The Kullback-Leibler divergence loss. + + KL divergence is a useful distance measure for continuous distributions + and is often useful when performing direct regression over the space of + (discretely sampled) continuous output distributions. + + .. _Kullback-Leibler divergence: + https://en.wikipedia.org/wiki/Kullback-Leibler_divergence + .. math:: + L = 1/n \\sum_i (label_i * (log(label_i) - output_i)) + + Label's shape should be the same as output's. + + Parameters + ---------- + from_logits : bool, default is `True` + Whether the input is log probability (usually from log_softmax) instead + of unnormalized numbers. + weight : float or None + Global scalar weight for loss. + sample_weight : Symbol or None + Per sample weighting. Must be broadcastable to + the same shape as loss. For example, if loss has + shape (64, 10) and you want to weight each sample + in the batch, `sample_weight` should have shape (64, 1). + batch_axis : int, default 0 + The axis that represents mini-batch. + """ + def __init__(self, from_logits=True, weight=None, batch_axis=0, **kwargs): + super(KLDivLoss, self).__init__(weight, batch_axis, **kwargs) + self._from_logits = from_logits + + def hybrid_forward(self, F, output, label, sample_weight=None): + if not self._from_logits: + output = F.log_softmax(output) + loss = label * (F.log(label+1e-8) - output) + loss = _apply_weighting(F, loss, self._weight, sample_weight) + return F.mean(loss, axis=self._batch_axis, exclude=True) diff --git a/python/mxnet/gluon/model_zoo/__init__.py b/python/mxnet/gluon/model_zoo/__init__.py new file mode 100644 index 000000000000..b8c32af38561 --- /dev/null +++ b/python/mxnet/gluon/model_zoo/__init__.py @@ -0,0 +1,23 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +# coding: utf-8 +"""Predefined and pretrained models.""" + +from . import model_store + +from . import vision diff --git a/python/mxnet/gluon/model_zoo/custom_layers.py b/python/mxnet/gluon/model_zoo/custom_layers.py new file mode 100644 index 000000000000..cf91876888ee --- /dev/null +++ b/python/mxnet/gluon/model_zoo/custom_layers.py @@ -0,0 +1,81 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +# coding: utf-8 +# pylint: disable= arguments-differ +"""Custom neural network layers in model_zoo.""" + +from ..block import Block, HybridBlock +from ..utils import _indent + +class HybridConcurrent(HybridBlock): + """Lays `HybridBlock`s concurrently. + + Example:: + + net = HybridConcurrent() + # use net's name_scope to give child Blocks appropriate names. + with net.name_scope(): + net.add(nn.Dense(10, activation='relu')) + net.add(nn.Dense(20)) + net.add(Identity()) + """ + def __init__(self, concat_dim, prefix=None, params=None): + super(HybridConcurrent, self).__init__(prefix=prefix, params=params) + self.concat_dim = concat_dim + + def add(self, block): + """Adds block on top of the stack.""" + self.register_child(block) + + def hybrid_forward(self, F, x): + out = [] + for block in self._children: + out.append(block(x)) + out = F.concat(*out, dim=self.concat_dim) + return out + + def __repr__(self): + s = '{name}(\n{modstr}\n)' + modstr = '\n'.join([' ({key}): {block}'.format(key=key, + block=_indent(block.__repr__(), 2)) + for key, block in enumerate(self._children) + if isinstance(block, Block)]) + return s.format(name=self.__class__.__name__, + modstr=modstr) + + +class Identity(HybridBlock): + """Block that passes through the input directly. + + This layer is often used in conjunction with HybridConcurrent + block for residual connection. + + Example:: + + net = HybridConcurrent() + # use net's name_scope to give child Blocks appropriate names. + with net.name_scope(): + net.add(nn.Dense(10, activation='relu')) + net.add(nn.Dense(20)) + net.add(Identity()) + """ + def __init__(self, prefix=None, params=None): + super(Identity, self).__init__(prefix=prefix, params=params) + + def hybrid_forward(self, F, x): + return x diff --git a/python/mxnet/gluon/model_zoo/model_store.py b/python/mxnet/gluon/model_zoo/model_store.py new file mode 100644 index 000000000000..e524f215416d --- /dev/null +++ b/python/mxnet/gluon/model_zoo/model_store.py @@ -0,0 +1,118 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +# coding: utf-8 +"""Model zoo for pre-trained models.""" +from __future__ import print_function +__all__ = ['get_model_file', 'purge'] +import os +import zipfile + +from ..utils import download, check_sha1 + +_model_sha1 = {name: checksum for checksum, name in [ + ('44335d1f0046b328243b32a26a4fbd62d9057b45', 'alexnet'), + ('f27dbf2dbd5ce9a80b102d89c7483342cd33cb31', 'densenet121'), + ('b6c8a95717e3e761bd88d145f4d0a214aaa515dc', 'densenet161'), + ('2603f878403c6aa5a71a124c4a3307143d6820e9', 'densenet169'), + ('1cdbc116bc3a1b65832b18cf53e1cb8e7da017eb', 'densenet201'), + ('ed47ec45a937b656fcc94dabde85495bbef5ba1f', 'inceptionv3'), + ('d2b128fa89477c2e20061607a53a8d9f66ce239d', 'resnet101_v1'), + ('6562166cd597a6328a32a0ce47bb651df80b3bbb', 'resnet152_v1'), + ('38d6d423c22828718ec3397924b8e116a03e6ac0', 'resnet18_v1'), + ('4dc2c2390a7c7990e0ca1e53aeebb1d1a08592d1', 'resnet34_v1'), + ('2a903ab21260c85673a78fe65037819a843a1f43', 'resnet50_v1'), + ('8aacf80ff4014c1efa2362a963ac5ec82cf92d5b', 'resnet18_v2'), + ('0ed3cd06da41932c03dea1de7bc2506ef3fb97b3', 'resnet34_v2'), + ('264ba4970a0cc87a4f15c96e25246a1307caf523', 'squeezenet1.0'), + ('33ba0f93753c83d86e1eb397f38a667eaf2e9376', 'squeezenet1.1'), + ('dd221b160977f36a53f464cb54648d227c707a05', 'vgg11'), + ('ee79a8098a91fbe05b7a973fed2017a6117723a8', 'vgg11_bn'), + ('6bc5de58a05a5e2e7f493e2d75a580d83efde38c', 'vgg13'), + ('7d97a06c3c7a1aecc88b6e7385c2b373a249e95e', 'vgg13_bn'), + ('649467530119c0f78c4859999e264e7bf14471a9', 'vgg16'), + ('6b9dbe6194e5bfed30fd7a7c9a71f7e5a276cb14', 'vgg16_bn'), + ('f713436691eee9a20d70a145ce0d53ed24bf7399', 'vgg19'), + ('9730961c9cea43fd7eeefb00d792e386c45847d6', 'vgg19_bn')]} + +_url_format = 'https://{bucket}.s3.amazonaws.com/gluon/models/{file_name}.zip' +bucket = 'apache-mxnet' + +def short_hash(name): + if name not in _model_sha1: + raise ValueError('Pretrained model for {name} is not available.'.format(name=name)) + return _model_sha1[name][:8] + +def get_model_file(name, local_dir=os.path.expanduser('~/.mxnet/models/')): + r"""Return location for the pretrained on local file system. + + This function will download from online model zoo when model cannot be found or has mismatch. + The local_dir directory will be created if it doesn't exist. + + Parameters + ---------- + name : str + Name of the model. + local_dir : str, default '~/.mxnet/models' + Location for keeping the model parameters. + + Returns + ------- + file_path + Path to the requested pretrained model file. + """ + file_name = '{name}-{short_hash}'.format(name=name, + short_hash=short_hash(name)) + file_path = os.path.join(local_dir, file_name+'.params') + sha1_hash = _model_sha1[name] + if os.path.exists(file_path): + if check_sha1(file_path, sha1_hash): + return file_path + else: + print('Mismatch in the content of model file detected. Downloading again.') + else: + print('Model file is not found. Downloading.') + + if not os.path.exists(local_dir): + os.makedirs(local_dir) + + zip_file_path = os.path.join(local_dir, file_name+'.zip') + download(_url_format.format(bucket=bucket, + file_name=file_name), + path=zip_file_path, + overwrite=True) + with zipfile.ZipFile(zip_file_path) as zf: + zf.extractall(local_dir) + os.remove(zip_file_path) + + if check_sha1(file_path, sha1_hash): + return file_path + else: + raise ValueError('Downloaded file has different hash. Please try again.') + +def purge(local_dir=os.path.expanduser('~/.mxnet/models/')): + r"""Purge all pretrained model files in local file store. + + Parameters + ---------- + local_dir : str, default '~/.mxnet/models' + Location for keeping the model parameters. + """ + files = os.listdir(local_dir) + for f in files: + if f.endswith(".params"): + os.remove(os.path.join(local_dir, f)) diff --git a/python/mxnet/gluon/model_zoo/vision/__init__.py b/python/mxnet/gluon/model_zoo/vision/__init__.py new file mode 100644 index 000000000000..354236b2d896 --- /dev/null +++ b/python/mxnet/gluon/model_zoo/vision/__init__.py @@ -0,0 +1,133 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +# coding: utf-8 +# pylint: disable=wildcard-import, arguments-differ +r"""Module for pre-defined neural network models. + +This module contains definitions for the following model architectures: +- `AlexNet`_ +- `DenseNet`_ +- `Inception V3`_ +- `ResNet V1`_ +- `ResNet V2`_ +- `SqueezeNet`_ +- `VGG`_ + +You can construct a model with random weights by calling its constructor: +.. code:: + + import mxnet.gluon.models as models + resnet18 = models.resnet18_v1() + alexnet = models.alexnet() + squeezenet = models.squeezenet1_0() + densenet = models.densenet_161() + +We provide pre-trained models for all the models except ResNet V2. +These can constructed by passing +``pretrained=True``: +.. code:: + + import mxnet.gluon.models as models + resnet18 = models.resnet18_v1(pretrained=True) + alexnet = models.alexnet(pretrained=True) + +Pretrained models are converted from torchvision. +All pre-trained models expect input images normalized in the same way, +i.e. mini-batches of 3-channel RGB images of shape (N x 3 x H x W), +where N is the batch size, and H and W are expected to be at least 224. +The images have to be loaded in to a range of [0, 1] and then normalized +using ``mean = [0.485, 0.456, 0.406]`` and ``std = [0.229, 0.224, 0.225]``. +The transformation should preferrably happen at preprocessing. You can use +``mx.image.color_normalize`` for such transformation:: + + image = image/255 + normalized = mx.image.color_normalize(image, + mean=mx.nd.array([0.485, 0.456, 0.406]), + std=mx.nd.array([0.229, 0.224, 0.225])) + +.. _AlexNet: https://arxiv.org/abs/1404.5997 +.. _DenseNet: https://arxiv.org/abs/1608.06993 +.. _Inception V3: http://arxiv.org/abs/1512.00567 +.. _ResNet V1: https://arxiv.org/abs/1512.03385 +.. _ResNet V2: https://arxiv.org/abs/1512.03385 +.. _SqueezeNet: https://arxiv.org/abs/1602.07360 +.. _VGG: https://arxiv.org/abs/1409.1556 +""" + +from .alexnet import * + +from .densenet import * + +from .inception import * + +from .resnet import * + +from .squeezenet import * + +from .vgg import * + +def get_model(name, **kwargs): + """Returns a pre-defined model by name + + Parameters + ---------- + name : str + Name of the model. + pretrained : bool + Whether to load the pretrained weights for model. + classes : int + Number of classes for the output layer. + + Returns + ------- + HybridBlock + The model. + """ + models = {'resnet18_v1': resnet18_v1, + 'resnet34_v1': resnet34_v1, + 'resnet50_v1': resnet50_v1, + 'resnet101_v1': resnet101_v1, + 'resnet152_v1': resnet152_v1, + 'resnet18_v2': resnet18_v2, + 'resnet34_v2': resnet34_v2, + 'resnet50_v2': resnet50_v2, + 'resnet101_v2': resnet101_v2, + 'resnet152_v2': resnet152_v2, + 'vgg11': vgg11, + 'vgg13': vgg13, + 'vgg16': vgg16, + 'vgg19': vgg19, + 'vgg11_bn': vgg11_bn, + 'vgg13_bn': vgg13_bn, + 'vgg16_bn': vgg16_bn, + 'vgg19_bn': vgg19_bn, + 'alexnet': alexnet, + 'densenet121': densenet121, + 'densenet161': densenet161, + 'densenet169': densenet169, + 'densenet201': densenet201, + 'squeezenet1.0': squeezenet1_0, + 'squeezenet1.1': squeezenet1_1, + 'inceptionv3': inception_v3, + } + name = name.lower() + if name not in models: + raise ValueError( + 'Model %s is not supported. Available options are\n\t%s'%( + name, '\n\t'.join(sorted(models.keys())))) + return models[name](**kwargs) diff --git a/python/mxnet/gluon/model_zoo/vision/alexnet.py b/python/mxnet/gluon/model_zoo/vision/alexnet.py new file mode 100644 index 000000000000..4d5bc8c85b67 --- /dev/null +++ b/python/mxnet/gluon/model_zoo/vision/alexnet.py @@ -0,0 +1,84 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +# coding: utf-8 +# pylint: disable= arguments-differ +"""Alexnet, implemented in Gluon.""" +__all__ = ['AlexNet', 'alexnet'] + +from ....context import cpu +from ...block import HybridBlock +from ... import nn + +# Net +class AlexNet(HybridBlock): + r"""AlexNet model from the `"One weird trick..." `_ paper. + + Parameters + ---------- + classes : int, default 1000 + Number of classes for the output layer. + """ + def __init__(self, classes=1000, **kwargs): + super(AlexNet, self).__init__(**kwargs) + with self.name_scope(): + self.features = nn.HybridSequential(prefix='') + with self.features.name_scope(): + self.features.add(nn.Conv2D(64, kernel_size=11, strides=4, + padding=2, activation='relu')) + self.features.add(nn.MaxPool2D(pool_size=3, strides=2)) + self.features.add(nn.Conv2D(192, kernel_size=5, padding=2, + activation='relu')) + self.features.add(nn.MaxPool2D(pool_size=3, strides=2)) + self.features.add(nn.Conv2D(384, kernel_size=3, padding=1, + activation='relu')) + self.features.add(nn.Conv2D(256, kernel_size=3, padding=1, + activation='relu')) + self.features.add(nn.Conv2D(256, kernel_size=3, padding=1, + activation='relu')) + self.features.add(nn.MaxPool2D(pool_size=3, strides=2)) + self.features.add(nn.Flatten()) + + self.classifier = nn.HybridSequential(prefix='') + with self.classifier.name_scope(): + self.classifier.add(nn.Dense(4096, activation='relu')) + self.classifier.add(nn.Dropout(0.5)) + self.classifier.add(nn.Dense(4096, activation='relu')) + self.classifier.add(nn.Dropout(0.5)) + self.classifier.add(nn.Dense(classes)) + + def hybrid_forward(self, F, x): + x = self.features(x) + x = self.classifier(x) + return x + +# Constructor +def alexnet(pretrained=False, ctx=cpu(), **kwargs): + r"""AlexNet model from the `"One weird trick..." `_ paper. + + Parameters + ---------- + pretrained : bool, default False + Whether to load the pretrained weights for model. + ctx : Context, default CPU + The context in which to load the pretrained weights. + """ + net = AlexNet(**kwargs) + if pretrained: + from ..model_store import get_model_file + net.load_params(get_model_file('alexnet'), ctx=ctx) + return net diff --git a/python/mxnet/gluon/model_zoo/vision/densenet.py b/python/mxnet/gluon/model_zoo/vision/densenet.py new file mode 100644 index 000000000000..57dbe5d188a7 --- /dev/null +++ b/python/mxnet/gluon/model_zoo/vision/densenet.py @@ -0,0 +1,192 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +# coding: utf-8 +# pylint: disable= arguments-differ +"""DenseNet, implemented in Gluon.""" +__all__ = ['DenseNet', 'densenet121', 'densenet161', 'densenet169', 'densenet201'] + +from ....context import cpu +from ...block import HybridBlock +from ... import nn +from ..custom_layers import HybridConcurrent, Identity + +# Helpers +def _make_dense_block(num_layers, bn_size, growth_rate, dropout, stage_index): + out = nn.HybridSequential(prefix='stage%d_'%stage_index) + with out.name_scope(): + for _ in range(num_layers): + out.add(_make_dense_layer(growth_rate, bn_size, dropout)) + return out + +def _make_dense_layer(growth_rate, bn_size, dropout): + new_features = nn.HybridSequential(prefix='') + new_features.add(nn.BatchNorm()) + new_features.add(nn.Activation('relu')) + new_features.add(nn.Conv2D(bn_size * growth_rate, kernel_size=1, use_bias=False)) + new_features.add(nn.BatchNorm()) + new_features.add(nn.Activation('relu')) + new_features.add(nn.Conv2D(growth_rate, kernel_size=3, padding=1, use_bias=False)) + if dropout: + new_features.add(nn.Dropout(dropout)) + + out = HybridConcurrent(concat_dim=1, prefix='') + out.add(Identity()) + out.add(new_features) + + return out + +def _make_transition(num_output_features): + out = nn.HybridSequential(prefix='') + out.add(nn.BatchNorm()) + out.add(nn.Activation('relu')) + out.add(nn.Conv2D(num_output_features, kernel_size=1, use_bias=False)) + out.add(nn.AvgPool2D(pool_size=2, strides=2)) + return out + +# Net +class DenseNet(HybridBlock): + r"""Densenet-BC model from the + `"Densely Connected Convolutional Networks" `_ paper. + + Parameters + ---------- + num_init_features : int + Number of filters to learn in the first convolution layer. + growth_rate : int + Number of filters to add each layer (`k` in the paper). + block_config : list of int + List of integers for numbers of layers in each pooling block. + bn_size : int, default 4 + Multiplicative factor for number of bottle neck layers. + (i.e. bn_size * k features in the bottleneck layer) + dropout : float, default 0 + Rate of dropout after each dense layer. + classes : int, default 1000 + Number of classification classes. + """ + def __init__(self, num_init_features, growth_rate, block_config, + bn_size=4, dropout=0, classes=1000, **kwargs): + + super(DenseNet, self).__init__(**kwargs) + with self.name_scope(): + self.features = nn.HybridSequential(prefix='') + self.features.add(nn.Conv2D(num_init_features, kernel_size=7, + strides=2, padding=3, use_bias=False)) + self.features.add(nn.BatchNorm()) + self.features.add(nn.Activation('relu')) + self.features.add(nn.MaxPool2D(pool_size=3, strides=2, padding=1)) + # Add dense blocks + num_features = num_init_features + for i, num_layers in enumerate(block_config): + self.features.add(_make_dense_block(num_layers, bn_size, growth_rate, dropout, i+1)) + num_features = num_features + num_layers * growth_rate + if i != len(block_config) - 1: + self.features.add(_make_transition(num_features // 2)) + num_features = num_features // 2 + self.features.add(nn.BatchNorm()) + self.features.add(nn.Activation('relu')) + self.features.add(nn.AvgPool2D(pool_size=7)) + self.features.add(nn.Flatten()) + + self.classifier = nn.Dense(classes) + + def hybrid_forward(self, F, x): + x = self.features(x) + x = self.classifier(x) + return x + + +# Specification +densenet_spec = {121: (64, 32, [6, 12, 24, 16]), + 161: (96, 48, [6, 12, 36, 24]), + 169: (64, 32, [6, 12, 32, 32]), + 201: (64, 32, [6, 12, 48, 32])} + + +# Constructor +def get_densenet(num_layers, pretrained=False, ctx=cpu(), **kwargs): + r"""Densenet-BC model from the + `"Densely Connected Convolutional Networks" `_ paper. + + Parameters + ---------- + num_layers : int + Number of layers for the variant of densenet. Options are 121, 161, 169, 201. + pretrained : bool, default False + Whether to load the pretrained weights for model. + ctx : Context, default CPU + The context in which to load the pretrained weights. + """ + num_init_features, growth_rate, block_config = densenet_spec[num_layers] + net = DenseNet(num_init_features, growth_rate, block_config, **kwargs) + if pretrained: + from ..model_store import get_model_file + net.load_params(get_model_file('densenet%d'%(num_layers)), ctx=ctx) + return net + +def densenet121(**kwargs): + r"""Densenet-BC 121-layer model from the + `"Densely Connected Convolutional Networks" `_ paper. + + Parameters + ---------- + pretrained : bool, default False + Whether to load the pretrained weights for model. + ctx : Context, default CPU + The context in which to load the pretrained weights. + """ + return get_densenet(121, **kwargs) + +def densenet161(**kwargs): + r"""Densenet-BC 161-layer model from the + `"Densely Connected Convolutional Networks" `_ paper. + + Parameters + ---------- + pretrained : bool, default False + Whether to load the pretrained weights for model. + ctx : Context, default CPU + The context in which to load the pretrained weights. + """ + return get_densenet(161, **kwargs) + +def densenet169(**kwargs): + r"""Densenet-BC 169-layer model from the + `"Densely Connected Convolutional Networks" `_ paper. + + Parameters + ---------- + pretrained : bool, default False + Whether to load the pretrained weights for model. + ctx : Context, default CPU + The context in which to load the pretrained weights. + """ + return get_densenet(169, **kwargs) + +def densenet201(**kwargs): + r"""Densenet-BC 201-layer model from the + `"Densely Connected Convolutional Networks" `_ paper. + + Parameters + ---------- + pretrained : bool, default False + Whether to load the pretrained weights for model. + ctx : Context, default CPU + The context in which to load the pretrained weights. + """ + return get_densenet(201, **kwargs) diff --git a/python/mxnet/gluon/model_zoo/vision/inception.py b/python/mxnet/gluon/model_zoo/vision/inception.py new file mode 100644 index 000000000000..1afd3e346113 --- /dev/null +++ b/python/mxnet/gluon/model_zoo/vision/inception.py @@ -0,0 +1,217 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +# coding: utf-8 +# pylint: disable= arguments-differ +"""Inception, implemented in Gluon.""" +__all__ = ['Inception3', 'inception_v3'] + +from ....context import cpu +from ...block import HybridBlock +from ... import nn +from ..custom_layers import HybridConcurrent + +# Helpers +def _make_basic_conv(**kwargs): + out = nn.HybridSequential(prefix='') + out.add(nn.Conv2D(use_bias=False, **kwargs)) + out.add(nn.BatchNorm(epsilon=0.001)) + out.add(nn.Activation('relu')) + return out + +def _make_branch(use_pool, *conv_settings): + out = nn.HybridSequential(prefix='') + if use_pool == 'avg': + out.add(nn.AvgPool2D(pool_size=3, strides=1, padding=1)) + elif use_pool == 'max': + out.add(nn.MaxPool2D(pool_size=3, strides=2)) + setting_names = ['channels', 'kernel_size', 'strides', 'padding'] + for setting in conv_settings: + kwargs = {} + for i, value in enumerate(setting): + if value is not None: + kwargs[setting_names[i]] = value + out.add(_make_basic_conv(**kwargs)) + return out + +def _make_A(pool_features, prefix): + out = HybridConcurrent(concat_dim=1, prefix=prefix) + with out.name_scope(): + out.add(_make_branch(None, + (64, 1, None, None))) + out.add(_make_branch(None, + (48, 1, None, None), + (64, 5, None, 2))) + out.add(_make_branch(None, + (64, 1, None, None), + (96, 3, None, 1), + (96, 3, None, 1))) + out.add(_make_branch('avg', + (pool_features, 1, None, None))) + return out + +def _make_B(prefix): + out = HybridConcurrent(concat_dim=1, prefix=prefix) + with out.name_scope(): + out.add(_make_branch(None, + (384, 3, 2, None))) + out.add(_make_branch(None, + (64, 1, None, None), + (96, 3, None, 1), + (96, 3, 2, None))) + out.add(_make_branch('max')) + return out + +def _make_C(channels_7x7, prefix): + out = HybridConcurrent(concat_dim=1, prefix=prefix) + with out.name_scope(): + out.add(_make_branch(None, + (192, 1, None, None))) + out.add(_make_branch(None, + (channels_7x7, 1, None, None), + (channels_7x7, (1, 7), None, (0, 3)), + (192, (7, 1), None, (3, 0)))) + out.add(_make_branch(None, + (channels_7x7, 1, None, None), + (channels_7x7, (7, 1), None, (3, 0)), + (channels_7x7, (1, 7), None, (0, 3)), + (channels_7x7, (7, 1), None, (3, 0)), + (192, (1, 7), None, (0, 3)))) + out.add(_make_branch('avg', + (192, 1, None, None))) + return out + +def _make_D(prefix): + out = HybridConcurrent(concat_dim=1, prefix=prefix) + with out.name_scope(): + out.add(_make_branch(None, + (192, 1, None, None), + (320, 3, 2, None))) + out.add(_make_branch(None, + (192, 1, None, None), + (192, (1, 7), None, (0, 3)), + (192, (7, 1), None, (3, 0)), + (192, 3, 2, None))) + out.add(_make_branch('max')) + return out + +def _make_E(prefix): + out = HybridConcurrent(concat_dim=1, prefix=prefix) + with out.name_scope(): + out.add(_make_branch(None, + (320, 1, None, None))) + + branch_3x3 = nn.HybridSequential(prefix='') + out.add(branch_3x3) + branch_3x3.add(_make_branch(None, + (384, 1, None, None))) + branch_3x3_split = HybridConcurrent(concat_dim=1, prefix='') + branch_3x3_split.add(_make_branch(None, + (384, (1, 3), None, (0, 1)))) + branch_3x3_split.add(_make_branch(None, + (384, (3, 1), None, (1, 0)))) + branch_3x3.add(branch_3x3_split) + + branch_3x3dbl = nn.HybridSequential(prefix='') + out.add(branch_3x3dbl) + branch_3x3dbl.add(_make_branch(None, + (448, 1, None, None), + (384, 3, None, 1))) + branch_3x3dbl_split = HybridConcurrent(concat_dim=1, prefix='') + branch_3x3dbl.add(branch_3x3dbl_split) + branch_3x3dbl_split.add(_make_branch(None, + (384, (1, 3), None, (0, 1)))) + branch_3x3dbl_split.add(_make_branch(None, + (384, (3, 1), None, (1, 0)))) + + out.add(_make_branch('avg', + (192, 1, None, None))) + return out + +def make_aux(classes): + out = nn.HybridSequential(prefix='') + out.add(nn.AvgPool2D(pool_size=5, strides=3)) + out.add(_make_basic_conv(channels=128, kernel_size=1)) + out.add(_make_basic_conv(channels=768, kernel_size=5)) + out.add(nn.Flatten()) + out.add(nn.Dense(classes)) + return out + +# Net +class Inception3(HybridBlock): + r"""Inception v3 model from + `"Rethinking the Inception Architecture for Computer Vision" + `_ paper. + + Parameters + ---------- + classes : int, default 1000 + Number of classification classes. + """ + def __init__(self, classes=1000, **kwargs): + super(Inception3, self).__init__(**kwargs) + # self.use_aux_logits = use_aux_logits + with self.name_scope(): + self.features = nn.HybridSequential(prefix='') + self.features.add(_make_basic_conv(channels=32, kernel_size=3, strides=2)) + self.features.add(_make_basic_conv(channels=32, kernel_size=3)) + self.features.add(_make_basic_conv(channels=64, kernel_size=3, padding=1)) + self.features.add(nn.MaxPool2D(pool_size=3, strides=2)) + self.features.add(_make_basic_conv(channels=80, kernel_size=1)) + self.features.add(_make_basic_conv(channels=192, kernel_size=3)) + self.features.add(nn.MaxPool2D(pool_size=3, strides=2)) + self.features.add(_make_A(32, 'A1_')) + self.features.add(_make_A(64, 'A2_')) + self.features.add(_make_A(64, 'A3_')) + self.features.add(_make_B('B_')) + self.features.add(_make_C(128, 'C1_')) + self.features.add(_make_C(160, 'C2_')) + self.features.add(_make_C(160, 'C3_')) + self.features.add(_make_C(192, 'C4_')) + + self.classifier = nn.HybridSequential(prefix='') + self.classifier.add(_make_D('D_')) + self.classifier.add(_make_E('E1_')) + self.classifier.add(_make_E('E2_')) + self.classifier.add(nn.AvgPool2D(pool_size=8)) + self.classifier.add(nn.Dropout(0.5)) + self.classifier.add(nn.Flatten()) + self.classifier.add(nn.Dense(classes)) + + def hybrid_forward(self, F, x): + x = self.features(x) + x = self.classifier(x) + return x + +# Constructor +def inception_v3(pretrained=False, ctx=cpu(), **kwargs): + r"""Inception v3 model from + `"Rethinking the Inception Architecture for Computer Vision" + `_ paper. + + Parameters + ---------- + pretrained : bool, default False + Whether to load the pretrained weights for model. + ctx : Context, default CPU + The context in which to load the pretrained weights. + """ + net = Inception3(**kwargs) + if pretrained: + from ..model_store import get_model_file + net.load_params(get_model_file('inceptionv3'), ctx=ctx) + return net diff --git a/python/mxnet/gluon/model_zoo/vision/resnet.py b/python/mxnet/gluon/model_zoo/vision/resnet.py new file mode 100644 index 000000000000..78bc726f41d9 --- /dev/null +++ b/python/mxnet/gluon/model_zoo/vision/resnet.py @@ -0,0 +1,515 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +# coding: utf-8 +# pylint: disable= arguments-differ +"""ResNets, implemented in Gluon.""" +from __future__ import division + +__all__ = ['ResNetV1', 'ResNetV2', + 'BasicBlockV1', 'BasicBlockV2', + 'BottleneckV1', 'BottleneckV2', + 'resnet18_v1', 'resnet34_v1', 'resnet50_v1', 'resnet101_v1', 'resnet152_v1', + 'resnet18_v2', 'resnet34_v2', 'resnet50_v2', 'resnet101_v2', 'resnet152_v2', + 'get_resnet'] + +from ....context import cpu +from ...block import HybridBlock +from ... import nn + +# Helpers +def _conv3x3(channels, stride, in_channels): + return nn.Conv2D(channels, kernel_size=3, strides=stride, padding=1, + use_bias=False, in_channels=in_channels) + + +# Blocks +class BasicBlockV1(HybridBlock): + r"""BasicBlock V1 from `"Deep Residual Learning for Image Recognition" + `_ paper. + This is used for ResNet V1 for 18, 34 layers. + + Parameters + ---------- + channels : int + Number of output channels. + stride : int + Stride size. + downsample : bool, default False + Whether to downsample the input. + in_channels : int, default 0 + Number of input channels. Default is 0, to infer from the graph. + """ + def __init__(self, channels, stride, downsample=False, in_channels=0, **kwargs): + super(BasicBlockV1, self).__init__(**kwargs) + self.body = nn.HybridSequential(prefix='') + self.body.add(_conv3x3(channels, stride, in_channels)) + self.body.add(nn.BatchNorm()) + self.body.add(nn.Activation('relu')) + self.body.add(_conv3x3(channels, 1, channels)) + self.body.add(nn.BatchNorm()) + if downsample: + self.downsample = nn.HybridSequential(prefix='') + self.downsample.add(nn.Conv2D(channels, kernel_size=1, strides=stride, + use_bias=False, in_channels=in_channels)) + self.downsample.add(nn.BatchNorm()) + else: + self.downsample = None + + def hybrid_forward(self, F, x): + residual = x + + x = self.body(x) + + if self.downsample: + residual = self.downsample(residual) + + x = F.Activation(residual+x, act_type='relu') + + return x + + +class BottleneckV1(HybridBlock): + r"""Bottleneck V1 from `"Deep Residual Learning for Image Recognition" + `_ paper. + This is used for ResNet V1 for 50, 101, 152 layers. + + Parameters + ---------- + channels : int + Number of output channels. + stride : int + Stride size. + downsample : bool, default False + Whether to downsample the input. + in_channels : int, default 0 + Number of input channels. Default is 0, to infer from the graph. + """ + def __init__(self, channels, stride, downsample=False, in_channels=0, **kwargs): + super(BottleneckV1, self).__init__(**kwargs) + self.body = nn.HybridSequential(prefix='') + self.body.add(nn.Conv2D(channels//4, kernel_size=1, strides=1)) + self.body.add(nn.BatchNorm()) + self.body.add(nn.Activation('relu')) + self.body.add(_conv3x3(channels//4, stride, channels//4)) + self.body.add(nn.BatchNorm()) + self.body.add(nn.Activation('relu')) + self.body.add(nn.Conv2D(channels, kernel_size=1, strides=1)) + self.body.add(nn.BatchNorm()) + if downsample: + self.downsample = nn.HybridSequential(prefix='') + self.downsample.add(nn.Conv2D(channels, kernel_size=1, strides=stride, + use_bias=False, in_channels=in_channels)) + self.downsample.add(nn.BatchNorm()) + else: + self.downsample = None + + def hybrid_forward(self, F, x): + residual = x + + x = self.body(x) + + if self.downsample: + residual = self.downsample(residual) + + x = F.Activation(x + residual, act_type='relu') + return x + + +class BasicBlockV2(HybridBlock): + r"""BasicBlock V2 from + `"Identity Mappings in Deep Residual Networks" + `_ paper. + This is used for ResNet V2 for 18, 34 layers. + + Parameters + ---------- + channels : int + Number of output channels. + stride : int + Stride size. + downsample : bool, default False + Whether to downsample the input. + in_channels : int, default 0 + Number of input channels. Default is 0, to infer from the graph. + """ + def __init__(self, channels, stride, downsample=False, in_channels=0, **kwargs): + super(BasicBlockV2, self).__init__(**kwargs) + self.bn1 = nn.BatchNorm() + self.conv1 = _conv3x3(channels, stride, in_channels) + self.bn2 = nn.BatchNorm() + self.conv2 = _conv3x3(channels, 1, channels) + if downsample: + self.downsample = nn.Conv2D(channels, 1, stride, use_bias=False, + in_channels=in_channels) + else: + self.downsample = None + + def hybrid_forward(self, F, x): + residual = x + x = self.bn1(x) + x = F.Activation(x, act_type='relu') + if self.downsample: + residual = self.downsample(x) + x = self.conv1(x) + + x = self.bn2(x) + x = F.Activation(x, act_type='relu') + x = self.conv2(x) + + return x + residual + + +class BottleneckV2(HybridBlock): + r"""Bottleneck V2 from + `"Identity Mappings in Deep Residual Networks" + `_ paper. + This is used for ResNet V2 for 50, 101, 152 layers. + + Parameters + ---------- + channels : int + Number of output channels. + stride : int + Stride size. + downsample : bool, default False + Whether to downsample the input. + in_channels : int, default 0 + Number of input channels. Default is 0, to infer from the graph. + """ + def __init__(self, channels, stride, downsample=False, in_channels=0, **kwargs): + super(BottleneckV2, self).__init__(**kwargs) + self.bn1 = nn.BatchNorm() + self.conv1 = nn.Conv2D(channels//4, kernel_size=1, strides=1, use_bias=False) + self.bn2 = nn.BatchNorm() + self.conv2 = _conv3x3(channels//4, stride, channels//4) + self.bn3 = nn.BatchNorm() + self.conv3 = nn.Conv2D(channels, kernel_size=1, strides=1, use_bias=False) + if downsample: + self.downsample = nn.Conv2D(channels, 1, stride, use_bias=False, + in_channels=in_channels) + else: + self.downsample = None + + def hybrid_forward(self, F, x): + residual = x + x = self.bn1(x) + x = F.Activation(x, act_type='relu') + if self.downsample: + residual = self.downsample(x) + x = self.conv1(x) + + x = self.bn2(x) + x = F.Activation(x, act_type='relu') + x = self.conv2(x) + + x = self.bn3(x) + x = F.Activation(x, act_type='relu') + x = self.conv3(x) + + return x + residual + + +# Nets +class ResNetV1(HybridBlock): + r"""ResNet V1 model from + `"Deep Residual Learning for Image Recognition" + `_ paper. + + Parameters + ---------- + block : HybridBlock + Class for the residual block. Options are BasicBlockV1, BottleneckV1. + layers : list of int + Numbers of layers in each block + channels : list of int + Numbers of channels in each block. Length should be one larger than layers list. + classes : int, default 1000 + Number of classification classes. + thumbnail : bool, default False + Enable thumbnail. + """ + def __init__(self, block, layers, channels, classes=1000, thumbnail=False, **kwargs): + super(ResNetV1, self).__init__(**kwargs) + assert len(layers) == len(channels) - 1 + with self.name_scope(): + self.features = nn.HybridSequential(prefix='') + if thumbnail: + self.features.add(_conv3x3(channels[0], 1, 3)) + else: + self.features.add(nn.Conv2D(channels[0], 7, 2, 3, use_bias=False, + in_channels=3)) + self.features.add(nn.BatchNorm()) + self.features.add(nn.Activation('relu')) + self.features.add(nn.MaxPool2D(3, 2, 1)) + + for i, num_layer in enumerate(layers): + stride = 1 if i == 0 else 2 + self.features.add(self._make_layer(block, num_layer, channels[i+1], + stride, i+1, in_channels=channels[i])) + + self.classifier = nn.HybridSequential(prefix='') + self.classifier.add(nn.GlobalAvgPool2D()) + self.classifier.add(nn.Flatten()) + self.classifier.add(nn.Dense(classes, in_units=channels[-1])) + + def _make_layer(self, block, layers, channels, stride, stage_index, in_channels=0): + layer = nn.HybridSequential(prefix='stage%d_'%stage_index) + with layer.name_scope(): + layer.add(block(channels, stride, channels != in_channels, in_channels=in_channels, + prefix='')) + for _ in range(layers-1): + layer.add(block(channels, 1, False, in_channels=channels, prefix='')) + return layer + + def hybrid_forward(self, F, x): + x = self.features(x) + x = self.classifier(x) + + return x + + +class ResNetV2(HybridBlock): + r"""ResNet V2 model from + `"Identity Mappings in Deep Residual Networks" + `_ paper. + + Parameters + ---------- + block : HybridBlock + Class for the residual block. Options are BasicBlockV1, BottleneckV1. + layers : list of int + Numbers of layers in each block + channels : list of int + Numbers of channels in each block. Length should be one larger than layers list. + classes : int, default 1000 + Number of classification classes. + thumbnail : bool, default False + Enable thumbnail. + """ + def __init__(self, block, layers, channels, classes=1000, thumbnail=False, **kwargs): + super(ResNetV2, self).__init__(**kwargs) + assert len(layers) == len(channels) - 1 + with self.name_scope(): + self.features = nn.HybridSequential(prefix='') + self.features.add(nn.BatchNorm(scale=False, center=False)) + if thumbnail: + self.features.add(_conv3x3(channels[0], 1, 3)) + else: + self.features.add(nn.Conv2D(channels[0], 7, 2, 3, use_bias=False, + in_channels=3)) + self.features.add(nn.BatchNorm()) + self.features.add(nn.Activation('relu')) + self.features.add(nn.MaxPool2D(3, 2, 1)) + + in_channels = channels[0] + for i, num_layer in enumerate(layers): + stride = 1 if i == 0 else 2 + self.features.add(self._make_layer(block, num_layer, channels[i+1], + stride, i+1, in_channels=in_channels)) + in_channels = channels[i+1] + + self.classifier = nn.HybridSequential(prefix='') + self.classifier.add(nn.BatchNorm()) + self.classifier.add(nn.Activation('relu')) + self.classifier.add(nn.GlobalAvgPool2D()) + self.classifier.add(nn.Flatten()) + self.classifier.add(nn.Dense(classes, in_units=in_channels)) + + def _make_layer(self, block, layers, channels, stride, stage_index, in_channels=0): + layer = nn.HybridSequential(prefix='stage%d_'%stage_index) + with layer.name_scope(): + layer.add(block(channels, stride, channels != in_channels, in_channels=in_channels, + prefix='')) + for _ in range(layers-1): + layer.add(block(channels, 1, False, in_channels=channels, prefix='')) + return layer + + def hybrid_forward(self, F, x): + x = self.features(x) + x = self.classifier(x) + return x + + +# Specification +resnet_spec = {18: ('basic_block', [2, 2, 2, 2], [64, 64, 128, 256, 512]), + 34: ('basic_block', [3, 4, 6, 3], [64, 64, 128, 256, 512]), + 50: ('bottle_neck', [3, 4, 6, 3], [64, 256, 512, 1024, 2048]), + 101: ('bottle_neck', [3, 4, 23, 3], [64, 256, 512, 1024, 2048]), + 152: ('bottle_neck', [3, 8, 36, 3], [64, 256, 512, 1024, 2048])} + +resnet_net_versions = [ResNetV1, ResNetV2] +resnet_block_versions = [{'basic_block': BasicBlockV1, 'bottle_neck': BottleneckV1}, + {'basic_block': BasicBlockV2, 'bottle_neck': BottleneckV2}] + + +# Constructor +def get_resnet(version, num_layers, pretrained=False, ctx=cpu(), **kwargs): + r"""ResNet V1 model from `"Deep Residual Learning for Image Recognition" + `_ paper. + ResNet V2 model from `"Identity Mappings in Deep Residual Networks" + `_ paper. + + Parameters + ---------- + version : int + Version of ResNet. Options are 1, 2. + num_layers : int + Numbers of layers. Options are 18, 34, 50, 101, 152. + pretrained : bool, default False + Whether to load the pretrained weights for model. + ctx : Context, default CPU + The context in which to load the pretrained weights. + """ + block_type, layers, channels = resnet_spec[num_layers] + resnet_class = resnet_net_versions[version-1] + block_class = resnet_block_versions[version-1][block_type] + net = resnet_class(block_class, layers, channels, **kwargs) + if pretrained: + from ..model_store import get_model_file + net.load_params(get_model_file('resnet%d_v%d'%(num_layers, version)), ctx=ctx) + return net + +def resnet18_v1(**kwargs): + r"""ResNet-18 V1 model from `"Deep Residual Learning for Image Recognition" + `_ paper. + + Parameters + ---------- + pretrained : bool, default False + Whether to load the pretrained weights for model. + ctx : Context, default CPU + The context in which to load the pretrained weights. + """ + return get_resnet(1, 18, **kwargs) + +def resnet34_v1(**kwargs): + r"""ResNet-34 V1 model from `"Deep Residual Learning for Image Recognition" + `_ paper. + + Parameters + ---------- + pretrained : bool, default False + Whether to load the pretrained weights for model. + ctx : Context, default CPU + The context in which to load the pretrained weights. + """ + return get_resnet(1, 34, **kwargs) + +def resnet50_v1(**kwargs): + r"""ResNet-50 V1 model from `"Deep Residual Learning for Image Recognition" + `_ paper. + + Parameters + ---------- + pretrained : bool, default False + Whether to load the pretrained weights for model. + ctx : Context, default CPU + The context in which to load the pretrained weights. + """ + return get_resnet(1, 50, **kwargs) + +def resnet101_v1(**kwargs): + r"""ResNet-101 V1 model from `"Deep Residual Learning for Image Recognition" + `_ paper. + + Parameters + ---------- + pretrained : bool, default False + Whether to load the pretrained weights for model. + ctx : Context, default CPU + The context in which to load the pretrained weights. + """ + return get_resnet(1, 101, **kwargs) + +def resnet152_v1(**kwargs): + r"""ResNet-152 V1 model from `"Deep Residual Learning for Image Recognition" + `_ paper. + + Parameters + ---------- + pretrained : bool, default False + Whether to load the pretrained weights for model. + ctx : Context, default CPU + The context in which to load the pretrained weights. + """ + return get_resnet(1, 152, **kwargs) + +def resnet18_v2(**kwargs): + r"""ResNet-18 V2 model from `"Identity Mappings in Deep Residual Networks" + `_ paper. + + Parameters + ---------- + pretrained : bool, default False + Whether to load the pretrained weights for model. + ctx : Context, default CPU + The context in which to load the pretrained weights. + """ + return get_resnet(2, 18, **kwargs) + +def resnet34_v2(**kwargs): + r"""ResNet-34 V2 model from `"Identity Mappings in Deep Residual Networks" + `_ paper. + + Parameters + ---------- + pretrained : bool, default False + Whether to load the pretrained weights for model. + ctx : Context, default CPU + The context in which to load the pretrained weights. + """ + return get_resnet(2, 34, **kwargs) + +def resnet50_v2(**kwargs): + r"""ResNet-50 V2 model from `"Identity Mappings in Deep Residual Networks" + `_ paper. + + Parameters + ---------- + pretrained : bool, default False + Whether to load the pretrained weights for model. + ctx : Context, default CPU + The context in which to load the pretrained weights. + """ + return get_resnet(2, 50, **kwargs) + +def resnet101_v2(**kwargs): + r"""ResNet-101 V2 model from `"Identity Mappings in Deep Residual Networks" + `_ paper. + + Parameters + ---------- + pretrained : bool, default False + Whether to load the pretrained weights for model. + ctx : Context, default CPU + The context in which to load the pretrained weights. + """ + return get_resnet(2, 101, **kwargs) + +def resnet152_v2(**kwargs): + r"""ResNet-152 V2 model from `"Identity Mappings in Deep Residual Networks" + `_ paper. + + Parameters + ---------- + pretrained : bool, default False + Whether to load the pretrained weights for model. + ctx : Context, default CPU + The context in which to load the pretrained weights. + """ + return get_resnet(2, 152, **kwargs) diff --git a/python/mxnet/gluon/model_zoo/vision/squeezenet.py b/python/mxnet/gluon/model_zoo/vision/squeezenet.py new file mode 100644 index 000000000000..1a14201c7998 --- /dev/null +++ b/python/mxnet/gluon/model_zoo/vision/squeezenet.py @@ -0,0 +1,159 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +# coding: utf-8 +# pylint: disable= arguments-differ +"""SqueezeNet, implemented in Gluon.""" +__all__ = ['SqueezeNet', 'squeezenet1_0', 'squeezenet1_1'] + +from ....context import cpu +from ...block import HybridBlock +from ... import nn +from ..custom_layers import HybridConcurrent + +# Helpers +def _make_fire(squeeze_channels, expand1x1_channels, expand3x3_channels): + out = nn.HybridSequential(prefix='') + out.add(_make_fire_conv(squeeze_channels, 1)) + + paths = HybridConcurrent(concat_dim=1, prefix='') + paths.add(_make_fire_conv(expand1x1_channels, 1)) + paths.add(_make_fire_conv(expand3x3_channels, 3, 1)) + out.add(paths) + + return out + +def _make_fire_conv(channels, kernel_size, padding=0): + out = nn.HybridSequential(prefix='') + out.add(nn.Conv2D(channels, kernel_size, padding=padding)) + out.add(nn.Activation('relu')) + return out + +# Net +class SqueezeNet(HybridBlock): + r"""SqueezeNet model from the `"SqueezeNet: AlexNet-level accuracy with 50x fewer parameters + and <0.5MB model size" `_ paper. + SqueezeNet 1.1 model from the `official SqueezeNet repo + `_. + SqueezeNet 1.1 has 2.4x less computation and slightly fewer parameters + than SqueezeNet 1.0, without sacrificing accuracy. + + Parameters + ---------- + version : str + Version of squeezenet. Options are '1.0', '1.1'. + classes : int, default 1000 + Number of classification classes. + """ + def __init__(self, version, classes=1000, **kwargs): + super(SqueezeNet, self).__init__(**kwargs) + assert version in ['1.0', '1.1'], ("Unsupported SqueezeNet version {version}:" + "1.0 or 1.1 expected".format(version=version)) + with self.name_scope(): + self.features = nn.HybridSequential(prefix='') + if version == '1.0': + self.features.add(nn.Conv2D(96, kernel_size=7, strides=2)) + self.features.add(nn.Activation('relu')) + self.features.add(nn.MaxPool2D(pool_size=3, strides=2, ceil_mode=True)) + self.features.add(_make_fire(16, 64, 64)) + self.features.add(_make_fire(16, 64, 64)) + self.features.add(_make_fire(32, 128, 128)) + self.features.add(nn.MaxPool2D(pool_size=3, strides=2, ceil_mode=True)) + self.features.add(_make_fire(32, 128, 128)) + self.features.add(_make_fire(48, 192, 192)) + self.features.add(_make_fire(48, 192, 192)) + self.features.add(_make_fire(64, 256, 256)) + self.features.add(nn.MaxPool2D(pool_size=3, strides=2, ceil_mode=True)) + self.features.add(_make_fire(64, 256, 256)) + else: + self.features.add(nn.Conv2D(64, kernel_size=3, strides=2)) + self.features.add(nn.Activation('relu')) + self.features.add(nn.MaxPool2D(pool_size=3, strides=2, ceil_mode=True)) + self.features.add(_make_fire(16, 64, 64)) + self.features.add(_make_fire(16, 64, 64)) + self.features.add(nn.MaxPool2D(pool_size=3, strides=2, ceil_mode=True)) + self.features.add(_make_fire(32, 128, 128)) + self.features.add(_make_fire(32, 128, 128)) + self.features.add(nn.MaxPool2D(pool_size=3, strides=2, ceil_mode=True)) + self.features.add(_make_fire(48, 192, 192)) + self.features.add(_make_fire(48, 192, 192)) + self.features.add(_make_fire(64, 256, 256)) + self.features.add(_make_fire(64, 256, 256)) + + self.classifier = nn.HybridSequential(prefix='') + self.classifier.add(nn.Dropout(0.5)) + self.classifier.add(nn.Conv2D(classes, kernel_size=1)) + self.classifier.add(nn.Activation('relu')) + self.classifier.add(nn.AvgPool2D(13)) + self.classifier.add(nn.Flatten()) + + def hybrid_forward(self, F, x): + x = self.features(x) + x = self.classifier(x) + return x + +# Constructor +def get_squeezenet(version, pretrained=False, ctx=cpu(), **kwargs): + r"""SqueezeNet model from the `"SqueezeNet: AlexNet-level accuracy with 50x fewer parameters + and <0.5MB model size" `_ paper. + SqueezeNet 1.1 model from the `official SqueezeNet repo + `_. + SqueezeNet 1.1 has 2.4x less computation and slightly fewer parameters + than SqueezeNet 1.0, without sacrificing accuracy. + + Parameters + ---------- + version : str + Version of squeezenet. Options are '1.0', '1.1'. + pretrained : bool, default False + Whether to load the pretrained weights for model. + ctx : Context, default CPU + The context in which to load the pretrained weights. + """ + net = SqueezeNet(version, **kwargs) + if pretrained: + from ..model_store import get_model_file + net.load_params(get_model_file('squeezenet%s'%version), ctx=ctx) + return net + +def squeezenet1_0(**kwargs): + r"""SqueezeNet 1.0 model from the `"SqueezeNet: AlexNet-level accuracy with 50x fewer parameters + and <0.5MB model size" `_ paper. + + Parameters + ---------- + pretrained : bool, default False + Whether to load the pretrained weights for model. + ctx : Context, default CPU + The context in which to load the pretrained weights. + """ + return get_squeezenet('1.0', **kwargs) + +def squeezenet1_1(**kwargs): + r"""SqueezeNet 1.1 model from the `official SqueezeNet repo + `_. + SqueezeNet 1.1 has 2.4x less computation and slightly fewer parameters + than SqueezeNet 1.0, without sacrificing accuracy. + + Parameters + ---------- + pretrained : bool, default False + Whether to load the pretrained weights for model. + ctx : Context, default CPU + The context in which to load the pretrained weights. + """ + return get_squeezenet('1.1', **kwargs) diff --git a/python/mxnet/gluon/model_zoo/vision/vgg.py b/python/mxnet/gluon/model_zoo/vision/vgg.py new file mode 100644 index 000000000000..2f4daf9f6437 --- /dev/null +++ b/python/mxnet/gluon/model_zoo/vision/vgg.py @@ -0,0 +1,226 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +# coding: utf-8 +# pylint: disable= arguments-differ +"""VGG, implemented in Gluon.""" +from __future__ import division +__all__ = ['VGG', + 'vgg11', 'vgg13', 'vgg16', 'vgg19', + 'vgg11_bn', 'vgg13_bn', 'vgg16_bn', 'vgg19_bn', + 'get_vgg'] + +from ....context import cpu +from ....initializer import Xavier +from ...block import HybridBlock +from ... import nn + + +class VGG(HybridBlock): + r"""VGG model from the `"Very Deep Convolutional Networks for Large-Scale Image Recognition" + `_ paper. + + Parameters + ---------- + layers : list of int + Numbers of layers in each feature block. + filters : list of int + Numbers of filters in each feature block. List length should match the layers. + classes : int, default 1000 + Number of classification classes. + batch_norm : bool, default False + Use batch normalization. + """ + def __init__(self, layers, filters, classes=1000, batch_norm=False, **kwargs): + super(VGG, self).__init__(**kwargs) + assert len(layers) == len(filters) + with self.name_scope(): + self.features = self._make_features(layers, filters, batch_norm) + self.classifier = nn.HybridSequential(prefix='') + self.classifier.add(nn.Dense(4096, activation='relu', + weight_initializer='normal', + bias_initializer='zeros')) + self.classifier.add(nn.Dropout(rate=0.5)) + self.classifier.add(nn.Dense(4096, activation='relu', + weight_initializer='normal', + bias_initializer='zeros')) + self.classifier.add(nn.Dropout(rate=0.5)) + self.classifier.add(nn.Dense(classes, + weight_initializer='normal', + bias_initializer='zeros')) + + def _make_features(self, layers, filters, batch_norm): + featurizer = nn.HybridSequential(prefix='') + for i, num in enumerate(layers): + for _ in range(num): + featurizer.add(nn.Conv2D(filters[i], kernel_size=3, padding=1, + weight_initializer=Xavier(rnd_type='gaussian', + factor_type='out', + magnitude=2), + bias_initializer='zeros')) + if batch_norm: + featurizer.add(nn.BatchNorm()) + featurizer.add(nn.Activation('relu')) + featurizer.add(nn.MaxPool2D(strides=2)) + return featurizer + + def hybrid_forward(self, F, x): + x = self.features(x) + x = self.classifier(x) + return x + + +# Specification +vgg_spec = {11: ([1, 1, 2, 2, 2], [64, 128, 256, 512, 512]), + 13: ([2, 2, 2, 2, 2], [64, 128, 256, 512, 512]), + 16: ([2, 2, 3, 3, 3], [64, 128, 256, 512, 512]), + 19: ([2, 2, 4, 4, 4], [64, 128, 256, 512, 512])} + + +# Constructors +def get_vgg(num_layers, pretrained=False, ctx=cpu(), **kwargs): + r"""VGG model from the `"Very Deep Convolutional Networks for Large-Scale Image Recognition" + `_ paper. + + Parameters + ---------- + num_layers : int + Number of layers for the variant of densenet. Options are 11, 13, 16, 19. + pretrained : bool, default False + Whether to load the pretrained weights for model. + ctx : Context, default CPU + The context in which to load the pretrained weights. + """ + layers, filters = vgg_spec[num_layers] + net = VGG(layers, filters, **kwargs) + if pretrained: + from ..model_store import get_model_file + batch_norm_suffix = '_bn' if kwargs.get('batch_norm') else '' + net.load_params(get_model_file('vgg%d%s'%(num_layers, batch_norm_suffix)), ctx=ctx) + return net + +def vgg11(**kwargs): + r"""VGG-11 model from the `"Very Deep Convolutional Networks for Large-Scale Image Recognition" + `_ paper. + + Parameters + ---------- + pretrained : bool, default False + Whether to load the pretrained weights for model. + ctx : Context, default CPU + The context in which to load the pretrained weights. + """ + return get_vgg(11, **kwargs) + +def vgg13(**kwargs): + r"""VGG-13 model from the `"Very Deep Convolutional Networks for Large-Scale Image Recognition" + `_ paper. + + Parameters + ---------- + pretrained : bool, default False + Whether to load the pretrained weights for model. + ctx : Context, default CPU + The context in which to load the pretrained weights. + """ + return get_vgg(13, **kwargs) + +def vgg16(**kwargs): + r"""VGG-16 model from the `"Very Deep Convolutional Networks for Large-Scale Image Recognition" + `_ paper. + + Parameters + ---------- + pretrained : bool, default False + Whether to load the pretrained weights for model. + ctx : Context, default CPU + The context in which to load the pretrained weights. + """ + return get_vgg(16, **kwargs) + +def vgg19(**kwargs): + r"""VGG-19 model from the `"Very Deep Convolutional Networks for Large-Scale Image Recognition" + `_ paper. + + Parameters + ---------- + pretrained : bool, default False + Whether to load the pretrained weights for model. + ctx : Context, default CPU + The context in which to load the pretrained weights. + """ + return get_vgg(19, **kwargs) + +def vgg11_bn(**kwargs): + r"""VGG-11 model with batch normalization from the + `"Very Deep Convolutional Networks for Large-Scale Image Recognition" + `_ paper. + + Parameters + ---------- + pretrained : bool, default False + Whether to load the pretrained weights for model. + ctx : Context, default CPU + The context in which to load the pretrained weights. + """ + kwargs['batch_norm'] = True + return get_vgg(11, **kwargs) + +def vgg13_bn(**kwargs): + r"""VGG-13 model with batch normalization from the + `"Very Deep Convolutional Networks for Large-Scale Image Recognition" + `_ paper. + + Parameters + ---------- + pretrained : bool, default False + Whether to load the pretrained weights for model. + ctx : Context, default CPU + The context in which to load the pretrained weights. + """ + kwargs['batch_norm'] = True + return get_vgg(13, **kwargs) + +def vgg16_bn(**kwargs): + r"""VGG-16 model with batch normalization from the + `"Very Deep Convolutional Networks for Large-Scale Image Recognition" + `_ paper. + + Parameters + ---------- + pretrained : bool, default False + Whether to load the pretrained weights for model. + ctx : Context, default CPU + The context in which to load the pretrained weights. + """ + kwargs['batch_norm'] = True + return get_vgg(16, **kwargs) + +def vgg19_bn(**kwargs): + r"""VGG-19 model with batch normalization from the + `"Very Deep Convolutional Networks for Large-Scale Image Recognition" + `_ paper. + + Parameters + ---------- + pretrained : bool, default False + Whether to load the pretrained weights for model. + ctx : Context, default CPU + The context in which to load the pretrained weights. + """ + kwargs['batch_norm'] = True + return get_vgg(19, **kwargs) diff --git a/python/mxnet/gluon/nn/__init__.py b/python/mxnet/gluon/nn/__init__.py new file mode 100644 index 000000000000..0fc1ff12dd13 --- /dev/null +++ b/python/mxnet/gluon/nn/__init__.py @@ -0,0 +1,24 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +# coding: utf-8 +# pylint: disable=wildcard-import +"""Neural network layers.""" + +from .basic_layers import * + +from .conv_layers import * diff --git a/python/mxnet/gluon/nn/basic_layers.py b/python/mxnet/gluon/nn/basic_layers.py new file mode 100644 index 000000000000..7901a7ae2350 --- /dev/null +++ b/python/mxnet/gluon/nn/basic_layers.py @@ -0,0 +1,432 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +# coding: utf-8 +# pylint: disable= arguments-differ +"""Basic neural network layers.""" + +from ..block import Block, HybridBlock +from ..utils import _indent + + +class Sequential(Block): + """Stacks `Block`s sequentially. + + Example:: + + net = nn.Sequential() + # use net's name_scope to give child Blocks appropriate names. + with net.name_scope(): + net.add(nn.Dense(10, activation='relu')) + net.add(nn.Dense(20)) + """ + def __init__(self, prefix=None, params=None): + super(Sequential, self).__init__(prefix=prefix, params=params) + + def add(self, block): + """Adds block on top of the stack.""" + self.register_child(block) + + def forward(self, x): + for block in self._children: + x = block(x) + return x + + def __repr__(self): + s = '{name}(\n{modstr}\n)' + modstr = '\n'.join([' ({key}): {block}'.format(key=key, + block=_indent(block.__repr__(), 2)) + for key, block in enumerate(self._children) + if isinstance(block, Block)]) + return s.format(name=self.__class__.__name__, + modstr=modstr) + + def __getitem__(self, i): + return self._children[i] + + def __len__(self): + return len(self._children) + + +class HybridSequential(HybridBlock): + """Stacks `HybridBlock`s sequentially. + + Example:: + + net = nn.Sequential() + # use net's name_scope to give child Blocks appropriate names. + with net.name_scope(): + net.add(nn.Dense(10, activation='relu')) + net.add(nn.Dense(20)) + """ + def __init__(self, prefix=None, params=None): + super(HybridSequential, self).__init__(prefix=prefix, params=params) + + def add(self, block): + """Adds block on top of the stack.""" + self.register_child(block) + + def hybrid_forward(self, F, x): + for block in self._children: + x = block(x) + return x + + def __repr__(self): + s = '{name}(\n{modstr}\n)' + modstr = '\n'.join([' ({key}): {block}'.format(key=key, + block=_indent(block.__repr__(), 2)) + for key, block in enumerate(self._children) + if isinstance(block, Block)]) + return s.format(name=self.__class__.__name__, + modstr=modstr) + + def __getitem__(self, i): + return self._children[i] + + def __len__(self): + return len(self._children) + + +class Dense(HybridBlock): + """Just your regular densely-connected NN layer. + + `Dense` implements the operation: + `output = activation(dot(input, weight) + bias)` + where `activation` is the element-wise activation function + passed as the `activation` argument, `weight` is a weights matrix + created by the layer, and `bias` is a bias vector created by the layer + (only applicable if `use_bias` is `True`). + + Note: the input must be a tensor with rank 2. Use `flatten` to convert it + to rank 2 manually if necessary. + + Parameters + ---------- + units : int + Dimensionality of the output space. + activation : str + Activation function to use. See help on `Activation` layer. + If you don't specify anything, no activation is applied + (ie. "linear" activation: `a(x) = x`). + use_bias : bool + Whether the layer uses a bias vector. + weight_initializer : str or `Initializer` + Initializer for the `kernel` weights matrix. + bias_initializer: str or `Initializer` + Initializer for the bias vector. + in_units : int, optional + Size of the input data. If not specified, initialization will be + deferred to the first time `forward` is called and `in_units` + will be inferred from the shape of input data. + prefix : str or None + See document of `Block`. + params : ParameterDict or None + See document of `Block`. + + + Input shape: + A 2D input with shape `(batch_size, in_units)`. + + Output shape: + The output would have shape `(batch_size, units)`. + """ + def __init__(self, units, activation=None, use_bias=True, + weight_initializer=None, bias_initializer='zeros', + in_units=0, **kwargs): + super(Dense, self).__init__(**kwargs) + with self.name_scope(): + self._units = units + self._in_units = in_units + self.weight = self.params.get('weight', shape=(units, in_units), + init=weight_initializer, + allow_deferred_init=True) + if use_bias: + self.bias = self.params.get('bias', shape=(units,), + init=bias_initializer, + allow_deferred_init=True) + else: + self.bias = None + if activation is not None: + self.act = Activation(activation, prefix=activation+'_') + else: + self.act = None + + def hybrid_forward(self, F, x, weight, bias=None): + if bias is None: + act = F.FullyConnected(x, weight, no_bias=True, num_hidden=self._units, + name='fwd') + else: + act = F.FullyConnected(x, weight, bias, num_hidden=self._units, + name='fwd') + if self.act is not None: + act = self.act(act) + return act + + def __repr__(self): + s = '{name}({layout}, {act})' + return s.format(name=self.__class__.__name__, + act=self.act if self.act else 'linear', + layout='{0} -> {1}'.format(self._in_units, self._units) if self._in_units + else self._units) + + +class Activation(HybridBlock): + """Applies an activation function to input. + + Parameters + ---------- + activation : str + Name of activation function to use. + See :func:`~mxnet.ndarray.Activation` for available choices. + + + Input shape: + Arbitrary. + + Output shape: + Same shape as input. + """ + def __init__(self, activation, **kwargs): + self._act_type = activation + super(Activation, self).__init__(**kwargs) + + def _alias(self): + return self._act_type + + def hybrid_forward(self, F, x): + return F.Activation(x, act_type=self._act_type, name='fwd') + + def __repr__(self): + s = '{name}({_act_type})' + return s.format(name=self.__class__.__name__, + **self.__dict__) + + +class Dropout(HybridBlock): + """Applies Dropout to the input. + + Dropout consists in randomly setting a fraction `rate` of input units + to 0 at each update during training time, which helps prevent overfitting. + + Parameters + ---------- + rate : float + Fraction of the input units to drop. Must be a number between 0 and 1. + + + Input shape: + Arbitrary. + + Output shape: + Same shape as input. + + References + ---------- + `Dropout: A Simple Way to Prevent Neural Networks from Overfitting + `_ + """ + def __init__(self, rate, **kwargs): + super(Dropout, self).__init__(**kwargs) + self._rate = rate + + def hybrid_forward(self, F, x): + return F.Dropout(x, p=self._rate, name='fwd') + + def __repr__(self): + s = '{name}(p = {_rate})' + return s.format(name=self.__class__.__name__, + **self.__dict__) + + +class BatchNorm(HybridBlock): + """Batch normalization layer (Ioffe and Szegedy, 2014). + Normalizes the input at each batch, i.e. applies a transformation + that maintains the mean activation close to 0 and the activation + standard deviation close to 1. + + Parameters + ---------- + axis : int, default 1 + The axis that should be normalized. This is typically the channels + (C) axis. For instance, after a `Conv2D` layer with `layout='NCHW'`, + set `axis=1` in `BatchNorm`. If `layout='NHWC'`, then set `axis=3`. + momentum: float, default 0.9 + Momentum for the moving average. + epsilon: float, default 1e-5 + Small float added to variance to avoid dividing by zero. + center: bool, default True + If True, add offset of `beta` to normalized tensor. + If False, `beta` is ignored. + scale: bool, default True + If True, multiply by `gamma`. If False, `gamma` is not used. + When the next layer is linear (also e.g. `nn.relu`), + this can be disabled since the scaling + will be done by the next layer. + beta_initializer: str or `Initializer`, default 'zeros' + Initializer for the beta weight. + gamma_initializer: str or `Initializer`, default 'ones' + Initializer for the gamma weight. + moving_mean_initializer: str or `Initializer`, default 'zeros' + Initializer for the moving mean. + moving_variance_initializer: str or `Initializer`, default 'ones' + Initializer for the moving variance. + in_channels : int, default 0 + Number of channels (feature maps) in input data. If not specified, + initialization will be deferred to the first time `forward` is called + and `in_channels` will be inferred from the shape of input data. + + + Input shape: + Arbitrary. + + Output shape: + Same shape as input. + """ + def __init__(self, axis=1, momentum=0.9, epsilon=1e-5, center=True, scale=True, + beta_initializer='zeros', gamma_initializer='ones', + running_mean_initializer='zeros', running_variance_initializer='ones', + in_channels=0, **kwargs): + super(BatchNorm, self).__init__(**kwargs) + self._kwargs = {'axis': axis, 'eps': epsilon, 'momentum': momentum, + 'fix_gamma': not scale} + if in_channels != 0: + self.in_channels = in_channels + + self.gamma = self.params.get('gamma', grad_req='write' if scale else 'null', + shape=(in_channels,), init=gamma_initializer, + allow_deferred_init=True, + differentiable=scale) + self.beta = self.params.get('beta', grad_req='write' if center else 'null', + shape=(in_channels,), init=beta_initializer, + allow_deferred_init=True, + differentiable=center) + self.running_mean = self.params.get('running_mean', grad_req='null', + shape=(in_channels,), + init=running_mean_initializer, + allow_deferred_init=True, + differentiable=False) + self.running_var = self.params.get('running_var', grad_req='null', + shape=(in_channels,), + init=running_variance_initializer, + allow_deferred_init=True, + differentiable=False) + + def hybrid_forward(self, F, x, gamma, beta, running_mean, running_var): + return F.BatchNorm(x, gamma, beta, running_mean, running_var, + name='fwd', **self._kwargs) + + def __repr__(self): + s = '{name}({content}' + if hasattr(self, 'in_channels'): + s += ', in_channels={0}'.format(self.in_channels) + s += ')' + return s.format(name=self.__class__.__name__, + content=', '.join(['='.join([k, v.__repr__()]) + for k, v in self._kwargs.items()])) + + +class LeakyReLU(HybridBlock): + """Leaky version of a Rectified Linear Unit. + + It allows a small gradient when the unit is not active:: + + `f(x) = alpha * x for x < 0`, + `f(x) = x for x >= 0`. + + Parameters + ---------- + alpha : float + slope coefficient for the negative half axis. Must be >= 0. + + + Input shape: + Arbitrary. + + Output shape: + Same shape as input. + """ + def __init__(self, alpha, **kwargs): + super(LeakyReLU, self).__init__(**kwargs) + self._alpha = alpha + + def hybrid_forward(self, F, x): + return F.LeakyReLU(x, act_type='leaky', slope=self._alpha, name='fwd') + + def __repr__(self): + s = '{name}({alpha})' + return s.format(name=self.__class__.__name__, + alpha=self._alpha) + + +class Embedding(HybridBlock): + """Turns non-negative integers (indexes/tokens) into dense vectors + of fixed size. eg. [[4], [20]] -> [[0.25, 0.1], [0.6, -0.2]] + + + Parameters + ---------- + input_dim : int + Size of the vocabulary, i.e. maximum integer index + 1. + output_dim : int + Dimension of the dense embedding. + dtype : str or np.dtype, default 'float32' + Data type of output embeddings. + weight_initializer : Initializer + Initializer for the `embeddings` matrix. + + + Input shape: + 2D tensor with shape: `(N, M)`. + + Output shape: + 3D tensor with shape: `(N, M, output_dim)`. + """ + def __init__(self, input_dim, output_dim, dtype='float32', + weight_initializer=None, **kwargs): + super(Embedding, self).__init__(**kwargs) + self._kwargs = {'input_dim': input_dim, 'output_dim': output_dim, + 'dtype': dtype} + self.weight = self.params.get('weight', shape=(input_dim, output_dim), + init=weight_initializer, + allow_deferred_init=True) + + def hybrid_forward(self, F, x, weight): + return F.Embedding(x, weight, name='fwd', **self._kwargs) + + def __repr__(self): + s = '{block_name}({input_dim} -> {output_dim}, {dtype})' + return s.format(block_name=self.__class__.__name__, + **self._kwargs) + + +class Flatten(HybridBlock): + """Flattens the input to two dimensional. + + Input shape: + Arbitrary shape `(N, a, b, c, ...)` + + Output shape: + 2D tensor with shape: `(N, a*b*c...)` + """ + def __init__(self, **kwargs): + super(Flatten, self).__init__(**kwargs) + + def hybrid_forward(self, F, x): + return x.reshape((0, -1)) + + def __repr__(self): + return self.__class__.__name__ diff --git a/python/mxnet/gluon/nn/conv_layers.py b/python/mxnet/gluon/nn/conv_layers.py new file mode 100644 index 000000000000..e49340d27a4e --- /dev/null +++ b/python/mxnet/gluon/nn/conv_layers.py @@ -0,0 +1,1011 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +# coding: utf-8 +# pylint: disable= arguments-differ, too-many-lines +"""Convolutional neural network layers.""" +from ..block import HybridBlock +from ... import symbol +from ...base import numeric_types +from .basic_layers import Activation + + +def _infer_weight_shape(op_name, data_shape, kwargs): + op = getattr(symbol, op_name) + sym = op(symbol.var('data', shape=data_shape), **kwargs) + return sym.infer_shape_partial()[0] + + +class _Conv(HybridBlock): + """Abstract nD convolution layer (private, used as implementation base). + + This layer creates a convolution kernel that is convolved + with the layer input to produce a tensor of outputs. + If `use_bias` is `True`, a bias vector is created and added to the outputs. + Finally, if `activation` is not `None`, + it is applied to the outputs as well. + + Parameters + ---------- + channels : int + The dimensionality of the output space + i.e. the number of output channels in the convolution. + kernel_size : int or tuple/list of n ints + Specifies the dimensions of the convolution window. + strides: int or tuple/list of n ints, + Specifies the strides of the convolution. + padding : int or tuple/list of n ints, + If padding is non-zero, then the input is implicitly zero-padded + on both sides for padding number of points + dilation: int or tuple/list of n ints, + Specifies the dilation rate to use for dilated convolution. + groups : int + Controls the connections between inputs and outputs. + At groups=1, all inputs are convolved to all outputs. + At groups=2, the operation becomes equivalent to having two convolution + layers side by side, each seeing half the input channels, and producing + half the output channels, and both subsequently concatenated. + layout : str, + Dimension ordering of data and weight. Can be 'NCW', 'NWC', 'NCHW', + 'NHWC', 'NCDHW', 'NDHWC', etc. 'N', 'C', 'H', 'W', 'D' stands for + batch, channel, height, width and depth dimensions respectively. + Convolution is performed over 'D', 'H', and 'W' dimensions. + in_channels : int, default 0 + The number of input channels to this layer. If not specified, + initialization will be deferred to the first time `forward` is called + and `in_channels` will be inferred from the shape of input data. + activation : str + Activation function to use. See :func:`~mxnet.ndarray.Activation`. + If you don't specify anything, no activation is applied + (ie. "linear" activation: `a(x) = x`). + use_bias: bool + Whether the layer uses a bias vector. + weight_initializer : str or `Initializer` + Initializer for the `weight` weights matrix. + bias_initializer: str or `Initializer` + Initializer for the bias vector. + """ + def __init__(self, channels, kernel_size, strides, padding, dilation, + groups, layout, in_channels=0, activation=None, use_bias=True, + weight_initializer=None, bias_initializer='zeros', + op_name='Convolution', adj=None, prefix=None, params=None): + super(_Conv, self).__init__(prefix=prefix, params=params) + with self.name_scope(): + self._channels = channels + self._in_channels = in_channels + if isinstance(strides, numeric_types): + strides = (strides,)*len(kernel_size) + if isinstance(padding, numeric_types): + padding = (padding,)*len(kernel_size) + if isinstance(dilation, numeric_types): + dilation = (dilation,)*len(kernel_size) + self._op_name = op_name + self._kwargs = { + 'kernel': kernel_size, 'stride': strides, 'dilate': dilation, + 'pad': padding, 'num_filter': channels, 'num_group': groups, + 'no_bias': not use_bias, 'layout': layout} + if adj is not None: + self._kwargs['adj'] = adj + + dshape = [0]*(len(kernel_size) + 2) + dshape[layout.find('N')] = 1 + dshape[layout.find('C')] = in_channels + wshapes = _infer_weight_shape(op_name, dshape, self._kwargs) + self.weight = self.params.get('weight', shape=wshapes[1], + init=weight_initializer, + allow_deferred_init=True) + if use_bias: + self.bias = self.params.get('bias', shape=wshapes[2], + init=bias_initializer, + allow_deferred_init=True) + else: + self.bias = None + + if activation is not None: + self.act = Activation(activation, prefix=activation+'_') + else: + self.act = None + + def hybrid_forward(self, F, x, weight, bias=None): + if bias is None: + act = getattr(F, self._op_name)(x, weight, name='fwd', **self._kwargs) + else: + act = getattr(F, self._op_name)(x, weight, bias, name='fwd', **self._kwargs) + if self.act is not None: + act = self.act(act) + return act + + def _alias(self): + return 'conv' + + def __repr__(self): + s = '{name}({mapping}, kernel_size={kernel}, stride={stride}' + len_kernel_size = len(self._kwargs['kernel']) + if self._kwargs['pad'] != (0,) * len_kernel_size: + s += ', padding={pad}' + if self._kwargs['dilate'] != (1,) * len_kernel_size: + s += ', dilation={dilate}' + if hasattr(self, 'out_pad') and self.out_pad != (0,) * len_kernel_size: + s += ', output_padding={out_pad}'.format(out_pad=self.out_pad) + if self._kwargs['num_group'] != 1: + s += ', groups={num_group}' + if self.bias is None: + s += ', bias=False' + s += ')' + return s.format(name=self.__class__.__name__, + mapping=self._channels if not self._in_channels + else '{0} -> {1}'.format(self._in_channels, + self._channels), + **self._kwargs) + + +class Conv1D(_Conv): + """1D convolution layer (e.g. temporal convolution). + + This layer creates a convolution kernel that is convolved + with the layer input over a single spatial (or temporal) dimension + to produce a tensor of outputs. + If `use_bias` is True, a bias vector is created and added to the outputs. + Finally, if `activation` is not `None`, + it is applied to the outputs as well. + + If `in_channels` is not specified, `Parameter` initialization will be + deferred to the first time `forward` is called and `in_channels` will be + inferred from the shape of input data. + + + Parameters + ---------- + channels : int + The dimensionality of the output space, i.e. the number of output + channels (filters) in the convolution. + kernel_size :int or tuple/list of 1 int + Specifies the dimensions of the convolution window. + strides : int or tuple/list of 1 int, + Specify the strides of the convolution. + padding : int or a tuple/list of 1 int, + If padding is non-zero, then the input is implicitly zero-padded + on both sides for padding number of points + dilation : int or tuple/list of 1 int + Specifies the dilation rate to use for dilated convolution. + groups : int + Controls the connections between inputs and outputs. + At groups=1, all inputs are convolved to all outputs. + At groups=2, the operation becomes equivalent to having two conv + layers side by side, each seeing half the input channels, and producing + half the output channels, and both subsequently concatenated. + layout: str, default 'NCW' + Dimension ordering of data and weight. Can be 'NCW', 'NWC', etc. + 'N', 'C', 'W' stands for batch, channel, and width (time) dimensions + respectively. Convolution is applied on the 'W' dimension. + in_channels : int, default 0 + The number of input channels to this layer. If not specified, + initialization will be deferred to the first time `forward` is called + and `in_channels` will be inferred from the shape of input data. + activation : str + Activation function to use. See :func:`~mxnet.ndarray.Activation`. + If you don't specify anything, no activation is applied + (ie. "linear" activation: `a(x) = x`). + use_bias : bool + Whether the layer uses a bias vector. + weight_initializer : str or `Initializer` + Initializer for the `weight` weights matrix. + bias_initializer : str or `Initializer` + Initializer for the bias vector. + + + Input shape: + This depends on the `layout` parameter. Input is 3D array of shape + (batch_size, in_channels, width) if `layout` is `NCW`. + + Output shape: + This depends on the `layout` parameter. Output is 3D array of shape + (batch_size, channels, out_width) if `layout` is `NCW`. + out_width is calculated as:: + + out_width = floor((width+2*padding-dilation*(kernel_size-1)-1)/stride)+1 + """ + def __init__(self, channels, kernel_size, strides=1, padding=0, dilation=1, + groups=1, layout='NCW', activation=None, use_bias=True, + weight_initializer=None, bias_initializer='zeros', + in_channels=0, **kwargs): + if isinstance(kernel_size, numeric_types): + kernel_size = (kernel_size,) + assert len(kernel_size) == 1, "kernel_size must be a number or a list of 1 ints" + super(Conv1D, self).__init__( + channels, kernel_size, strides, padding, dilation, groups, layout, + in_channels, activation, use_bias, weight_initializer, bias_initializer, **kwargs) + + +class Conv2D(_Conv): + """2D convolution layer (e.g. spatial convolution over images). + + This layer creates a convolution kernel that is convolved + with the layer input to produce a tensor of + outputs. If `use_bias` is True, + a bias vector is created and added to the outputs. Finally, if + `activation` is not `None`, it is applied to the outputs as well. + + If `in_channels` is not specified, `Parameter` initialization will be + deferred to the first time `forward` is called and `in_channels` will be + inferred from the shape of input data. + + Parameters + ---------- + channels : int + The dimensionality of the output space, i.e. the number of output + channels (filters) in the convolution. + kernel_size :int or tuple/list of 2 int + Specifies the dimensions of the convolution window. + strides : int or tuple/list of 2 int, + Specify the strides of the convolution. + padding : int or a tuple/list of 2 int, + If padding is non-zero, then the input is implicitly zero-padded + on both sides for padding number of points + dilation : int or tuple/list of 2 int + Specifies the dilation rate to use for dilated convolution. + groups : int + Controls the connections between inputs and outputs. + At groups=1, all inputs are convolved to all outputs. + At groups=2, the operation becomes equivalent to having two conv + layers side by side, each seeing half the input channels, and producing + half the output channels, and both subsequently concatenated. + layout : str, default 'NCHW' + Dimension ordering of data and weight. Can be 'NCHW', 'NHWC', etc. + 'N', 'C', 'H', 'W' stands for batch, channel, height, and width + dimensions respectively. Convolution is applied on the 'H' and + 'W' dimensions. + in_channels : int, default 0 + The number of input channels to this layer. If not specified, + initialization will be deferred to the first time `forward` is called + and `in_channels` will be inferred from the shape of input data. + activation : str + Activation function to use. See :func:`~mxnet.ndarray.Activation`. + If you don't specify anything, no activation is applied + (ie. "linear" activation: `a(x) = x`). + use_bias : bool + Whether the layer uses a bias vector. + weight_initializer : str or `Initializer` + Initializer for the `weight` weights matrix. + bias_initializer : str or `Initializer` + Initializer for the bias vector. + + + Input shape: + This depends on the `layout` parameter. Input is 4D array of shape + (batch_size, in_channels, height, width) if `layout` is `NCHW`. + + Output shape: + This depends on the `layout` parameter. Output is 4D array of shape + (batch_size, channels, out_height, out_width) if `layout` is `NCHW`. + + out_height and out_width are calculated as:: + + out_height = floor((height+2*padding[0]-dilation[0]*(kernel_size[0]-1)-1)/stride[0])+1 + out_width = floor((width+2*padding[1]-dilation[1]*(kernel_size[1]-1)-1)/stride[1])+1 + """ + def __init__(self, channels, kernel_size, strides=(1, 1), padding=(0, 0), + dilation=(1, 1), groups=1, layout='NCHW', + activation=None, use_bias=True, weight_initializer=None, + bias_initializer='zeros', in_channels=0, **kwargs): + if isinstance(kernel_size, numeric_types): + kernel_size = (kernel_size,)*2 + assert len(kernel_size) == 2, "kernel_size must be a number or a list of 2 ints" + super(Conv2D, self).__init__( + channels, kernel_size, strides, padding, dilation, groups, layout, + in_channels, activation, use_bias, weight_initializer, bias_initializer, **kwargs) + + +class Conv3D(_Conv): + """3D convolution layer (e.g. spatial convolution over volumes). + + This layer creates a convolution kernel that is convolved + with the layer input to produce a tensor of + outputs. If `use_bias` is `True`, + a bias vector is created and added to the outputs. Finally, if + `activation` is not `None`, it is applied to the outputs as well. + + If `in_channels` is not specified, `Parameter` initialization will be + deferred to the first time `forward` is called and `in_channels` will be + inferred from the shape of input data. + + Parameters + ---------- + channels : int + The dimensionality of the output space, i.e. the number of output + channels (filters) in the convolution. + kernel_size :int or tuple/list of 3 int + Specifies the dimensions of the convolution window. + strides : int or tuple/list of 3 int, + Specify the strides of the convolution. + padding : int or a tuple/list of 3 int, + If padding is non-zero, then the input is implicitly zero-padded + on both sides for padding number of points + dilation : int or tuple/list of 3 int + Specifies the dilation rate to use for dilated convolution. + groups : int + Controls the connections between inputs and outputs. + At groups=1, all inputs are convolved to all outputs. + At groups=2, the operation becomes equivalent to having two conv + layers side by side, each seeing half the input channels, and producing + half the output channels, and both subsequently concatenated. + layout : str, default 'NCDHW' + Dimension ordering of data and weight. Can be 'NCDHW', 'NDHWC', etc. + 'N', 'C', 'H', 'W', 'D' stands for batch, channel, height, width and + depth dimensions respectively. Convolution is applied on the 'D', + 'H' and 'W' dimensions. + in_channels : int, default 0 + The number of input channels to this layer. If not specified, + initialization will be deferred to the first time `forward` is called + and `in_channels` will be inferred from the shape of input data. + activation : str + Activation function to use. See :func:`~mxnet.ndarray.Activation`. + If you don't specify anything, no activation is applied + (ie. "linear" activation: `a(x) = x`). + use_bias : bool + Whether the layer uses a bias vector. + weight_initializer : str or `Initializer` + Initializer for the `weight` weights matrix. + bias_initializer : str or `Initializer` + Initializer for the bias vector. + + + Input shape: + This depends on the `layout` parameter. Input is 5D array of shape + (batch_size, in_channels, depth, height, width) if `layout` is `NCDHW`. + + Output shape: + This depends on the `layout` parameter. Output is 5D array of shape + (batch_size, channels, out_depth, out_height, out_width) if `layout` is + `NCDHW`. + + out_depth, out_height and out_width are calculated as:: + + out_depth = floor((depth+2*padding[0]-dilation[0]*(kernel_size[0]-1)-1)/stride[0])+1 + out_height = floor((height+2*padding[1]-dilation[1]*(kernel_size[1]-1)-1)/stride[1])+1 + out_width = floor((width+2*padding[2]-dilation[2]*(kernel_size[2]-1)-1)/stride[2])+1 + """ + def __init__(self, channels, kernel_size, strides=(1, 1, 1), padding=(0, 0, 0), + dilation=(1, 1, 1), groups=1, layout='NCDHW', activation=None, + use_bias=True, weight_initializer=None, bias_initializer='zeros', + in_channels=0, **kwargs): + if isinstance(kernel_size, numeric_types): + kernel_size = (kernel_size,)*3 + assert len(kernel_size) == 3, "kernel_size must be a number or a list of 3 ints" + super(Conv3D, self).__init__( + channels, kernel_size, strides, padding, dilation, groups, layout, + in_channels, activation, use_bias, weight_initializer, bias_initializer, **kwargs) + + +class Conv1DTranspose(_Conv): + """Transposed 1D convolution layer (sometimes called Deconvolution). + + The need for transposed convolutions generally arises + from the desire to use a transformation going in the opposite direction + of a normal convolution, i.e., from something that has the shape of the + output of some convolution to something that has the shape of its input + while maintaining a connectivity pattern that is compatible with + said convolution. + + If `in_channels` is not specified, `Parameter` initialization will be + deferred to the first time `forward` is called and `in_channels` will be + inferred from the shape of input data. + + Parameters + ---------- + channels : int + The dimensionality of the output space, i.e. the number of output + channels (filters) in the convolution. + kernel_size :int or tuple/list of 3 int + Specifies the dimensions of the convolution window. + strides : int or tuple/list of 3 int, + Specify the strides of the convolution. + padding : int or a tuple/list of 3 int, + If padding is non-zero, then the input is implicitly zero-padded + on both sides for padding number of points + dilation : int or tuple/list of 3 int + Specifies the dilation rate to use for dilated convolution. + groups : int + Controls the connections between inputs and outputs. + At groups=1, all inputs are convolved to all outputs. + At groups=2, the operation becomes equivalent to having two conv + layers side by side, each seeing half the input channels, and producing + half the output channels, and both subsequently concatenated. + layout : str, default 'NCW' + Dimension ordering of data and weight. Can be 'NCW', 'NWC', etc. + 'N', 'C', 'W' stands for batch, channel, and width (time) dimensions + respectively. Convolution is applied on the 'W' dimension. + in_channels : int, default 0 + The number of input channels to this layer. If not specified, + initialization will be deferred to the first time `forward` is called + and `in_channels` will be inferred from the shape of input data. + activation : str + Activation function to use. See :func:`~mxnet.ndarray.Activation`. + If you don't specify anything, no activation is applied + (ie. "linear" activation: `a(x) = x`). + use_bias : bool + Whether the layer uses a bias vector. + weight_initializer : str or `Initializer` + Initializer for the `weight` weights matrix. + bias_initializer : str or `Initializer` + Initializer for the bias vector. + + + Input shape: + This depends on the `layout` parameter. Input is 3D array of shape + (batch_size, in_channels, width) if `layout` is `NCW`. + + Output shape: + This depends on the `layout` parameter. Output is 3D array of shape + (batch_size, channels, out_width) if `layout` is `NCW`. + + out_width is calculated as:: + + out_width = (width-1)*strides-2*padding+kernel_size+output_padding + """ + def __init__(self, channels, kernel_size, strides=1, padding=0, output_padding=0, + dilation=1, groups=1, layout='NCW', activation=None, use_bias=True, + weight_initializer=None, bias_initializer='zeros', + in_channels=0, **kwargs): + if isinstance(kernel_size, numeric_types): + kernel_size = (kernel_size,) + if isinstance(output_padding, numeric_types): + output_padding = (output_padding,) + assert len(kernel_size) == 1, "kernel_size must be a number or a list of 1 ints" + assert len(output_padding) == 1, "output_padding must be a number or a list of 1 ints" + super(Conv1DTranspose, self).__init__( + channels, kernel_size, strides, padding, dilation, groups, layout, + in_channels, activation, use_bias, weight_initializer, + bias_initializer, op_name='Deconvolution', adj=output_padding, **kwargs) + self.outpad = output_padding + + +class Conv2DTranspose(_Conv): + """Transposed 2D convolution layer (sometimes called Deconvolution). + + The need for transposed convolutions generally arises + from the desire to use a transformation going in the opposite direction + of a normal convolution, i.e., from something that has the shape of the + output of some convolution to something that has the shape of its input + while maintaining a connectivity pattern that is compatible with + said convolution. + + If `in_channels` is not specified, `Parameter` initialization will be + deferred to the first time `forward` is called and `in_channels` will be + inferred from the shape of input data. + + + Parameters + ---------- + channels : int + The dimensionality of the output space, i.e. the number of output + channels (filters) in the convolution. + kernel_size :int or tuple/list of 3 int + Specifies the dimensions of the convolution window. + strides : int or tuple/list of 3 int, + Specify the strides of the convolution. + padding : int or a tuple/list of 3 int, + If padding is non-zero, then the input is implicitly zero-padded + on both sides for padding number of points + dilation : int or tuple/list of 3 int + Specifies the dilation rate to use for dilated convolution. + groups : int + Controls the connections between inputs and outputs. + At groups=1, all inputs are convolved to all outputs. + At groups=2, the operation becomes equivalent to having two conv + layers side by side, each seeing half the input channels, and producing + half the output channels, and both subsequently concatenated. + layout : str, default 'NCHW' + Dimension ordering of data and weight. Can be 'NCHW', 'NHWC', etc. + 'N', 'C', 'H', 'W' stands for batch, channel, height, and width + dimensions respectively. Convolution is applied on the 'H' and + 'W' dimensions. + in_channels : int, default 0 + The number of input channels to this layer. If not specified, + initialization will be deferred to the first time `forward` is called + and `in_channels` will be inferred from the shape of input data. + activation : str + Activation function to use. See :func:`~mxnet.ndarray.Activation`. + If you don't specify anything, no activation is applied + (ie. "linear" activation: `a(x) = x`). + use_bias : bool + Whether the layer uses a bias vector. + weight_initializer : str or `Initializer` + Initializer for the `weight` weights matrix. + bias_initializer : str or `Initializer` + Initializer for the bias vector. + + + Input shape: + This depends on the `layout` parameter. Input is 4D array of shape + (batch_size, in_channels, height, width) if `layout` is `NCHW`. + + Output shape: + This depends on the `layout` parameter. Output is 4D array of shape + (batch_size, channels, out_height, out_width) if `layout` is `NCHW`. + + out_height and out_width are calculated as:: + + out_height = (height-1)*strides[0]-2*padding[0]+kernel_size[0]+output_padding[0] + out_width = (width-1)*strides[1]-2*padding[1]+kernel_size[1]+output_padding[1] + """ + def __init__(self, channels, kernel_size, strides=(1, 1), padding=(0, 0), + output_padding=(0, 0), dilation=(1, 1), groups=1, layout='NCHW', + activation=None, use_bias=True, weight_initializer=None, + bias_initializer='zeros', in_channels=0, **kwargs): + if isinstance(kernel_size, numeric_types): + kernel_size = (kernel_size,)*2 + if isinstance(output_padding, numeric_types): + output_padding = (output_padding,)*2 + assert len(kernel_size) == 2, "kernel_size must be a number or a list of 2 ints" + assert len(output_padding) == 2, "output_padding must be a number or a list of 2 ints" + super(Conv2DTranspose, self).__init__( + channels, kernel_size, strides, padding, dilation, groups, layout, + in_channels, activation, use_bias, weight_initializer, + bias_initializer, op_name='Deconvolution', adj=output_padding, **kwargs) + self.outpad = output_padding + + +class Conv3DTranspose(_Conv): + """Transposed 3D convolution layer (sometimes called Deconvolution). + + The need for transposed convolutions generally arises + from the desire to use a transformation going in the opposite direction + of a normal convolution, i.e., from something that has the shape of the + output of some convolution to something that has the shape of its input + while maintaining a connectivity pattern that is compatible with + said convolution. + + If `in_channels` is not specified, `Parameter` initialization will be + deferred to the first time `forward` is called and `in_channels` will be + inferred from the shape of input data. + + + Parameters + ---------- + channels : int + The dimensionality of the output space, i.e. the number of output + channels (filters) in the convolution. + kernel_size :int or tuple/list of 3 int + Specifies the dimensions of the convolution window. + strides : int or tuple/list of 3 int, + Specify the strides of the convolution. + padding : int or a tuple/list of 3 int, + If padding is non-zero, then the input is implicitly zero-padded + on both sides for padding number of points + dilation : int or tuple/list of 3 int + Specifies the dilation rate to use for dilated convolution. + groups : int + Controls the connections between inputs and outputs. + At groups=1, all inputs are convolved to all outputs. + At groups=2, the operation becomes equivalent to having two conv + layers side by side, each seeing half the input channels, and producing + half the output channels, and both subsequently concatenated. + layout : str, default 'NCDHW' + Dimension ordering of data and weight. Can be 'NCDHW', 'NDHWC', etc. + 'N', 'C', 'H', 'W', 'D' stands for batch, channel, height, width and + depth dimensions respectively. Convolution is applied on the 'D', + 'H', and 'W' dimensions. + in_channels : int, default 0 + The number of input channels to this layer. If not specified, + initialization will be deferred to the first time `forward` is called + and `in_channels` will be inferred from the shape of input data. + activation : str + Activation function to use. See :func:`~mxnet.ndarray.Activation`. + If you don't specify anything, no activation is applied + (ie. "linear" activation: `a(x) = x`). + use_bias : bool + Whether the layer uses a bias vector. + weight_initializer : str or `Initializer` + Initializer for the `weight` weights matrix. + bias_initializer : str or `Initializer` + Initializer for the bias vector. + + + Input shape: + This depends on the `layout` parameter. Input is 5D array of shape + (batch_size, in_channels, depth, height, width) if `layout` is `NCDHW`. + + Output shape: + This depends on the `layout` parameter. Output is 5D array of shape + (batch_size, channels, out_depth, out_height, out_width) if `layout` is `NCDHW`. + out_depth, out_height and out_width are calculated as:: + + out_depth = (depth-1)*strides[0]-2*padding[0]+kernel_size[0]+output_padding[0] + out_height = (height-1)*strides[1]-2*padding[1]+kernel_size[1]+output_padding[1] + out_width = (width-1)*strides[2]-2*padding[2]+kernel_size[2]+output_padding[2] + """ + def __init__(self, channels, kernel_size, strides=(1, 1, 1), padding=(0, 0, 0), + output_padding=(0, 0, 0), dilation=(1, 1, 1), groups=1, layout='NCDHW', + activation=None, use_bias=True, weight_initializer=None, + bias_initializer='zeros', in_channels=0, **kwargs): + if isinstance(kernel_size, numeric_types): + kernel_size = (kernel_size,)*3 + if isinstance(output_padding, numeric_types): + output_padding = (output_padding,)*3 + assert len(kernel_size) == 3, "kernel_size must be a number or a list of 3 ints" + assert len(output_padding) == 3, "output_padding must be a number or a list of 3 ints" + super(Conv3DTranspose, self).__init__( + channels, kernel_size, strides, padding, dilation, groups, layout, + in_channels, activation, use_bias, weight_initializer, bias_initializer, + op_name='Deconvolution', adj=output_padding, **kwargs) + self.outpad = output_padding + + +class _Pooling(HybridBlock): + """Abstract class for different pooling layers.""" + def __init__(self, pool_size, strides, padding, ceil_mode, global_pool, + pool_type, **kwargs): + super(_Pooling, self).__init__(**kwargs) + if strides is None: + strides = pool_size + if isinstance(strides, numeric_types): + strides = (strides,)*len(pool_size) + if isinstance(padding, numeric_types): + padding = (padding,)*len(pool_size) + self._kwargs = { + 'kernel': pool_size, 'stride': strides, 'pad': padding, + 'global_pool': global_pool, 'pool_type': pool_type, + 'pooling_convention': 'full' if ceil_mode else 'valid'} + + def _alias(self): + return 'pool' + + def hybrid_forward(self, F, x): + return F.Pooling(x, name='fwd', **self._kwargs) + + def __repr__(self): + s = '{name}(size={kernel}, stride={stride}, padding={pad}, ceil_mode={ceil_mode})' + return s.format(name=self.__class__.__name__, + ceil_mode=self._kwargs['pooling_convention'] == 'full', + **self._kwargs) + + +class MaxPool1D(_Pooling): + """Max pooling operation for one dimensional data. + + + Parameters + ---------- + pool_size: int + Size of the max pooling windows. + strides: int, or None + Factor by which to downscale. E.g. 2 will halve the input size. + If `None`, it will default to `pool_size`. + padding: int + If padding is non-zero, then the input is implicitly + zero-padded on both sides for padding number of points. + layout : str, default 'NCW' + Dimension ordering of data and weight. Can be 'NCW', 'NWC', etc. + 'N', 'C', 'W' stands for batch, channel, and width (time) dimensions + respectively. Pooling is applied on the W dimension. + ceil_mode : bool, default False + When `True`, will use ceil instead of floor to compute the output shape. + + + Input shape: + This depends on the `layout` parameter. Input is 3D array of shape + (batch_size, channels, width) if `layout` is `NCW`. + + Output shape: + This depends on the `layout` parameter. Output is 3D array of shape + (batch_size, channels, out_width) if `layout` is `NCW`. + + out_width is calculated as:: + + out_width = floor((width+2*padding-pool_size)/strides)+1 + + When `ceil_mode` is `True`, ceil will be used instead of floor in this + equation. + """ + def __init__(self, pool_size=2, strides=None, padding=0, layout='NCW', + ceil_mode=False, **kwargs): + assert layout == 'NCW', "Only supports NCW layout for now" + if isinstance(pool_size, numeric_types): + pool_size = (pool_size,) + assert len(pool_size) == 1, "pool_size must be a number or a list of 1 ints" + super(MaxPool1D, self).__init__( + pool_size, strides, padding, ceil_mode, False, 'max', **kwargs) + + +class MaxPool2D(_Pooling): + """Max pooling operation for two dimensional (spatial) data. + + + Parameters + ---------- + pool_size: int or list/tuple of 2 ints, + Size of the max pooling windows. + strides: int, list/tuple of 2 ints, or None. + Factor by which to downscale. E.g. 2 will halve the input size. + If `None`, it will default to `pool_size`. + padding: int or list/tuple of 2 ints, + If padding is non-zero, then the input is implicitly + zero-padded on both sides for padding number of points. + layout : str, default 'NCHW' + Dimension ordering of data and weight. Can be 'NCHW', 'NHWC', etc. + 'N', 'C', 'H', 'W' stands for batch, channel, height, and width + dimensions respectively. padding is applied on 'H' and 'W' dimension. + ceil_mode : bool, default False + When `True`, will use ceil instead of floor to compute the output shape. + + + Input shape: + This depends on the `layout` parameter. Input is 4D array of shape + (batch_size, channels, height, width) if `layout` is `NCHW`. + + Output shape: + This depends on the `layout` parameter. Output is 4D array of shape + (batch_size, channels, out_height, out_width) if `layout` is `NCHW`. + + out_height and out_width are calculated as:: + + out_height = floor((height+2*padding[0]-pool_size[0])/strides[0])+1 + out_width = floor((width+2*padding[1]-pool_size[1])/strides[1])+1 + + When `ceil_mode` is `True`, ceil will be used instead of floor in this + equation. + """ + def __init__(self, pool_size=(2, 2), strides=None, padding=0, layout='NCHW', + ceil_mode=False, **kwargs): + assert layout == 'NCHW', "Only supports NCHW layout for now" + if isinstance(pool_size, numeric_types): + pool_size = (pool_size,)*2 + assert len(pool_size) == 2, "pool_size must be a number or a list of 2 ints" + super(MaxPool2D, self).__init__( + pool_size, strides, padding, ceil_mode, False, 'max', **kwargs) + + +class MaxPool3D(_Pooling): + """Max pooling operation for 3D data (spatial or spatio-temporal). + + + Parameters + ---------- + pool_size: int or list/tuple of 3 ints, + Size of the max pooling windows. + strides: int, list/tuple of 3 ints, or None. + Factor by which to downscale. E.g. 2 will halve the input size. + If `None`, it will default to `pool_size`. + padding: int or list/tuple of 3 ints, + If padding is non-zero, then the input is implicitly + zero-padded on both sides for padding number of points. + layout : str, default 'NCDHW' + Dimension ordering of data and weight. Can be 'NCDHW', 'NDHWC', etc. + 'N', 'C', 'H', 'W', 'D' stands for batch, channel, height, width and + depth dimensions respectively. padding is applied on 'D', 'H' and 'W' + dimension. + ceil_mode : bool, default False + When `True`, will use ceil instead of floor to compute the output shape. + + + Input shape: + This depends on the `layout` parameter. Input is 5D array of shape + (batch_size, channels, depth, height, width) if `layout` is `NCDHW`. + + Output shape: + This depends on the `layout` parameter. Output is 5D array of shape + (batch_size, channels, out_depth, out_height, out_width) if `layout` + is `NCDHW`. + + out_depth, out_height and out_width are calculated as :: + + out_depth = floor((depth+2*padding[0]-pool_size[0])/strides[0])+1 + out_height = floor((height+2*padding[1]-pool_size[1])/strides[1])+1 + out_width = floor((width+2*padding[2]-pool_size[2])/strides[2])+1 + + When `ceil_mode` is `True`, ceil will be used instead of floor in this + equation. + """ + def __init__(self, pool_size=(2, 2, 2), strides=None, padding=0, + ceil_mode=False, layout='NCDHW', **kwargs): + assert layout == 'NCDHW', "Only supports NCDHW layout for now" + if isinstance(pool_size, numeric_types): + pool_size = (pool_size,)*3 + assert len(pool_size) == 3, "pool_size must be a number or a list of 3 ints" + super(MaxPool3D, self).__init__( + pool_size, strides, padding, ceil_mode, False, 'max', **kwargs) + + +class AvgPool1D(_Pooling): + """Average pooling operation for temporal data. + + Parameters + ---------- + pool_size: int + Size of the max pooling windows. + strides: int, or None + Factor by which to downscale. E.g. 2 will halve the input size. + If `None`, it will default to `pool_size`. + padding: int + If padding is non-zero, then the input is implicitly + zero-padded on both sides for padding number of points. + layout : str, default 'NCW' + Dimension ordering of data and weight. Can be 'NCW', 'NWC', etc. + 'N', 'C', 'W' stands for batch, channel, and width (time) dimensions + respectively. padding is applied on 'W' dimension. + ceil_mode : bool, default False + When `True`, will use ceil instead of floor to compute the output shape. + + + Input shape: + This depends on the `layout` parameter. Input is 3D array of shape + (batch_size, channels, width) if `layout` is `NCW`. + + Output shape: + This depends on the `layout` parameter. Output is 3D array of shape + (batch_size, channels, out_width) if `layout` is `NCW`. + + out_width is calculated as:: + + out_width = floor((width+2*padding-pool_size)/strides)+1 + + When `ceil_mode` is `True`, ceil will be used instead of floor in this + equation. + """ + def __init__(self, pool_size=2, strides=None, padding=0, layout='NCW', + ceil_mode=False, **kwargs): + assert layout == 'NCW', "Only supports NCW layout for now" + if isinstance(pool_size, numeric_types): + pool_size = (pool_size,) + assert len(pool_size) == 1, "pool_size must be a number or a list of 1 ints" + super(AvgPool1D, self).__init__( + pool_size, strides, padding, ceil_mode, False, 'avg', **kwargs) + + +class AvgPool2D(_Pooling): + """Average pooling operation for spatial data. + + Parameters + ---------- + pool_size: int or list/tuple of 2 ints, + Size of the max pooling windows. + strides: int, list/tuple of 2 ints, or None. + Factor by which to downscale. E.g. 2 will halve the input size. + If `None`, it will default to `pool_size`. + padding: int or list/tuple of 2 ints, + If padding is non-zero, then the input is implicitly + zero-padded on both sides for padding number of points. + layout : str, default 'NCHW' + Dimension ordering of data and weight. Can be 'NCHW', 'NHWC', etc. + 'N', 'C', 'H', 'W' stands for batch, channel, height, and width + dimensions respectively. padding is applied on 'H' and 'W' dimension. + ceil_mode : bool, default False + When True, will use ceil instead of floor to compute the output shape. + + + Input shape: + This depends on the `layout` parameter. Input is 4D array of shape + (batch_size, channels, height, width) if `layout` is `NCHW`. + + Output shape: + This depends on the `layout` parameter. Output is 4D array of shape + (batch_size, channels, out_height, out_width) if `layout` is `NCHW`. + + out_height and out_width are calculated as:: + + out_height = floor((height+2*padding[0]-pool_size[0])/strides[0])+1 + out_width = floor((width+2*padding[1]-pool_size[1])/strides[1])+1 + + When `ceil_mode` is `True`, ceil will be used instead of floor in this + equation. + """ + def __init__(self, pool_size=(2, 2), strides=None, padding=0, + ceil_mode=False, layout='NCHW', **kwargs): + assert layout == 'NCHW', "Only supports NCHW layout for now" + if isinstance(pool_size, numeric_types): + pool_size = (pool_size,)*2 + assert len(pool_size) == 2, "pool_size must be a number or a list of 2 ints" + super(AvgPool2D, self).__init__( + pool_size, strides, padding, ceil_mode, False, 'avg', **kwargs) + + +class AvgPool3D(_Pooling): + """Average pooling operation for 3D data (spatial or spatio-temporal). + + Parameters + ---------- + pool_size: int or list/tuple of 3 ints, + Size of the max pooling windows. + strides: int, list/tuple of 3 ints, or None. + Factor by which to downscale. E.g. 2 will halve the input size. + If `None`, it will default to `pool_size`. + padding: int or list/tuple of 3 ints, + If padding is non-zero, then the input is implicitly + zero-padded on both sides for padding number of points. + layout : str, default 'NCDHW' + Dimension ordering of data and weight. Can be 'NCDHW', 'NDHWC', etc. + 'N', 'C', 'H', 'W', 'D' stands for batch, channel, height, width and + depth dimensions respectively. padding is applied on 'D', 'H' and 'W' + dimension. + ceil_mode : bool, default False + When True, will use ceil instead of floor to compute the output shape. + + + Input shape: + This depends on the `layout` parameter. Input is 5D array of shape + (batch_size, channels, depth, height, width) if `layout` is `NCDHW`. + + Output shape: + This depends on the `layout` parameter. Output is 5D array of shape + (batch_size, channels, out_depth, out_height, out_width) if `layout` + is `NCDHW`. + + out_depth, out_height and out_width are calculated as :: + + out_depth = floor((depth+2*padding[0]-pool_size[0])/strides[0])+1 + out_height = floor((height+2*padding[1]-pool_size[1])/strides[1])+1 + out_width = floor((width+2*padding[2]-pool_size[2])/strides[2])+1 + + When `ceil_mode` is `True,` ceil will be used instead of floor in this + equation. + """ + def __init__(self, pool_size=(2, 2, 2), strides=None, padding=0, + ceil_mode=False, layout='NCDHW', **kwargs): + assert layout == 'NCDHW', "Only supports NCDHW layout for now" + if isinstance(pool_size, numeric_types): + pool_size = (pool_size,)*3 + assert len(pool_size) == 3, "pool_size must be a number or a list of 3 ints" + super(AvgPool3D, self).__init__( + pool_size, strides, padding, ceil_mode, False, 'avg', **kwargs) + + +class GlobalMaxPool1D(_Pooling): + """Global max pooling operation for temporal data.""" + def __init__(self, layout='NCW', **kwargs): + assert layout == 'NCW', "Only supports NCW layout for now" + super(GlobalMaxPool1D, self).__init__( + (1,), None, 0, True, True, 'max', **kwargs) + + +class GlobalMaxPool2D(_Pooling): + """Global max pooling operation for spatial data.""" + def __init__(self, layout='NCHW', **kwargs): + assert layout == 'NCHW', "Only supports NCW layout for now" + super(GlobalMaxPool2D, self).__init__( + (1, 1), None, 0, True, True, 'max', **kwargs) + +class GlobalMaxPool3D(_Pooling): + """Global max pooling operation for 3D data.""" + def __init__(self, layout='NCDHW', **kwargs): + assert layout == 'NCDHW', "Only supports NCW layout for now" + super(GlobalMaxPool3D, self).__init__( + (1, 1, 1), None, 0, True, True, 'max', **kwargs) + + +class GlobalAvgPool1D(_Pooling): + """Global average pooling operation for temporal data.""" + def __init__(self, layout='NCW', **kwargs): + assert layout == 'NCW', "Only supports NCW layout for now" + super(GlobalAvgPool1D, self).__init__( + (1,), None, 0, True, True, 'avg', **kwargs) + + +class GlobalAvgPool2D(_Pooling): + """Global average pooling operation for spatial data.""" + def __init__(self, layout='NCHW', **kwargs): + assert layout == 'NCHW', "Only supports NCW layout for now" + super(GlobalAvgPool2D, self).__init__( + (1, 1), None, 0, True, True, 'avg', **kwargs) + + +class GlobalAvgPool3D(_Pooling): + """Global max pooling operation for 3D data.""" + def __init__(self, layout='NCDHW', **kwargs): + assert layout == 'NCDHW', "Only supports NCW layout for now" + super(GlobalAvgPool3D, self).__init__( + (1, 1, 1), None, 0, True, True, 'avg', **kwargs) diff --git a/python/mxnet/gluon/parameter.py b/python/mxnet/gluon/parameter.py new file mode 100644 index 000000000000..bef55d67e140 --- /dev/null +++ b/python/mxnet/gluon/parameter.py @@ -0,0 +1,611 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +# coding: utf-8 +# pylint: disable= +"""Neural network parameter.""" + +from collections import OrderedDict +import warnings +import numpy as np + + +from ..base import mx_real_t, MXNetError +from .. import symbol, ndarray, initializer, context +from ..context import Context +from .. import autograd +from .utils import _indent + +# pylint: disable= invalid-name +tensor_types = (symbol.Symbol, ndarray.NDArray) +# pylint: enable= invalid-name + +class DeferredInitializationError(MXNetError): + """Error for unfinished deferred initialization.""" + pass + +class Parameter(object): + """A Container holding parameters (weights) of `Block`s. + + `Parameter` holds a copy of the parameter on each `Context` after + it is initialized with `Parameter.initialize(...)`. If `grad_req` is + not `null`, it will also hold a gradient array on each `Context`:: + + ctx = mx.gpu(0) + x = mx.nd.zeros((16, 100), ctx=ctx) + w = mx.gluon.Parameter('fc_weight', shape=(64, 100), init=mx.init.Xavier()) + b = mx.gluon.Parameter('fc_bias', shape=(64,), init=mx.init.Zero()) + w.initialize(ctx=ctx) + b.initialize(ctx=ctx) + out = mx.nd.FullyConnected(x, w.data(ctx), b.data(ctx), num_hidden=64) + + Parameters + ---------- + name : str + Name of this parameter. + grad_req : {'write', 'add', 'null'}, default 'write' + Specifies how to update gradient to grad arrays. + + - 'write' means everytime gradient is written to grad `NDArray`. + - 'add' means everytime gradient is added to the grad `NDArray`. You need + to manually call `zero_grad()` to clear the gradient buffer before each + iteration when using this option. + - 'null' means gradient is not requested for this parameter. gradient arrays + will not be allocated. + shape : tuple of int, default None + Shape of this parameter. By default shape is not specified. Parameter with + unknown shape can be used for `Symbol` API, but `init` will throw an error + when using `NDArray` API. + dtype : numpy.dtype or str, default 'float32' + Data type of this parameter. For example, numpy.float32 or 'float32'. + lr_mult : float, default 1.0 + Learning rate multiplier. Learning rate will be multiplied by lr_mult + when updating this parameter with optimizer. + wd_mult : float, default 1.0 + Weight decay multiplier (L2 regularizer coefficient). Works similar to lr_mult. + init : Initializer, default None + Initializer of this parameter. Will use the global initializer by default. + + Attributes + ---------- + grad_req : {'write', 'add', 'null'} + This can be set before or after initialization. Setting grad_req to null + with `x.grad_req = 'null'` saves memory and computation when you don't + need gradient w.r.t x. + """ + def __init__(self, name, grad_req='write', shape=None, dtype=mx_real_t, + lr_mult=1.0, wd_mult=1.0, init=None, allow_deferred_init=False, + differentiable=True): + self._var = None + self._data = None + self._grad = None + self._deferred_init = () + self._differentiable = differentiable + self._grad_req = None + self.name = name + self.shape = shape + self.dtype = dtype + self.lr_mult = lr_mult + self.wd_mult = wd_mult + self.grad_req = grad_req + self.init = init + self.allow_deferred_init = allow_deferred_init + + def __repr__(self): + s = 'Parameter {name} (shape={shape}, dtype={dtype})' + return s.format(**self.__dict__) + + @property + def grad_req(self): + return self._grad_req + + @grad_req.setter + def grad_req(self, req): + assert req in ['write', 'add', 'null'], \ + "grad_req must be one of write, add, or null, but got %s"%req + if not self._differentiable: + req = 'null' + if self._grad_req == req: + return + self._grad_req = req + if req == 'null' and self._grad is not None: + self._grad = None + for ctx in self._data: + self._data[ctx] = self._data[ctx].detach() + elif self._data is not None: + self._init_grad() + + def _check_initialized(self, ctx=None): + if self._data is not None: + if ctx is not None and ctx not in self._data: + raise RuntimeError( + "Parameter %s was not initialized on context %s. " + "It was only initialized on %s."%( + self.name, str(ctx), str(self.list_ctx()))) + return + if self._deferred_init: + raise DeferredInitializationError + raise RuntimeError( + "Parameter %s has not been initialized. Note that " \ + "you should initialize parameters and create Trainer " \ + "with Block.collect_params() instead of Block.params " \ + "because the later does not include Parameters of " \ + "nested child Blocks"%(self.name)) + + def _load_init(self, data, ctx): + """(Re)initializes by loading from data.""" + if self.shape: + for i, j in zip(self.shape, data.shape): + assert i == 0 or i == j, \ + "Failed loading Parameter %s from saved params: " \ + "shape incompatible expacted %s vs saved %s"%( + self.name, str(self.shape), str(data.shape)) + if self.dtype: + assert np.dtype(self.dtype).type == data.dtype, \ + "Failed loading Parameter %s from saved params: " \ + "dtype incompatible expacted %s vs saved %s"%( + self.name, str(self.dtype), str(data.dtype)) + if isinstance(ctx, Context): + ctx = [ctx] + if self._data is None: + if self._deferred_init: + assert set(ctx) == set(self._deferred_init[1]), \ + "Failed to load Parameter %s on %s because it was " \ + "previous initialized on %s."%( + self.name, str(ctx), str(self.list_ctx())) + self._init_impl(data, ctx) + else: + assert set(ctx) == set(self.list_ctx()), \ + "Failed to load Parameter %s on %s because it was " \ + "previous initialized on %s."%( + self.name, str(ctx), str(self.list_ctx())) + self.set_data(data) + self._deferred_init = () + + def _finish_deferred_init(self): + """Finishes deferred initialization.""" + if not self._deferred_init: + return + init, ctx, default_init = self._deferred_init + self._deferred_init = () + assert self.shape is not None and np.prod(self.shape) > 0, \ + "Cannot initialize Parameter %s because it has " \ + "invalid shape: %s. Please specify in_units, " \ + "in_channels, etc for `Block`s."%( + self.name, str(self.shape)) + + with autograd.pause(): + data = ndarray.zeros(shape=self.shape, dtype=self.dtype, + ctx=context.cpu()) + initializer.create(default_init)( + initializer.InitDesc(self.name, {'__init__': init}), data) + + self._init_impl(data, ctx) + + def _init_impl(self, data, ctx): + """Sets data and grad.""" + self._data = OrderedDict() + for i in ctx: + self._data[i] = data.copyto(i) + self._init_grad() + + def _init_grad(self): + """Initialize grad buffers.""" + if self.grad_req == 'null': + self._grad = None + return + + self._grad = OrderedDict() + for i in self._data: + self._grad[i] = ndarray.zeros_like(self._data[i]) + + autograd.mark_variables(self.list_data(), self.list_grad(), self.grad_req) + + def _reduce(self): + """Reduce data from multiple context.""" + block = self.list_data() + data = ndarray.add_n(*(w.copyto(context.cpu()) for w in block)) / len(block) + return data + + def initialize(self, init=None, ctx=None, default_init=initializer.Uniform(), + force_reinit=False): + """Initializes parameter and gradient arrays. Only used for `NDArray` API. + + Parameters + ---------- + init : Initializer + The initializer to use. Overrides `Parameter.init` and default_init. + ctx : Context or list of Context, defaults to `context.current_context()`. + Initialize Parameter on given context. If ctx is a list of Context, a + copy will be made for each context. + + .. note:: Copies are independent arrays. User is responsible for keeping + their values consistent when updating. Normally `gluon.Trainer` does this for you. + default_init : Initializer + Default initializer is used when both `init` and `Parameter.init` are `None`. + force_reinit : bool, default False + Whether to force re-initialization if parameter is already initialized. + + Examples + -------- + >>> weight = mx.gluon.Parameter('weight', shape=(2, 2)) + >>> weight.initialize(ctx=mx.cpu(0)) + >>> weight.data() + [[-0.01068833 0.01729892] + [ 0.02042518 -0.01618656]] + + >>> weight.grad() + [[ 0. 0.] + [ 0. 0.]] + + >>> weight.initialize(ctx=[mx.gpu(0), mx.gpu(1)]) + >>> weight.data(mx.gpu(0)) + [[-0.00873779 -0.02834515] + [ 0.05484822 -0.06206018]] + + >>> weight.data(mx.gpu(1)) + [[-0.00873779 -0.02834515] + [ 0.05484822 -0.06206018]] + + """ + if self._data is not None and not force_reinit: + warnings.warn("Parameter %s is already initialized, ignoring. " \ + "Set force_reinit=True to re-initialize."%self.name) + return + self._data = self._grad = None + + if ctx is None: + ctx = [context.current_context()] + if isinstance(ctx, Context): + ctx = [ctx] + if init is None: + init = default_init if self.init is None else self.init + if not self.shape or np.prod(self.shape) <= 0: + if self.allow_deferred_init: + self._deferred_init = (init, ctx, default_init) + return + raise ValueError("Cannot initialize Parameter %s because it has " \ + "invalid shape: %s."%(self.name, str(self.shape))) + + self._deferred_init = (init, ctx, default_init) + self._finish_deferred_init() + + def reset_ctx(self, ctx): + """Re-assign Parameter to other contexts. + + ctx : Context or list of Context, default `context.current_context()`. + Assign Parameter to given context. If ctx is a list of Context, a + copy will be made for each context. + """ + if ctx is None: + ctx = [context.current_context()] + if isinstance(ctx, Context): + ctx = [ctx] + if self._data: + data = self._reduce() + with autograd.pause(): + self._init_impl(data, ctx) + elif self._deferred_init: + init, _, default_init = self._deferred_init + self._deferred_init = (init, ctx, default_init) + else: + raise ValueError("Cannot reset context for Parameter %s because it " + "has not been initialized."%self.name) + + + def set_data(self, data): + """Sets this parameter's value on all contexts to data.""" + assert self._data is not None, \ + "Parameter %s has not been initialized"%self.name + for arr in self.list_data(): + arr[:] = data + + def data(self, ctx=None): + """Returns a copy of this parameter on one context. Must have been + initialized on this context before. + + Parameters + ---------- + ctx : Context + Desired context. + + Returns + ------- + NDArray on ctx + """ + if ctx is None: + list_ctx = self.list_ctx() + if len(list_ctx) == 1: + ctx = list_ctx[0] + else: + ctx = context.current_context() + self._check_initialized(ctx) + return self._data[ctx] + + def list_data(self): + """Returns copies of this parameter on all contexts, in the same order + as creation.""" + self._check_initialized() + return list(self._data.values()) + + def grad(self, ctx=None): + """Returns a gradient buffer for this parameter on one context. + + Parameters + ---------- + ctx : Context + Desired context. + """ + if ctx is None: + list_ctx = self.list_ctx() + if len(list_ctx) == 1: + ctx = list_ctx[0] + else: + ctx = context.current_context() + self._check_initialized(ctx) + if self._grad is None: + raise RuntimeError( + "Cannot get gradient array for Parameter %s " \ + "because grad_req='null'"%(self.name)) + return self._grad[ctx] + + def list_grad(self): + """Returns gradient buffers on all contexts, in the same order + as `values`.""" + self._check_initialized() + assert self._grad is not None, \ + "Parameter %s does not have gradients because grad_req='null'"%self.name + return list(self._grad.values()) + + def list_ctx(self): + """Returns a list of contexts this parameter is initialized on.""" + if self._data is None: + if self._deferred_init: + return self._deferred_init[1] + raise RuntimeError("Parameter %s has not been initialized"%self.name) + return list(self._data.keys()) + + def zero_grad(self): + """Sets gradient buffer on all contexts to 0. No action is taken if + parameter is uninitialized or doesn't require gradient.""" + if self._grad is None: + return + for i in self._grad.values(): + i[:] = 0 + + def var(self): + """Returns a symbol representing this parameter.""" + if self._var is None: + self._var = symbol.var(self.name, shape=self.shape, dtype=self.dtype, + lr_mult=self.lr_mult, wd_mult=self.wd_mult, + init=self.init) + return self._var + + +class ParameterDict(object): + """A dictionary managing a set of parameters. + + Parameters + ---------- + prefix : str, default '' + The prefix to be prepended to all Parameters' names created by this dict. + shared : ParameterDict or None + If not `None`, when this dict's `get` method creates a new parameter, will + first try to retrieve it from `shared` dict. Usually used for sharing + parameters with another `Block`. + """ + def __init__(self, prefix='', shared=None): + self._prefix = prefix + self._params = OrderedDict() + self._shared = shared + + def __repr__(self): + s = '{name}(\n{content}\n)' + name = self._prefix+' ' if self._prefix else '' + return s.format(name=name, + content='\n'.join([_indent(' {0}'.format(v), 2) + for v in self.values()])) + + def __getitem__(self, key): + return self._params[key] + + def __iter__(self): + return iter(self._params) + + def items(self): + return self._params.items() + + def keys(self): + return self._params.keys() + + def values(self): + return self._params.values() + + @property + def prefix(self): + """Prefix of this dict. It will be prepended to Parameters' name created + with `get`.""" + return self._prefix + + def _get_impl(self, name): + if name in self._params: + return self._params[name] + if self._shared is not None and name in self._shared._params: + self._params[name] = self._shared._params[name] + return self._shared._params[name] + return None + + def get(self, name, **kwargs): + """Retrieves a `Parameter` with name `self.prefix+name`. If not found, + `get` will first try to retrieve it from `shared` dict. If still not + found, `get` will create a new `Parameter` with key-word arguments and + insert it to self. + + Parameters + ---------- + name : str + Name of the desired Parameter. It will be prepended with this dictionary's + prefix. + **kwargs : dict + The rest of key-word arguments for the created `Parameter`. + + Returns + ------- + Parameter + The created or retrieved `Parameter`. + """ + name = self.prefix + name + param = self._get_impl(name) + if param is None: + param = Parameter(name, **kwargs) + self._params[name] = param + else: + for k, v in kwargs.items(): + if hasattr(param, k) and getattr(param, k) is not None: + assert v is None or v == getattr(param, k), \ + "Cannot retrieve Parameter %s because desired attribute " \ + "does not match with stored for attribute %s: " \ + "desired %s vs stored %s."%( + name, k, str(v), str(getattr(param, k))) + else: + setattr(param, k, v) + return param + + def update(self, other): + """Copies all Parameters in `other` to self.""" + for k, v in other.items(): + if k in self._params: + assert self._params[k] is v, \ + "Cannot update self with other because they have different " \ + "Parameters with the same name %s"%k + else: + self._params[k] = v + + def initialize(self, init=initializer.Uniform(), ctx=None, verbose=False, + force_reinit=False): + """Initializes all Parameters managed by this dictionary to be used for `NDArray` + API. It has no effect when using `Symbol` API. + + Parameters + ---------- + init : Initializer + Global default Initializer to be used when `Parameter.init` is `None`. + Otherwise, `Parameter.init` takes precedence. + ctx : Context or list of Context + Keeps a copy of Parameters on one or many context(s). + force_reinit : bool, default False + Whether to force re-initialization if parameter is already initialized. + """ + if verbose: + init.set_verbosity(verbose=verbose) + for _, v in self.items(): + v.initialize(None, ctx, init, force_reinit=force_reinit) + + def zero_grad(self): + """Sets all Parameters' gradient buffer to 0.""" + for i in self.values(): + i.zero_grad() + + def reset_ctx(self, ctx): + """Re-assign all Parameters to other contexts. + + ctx : Context or list of Context, default `context.current_context()`. + Assign Parameter to given context. If ctx is a list of Context, a + copy will be made for each context. + """ + for i in self.values(): + i.reset_ctx(ctx) + + def setattr(self, name, value): + """Set an attribute to a new value for all Parameters. + + For example, set grad_req to null if you don't need gradient w.r.t a + model's Parameters:: + + model.collect_params().setattr('grad_req', 'null') + + or change the learning rate multiplier:: + + model.collect_params().setattr('lr_mult', 0.5) + + Parameters + ---------- + name : str + Name of the attribute. + value : valid type for attribute name + The new value for the attribute. + """ + for i in self.values(): + setattr(i, name, value) + + def save(self, filename, strip_prefix=''): + """Save parameters to file. + + filename : str + Path to parameter file. + strip_prefix : str, default '' + Strip prefix from parameter names before saving. + """ + arg_dict = {} + for param in self.values(): + weight = param._reduce() + if not param.name.startswith(strip_prefix): + raise ValueError( + "Prefix %s is to be striped before saving, but Parameter " \ + "%s does not start with %s. If you are using Block.save_params, " \ + "This may be due to your Block shares parameters from other " \ + "Blocks or you forgot to use `with name_scope()`` during init. " \ + "Consider switching to Block.collect_params.save and " \ + "Block.collect_params.load instead."%( + strip_prefix, param.name, strip_prefix)) + arg_dict[param.name[len(strip_prefix):]] = weight + ndarray.save(filename, arg_dict) + + def load(self, filename, ctx, allow_missing=False, + ignore_extra=False, restore_prefix=''): + """Load parameters from file. + + filename : str + Path to parameter file. + ctx : Context or list of Context + Context(s) initialize loaded parameters on. + allow_missing : bool, default False + Whether to silently skip loading parameters not represents in the file. + ignore_extra : bool, default False + Whether to silently ignore parameters from the file that are not + present in this ParameterDict. + restore_prefix : str, default '' + prepend prefix to names of stored parameters before loading. + """ + if restore_prefix: + for name in self.keys(): + assert name.startswith(restore_prefix), \ + "restore_prefix is %s but Parameters name %s does not start " \ + "with %s"%(restore_prefix, name, restore_prefix) + lprefix = len(restore_prefix) + arg_dict = {restore_prefix+k: v for k, v in ndarray.load(filename).items()} + if not allow_missing: + for name in self.keys(): + assert name in arg_dict, \ + "Parameter %s is missing in file %s"%(name[lprefix:], filename) + for name in arg_dict: + if name not in self._params: + assert ignore_extra, \ + "Parameter %s loaded from file %s is not present in ParameterDict"%( + name[lprefix:], filename) + continue + self[name]._load_init(arg_dict[name], ctx) diff --git a/python/mxnet/gluon/rnn/__init__.py b/python/mxnet/gluon/rnn/__init__.py new file mode 100644 index 000000000000..24cce542274c --- /dev/null +++ b/python/mxnet/gluon/rnn/__init__.py @@ -0,0 +1,24 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +# coding: utf-8 +# pylint: disable=wildcard-import +"""Recurrent neural network module.""" + +from .rnn_cell import * + +from .rnn_layer import * diff --git a/python/mxnet/gluon/rnn/rnn_cell.py b/python/mxnet/gluon/rnn/rnn_cell.py new file mode 100644 index 000000000000..c9186fd3ce09 --- /dev/null +++ b/python/mxnet/gluon/rnn/rnn_cell.py @@ -0,0 +1,803 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +# coding: utf-8 +# pylint: disable=no-member, invalid-name, protected-access, no-self-use +# pylint: disable=too-many-branches, too-many-arguments, no-self-use +# pylint: disable=too-many-lines, arguments-differ +"""Definition of various recurrent neural network cells.""" +from __future__ import print_function + +from ... import symbol, ndarray +from ...base import string_types, numeric_types, _as_list +from ..block import Block, HybridBlock +from ..utils import _indent +from .. import tensor_types + + +def _cells_state_info(cells, batch_size): + return sum([c.state_info(batch_size) for c in cells], []) + +def _cells_begin_state(cells, **kwargs): + return sum([c.begin_state(**kwargs) for c in cells], []) + +def _get_begin_state(cell, F, begin_state, inputs, batch_size): + if begin_state is None: + if F is ndarray: + ctx = inputs.context if isinstance(inputs, tensor_types) else inputs[0].context + with ctx: + begin_state = cell.begin_state(func=F.zeros, batch_size=batch_size) + else: + begin_state = cell.begin_state(func=F.zeros, batch_size=batch_size) + return begin_state + +def _format_sequence(length, inputs, layout, merge, in_layout=None): + assert inputs is not None, \ + "unroll(inputs=None) has been deprecated. " \ + "Please create input variables outside unroll." + + axis = layout.find('T') + batch_axis = layout.find('N') + batch_size = 0 + in_axis = in_layout.find('T') if in_layout is not None else axis + if isinstance(inputs, symbol.Symbol): + F = symbol + if merge is False: + assert len(inputs.list_outputs()) == 1, \ + "unroll doesn't allow grouped symbol as input. Please convert " \ + "to list with list(inputs) first or let unroll handle splitting." + inputs = list(symbol.split(inputs, axis=in_axis, num_outputs=length, + squeeze_axis=1)) + elif isinstance(inputs, ndarray.NDArray): + F = ndarray + batch_size = inputs.shape[batch_axis] + if merge is False: + assert length is None or length == inputs.shape[in_axis] + inputs = _as_list(ndarray.split(inputs, axis=in_axis, + num_outputs=inputs.shape[in_axis], + squeeze_axis=1)) + else: + assert length is None or len(inputs) == length + if isinstance(inputs[0], symbol.Symbol): + F = symbol + else: + F = ndarray + batch_size = inputs[0].shape[batch_axis] + if merge is True: + inputs = [F.expand_dims(i, axis=axis) for i in inputs] + inputs = F.concat(*inputs, dim=axis) + in_axis = axis + + if isinstance(inputs, tensor_types) and axis != in_axis: + inputs = F.swapaxes(inputs, dim1=axis, dim2=in_axis) + + return inputs, axis, F, batch_size + + +class RecurrentCell(Block): + """Abstract base class for RNN cells + + Parameters + ---------- + prefix : str, optional + Prefix for names of `Block`s + (this prefix is also used for names of weights if `params` is `None` + i.e. if `params` are being created and not reused) + params : Parameter or None, optional + Container for weight sharing between cells. + A new Parameter container is created if `params` is `None`. + """ + def __init__(self, prefix=None, params=None): + super(RecurrentCell, self).__init__(prefix=prefix, params=params) + self._modified = False + self.reset() + + def __repr__(self): + s = '{name}({mapping}' + if hasattr(self, '_activation'): + s += ', {_activation}' + s += ')' + mapping = ('{_input_size} -> {_hidden_size}'.format(**self.__dict__) if self._input_size + else self._hidden_size) + return s.format(name=self.__class__.__name__, + mapping=mapping, + **self.__dict__) + + def reset(self): + """Reset before re-using the cell for another graph.""" + self._init_counter = -1 + self._counter = -1 + + def state_info(self, batch_size=0): + """shape and layout information of states""" + raise NotImplementedError() + + def begin_state(self, batch_size=0, func=ndarray.zeros, **kwargs): + """Initial state for this cell. + + Parameters + ---------- + func : callable, default symbol.zeros + Function for creating initial state. + + For Symbol API, func can be `symbol.zeros`, `symbol.uniform`, + `symbol.var etc`. Use `symbol.var` if you want to directly + feed input as states. + + For NDArray API, func can be `ndarray.zeros`, `ndarray.ones`, etc. + batch_size: int, default 0 + Only required for NDArray API. Size of the batch ('N' in layout) + dimension of input. + + **kwargs : + Additional keyword arguments passed to func. For example + `mean`, `std`, `dtype`, etc. + + Returns + ------- + states : nested list of Symbol + Starting states for the first RNN step. + """ + assert not self._modified, \ + "After applying modifier cells (e.g. ZoneoutCell) the base " \ + "cell cannot be called directly. Call the modifier cell instead." + states = [] + for info in self.state_info(batch_size): + self._init_counter += 1 + if info is not None: + info.update(kwargs) + else: + info = kwargs + state = func(name='%sbegin_state_%d'%(self._prefix, self._init_counter), + **info) + states.append(state) + return states + + def unroll(self, length, inputs, begin_state=None, layout='NTC', merge_outputs=None): + """Unrolls an RNN cell across time steps. + + Parameters + ---------- + length : int + Number of steps to unroll. + inputs : Symbol, list of Symbol, or None + If `inputs` is a single Symbol (usually the output + of Embedding symbol), it should have shape + (batch_size, length, ...) if `layout` is 'NTC', + or (length, batch_size, ...) if `layout` is 'TNC'. + + If `inputs` is a list of symbols (usually output of + previous unroll), they should all have shape + (batch_size, ...). + begin_state : nested list of Symbol, optional + Input states created by `begin_state()` + or output state of another cell. + Created from `begin_state()` if `None`. + layout : str, optional + `layout` of input symbol. Only used if inputs + is a single Symbol. + merge_outputs : bool, optional + If `False`, returns outputs as a list of Symbols. + If `True`, concatenates output across time steps + and returns a single symbol with shape + (batch_size, length, ...) if layout is 'NTC', + or (length, batch_size, ...) if layout is 'TNC'. + If `None`, output whatever is faster. + + Returns + ------- + outputs : list of Symbol or Symbol + Symbol (if `merge_outputs` is True) or list of Symbols + (if `merge_outputs` is False) corresponding to the output from + the RNN from this unrolling. + + states : list of Symbol + The new state of this RNN after this unrolling. + The type of this symbol is same as the output of `begin_state()`. + """ + self.reset() + + inputs, _, F, batch_size = _format_sequence(length, inputs, layout, False) + begin_state = _get_begin_state(self, F, begin_state, inputs, batch_size) + + states = begin_state + outputs = [] + for i in range(length): + output, states = self(inputs[i], states) + outputs.append(output) + + outputs, _, _, _ = _format_sequence(length, outputs, layout, merge_outputs) + + return outputs, states + + #pylint: disable=no-self-use + def _get_activation(self, F, inputs, activation, **kwargs): + """Get activation function. Convert if is string""" + if isinstance(activation, string_types): + return F.Activation(inputs, act_type=activation, **kwargs) + else: + return activation(inputs, **kwargs) + + def forward(self, inputs, states): + """Unrolls the recurrent cell for one time step. + + Parameters + ---------- + inputs : sym.Variable + Input symbol, 2D, of shape (batch_size * num_units). + states : list of sym.Variable + RNN state from previous step or the output of begin_state(). + + Returns + ------- + output : Symbol + Symbol corresponding to the output from the RNN when unrolling + for a single time step. + states : list of Symbol + The new state of this RNN after this unrolling. + The type of this symbol is same as the output of `begin_state()`. + This can be used as an input state to the next time step + of this RNN. + + See Also + -------- + begin_state: This function can provide the states for the first time step. + unroll: This function unrolls an RNN for a given number of (>=1) time steps. + """ + # pylint: disable= arguments-differ + self._counter += 1 + return super(RecurrentCell, self).forward(inputs, states) + + +class HybridRecurrentCell(RecurrentCell, HybridBlock): + """HybridRecurrentCell supports hybridize.""" + def __init__(self, prefix=None, params=None): + super(HybridRecurrentCell, self).__init__(prefix=prefix, params=params) + + def hybrid_forward(self, F, x, *args, **kwargs): + raise NotImplementedError + + +class RNNCell(HybridRecurrentCell): + """Simple recurrent neural network cell. + + Parameters + ---------- + hidden_size : int + Number of units in output symbol + activation : str or Symbol, default 'tanh' + Type of activation function. + i2h_weight_initializer : str or Initializer + Initializer for the input weights matrix, used for the linear + transformation of the inputs. + h2h_weight_initializer : str or Initializer + Initializer for the recurrent weights matrix, used for the linear + transformation of the recurrent state. + i2h_bias_initializer : str or Initializer + Initializer for the bias vector. + h2h_bias_initializer : str or Initializer + Initializer for the bias vector. + prefix : str, default 'rnn_' + Prefix for name of `Block`s + (and name of weight if params is `None`). + params : Parameter or None + Container for weight sharing between cells. + Created if `None`. + """ + def __init__(self, hidden_size, activation='tanh', + i2h_weight_initializer=None, h2h_weight_initializer=None, + i2h_bias_initializer='zeros', h2h_bias_initializer='zeros', + input_size=0, prefix=None, params=None): + super(RNNCell, self).__init__(prefix=prefix, params=params) + self._hidden_size = hidden_size + self._activation = activation + self._input_size = input_size + self.i2h_weight = self.params.get('i2h_weight', shape=(hidden_size, input_size), + init=i2h_weight_initializer, + allow_deferred_init=True) + self.h2h_weight = self.params.get('h2h_weight', shape=(hidden_size, hidden_size), + init=h2h_weight_initializer, + allow_deferred_init=True) + self.i2h_bias = self.params.get('i2h_bias', shape=(hidden_size,), + init=i2h_bias_initializer, + allow_deferred_init=True) + self.h2h_bias = self.params.get('h2h_bias', shape=(hidden_size,), + init=h2h_bias_initializer, + allow_deferred_init=True) + + def state_info(self, batch_size=0): + return [{'shape': (batch_size, self._hidden_size), '__layout__': 'NC'}] + + def _alias(self): + return 'rnn' + + def hybrid_forward(self, F, inputs, states, i2h_weight, + h2h_weight, i2h_bias, h2h_bias): + prefix = 't%d_'%self._counter + i2h = F.FullyConnected(data=inputs, weight=i2h_weight, bias=i2h_bias, + num_hidden=self._hidden_size, + name=prefix+'i2h') + h2h = F.FullyConnected(data=states[0], weight=h2h_weight, bias=h2h_bias, + num_hidden=self._hidden_size, + name=prefix+'h2h') + output = self._get_activation(F, i2h + h2h, self._activation, + name=prefix+'out') + + return output, [output] + + +class LSTMCell(HybridRecurrentCell): + """Long-Short Term Memory (LSTM) network cell. + + Parameters + ---------- + hidden_size : int + Number of units in output symbol. + i2h_weight_initializer : str or Initializer + Initializer for the input weights matrix, used for the linear + transformation of the inputs. + h2h_weight_initializer : str or Initializer + Initializer for the recurrent weights matrix, used for the linear + transformation of the recurrent state. + i2h_bias_initializer : str or Initializer, default 'lstmbias' + Initializer for the bias vector. By default, bias for the forget + gate is initialized to 1 while all other biases are initialized + to zero. + h2h_bias_initializer : str or Initializer + Initializer for the bias vector. + prefix : str, default 'lstm_' + Prefix for name of `Block`s + (and name of weight if params is `None`). + params : Parameter or None + Container for weight sharing between cells. + Created if `None`. + """ + def __init__(self, hidden_size, + i2h_weight_initializer=None, h2h_weight_initializer=None, + i2h_bias_initializer='zeros', h2h_bias_initializer='zeros', + input_size=0, prefix=None, params=None): + super(LSTMCell, self).__init__(prefix=prefix, params=params) + + self._hidden_size = hidden_size + self._input_size = input_size + self.i2h_weight = self.params.get('i2h_weight', shape=(4*hidden_size, input_size), + init=i2h_weight_initializer, + allow_deferred_init=True) + self.h2h_weight = self.params.get('h2h_weight', shape=(4*hidden_size, hidden_size), + init=h2h_weight_initializer, + allow_deferred_init=True) + self.i2h_bias = self.params.get('i2h_bias', shape=(4*hidden_size,), + init=i2h_bias_initializer, + allow_deferred_init=True) + self.h2h_bias = self.params.get('h2h_bias', shape=(4*hidden_size,), + init=h2h_bias_initializer, + allow_deferred_init=True) + + def state_info(self, batch_size=0): + return [{'shape': (batch_size, self._hidden_size), '__layout__': 'NC'}, + {'shape': (batch_size, self._hidden_size), '__layout__': 'NC'}] + + def _alias(self): + return 'lstm' + + def hybrid_forward(self, F, inputs, states, i2h_weight, + h2h_weight, i2h_bias, h2h_bias): + prefix = 't%d_'%self._counter + i2h = F.FullyConnected(data=inputs, weight=i2h_weight, bias=i2h_bias, + num_hidden=self._hidden_size*4, name=prefix+'i2h') + h2h = F.FullyConnected(data=states[0], weight=h2h_weight, bias=h2h_bias, + num_hidden=self._hidden_size*4, name=prefix+'h2h') + gates = i2h + h2h + slice_gates = F.SliceChannel(gates, num_outputs=4, name=prefix+'slice') + in_gate = F.Activation(slice_gates[0], act_type="sigmoid", name=prefix+'i') + forget_gate = F.Activation(slice_gates[1], act_type="sigmoid", name=prefix+'f') + in_transform = F.Activation(slice_gates[2], act_type="tanh", name=prefix+'c') + out_gate = F.Activation(slice_gates[3], act_type="sigmoid", name=prefix+'o') + next_c = F._internal._plus(forget_gate * states[1], in_gate * in_transform, + name=prefix+'state') + next_h = F._internal._mul(out_gate, F.Activation(next_c, act_type="tanh"), + name=prefix+'out') + + return next_h, [next_h, next_c] + + +class GRUCell(HybridRecurrentCell): + """Gated Rectified Unit (GRU) network cell. + Note: this is an implementation of the cuDNN version of GRUs + (slight modification compared to Cho et al. 2014). + + Parameters + ---------- + hidden_size : int + Number of units in output symbol. + i2h_weight_initializer : str or Initializer + Initializer for the input weights matrix, used for the linear + transformation of the inputs. + h2h_weight_initializer : str or Initializer + Initializer for the recurrent weights matrix, used for the linear + transformation of the recurrent state. + i2h_bias_initializer : str or Initializer + Initializer for the bias vector. + h2h_bias_initializer : str or Initializer + Initializer for the bias vector. + prefix : str, default 'gru_' + prefix for name of `Block`s + (and name of weight if params is `None`). + params : Parameter or None + Container for weight sharing between cells. + Created if `None`. + """ + def __init__(self, hidden_size, + i2h_weight_initializer=None, h2h_weight_initializer=None, + i2h_bias_initializer='zeros', h2h_bias_initializer='zeros', + input_size=0, prefix=None, params=None): + super(GRUCell, self).__init__(prefix=prefix, params=params) + self._hidden_size = hidden_size + self._input_size = input_size + self.i2h_weight = self.params.get('i2h_weight', shape=(3*hidden_size, input_size), + init=i2h_weight_initializer, + allow_deferred_init=True) + self.h2h_weight = self.params.get('h2h_weight', shape=(3*hidden_size, hidden_size), + init=h2h_weight_initializer, + allow_deferred_init=True) + self.i2h_bias = self.params.get('i2h_bias', shape=(3*hidden_size,), + init=i2h_bias_initializer, + allow_deferred_init=True) + self.h2h_bias = self.params.get('h2h_bias', shape=(3*hidden_size,), + init=h2h_bias_initializer, + allow_deferred_init=True) + + def state_info(self, batch_size=0): + return [{'shape': (batch_size, self._hidden_size), '__layout__': 'NC'}] + + def _alias(self): + return 'gru' + + def hybrid_forward(self, F, inputs, states, i2h_weight, + h2h_weight, i2h_bias, h2h_bias): + # pylint: disable=too-many-locals + prefix = 't%d_'%self._counter + prev_state_h = states[0] + i2h = F.FullyConnected(data=inputs, + weight=i2h_weight, + bias=i2h_bias, + num_hidden=self._hidden_size * 3, + name=prefix+'i2h') + h2h = F.FullyConnected(data=prev_state_h, + weight=h2h_weight, + bias=h2h_bias, + num_hidden=self._hidden_size * 3, + name=prefix+'h2h') + + i2h_r, i2h_z, i2h = F.SliceChannel(i2h, num_outputs=3, + name=prefix+'i2h_slice') + h2h_r, h2h_z, h2h = F.SliceChannel(h2h, num_outputs=3, + name=prefix+'h2h_slice') + + reset_gate = F.Activation(i2h_r + h2h_r, act_type="sigmoid", + name=prefix+'r_act') + update_gate = F.Activation(i2h_z + h2h_z, act_type="sigmoid", + name=prefix+'z_act') + + next_h_tmp = F.Activation(i2h + reset_gate * h2h, act_type="tanh", + name=prefix+'h_act') + + next_h = F._internal._plus((1. - update_gate) * next_h_tmp, update_gate * prev_state_h, + name=prefix+'out') + + return next_h, [next_h] + + +class SequentialRNNCell(RecurrentCell): + """Sequentially stacking multiple RNN cells.""" + def __init__(self, prefix=None, params=None): + super(SequentialRNNCell, self).__init__(prefix=prefix, params=params) + + def __repr__(self): + s = '{name}(\n{modstr}\n)' + return s.format(name=self.__class__.__name__, + modstr='\n'.join(['({i}): {m}'.format(i=i, m=_indent(m.__repr__(), 2)) + for i, m in enumerate(self._children)])) + + def add(self, cell): + """Appends a cell into the stack. + + Parameters + ---------- + cell : rnn cell + """ + self.register_child(cell) + + def state_info(self, batch_size=0): + return _cells_state_info(self._children, batch_size) + + def begin_state(self, **kwargs): + assert not self._modified, \ + "After applying modifier cells (e.g. ZoneoutCell) the base " \ + "cell cannot be called directly. Call the modifier cell instead." + return _cells_begin_state(self._children, **kwargs) + + def __call__(self, inputs, states): + self._counter += 1 + next_states = [] + p = 0 + for cell in self._children: + assert not isinstance(cell, BidirectionalCell) + n = len(cell.state_info()) + state = states[p:p+n] + p += n + inputs, state = cell(inputs, state) + next_states.append(state) + return inputs, sum(next_states, []) + + def unroll(self, length, inputs, begin_state=None, layout='NTC', merge_outputs=None): + self.reset() + + inputs, _, F, batch_size = _format_sequence(length, inputs, layout, None) + num_cells = len(self._children) + begin_state = _get_begin_state(self, F, begin_state, inputs, batch_size) + + p = 0 + next_states = [] + for i, cell in enumerate(self._children): + n = len(cell.state_info()) + states = begin_state[p:p+n] + p += n + inputs, states = cell.unroll(length, inputs=inputs, begin_state=states, layout=layout, + merge_outputs=None if i < num_cells-1 else merge_outputs) + next_states.extend(states) + + return inputs, next_states + + def __getitem__(self, i): + return self._children[i] + + def __len__(self): + return len(self._children) + + def hybrid_forward(self, *args, **kwargs): + raise NotImplementedError + + +class DropoutCell(HybridRecurrentCell): + """Applies dropout on input. + + Parameters + ---------- + rate : float + Percentage of elements to drop out, which + is 1 - percentage to retain. + """ + def __init__(self, rate, prefix=None, params=None): + super(DropoutCell, self).__init__(prefix, params) + assert isinstance(rate, numeric_types), "rate must be a number" + self.rate = rate + + def __repr__(self): + s = '{name}(rate = {rate})' + return s.format(name=self.__class__.__name__, + **self.__dict__) + + def state_info(self, batch_size=0): + return [] + + def _alias(self): + return 'dropout' + + def hybrid_forward(self, F, inputs, states): + if self.rate > 0: + inputs = F.Dropout(data=inputs, p=self.rate, name='t%d_fwd'%self._counter) + return inputs, states + + def unroll(self, length, inputs, begin_state=None, layout='NTC', merge_outputs=None): + self.reset() + + inputs, _, F, _ = _format_sequence(length, inputs, layout, merge_outputs) + if isinstance(inputs, tensor_types): + return self.hybrid_forward(F, inputs, begin_state if begin_state else []) + else: + return super(DropoutCell, self).unroll( + length, inputs, begin_state=begin_state, layout=layout, + merge_outputs=merge_outputs) + + +class ModifierCell(HybridRecurrentCell): + """Base class for modifier cells. A modifier + cell takes a base cell, apply modifications + on it (e.g. Zoneout), and returns a new cell. + + After applying modifiers the base cell should + no longer be called directly. The modifier cell + should be used instead. + """ + def __init__(self, base_cell): + assert not base_cell._modified, \ + "Cell %s is already modified. One cell cannot be modified twice"%base_cell.name + base_cell._modified = True + super(ModifierCell, self).__init__(prefix=base_cell.prefix+self._alias(), + params=None) + self.base_cell = base_cell + + @property + def params(self): + return self.base_cell.params + + def state_info(self, batch_size=0): + return self.base_cell.state_info(batch_size) + + def begin_state(self, func=symbol.zeros, **kwargs): + assert not self._modified, \ + "After applying modifier cells (e.g. DropoutCell) the base " \ + "cell cannot be called directly. Call the modifier cell instead." + self.base_cell._modified = False + begin = self.base_cell.begin_state(func=func, **kwargs) + self.base_cell._modified = True + return begin + + def hybrid_forward(self, F, inputs, states): + raise NotImplementedError + + def __repr__(self): + s = '{name}({base_cell})' + return s.format(name=self.__class__.__name__, + **self.__dict__) + + +class ZoneoutCell(ModifierCell): + """Applies Zoneout on base cell.""" + def __init__(self, base_cell, zoneout_outputs=0., zoneout_states=0.): + assert not isinstance(base_cell, BidirectionalCell), \ + "BidirectionalCell doesn't support zoneout since it doesn't support step. " \ + "Please add ZoneoutCell to the cells underneath instead." + assert not isinstance(base_cell, SequentialRNNCell) or not base_cell._bidirectional, \ + "Bidirectional SequentialRNNCell doesn't support zoneout. " \ + "Please add ZoneoutCell to the cells underneath instead." + super(ZoneoutCell, self).__init__(base_cell) + self.zoneout_outputs = zoneout_outputs + self.zoneout_states = zoneout_states + self.prev_output = None + + def __repr__(self): + s = '{name}(p_out={zoneout_outputs}, p_state={zoneout_states}, {base_cell})' + return s.format(name=self.__class__.__name__, + **self.__dict__) + + def _alias(self): + return 'zoneout' + + def reset(self): + super(ZoneoutCell, self).reset() + self.prev_output = None + + def hybrid_forward(self, F, inputs, states): + cell, p_outputs, p_states = self.base_cell, self.zoneout_outputs, self.zoneout_states + next_output, next_states = cell(inputs, states) + mask = (lambda p, like: F.Dropout(F.ones_like(like), p=p)) + + prev_output = self.prev_output + if prev_output is None: + prev_output = F.zeros_like(next_output) + + output = (F.where(mask(p_outputs, next_output), next_output, prev_output) + if p_outputs != 0. else next_output) + states = ([F.where(mask(p_states, new_s), new_s, old_s) for new_s, old_s in + zip(next_states, states)] if p_states != 0. else next_states) + + self.prev_output = output + + return output, states + + +class ResidualCell(ModifierCell): + """ + Adds residual connection as described in Wu et al, 2016 + (https://arxiv.org/abs/1609.08144). + Output of the cell is output of the base cell plus input. + """ + + def __init__(self, base_cell): + super(ResidualCell, self).__init__(base_cell) + + def hybrid_forward(self, F, inputs, states): + output, states = self.base_cell(inputs, states) + output = F.elemwise_add(output, inputs, name='t%d_fwd'%self._counter) + return output, states + + def unroll(self, length, inputs, begin_state=None, layout='NTC', merge_outputs=None): + self.reset() + + self.base_cell._modified = False + outputs, states = self.base_cell.unroll(length, inputs=inputs, begin_state=begin_state, + layout=layout, merge_outputs=merge_outputs) + self.base_cell._modified = True + + merge_outputs = isinstance(outputs, tensor_types) if merge_outputs is None else \ + merge_outputs + inputs, _, F, _ = _format_sequence(length, inputs, layout, merge_outputs) + if merge_outputs: + outputs = F.elemwise_add(outputs, inputs) + else: + outputs = [F.elemwise_add(i, j) for i, j in zip(outputs, inputs)] + + return outputs, states + + +class BidirectionalCell(HybridRecurrentCell): + """Bidirectional RNN cell. + + Parameters + ---------- + l_cell : RecurrentCell + Cell for forward unrolling + r_cell : RecurrentCell + Cell for backward unrolling + """ + def __init__(self, l_cell, r_cell, output_prefix='bi_'): + super(BidirectionalCell, self).__init__(prefix='', params=None) + self.register_child(l_cell) + self.register_child(r_cell) + self._output_prefix = output_prefix + + def __call__(self, inputs, states): + raise NotImplementedError("Bidirectional cannot be stepped. Please use unroll") + + def __repr__(self): + s = '{name}(forward={l_cell}, backward={r_cell})' + return s.format(name=self.__class__.__name__, + l_cell=self._children[0], + r_cell=self._children[1]) + + def state_info(self, batch_size=0): + return _cells_state_info(self._children, batch_size) + + def begin_state(self, **kwargs): + assert not self._modified, \ + "After applying modifier cells (e.g. DropoutCell) the base " \ + "cell cannot be called directly. Call the modifier cell instead." + return _cells_begin_state(self._children, **kwargs) + + def unroll(self, length, inputs, begin_state=None, layout='NTC', merge_outputs=None): + self.reset() + + inputs, axis, F, batch_size = _format_sequence(length, inputs, layout, False) + begin_state = _get_begin_state(self, F, begin_state, inputs, batch_size) + + states = begin_state + l_cell, r_cell = self._children + l_outputs, l_states = l_cell.unroll(length, inputs=inputs, + begin_state=states[:len(l_cell.state_info(batch_size))], + layout=layout, merge_outputs=merge_outputs) + r_outputs, r_states = r_cell.unroll(length, + inputs=list(reversed(inputs)), + begin_state=states[len(l_cell.state_info(batch_size)):], + layout=layout, merge_outputs=merge_outputs) + + if merge_outputs is None: + merge_outputs = (isinstance(l_outputs, tensor_types) + and isinstance(r_outputs, tensor_types)) + l_outputs, _, _, _ = _format_sequence(None, l_outputs, layout, merge_outputs) + r_outputs, _, _, _ = _format_sequence(None, r_outputs, layout, merge_outputs) + + if merge_outputs: + r_outputs = F.reverse(r_outputs, axis=axis) + outputs = F.concat(l_outputs, r_outputs, dim=2, name='%sout'%self._output_prefix) + else: + outputs = [F.concat(l_o, r_o, dim=1, name='%st%d'%(self._output_prefix, i)) + for i, (l_o, r_o) in enumerate(zip(l_outputs, reversed(r_outputs)))] + + states = l_states + r_states + return outputs, states diff --git a/python/mxnet/gluon/rnn/rnn_layer.py b/python/mxnet/gluon/rnn/rnn_layer.py new file mode 100644 index 000000000000..86b7c618e503 --- /dev/null +++ b/python/mxnet/gluon/rnn/rnn_layer.py @@ -0,0 +1,526 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +# coding: utf-8 +# pylint: disable=no-member, invalid-name, protected-access, no-self-use +# pylint: disable=too-many-branches, too-many-arguments, no-self-use +# pylint: disable=too-many-lines, arguments-differ +"""Definition of various recurrent neural network layers.""" +from __future__ import print_function + +from ... import ndarray +from ..nn import Block +from . import rnn_cell + + +class _RNNLayer(Block): + """Implementation of recurrent layers.""" + def __init__(self, hidden_size, num_layers, layout, + dropout, bidirectional, input_size, + i2h_weight_initializer, h2h_weight_initializer, + i2h_bias_initializer, h2h_bias_initializer, + mode, **kwargs): + super(_RNNLayer, self).__init__(**kwargs) + assert layout == 'TNC' or layout == 'NTC', \ + "Invalid layout %s; must be one of ['TNC' or 'NTC']"%layout + self._hidden_size = hidden_size + self._num_layers = num_layers + self._mode = mode + self._layout = layout + self._dropout = dropout + self._dir = 2 if bidirectional else 1 + self._input_size = input_size + self._i2h_weight_initializer = i2h_weight_initializer + self._h2h_weight_initializer = h2h_weight_initializer + self._i2h_bias_initializer = i2h_bias_initializer + self._h2h_bias_initializer = h2h_bias_initializer + + self._gates = {'rnn_relu': 1, 'rnn_tanh': 1, 'lstm': 4, 'gru': 3}[mode] + + self.i2h_weight = [] + self.h2h_weight = [] + self.i2h_bias = [] + self.h2h_bias = [] + + ng, ni, nh = self._gates, input_size, hidden_size + for i in range(num_layers): + for j in (['l', 'r'] if self._dir == 2 else ['l']): + self.i2h_weight.append( + self.params.get('%s%d_i2h_weight'%(j, i), shape=(ng*nh, ni), + init=i2h_weight_initializer, + allow_deferred_init=True)) + self.h2h_weight.append( + self.params.get('%s%d_h2h_weight'%(j, i), shape=(ng*nh, nh), + init=h2h_weight_initializer, + allow_deferred_init=True)) + self.i2h_bias.append( + self.params.get('%s%d_i2h_bias'%(j, i), shape=(ng*nh,), + init=i2h_bias_initializer, + allow_deferred_init=True)) + self.h2h_bias.append( + self.params.get('%s%d_h2h_bias'%(j, i), shape=(ng*nh,), + init=h2h_bias_initializer, + allow_deferred_init=True)) + ni = nh * self._dir + + self._unfused = self._unfuse() + + def __repr__(self): + s = '{name}({mapping}, {_layout}' + if self._num_layers != 1: + s += ', num_layers={_num_layers}' + if self._dropout != 0: + s += ', dropout={_dropout}' + if self._dir == 2: + s += ', bidirectional' + s += ')' + mapping = ('{_input_size} -> {_hidden_size}'.format(**self.__dict__) if self._input_size + else self._hidden_size) + return s.format(name=self.__class__.__name__, + mapping=mapping, + **self.__dict__) + + def state_info(self, batch_size=0): + raise NotImplementedError + + def _unfuse(self): + """Unfuses the fused RNN in to a stack of rnn cells.""" + get_cell = {'rnn_relu': lambda **kwargs: rnn_cell.RNNCell(self._hidden_size, + activation='relu', + **kwargs), + 'rnn_tanh': lambda **kwargs: rnn_cell.RNNCell(self._hidden_size, + activation='tanh', + **kwargs), + 'lstm': lambda **kwargs: rnn_cell.LSTMCell(self._hidden_size, + **kwargs), + 'gru': lambda **kwargs: rnn_cell.GRUCell(self._hidden_size, + **kwargs)}[self._mode] + + stack = rnn_cell.SequentialRNNCell(prefix=self.prefix, params=self.params) + with stack.name_scope(): + ni = self._input_size + for i in range(self._num_layers): + kwargs = {'input_size': ni, + 'i2h_weight_initializer': self._i2h_weight_initializer, + 'h2h_weight_initializer': self._h2h_weight_initializer, + 'i2h_bias_initializer': self._i2h_bias_initializer, + 'h2h_bias_initializer': self._h2h_bias_initializer} + if self._dir == 2: + stack.add(rnn_cell.BidirectionalCell( + get_cell(prefix='l%d_'%i, **kwargs), + get_cell(prefix='r%d_'%i, **kwargs))) + else: + stack.add(get_cell(prefix='l%d_'%i, **kwargs)) + + if self._dropout > 0 and i != self._num_layers - 1: + stack.add(rnn_cell.DropoutCell(self._dropout)) + + ni = self._hidden_size * self._dir + + return stack + + def begin_state(self, batch_size=0, func=ndarray.zeros, **kwargs): + """Initial state for this cell. + + Parameters + ---------- + batch_size: int + Only required for `NDArray` API. Size of the batch ('N' in layout). + Dimension of the input. + func : callable, default `symbol.zeros` + Function for creating initial state. + + For Symbol API, func can be `symbol.zeros`, `symbol.uniform`, + `symbol.var` etc. Use `symbol.var` if you want to directly + feed input as states. + + For NDArray API, func can be `ndarray.zeros`, `ndarray.ones`, etc. + + **kwargs : + Additional keyword arguments passed to func. For example + `mean`, `std`, `dtype`, etc. + + Returns + ------- + states : nested list of Symbol + Starting states for the first RNN step. + """ + states = [] + for i, info in enumerate(self.state_info(batch_size)): + if info is not None: + info.update(kwargs) + else: + info = kwargs + states.append(func(name='%sh0_%d'%(self.prefix, i), **info)) + return states + + def forward(self, inputs, states=None): + batch_size = inputs.shape[self._layout.find('N')] + skip_states = states is None + if skip_states: + states = self.begin_state(batch_size) + if isinstance(states, ndarray.NDArray): + states = [states] + for state, info in zip(states, self.state_info(batch_size)): + if state.shape != info['shape']: + raise ValueError( + "Invalid recurrent state shape. Expecting %s, got %s."%( + str(info['shape']), str(state.shape))) + if self._input_size == 0: + for i in range(self._dir): + self.i2h_weight[i].shape = (self._gates*self._hidden_size, inputs.shape[2]) + self.i2h_weight[i]._finish_deferred_init() + if inputs.context.device_type == 'gpu': + out = self._forward_gpu(inputs, states) + else: + out = self._forward_cpu(inputs, states) + + # out is (output, state) + return out[0] if skip_states else out + + def _forward_cpu(self, inputs, states): + ns = len(states) + axis = self._layout.find('T') + states = sum(zip(*((j for j in i) for i in states)), ()) + outputs, states = self._unfused.unroll( + inputs.shape[axis], inputs, states, + layout=self._layout, merge_outputs=True) + new_states = [] + for i in range(ns): + state = ndarray.concat(*(j.reshape((1,)+j.shape) for j in states[i::ns]), dim=0) + new_states.append(state) + + return outputs, new_states + + def _forward_gpu(self, inputs, states): + if self._layout == 'NTC': + inputs = ndarray.swapaxes(inputs, dim1=0, dim2=1) + ctx = inputs.context + params = sum(zip(self.i2h_weight, self.h2h_weight), ()) + params += sum(zip(self.i2h_bias, self.h2h_bias), ()) + params = (i.data(ctx).reshape((-1,)) for i in params) + params = ndarray.concat(*params, dim=0) + + rnn = ndarray.RNN(inputs, params, *states, state_size=self._hidden_size, + num_layers=self._num_layers, bidirectional=self._dir == 2, + p=self._dropout, state_outputs=True, mode=self._mode) + + if self._mode == 'lstm': + outputs, states = rnn[0], [rnn[1], rnn[2]] + else: + outputs, states = rnn[0], [rnn[1]] + + if self._layout == 'NTC': + outputs = ndarray.swapaxes(outputs, dim1=0, dim2=1) + + return outputs, states + + +class RNN(_RNNLayer): + r"""Applies a multi-layer Elman RNN with `tanh` or `ReLU` non-linearity to an input sequence. + + For each element in the input sequence, each layer computes the following + function: + + .. math:: + h_t = \tanh(w_{ih} * x_t + b_{ih} + w_{hh} * h_{(t-1)} + b_{hh}) + + where :math:`h_t` is the hidden state at time `t`, and :math:`x_t` is the hidden + state of the previous layer at time `t` or :math:`input_t` for the first layer. + If nonlinearity='relu', then `ReLU` is used instead of `tanh`. + + Parameters + ---------- + hidden_size: int + The number of features in the hidden state h. + num_layers: int, default 1 + Number of recurrent layers. + activation: {'relu' or 'tanh'}, default 'tanh' + The activation function to use. + layout : str, default 'TNC' + The format of input and output tensors. T, N and C stand for + sequence length, batch size, and feature dimensions respectively. + dropout: float, default 0 + If non-zero, introduces a dropout layer on the outputs of each + RNN layer except the last layer. + bidirectional: bool, default False + If `True`, becomes a bidirectional RNN. + i2h_weight_initializer : str or Initializer + Initializer for the input weights matrix, used for the linear + transformation of the inputs. + h2h_weight_initializer : str or Initializer + Initializer for the recurrent weights matrix, used for the linear + transformation of the recurrent state. + i2h_bias_initializer : str or Initializer + Initializer for the bias vector. + h2h_bias_initializer : str or Initializer + Initializer for the bias vector. + input_size: int, default 0 + The number of expected features in the input x. + If not specified, it will be inferred from input. + prefix : str or None + Prefix of this `Block`. + params : ParameterDict or None + Shared Parameters for this `Block`. + + + Input shapes: + The input shape depends on `layout`. For `layout='TNC'`, the + input has shape `(sequence_length, batch_size, input_size)` + + + Output shape: + The output shape depends on `layout`. For `layout='TNC'`, the + output has shape `(sequence_length, batch_size, num_hidden)`. + If `bidirectional` is True, output shape will instead be + `(sequence_length, batch_size, 2*num_hidden)` + + Recurrent state: + The recurrent state is an NDArray with shape `(num_layers, batch_size, num_hidden)`. + If `bidirectional` is True, the recurrent state shape will instead be + `(2*num_layers, batch_size, num_hidden)` + If input recurrent state is None, zeros are used as default begin states, + and the output recurrent state is omitted. + + + Examples + -------- + >>> layer = mx.gluon.rnn.RNN(100, 3) + >>> layer.initialize() + >>> input = mx.nd.random_uniform(shape=(5, 3, 10)) + >>> # by default zeros are used as begin state + >>> output = layer(input) + >>> # manually specify begin state. + >>> h0 = mx.nd.random_uniform(shape=(3, 3, 100)) + >>> output, hn = layer(input, h0) + """ + def __init__(self, hidden_size, num_layers=1, activation='relu', + layout='TNC', dropout=0, bidirectional=False, + i2h_weight_initializer=None, h2h_weight_initializer=None, + i2h_bias_initializer='zeros', h2h_bias_initializer='zeros', + input_size=0, **kwargs): + super(RNN, self).__init__(hidden_size, num_layers, layout, + dropout, bidirectional, input_size, + i2h_weight_initializer, h2h_weight_initializer, + i2h_bias_initializer, h2h_bias_initializer, + 'rnn_'+activation, **kwargs) + + def state_info(self, batch_size=0): + return [{'shape': (self._num_layers * self._dir, batch_size, self._hidden_size), + '__layout__': 'LNC'}] + + +class LSTM(_RNNLayer): + r"""Applies a multi-layer long short-term memory (LSTM) RNN to an input sequence. + + For each element in the input sequence, each layer computes the following + function: + + .. math:: + \begin{array}{ll} + i_t = sigmoid(W_{ii} x_t + b_{ii} + W_{hi} h_{(t-1)} + b_{hi}) \\ + f_t = sigmoid(W_{if} x_t + b_{if} + W_{hf} h_{(t-1)} + b_{hf}) \\ + g_t = \tanh(W_{ig} x_t + b_{ig} + W_{hc} h_{(t-1)} + b_{hg}) \\ + o_t = sigmoid(W_{io} x_t + b_{io} + W_{ho} h_{(t-1)} + b_{ho}) \\ + c_t = f_t * c_{(t-1)} + i_t * g_t \\ + h_t = o_t * \tanh(c_t) + \end{array} + + where :math:`h_t` is the hidden state at time `t`, :math:`c_t` is the + cell state at time `t`, :math:`x_t` is the hidden state of the previous + layer at time `t` or :math:`input_t` for the first layer, and :math:`i_t`, + :math:`f_t`, :math:`g_t`, :math:`o_t` are the input, forget, cell, and + out gates, respectively. + + Parameters + ---------- + hidden_size: int + The number of features in the hidden state h. + num_layers: int, default 1 + Number of recurrent layers. + layout : str, default 'TNC' + The format of input and output tensors. T, N and C stand for + sequence length, batch size, and feature dimensions respectively. + dropout: float, default 0 + If non-zero, introduces a dropout layer on the outputs of each + RNN layer except the last layer. + bidirectional: bool, default False + If `True`, becomes a bidirectional RNN. + i2h_weight_initializer : str or Initializer + Initializer for the input weights matrix, used for the linear + transformation of the inputs. + h2h_weight_initializer : str or Initializer + Initializer for the recurrent weights matrix, used for the linear + transformation of the recurrent state. + i2h_bias_initializer : str or Initializer, default 'lstmbias' + Initializer for the bias vector. By default, bias for the forget + gate is initialized to 1 while all other biases are initialized + to zero. + h2h_bias_initializer : str or Initializer + Initializer for the bias vector. + input_size: int, default 0 + The number of expected features in the input x. + If not specified, it will be inferred from input. + prefix : str or None + Prefix of this `Block`. + params : `ParameterDict` or `None` + Shared Parameters for this `Block`. + + + Input shapes: + The input shape depends on `layout`. For `layout='TNC'`, the + input has shape `(sequence_length, batch_size, input_size)` + + Output shape: + The output shape depends on `layout`. For `layout='TNC'`, the + output has shape `(sequence_length, batch_size, num_hidden)`. + If `bidirectional` is True, output shape will instead be + `(sequence_length, batch_size, 2*num_hidden)` + + Recurrent state: + The recurrent state is a list of two NDArrays. Both has shape + `(num_layers, batch_size, num_hidden)`. + If `bidirectional` is True, each recurrent state will instead have shape + `(2*num_layers, batch_size, num_hidden)`. + If input recurrent state is None, zeros are used as default begin states, + and the output recurrent state is omitted. + + + Examples + -------- + >>> layer = mx.gluon.rnn.LSTM(100, 3) + >>> layer.initialize() + >>> input = mx.nd.random_uniform(shape=(5, 3, 10)) + >>> # by default zeros are used as begin state + >>> output = layer(input) + >>> # manually specify begin state. + >>> h0 = mx.nd.random_uniform(shape=(3, 3, 100)) + >>> c0 = mx.nd.random_uniform(shape=(3, 3, 100)) + >>> output, hn = layer(input, [h0, c0]) + """ + def __init__(self, hidden_size, num_layers=1, layout='TNC', + dropout=0, bidirectional=False, input_size=0, + i2h_weight_initializer=None, h2h_weight_initializer=None, + i2h_bias_initializer='zeros', h2h_bias_initializer='zeros', + **kwargs): + super(LSTM, self).__init__(hidden_size, num_layers, layout, + dropout, bidirectional, input_size, + i2h_weight_initializer, h2h_weight_initializer, + i2h_bias_initializer, h2h_bias_initializer, + 'lstm', **kwargs) + + def state_info(self, batch_size=0): + return [{'shape': (self._num_layers * self._dir, batch_size, self._hidden_size), + '__layout__': 'LNC'}, + {'shape': (self._num_layers * self._dir, batch_size, self._hidden_size), + '__layout__': 'LNC'}] + + +class GRU(_RNNLayer): + r"""Applies a multi-layer gated recurrent unit (GRU) RNN to an input sequence. + + For each element in the input sequence, each layer computes the following + function: + + .. math:: + \begin{array}{ll} + r_t = sigmoid(W_{ir} x_t + b_{ir} + W_{hr} h_{(t-1)} + b_{hr}) \\ + i_t = sigmoid(W_{ii} x_t + b_{ii} + W_hi h_{(t-1)} + b_{hi}) \\ + n_t = \tanh(W_{in} x_t + b_{in} + r_t * (W_{hn} h_{(t-1)}+ b_{hn})) \\ + h_t = (1 - i_t) * n_t + i_t * h_{(t-1)} \\ + \end{array} + + where :math:`h_t` is the hidden state at time `t`, :math:`x_t` is the hidden + state of the previous layer at time `t` or :math:`input_t` for the first layer, + and :math:`r_t`, :math:`i_t`, :math:`n_t` are the reset, input, and new gates, respectively. + + Parameters + ---------- + hidden_size: int + The number of features in the hidden state h + num_layers: int, default 1 + Number of recurrent layers. + layout : str, default 'TNC' + The format of input and output tensors. T, N and C stand for + sequence length, batch size, and feature dimensions respectively. + dropout: float, default 0 + If non-zero, introduces a dropout layer on the outputs of each + RNN layer except the last layer + bidirectional: bool, default False + If True, becomes a bidirectional RNN. + i2h_weight_initializer : str or Initializer + Initializer for the input weights matrix, used for the linear + transformation of the inputs. + h2h_weight_initializer : str or Initializer + Initializer for the recurrent weights matrix, used for the linear + transformation of the recurrent state. + i2h_bias_initializer : str or Initializer + Initializer for the bias vector. + h2h_bias_initializer : str or Initializer + Initializer for the bias vector. + input_size: int, default 0 + The number of expected features in the input x. + If not specified, it will be inferred from input. + prefix : str or None + Prefix of this `Block`. + params : ParameterDict or None + Shared Parameters for this `Block`. + + + Input shapes: + The input shape depends on `layout`. For `layout='TNC'`, the + input has shape `(sequence_length, batch_size, input_size)` + + Output shape: + The output shape depends on `layout`. For `layout='TNC'`, the + output has shape `(sequence_length, batch_size, num_hidden)`. + If `bidirectional` is True, output shape will instead be + `(sequence_length, batch_size, 2*num_hidden)` + + Recurrent state: + The recurrent state is an NDArray with shape `(num_layers, batch_size, num_hidden)`. + If `bidirectional` is True, the recurrent state shape will instead be + `(2*num_layers, batch_size, num_hidden)` + If input recurrent state is None, zeros are used as default begin states, + and the output recurrent state is omitted. + + + Examples + -------- + >>> layer = mx.gluon.rnn.GRU(100, 3) + >>> layer.initialize() + >>> input = mx.nd.random_uniform(shape=(5, 3, 10)) + >>> # by default zeros are used as begin state + >>> output = layer(input) + >>> # manually specify begin state. + >>> h0 = mx.nd.random_uniform(shape=(3, 3, 100)) + >>> output, hn = layer(input, h0) + """ + def __init__(self, hidden_size, num_layers=1, layout='TNC', + dropout=0, bidirectional=False, input_size=0, + i2h_weight_initializer=None, h2h_weight_initializer=None, + i2h_bias_initializer='zeros', h2h_bias_initializer='zeros', + **kwargs): + super(GRU, self).__init__(hidden_size, num_layers, layout, + dropout, bidirectional, input_size, + i2h_weight_initializer, h2h_weight_initializer, + i2h_bias_initializer, h2h_bias_initializer, + 'gru', **kwargs) + + def state_info(self, batch_size=0): + return [{'shape': (self._num_layers * self._dir, batch_size, self._hidden_size), + '__layout__': 'LNC'}] diff --git a/python/mxnet/gluon/trainer.py b/python/mxnet/gluon/trainer.py new file mode 100644 index 000000000000..bb2cc763b5ba --- /dev/null +++ b/python/mxnet/gluon/trainer.py @@ -0,0 +1,160 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +# coding: utf-8 +# pylint: disable= +"""Parameter optimizer.""" + +from .. import optimizer as opt +from ..model import _create_kvstore +from .parameter import ParameterDict, Parameter + +class Trainer(object): + """Applies an `Optimizer` on a set of Parameters. Trainer should + be used together with `autograd`. + + Parameters + ---------- + params : ParameterDict + The set of parameters to optimize. + optimizer : str or Optimizer + The optimizer to use. See + `help `_ + on Optimizer for a list of available optimizers. + optimizer_params : dict + Key-word arguments to be passed to optimizer constructor. For example, + `{'learning_rate': 0.1}`. All optimizers accept learning_rate, wd (weight decay), + clip_gradient, and lr_scheduler. See each optimizer's + constructor for a list of additional supported arguments. + kvstore : str or KVStore + kvstore type for multi-gpu and distributed training. See help on + :any:`mxnet.kvstore.create` for more information. + """ + def __init__(self, params, optimizer, optimizer_params=None, kvstore='device'): + if isinstance(params, (dict, ParameterDict)): + params = list(params.values()) + if not isinstance(params, (list, tuple)): + raise ValueError( + "First argument must be a list or dict of Parameters, " \ + "got %s."%(type(params))) + self._params = [] + for param in params: + if not isinstance(param, Parameter): + raise ValueError( + "First argument must be a list or dict of Parameters, " \ + "got list of %s."%(type(param))) + self._params.append(param) + + optimizer_params = optimizer_params if optimizer_params else {} + self._scale = optimizer_params.get('rescale_grad', 1.0) + self._contexts = self._check_contexts() + self._init_optimizer(optimizer, optimizer_params) + self._kv_initialized = False + self._kvstore = kvstore + + def _check_contexts(self): + contexts = None + for param in self._params: + ctx = param.list_ctx() + assert contexts is None or contexts == ctx, \ + "All Parameters must be initialized on the same set of contexts, " \ + "but Parameter %s is initialized on %s while previous Parameters " \ + "are initialized on %s."%(param.name, str(ctx), str(contexts)) + contexts = ctx + return contexts + + def _init_optimizer(self, optimizer, optimizer_params): + param_dict = {i: param for i, param in enumerate(self._params)} + if isinstance(optimizer, opt.Optimizer): + assert not optimizer_params, \ + "optimizer_params must be None if optimizer is an instance of " \ + "Optimizer instead of str" + self._optimizer = optimizer + self._optimizer.param_dict = param_dict + else: + self._optimizer = opt.create(optimizer, param_dict=param_dict, + **optimizer_params) + + self._updaters = [opt.get_updater(self._optimizer) \ + for _ in self._contexts] + + def _init_kvstore(self): + arg_arrays = {param.name: param.data(self._contexts[0]) for param in self._params} + kvstore, update_on_kvstore = _create_kvstore(self._kvstore, len(self._contexts), + arg_arrays) + if kvstore: + if 'dist' in kvstore.type: + update_on_kvstore = False + for i, param in enumerate(self._params): + param_arrays = param.list_data() + kvstore.init(i, param_arrays[0]) + kvstore.pull(i, param_arrays, priority=-i) + if update_on_kvstore: + kvstore.set_optimizer(self._optimizer) + self._kvstore = kvstore + self._update_on_kvstore = update_on_kvstore + else: + self._kvstore = None + self._update_on_kvstore = None + + self._kv_initialized = True + + def step(self, batch_size, ignore_stale_grad=False): + """Makes one step of parameter update. Should be called after + `autograd.compute_gradient` and outside of `record()` scope. + + Parameters + ---------- + batch_size : int + Batch size of data processed. Gradient will be normalized by `1/batch_size`. + Set this to 1 if you normalized loss manually with `loss = mean(loss)`. + ignore_stale_grad : bool, optional, default=False + If true, ignores Parameters with stale gradient (gradient that has not + been updated by `backward` after last step) and skip update. + """ + if not self._kv_initialized: + self._init_kvstore() + + self._optimizer.rescale_grad = self._scale / batch_size + + for i, param in enumerate(self._params): + if param.grad_req == 'null': + continue + if not ignore_stale_grad: + for data in param.list_data(): + if not data._fresh_grad: + raise UserWarning( + "Gradient of Parameter `%s` on context %s has not been updated " + "by backward since last `step`. This could mean a bug in your " + "model that maked it only use a subset of the Parameters (Blocks) " + "for this iteration. If you are intentionally only using a subset, " + "call step with ignore_stale_grad=True to suppress this " + "warning and skip updating of Parameters with stale gradient" \ + %(param.name, str(data.context))) + + if self._kvstore: + self._kvstore.push(i, param.list_grad(), priority=-i) + if self._update_on_kvstore: + self._kvstore.pull(i, param.list_data(), priority=-i) + continue + else: + self._kvstore.pull(i, param.list_grad(), priority=-i) + + for upd, arr, grad in zip(self._updaters, param.list_data(), param.list_grad()): + if not ignore_stale_grad or arr._fresh_grad: + upd(i, grad, arr) + arr._fresh_grad = False diff --git a/python/mxnet/gluon/utils.py b/python/mxnet/gluon/utils.py new file mode 100644 index 000000000000..cece22b75b14 --- /dev/null +++ b/python/mxnet/gluon/utils.py @@ -0,0 +1,208 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +# coding: utf-8 +# pylint: disable= +"""Parallelization utility optimizer.""" +import os +import hashlib +try: + import requests +except ImportError: + class requests_failed_to_import(object): + pass + requests = requests_failed_to_import + +import math + +from .. import ndarray + +def split_data(data, num_slice, batch_axis=0, even_split=True): + """Splits an NDArray into `num_slice` slices along `batch_axis`. + Usually used for data parallelism where each slices is sent + to one device (i.e. GPU). + + Parameters + ---------- + data : NDArray + A batch of data. + num_slice : int + Number of desired slices. + batch_axis : int, default 0 + The axis along which to slice. + even_split : bool, default True + Whether to force all slices to have the same number of elements. + If `True`, an error will be raised when `num_slice` does not evenly + divide `data.shape[batch_axis]`. + + Returns + ------- + list of NDArray + Return value is a list even if `num_slice` is 1. + """ + size = data.shape[batch_axis] + if size < num_slice: + raise ValueError( + "Too many slices for data with shape %s. Arguments are " \ + "num_slice=%d and batch_axis=%d."%(str(data.shape), num_slice, batch_axis)) + if even_split and size % num_slice != 0: + raise ValueError( + "data with shape %s cannot be evenly split into %d slices along axis %d. " \ + "Use a batch size that's multiple of %d or set even_split=False to allow " \ + "uneven partitioning of data."%( + str(data.shape), num_slice, batch_axis, num_slice)) + + step = size // num_slice + if batch_axis == 0: + slices = [data[i*step:(i+1)*step] if i < num_slice - 1 else data[i*step:size] + for i in range(num_slice)] + elif even_split: + slices = ndarray.split(data, num_outputs=num_slice, axis=batch_axis) + else: + slices = [ndarray.slice_axis(data, batch_axis, i*step, (i+1)*step) + if i < num_slice - 1 else + ndarray.slice_axis(data, batch_axis, i*step, size) + for i in range(num_slice)] + return slices + + +def split_and_load(data, ctx_list, batch_axis=0, even_split=True): + """Splits an NDArray into `len(ctx_list)` slices along `batch_axis` and loads + each slice to one context in `ctx_list`. + + Parameters + ---------- + data : NDArray + A batch of data. + ctx_list : list of Context + A list of Contexts. + batch_axis : int, default 0 + The axis along which to slice. + even_split : bool, default True + Whether to force all slices to have the same number of elements. + + Returns + ------- + list of NDArray + Each corresponds to a context in `ctx_list`. + """ + if not isinstance(data, ndarray.NDArray): + data = ndarray.array(data, ctx=ctx_list[0]) + if len(ctx_list) == 1: + return [data.as_in_context(ctx_list[0])] + + slices = split_data(data, len(ctx_list), batch_axis, even_split) + return [i.as_in_context(ctx) for i, ctx in zip(slices, ctx_list)] + + +def clip_global_norm(arrays, max_norm): + """Rescales NDArrays so that the sum of their 2-norm is smaller than `max_norm`. + """ + assert len(arrays) > 0 + total_norm = 0 + for arr in arrays: + arr = arr.reshape((-1,)) + total_norm += ndarray.dot(arr, arr) + total_norm = math.sqrt(total_norm.asscalar()) + scale = max_norm / (total_norm + 1e-8) + if scale < 1.0: + for arr in arrays: + arr *= scale + return total_norm + + +def _indent(s_, numSpaces): + """Indent string + """ + s = s_.split('\n') + if len(s) == 1: + return s_ + first = s.pop(0) + s = [first] + [(numSpaces * ' ') + line for line in s] + s = '\n'.join(s) + return s + + +def check_sha1(filename, sha1_hash): + """Check whether the sha1 hash of the file content matches the expected hash. + + Parameters + ---------- + filename : str + Path to the file. + sha1_hash : str + Expected sha1 hash in hexadecimal digits. + + Returns + ------- + bool + Whether the file content matches the expected hash. + """ + sha1 = hashlib.sha1() + with open(filename, 'rb') as f: + while True: + data = f.read(1048576) + if not data: + break + sha1.update(data) + + return sha1.hexdigest() == sha1_hash + + +def download(url, path=None, overwrite=False, sha1_hash=None): + """Download an given URL + + Parameters + ---------- + url : str + URL to download + path : str, optional + Destination path to store downloaded file. By default stores to the + current directory with same name as in url. + overwrite : bool, optional + Whether to overwrite destination file if already exists. + sha1_hash : str, optional + Expected sha1 hash in hexadecimal digits. Will ignore existing file when hash is specified + but doesn't match. + + Returns + ------- + str + The file path of the downloaded file. + """ + if path is None: + fname = url.split('/')[-1] + elif os.path.isdir(path): + fname = os.path.join(path, url.split('/')[-1]) + else: + fname = path + + if overwrite or not os.path.exists(fname) or (sha1_hash and not check_sha1(fname, sha1_hash)): + dirname = os.path.dirname(os.path.abspath(os.path.expanduser(fname))) + if not os.path.exists(dirname): + os.makedirs(dirname) + + print('Downloading %s from %s...'%(fname, url)) + r = requests.get(url, stream=True) + if r.status_code != 200: + raise RuntimeError("Failed downloading url %s"%url) + with open(fname, 'wb') as f: + for chunk in r.iter_content(chunk_size=1024): + if chunk: # filter out keep-alive new chunks + f.write(chunk) + + return fname diff --git a/python/mxnet/image.py b/python/mxnet/image.py deleted file mode 100644 index 1c133b3efa3b..000000000000 --- a/python/mxnet/image.py +++ /dev/null @@ -1,725 +0,0 @@ -# pylint: disable=no-member, too-many-lines, redefined-builtin, protected-access, unused-import, invalid-name -# pylint: disable=too-many-arguments, too-many-locals, no-name-in-module, too-many-branches, too-many-statements -"""Read invidual image files and perform augmentations.""" - -from __future__ import absolute_import, print_function - -import os -import random -import logging -import numpy as np - -try: - import cv2 -except ImportError: - cv2 = None - -from .base import numeric_types -from . import ndarray as nd -from . import _ndarray_internal as _internal -from ._ndarray_internal import _cvimresize as imresize -from ._ndarray_internal import _cvcopyMakeBorder as copyMakeBorder -from . import io -from . import recordio - - -def imdecode(buf, **kwargs): - """Decode an image to an NDArray. - - Note: `imdecode` uses OpenCV (not the CV2 Python library). - MXNet must have been built with OpenCV for `imdecode` to work. - - Parameters - ---------- - buf : str/bytes or numpy.ndarray - Binary image data as string or numpy ndarray. - flag : int, optional, default=1 - 1 for three channel color output. 0 for grayscale output. - to_rgb : int, optional, default=1 - 1 for RGB formatted output (MXNet default). 0 for BGR formatted output (OpenCV default). - out : NDArray, optional - Output buffer. Use `None` for automatic allocation. - - Returns - ------- - NDArray - An `NDArray` containing the image. - - Example - ------- - >>> with open("flower.jpg", 'rb') as fp: - ... str_image = fp.read() - ... - >>> image = mx.img.imdecode(str_image) - >>> image - - - Set `flag` parameter to 0 to get grayscale output - - >>> with open("flower.jpg", 'rb') as fp: - ... str_image = fp.read() - ... - >>> image = mx.img.imdecode(str_image, flag=0) - >>> image - - - Set `to_rgb` parameter to 0 to get output in OpenCV format (BGR) - - >>> with open("flower.jpg", 'rb') as fp: - ... str_image = fp.read() - ... - >>> image = mx.img.imdecode(str_image, to_rgb=0) - >>> image - - """ - if not isinstance(buf, nd.NDArray): - buf = nd.array(np.frombuffer(buf, dtype=np.uint8), dtype=np.uint8) - return _internal._cvimdecode(buf, **kwargs) - - -def scale_down(src_size, size): - """Scales down crop size if it's larger than image size. - - If width/height of the crop is larger than the width/height of the image, - sets the width/height to the width/height of the image. - - Parameters - ---------- - src_size : tuple of int - Size of the image in (width, height) format. - size : tuple of int - Size of the crop in (width, height) format. - - Returns - ------- - tuple of int - A tuple containing the scaled crop size in (width, height) format. - - Example - -------- - >>> src_size = (640,480) - >>> size = (720,120) - >>> new_size = mx.img.scale_down(src_size, size) - >>> new_size - (640,106) - """ - w, h = size - sw, sh = src_size - if sh < h: - w, h = float(w * sh) / h, sh - if sw < w: - w, h = sw, float(h * sw) / w - return int(w), int(h) - - -def resize_short(src, size, interp=2): - """Resizes shorter edge to size. - - Note: `resize_short` uses OpenCV (not the CV2 Python library). - MXNet must have been built with OpenCV for `resize_short` to work. - - Resizes the original image by setting the shorter edge to size - and setting the longer edge accordingly. - Resizing function is called from OpenCV. - - Parameters - ---------- - src : NDArray - The original image. - size : int - The length to be set for the shorter edge. - interp : int, optional, default=2 - Interpolation method used for resizing the image. - Default method is bicubic interpolation. - More details can be found in the documentation of OpenCV, please refer to - http://docs.opencv.org/master/da/d54/group__imgproc__transform.html. - - Returns - ------- - NDArray - An 'NDArray' containing the resized image. - - Example - ------- - >>> with open("flower.jpeg", 'rb') as fp: - ... str_image = fp.read() - ... - >>> image = mx.img.imdecode(str_image) - >>> image - - >>> size = 640 - >>> new_image = mx.img.resize_short(image, size) - >>> new_image - - """ - h, w, _ = src.shape - if h > w: - new_h, new_w = size * h / w, size - else: - new_h, new_w = size, size * w / h - return imresize(src, new_w, new_h, interp=interp) - - -def fixed_crop(src, x0, y0, w, h, size=None, interp=2): - """Crop src at fixed location, and (optionally) resize it to size.""" - out = nd.crop(src, begin=(y0, x0, 0), end=(y0 + h, x0 + w, int(src.shape[2]))) - if size is not None and (w, h) != size: - out = imresize(out, *size, interp=interp) - return out - - -def random_crop(src, size, interp=2): - """Randomly crop `src` with `size` (width, height). - Upsample result if `src` is smaller than `size`. - - Parameters - ---------- - src: Source image `NDArray` - size: Size of the crop formatted as (width, height). If the `size` is larger - than the image, then the source image is upsampled to `size` and returned. - interp: Interpolation method to be used in case the size is larger (default: bicubic). - Uses OpenCV convention for the parameters. Nearest - 0, Bilinear - 1, Bicubic - 2, - Area - 3. See OpenCV imresize function for more details. - Returns - ------- - NDArray - An `NDArray` containing the cropped image. - Tuple - A tuple (x, y, width, height) where (x, y) is top-left position of the crop in the - original image and (width, height) are the dimensions of the cropped image. - - Example - ------- - >>> im = mx.nd.array(cv2.imread("flower.jpg")) - >>> cropped_im, rect = mx.image.random_crop(im, (100, 100)) - >>> print cropped_im - - >>> print rect - (20, 21, 100, 100) - """ - - h, w, _ = src.shape - new_w, new_h = scale_down((w, h), size) - - x0 = random.randint(0, w - new_w) - y0 = random.randint(0, h - new_h) - - out = fixed_crop(src, x0, y0, new_w, new_h, size, interp) - return out, (x0, y0, new_w, new_h) - - -def center_crop(src, size, interp=2): - """Crops the image `src` to the given `size` by trimming on all four - sides and preserving the center of the image. Upsamples if `src` is smaller - than `size`. - - .. note:: This requires MXNet to be compiled with USE_OPENCV. - - Parameters - ---------- - src : NDArray - Binary source image data. - size : list or tuple of int - The desired output image size. - interp : interpolation, optional, default=Area-based - The type of interpolation that is done to the image. - - Possible values: - - 0: Nearest Neighbors Interpolation. - - 1: Bilinear interpolation. - - 2: Area-based (resampling using pixel area relation). It may be a - preferred method for image decimation, as it gives moire-free - results. But when the image is zoomed, it is similar to the Nearest - Neighbors method. (used by default). - - 3: Bicubic interpolation over 4x4 pixel neighborhood. - - 4: Lanczos interpolation over 8x8 pixel neighborhood. - - When shrinking an image, it will generally look best with AREA-based - interpolation, whereas, when enlarging an image, it will generally look best - with Bicubic (slow) or Bilinear (faster but still looks OK). - - Returns - ------- - NDArray - The cropped image. - Tuple - (x, y, width, height) where x, y are the positions of the crop in the - original image and width, height the dimensions of the crop. - - Example - ------- - >>> with open("flower.jpg", 'rb') as fp: - ... str_image = fp.read() - ... - >>> image = mx.image.imdecode(str_image) - >>> image - - >>> cropped_image, (x, y, width, height) = mx.image.center_crop(image, (1000, 500)) - >>> cropped_image - - >>> x, y, width, height - (1241, 910, 1000, 500) - """ - - h, w, _ = src.shape - new_w, new_h = scale_down((w, h), size) - - x0 = int((w - new_w) / 2) - y0 = int((h - new_h) / 2) - - out = fixed_crop(src, x0, y0, new_w, new_h, size, interp) - return out, (x0, y0, new_w, new_h) - - -def color_normalize(src, mean, std=None): - """Normalize src with mean and std.""" - src -= mean - if std is not None: - src /= std - return src - - -def random_size_crop(src, size, min_area, ratio, interp=2): - """Randomly crop src with size. Randomize area and aspect ratio.""" - h, w, _ = src.shape - new_ratio = random.uniform(*ratio) - if new_ratio * h > w: - max_area = w * int(w / new_ratio) - else: - max_area = h * int(h * new_ratio) - - min_area *= h * w - if max_area < min_area: - return random_crop(src, size, interp) - new_area = random.uniform(min_area, max_area) - new_w = int(np.sqrt(new_area * new_ratio)) - new_h = int(np.sqrt(new_area / new_ratio)) - - assert new_w <= w and new_h <= h - x0 = random.randint(0, w - new_w) - y0 = random.randint(0, h - new_h) - - out = fixed_crop(src, x0, y0, new_w, new_h, size, interp) - return out, (x0, y0, new_w, new_h) - - -def ResizeAug(size, interp=2): - """Make resize shorter edge to size augmenter.""" - - def aug(src): - """Augmenter body""" - return [resize_short(src, size, interp)] - - return aug - - -def RandomCropAug(size, interp=2): - """Make random crop augmenter""" - - def aug(src): - """Augmenter body""" - return [random_crop(src, size, interp)[0]] - - return aug - - -def RandomSizedCropAug(size, min_area, ratio, interp=2): - """Make random crop with random resizing and random aspect ratio jitter augmenter.""" - - def aug(src): - """Augmenter body""" - return [random_size_crop(src, size, min_area, ratio, interp)[0]] - - return aug - - -def CenterCropAug(size, interp=2): - """Make center crop augmenter.""" - - def aug(src): - """Augmenter body""" - return [center_crop(src, size, interp)[0]] - - return aug - - -def RandomOrderAug(ts): - """Apply list of augmenters in random order""" - - def aug(src): - """Augmenter body""" - src = [src] - random.shuffle(ts) - for t in ts: - src = [j for i in src for j in t(i)] - return src - - return aug - - -def ColorJitterAug(brightness, contrast, saturation): - """Apply random brightness, contrast and saturation jitter in random order.""" - ts = [] - coef = nd.array([[[0.299, 0.587, 0.114]]]) - if brightness > 0: - def baug(src): - """Augmenter body""" - alpha = 1.0 + random.uniform(-brightness, brightness) - src *= alpha - return [src] - - ts.append(baug) - - if contrast > 0: - def caug(src): - """Augmenter body""" - alpha = 1.0 + random.uniform(-contrast, contrast) - gray = src * coef - gray = (3.0 * (1.0 - alpha) / gray.size) * nd.sum(gray) - src *= alpha - src += gray - return [src] - - ts.append(caug) - - if saturation > 0: - def saug(src): - """Augmenter body""" - alpha = 1.0 + random.uniform(-saturation, saturation) - gray = src * coef - gray = nd.sum(gray, axis=2, keepdims=True) - gray *= (1.0 - alpha) - src *= alpha - src += gray - return [src] - - ts.append(saug) - return RandomOrderAug(ts) - - -def LightingAug(alphastd, eigval, eigvec): - """Add PCA based noise.""" - - def aug(src): - """Augmenter body""" - alpha = np.random.normal(0, alphastd, size=(3,)) - rgb = np.dot(eigvec * alpha, eigval) - src += nd.array(rgb) - return [src] - - return aug - - -def ColorNormalizeAug(mean, std): - """Mean and std normalization.""" - mean = nd.array(mean) - std = nd.array(std) - - def aug(src): - """Augmenter body""" - return [color_normalize(src, mean, std)] - - return aug - - -def HorizontalFlipAug(p): - """Random horizontal flipping.""" - - def aug(src): - """Augmenter body""" - if random.random() < p: - src = nd.flip(src, axis=1) - return [src] - - return aug - - -def CastAug(): - """Cast to float32""" - - def aug(src): - """Augmenter body""" - src = src.astype(np.float32) - return [src] - - return aug - - -def CreateAugmenter(data_shape, resize=0, rand_crop=False, rand_resize=False, rand_mirror=False, - mean=None, std=None, brightness=0, contrast=0, saturation=0, - pca_noise=0, inter_method=2): - """Creates an augmenter list.""" - auglist = [] - - if resize > 0: - auglist.append(ResizeAug(resize, inter_method)) - - crop_size = (data_shape[2], data_shape[1]) - if rand_resize: - assert rand_crop - auglist.append(RandomSizedCropAug(crop_size, 0.08, (3.0 / 4.0, 4.0 / 3.0), inter_method)) - elif rand_crop: - auglist.append(RandomCropAug(crop_size, inter_method)) - else: - auglist.append(CenterCropAug(crop_size, inter_method)) - - if rand_mirror: - auglist.append(HorizontalFlipAug(0.5)) - - auglist.append(CastAug()) - - if brightness or contrast or saturation: - auglist.append(ColorJitterAug(brightness, contrast, saturation)) - - if pca_noise > 0: - eigval = np.array([55.46, 4.794, 1.148]) - eigvec = np.array([[-0.5675, 0.7192, 0.4009], - [-0.5808, -0.0045, -0.8140], - [-0.5836, -0.6948, 0.4203]]) - auglist.append(LightingAug(pca_noise, eigval, eigvec)) - - if mean is True: - mean = np.array([123.68, 116.28, 103.53]) - elif mean is not None: - assert isinstance(mean, np.ndarray) and mean.shape[0] in [1, 3] - - if std is True: - std = np.array([58.395, 57.12, 57.375]) - elif std is not None: - assert isinstance(std, np.ndarray) and std.shape[0] in [1, 3] - - if mean is not None and std is not None: - auglist.append(ColorNormalizeAug(mean, std)) - - return auglist - - -class ImageIter(io.DataIter): - """Image data iterator with a large number of augmentation choices. - This iterator supports reading from both .rec files and raw image files. - - To load input images from .rec files, use `path_imgrec` parameter and to load from raw image - files, use `path_imglist` and `path_root` parameters. - - To use data partition (for distributed training) or shuffling, specify `path_imgidx` parameter. - - Parameters - ---------- - batch_size : int - Number of examples per batch. - data_shape : tuple - Data shape in (channels, height, width) format. - For now, only RGB image with 3 channels is supported. - label_width : int, optional - Number of labels per example. The default label width is 1. - path_imgrec : str - Path to image record file (.rec). - Created with tools/im2rec.py or bin/im2rec. - path_imglist : str - Path to image list (.lst). - Created with tools/im2rec.py or with custom script. - Format: Tab separated record of index, one or more labels and relative_path_from_root. - imglist: list - A list of images with the label(s). - Each item is a list [imagelabel: float or list of float, imgpath]. - path_root : str - Root folder of image files. - path_imgidx : str - Path to image index file. Needed for partition and shuffling when using .rec source. - shuffle : bool - Whether to shuffle all images at the start of each iteration or not. - Can be slow for HDD. - part_index : int - Partition index. - num_parts : int - Total number of partitions. - data_name : str - Data name for provided symbols. - label_name : str - Label name for provided symbols. - kwargs : ... - More arguments for creating augmenter. See mx.image.CreateAugmenter. - """ - - def __init__(self, batch_size, data_shape, label_width=1, - path_imgrec=None, path_imglist=None, path_root=None, path_imgidx=None, - shuffle=False, part_index=0, num_parts=1, aug_list=None, imglist=None, - data_name='data', label_name='softmax_label', **kwargs): - super(ImageIter, self).__init__() - assert path_imgrec or path_imglist or (isinstance(imglist, list)) - if path_imgrec: - print('loading recordio...') - if path_imgidx: - self.imgrec = recordio.MXIndexedRecordIO(path_imgidx, path_imgrec, 'r') # pylint: disable=redefined-variable-type - self.imgidx = list(self.imgrec.keys) - else: - self.imgrec = recordio.MXRecordIO(path_imgrec, 'r') # pylint: disable=redefined-variable-type - self.imgidx = None - else: - self.imgrec = None - - if path_imglist: - print('loading image list...') - with open(path_imglist) as fin: - imglist = {} - imgkeys = [] - for line in iter(fin.readline, ''): - line = line.strip().split('\t') - label = nd.array([float(i) for i in line[1:-1]]) - key = int(line[0]) - imglist[key] = (label, line[-1]) - imgkeys.append(key) - self.imglist = imglist - elif isinstance(imglist, list): - print('loading image list...') - result = {} - imgkeys = [] - index = 1 - for img in imglist: - key = str(index) # pylint: disable=redefined-variable-type - index += 1 - if isinstance(img[0], numeric_types): - label = nd.array([img[0]]) - else: - label = nd.array(img[0]) - result[key] = (label, img[1]) - imgkeys.append(str(key)) - self.imglist = result - else: - self.imglist = None - self.path_root = path_root - - self.check_data_shape(data_shape) - self.provide_data = [(data_name, (batch_size,) + data_shape)] - if label_width > 1: - self.provide_label = [(label_name, (batch_size, label_width))] - else: - self.provide_label = [(label_name, (batch_size,))] - self.batch_size = batch_size - self.data_shape = data_shape - self.label_width = label_width - - self.shuffle = shuffle - if self.imgrec is None: - self.seq = imgkeys - elif shuffle or num_parts > 1: - assert self.imgidx is not None - self.seq = self.imgidx - else: - self.seq = None - - if num_parts > 1: - assert part_index < num_parts - N = len(self.seq) - C = N / num_parts - self.seq = self.seq[part_index * C:(part_index + 1) * C] - if aug_list is None: - self.auglist = CreateAugmenter(data_shape, **kwargs) - else: - self.auglist = aug_list - self.cur = 0 - self.reset() - - def reset(self): - """Resets the iterator to the beginning of the data.""" - if self.shuffle: - random.shuffle(self.seq) - if self.imgrec is not None: - self.imgrec.reset() - self.cur = 0 - - def next_sample(self): - """Helper function for reading in next sample.""" - if self.seq is not None: - if self.cur >= len(self.seq): - raise StopIteration - idx = self.seq[self.cur] - self.cur += 1 - if self.imgrec is not None: - s = self.imgrec.read_idx(idx) - header, img = recordio.unpack(s) - if self.imglist is None: - return header.label, img - else: - return self.imglist[idx][0], img - else: - label, fname = self.imglist[idx] - return label, self.read_image(fname) - else: - s = self.imgrec.read() - if s is None: - raise StopIteration - header, img = recordio.unpack(s) - return header.label, img - - def next(self): - """Returns the next batch of data.""" - batch_size = self.batch_size - c, h, w = self.data_shape - batch_data = nd.empty((batch_size, c, h, w)) - batch_label = nd.empty(self.provide_label[0][1]) - i = 0 - try: - while i < batch_size: - label, s = self.next_sample() - data = [self.imdecode(s)] - try: - self.check_valid_image(data) - except RuntimeError as e: - logging.debug('Invalid image, skipping: %s', str(e)) - continue - data = self.augmentation_transform(data) - for datum in data: - assert i < batch_size, 'Batch size must be multiples of augmenter output length' - batch_data[i][:] = self.postprocess_data(datum) - batch_label[i][:] = label - i += 1 - except StopIteration: - if not i: - raise StopIteration - - return io.DataBatch([batch_data], [batch_label], batch_size - i) - - def check_data_shape(self, data_shape): - """Checks if the input data shape is valid""" - if not len(data_shape) == 3: - raise ValueError('data_shape should have length 3, with dimensions CxHxW') - if not data_shape[0] == 3: - raise ValueError('This iterator expects inputs to have 3 channels.') - - def check_valid_image(self, data): - """Checks if the input data is valid""" - if len(data[0].shape) == 0: - raise RuntimeError('Data shape is wrong') - - def imdecode(self, s): - """Decodes a string or byte string to an NDArray. - See mx.img.imdecode for more details.""" - return imdecode(s) - - def read_image(self, fname): - """Reads an input image `fname` and returns the decoded raw bytes. - - Example usage: - ---------- - >>> dataIter.read_image('Face.jpg') # returns decoded raw bytes. - '\xff\xd8\xff\xe0\x00...' - """ - with open(os.path.join(self.path_root, fname), 'rb') as fin: - img = fin.read() - return img - - def augmentation_transform(self, data): - """Transforms input data with specified augmentation.""" - for aug in self.auglist: - data = [ret for src in data for ret in aug(src)] - return data - - def postprocess_data(self, datum): - """Final postprocessing step before image is loaded into the batch.""" - return nd.transpose(datum, axes=(2, 0, 1)) diff --git a/python/mxnet/image/__init__.py b/python/mxnet/image/__init__.py new file mode 100644 index 000000000000..9bb55fbfdddc --- /dev/null +++ b/python/mxnet/image/__init__.py @@ -0,0 +1,27 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +# coding: utf-8 +# pylint: disable=wildcard-import +"""Image Iterators and image augmentation functions""" + +from . import image +from .image import * + +from . import detection +from . import detection as det +from .detection import * diff --git a/python/mxnet/image/detection.py b/python/mxnet/image/detection.py new file mode 100644 index 000000000000..8ac1aebe72dd --- /dev/null +++ b/python/mxnet/image/detection.py @@ -0,0 +1,941 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +# pylint: disable=unused-import +"""Read images and perform augmentations for object detection.""" + +from __future__ import absolute_import, print_function + +import random +import logging +import json +import numpy as np + +from ..base import numeric_types +from .. import ndarray as nd +from .._ndarray_internal import _cvcopyMakeBorder as copyMakeBorder +from .. import io +from .image import RandomOrderAug, ColorJitterAug, LightingAug, ColorNormalizeAug +from .image import ResizeAug, ForceResizeAug, CastAug, HueJitterAug, RandomGrayAug +from .image import fixed_crop, ImageIter, Augmenter + + +class DetAugmenter(object): + """Detection base augmenter""" + def __init__(self, **kwargs): + self._kwargs = kwargs + for k, v in self._kwargs.items(): + if isinstance(v, nd.NDArray): + v = v.asnumpy() + if isinstance(v, np.ndarray): + v = v.tolist() + self._kwargs[k] = v + + def dumps(self): + """Saves the Augmenter to string + + Returns + ------- + str + JSON formatted string that describes the Augmenter. + """ + return json.dumps([self.__class__.__name__.lower(), self._kwargs]) + + def __call__(self, src, label): + """Abstract implementation body""" + raise NotImplementedError("Must override implementation.") + + +class DetBorrowAug(DetAugmenter): + """Borrow standard augmenter from image classification. + Which is good once you know label won't be affected after this augmenter. + + Parameters + ---------- + augmenter : mx.image.Augmenter + The borrowed standard augmenter which has no effect on label + """ + def __init__(self, augmenter): + if not isinstance(augmenter, Augmenter): + raise TypeError('Borrowing from invalid Augmenter') + super(DetBorrowAug, self).__init__(augmenter=augmenter.dumps()) + self.augmenter = augmenter + + def dumps(self): + """Override the default one to avoid duplicate dump.""" + return [self.__class__.__name__.lower(), self.augmenter.dumps()] + + def __call__(self, src, label): + """Augmenter implementation body""" + src = self.augmenter(src) + return (src, label) + + +class DetRandomSelectAug(DetAugmenter): + """Randomly select one augmenter to apply, with chance to skip all. + + Parameters + ---------- + aug_list : list of DetAugmenter + The random selection will be applied to one of the augmenters + skip_prob : float + The probability to skip all augmenters and return input directly + """ + def __init__(self, aug_list, skip_prob=0): + super(DetRandomSelectAug, self).__init__(skip_prob=skip_prob) + if not isinstance(aug_list, (list, tuple)): + aug_list = [aug_list] + for aug in aug_list: + if not isinstance(aug, DetAugmenter): + raise ValueError('Allow DetAugmenter in list only') + if not aug_list: + skip_prob = 1 # disabled + + self.aug_list = aug_list + self.skip_prob = skip_prob + + def dumps(self): + """Override default.""" + return [self.__class__.__name__.lower(), [x.dumps() for x in self.aug_list]] + + def __call__(self, src, label): + """Augmenter implementation body""" + if random.random() < self.skip_prob: + return (src, label) + else: + random.shuffle(self.aug_list) + return self.aug_list[0](src, label) + + +class DetHorizontalFlipAug(DetAugmenter): + """Random horizontal flipping. + + Parameters + ---------- + p : float + chance [0, 1] to flip + """ + def __init__(self, p): + super(DetHorizontalFlipAug, self).__init__(p=p) + self.p = p + + def __call__(self, src, label): + """Augmenter implementation""" + if random.random() < self.p: + src = nd.flip(src, axis=1) + self._flip_label(label) + return (src, label) + + def _flip_label(self, label): + """Helper function to flip label.""" + tmp = 1.0 - label[:, 1] + label[:, 1] = 1.0 - label[:, 3] + label[:, 3] = tmp + + +class DetRandomCropAug(DetAugmenter): + """Random cropping with constraints + + Parameters + ---------- + min_object_covered : float, default=0.1 + The cropped area of the image must contain at least this fraction of + any bounding box supplied. The value of this parameter should be non-negative. + In the case of 0, the cropped area does not need to overlap any of the + bounding boxes supplied. + min_eject_coverage : float, default=0.3 + The minimum coverage of cropped sample w.r.t its original size. With this + constraint, objects that have marginal area after crop will be discarded. + aspect_ratio_range : tuple of floats, default=(0.75, 1.33) + The cropped area of the image must have an aspect ratio = width / height + within this range. + area_range : tuple of floats, default=(0.05, 1.0) + The cropped area of the image must contain a fraction of the supplied + image within in this range. + max_attempts : int, default=50 + Number of attempts at generating a cropped/padded region of the image of the + specified constraints. After max_attempts failures, return the original image. + """ + def __init__(self, min_object_covered=0.1, aspect_ratio_range=(0.75, 1.33), + area_range=(0.05, 1.0), min_eject_coverage=0.3, max_attempts=50): + if not isinstance(aspect_ratio_range, (tuple, list)): + assert isinstance(aspect_ratio_range, numeric_types) + logging.info('Using fixed aspect ratio: %s in DetRandomCropAug', + str(aspect_ratio_range)) + aspect_ratio_range = (aspect_ratio_range, aspect_ratio_range) + if not isinstance(area_range, (tuple, list)): + assert isinstance(area_range, numeric_types) + logging.info('Using fixed area range: %s in DetRandomCropAug', area_range) + area_range = (area_range, area_range) + super(DetRandomCropAug, self).__init__(min_object_covered=min_object_covered, + aspect_ratio_range=aspect_ratio_range, + area_range=area_range, + min_eject_coverage=min_eject_coverage, + max_attempts=max_attempts) + self.min_object_covered = min_object_covered + self.min_eject_coverage = min_eject_coverage + self.max_attempts = max_attempts + self.aspect_ratio_range = aspect_ratio_range + self.area_range = area_range + self.enabled = False + if (area_range[1] <= 0 or area_range[0] > area_range[1]): + logging.warn('Skip DetRandomCropAug due to invalid area_range: %s', area_range) + elif (aspect_ratio_range[0] > aspect_ratio_range[1] or aspect_ratio_range[0] <= 0): + logging.warn('Skip DetRandomCropAug due to invalid aspect_ratio_range: %s', + aspect_ratio_range) + else: + self.enabled = True + + def __call__(self, src, label): + """Augmenter implementation body""" + crop = self._random_crop_proposal(label, src.shape[0], src.shape[1]) + if crop: + x, y, w, h, label = crop + src = fixed_crop(src, x, y, w, h, None) + return (src, label) + + def _calculate_areas(self, label): + """Calculate areas for multiple labels""" + heights = np.maximum(0, label[:, 3] - label[:, 1]) + widths = np.maximum(0, label[:, 2] - label[:, 0]) + return heights * widths + + + def _intersect(self, label, xmin, ymin, xmax, ymax): + """Calculate intersect areas, normalized.""" + left = np.maximum(label[:, 0], xmin) + right = np.minimum(label[:, 2], xmax) + top = np.maximum(label[:, 1], ymin) + bot = np.minimum(label[:, 3], ymax) + invalid = np.where(np.logical_or(left >= right, top >= bot))[0] + out = label.copy() + out[:, 0] = left + out[:, 1] = top + out[:, 2] = right + out[:, 3] = bot + out[invalid, :] = 0 + return out + + def _check_satisfy_constraints(self, label, xmin, ymin, xmax, ymax, width, height): + """Check if constrains are satisfied""" + if (xmax - xmin) * (ymax - ymin) < 2: + return False # only 1 pixel + x1 = float(xmin) / width + y1 = float(ymin) / height + x2 = float(xmax) / width + y2 = float(ymax) / height + object_areas = self._calculate_areas(label[:, 1:]) + valid_objects = np.where(object_areas * width * height > 2)[0] + if valid_objects.size < 1: + return False + intersects = self._intersect(label[valid_objects, 1:], x1, y1, x2, y2) + coverages = self._calculate_areas(intersects) / object_areas + coverages = coverages[np.where(coverages > 0)[0]] + if coverages.size > 0 and np.amin(coverages) > self.min_object_covered: + return True + + def _update_labels(self, label, crop_box, height, width): + """Convert labels according to crop box""" + xmin = float(crop_box[0]) / width + ymin = float(crop_box[1]) / height + w = float(crop_box[2]) / width + h = float(crop_box[3]) / height + out = label.copy() + out[:, (1, 3)] -= xmin + out[:, (2, 4)] -= ymin + out[:, (1, 3)] /= w + out[:, (2, 4)] /= h + out[:, 1:5] = np.maximum(0, out[:, 1:5]) + out[:, 1:5] = np.minimum(1, out[:, 1:5]) + coverage = self._calculate_areas(out[:, 1:]) * w * h / self._calculate_areas(label[:, 1:]) + valid = np.logical_and(out[:, 3] > out[:, 1], out[:, 4] > out[:, 2]) + valid = np.logical_and(valid, coverage > self.min_eject_coverage) + valid = np.where(valid)[0] + if valid.size < 1: + return None + out = out[valid, :] + return out + + def _random_crop_proposal(self, label, height, width): + """Propose cropping areas""" + from math import sqrt + + if not self.enabled or height <= 0 or width <= 0: + return () + min_area = self.area_range[0] * height * width + max_area = self.area_range[1] * height * width + for _ in range(self.max_attempts): + ratio = random.uniform(*self.aspect_ratio_range) + if ratio <= 0: + continue + h = int(round(sqrt(min_area / ratio))) + max_h = int(round(sqrt(max_area / ratio))) + if round(max_h * ratio) > width: + # find smallest max_h satifying round(max_h * ratio) <= width + max_h = int((width + 0.4999999) / ratio) + if max_h > height: + max_h = height + if h > max_h: + h = max_h + if h < max_h: + # generate random h in range [h, max_h] + h = random.randint(h, max_h) + w = int(round(h * ratio)) + assert w <= width + + # trying to fix rounding problems + area = w * h + if area < min_area: + h += 1 + w = int(round(h * ratio)) + area = w * h + if area > max_area: + h -= 1 + w = int(round(h * ratio)) + area = w * h + if (area < min_area or area > max_area or w > width or h > height \ + or w <= 0 or h <= 0): + continue + + y = random.randint(0, max(0, height - h)) + x = random.randint(0, max(0, width - w)) + if self._check_satisfy_constraints(label, x, y, x + w, y + h, width, height): + new_label = self._update_labels(label, (x, y, w, h), height, width) + if new_label is not None: + return (x, y, w, h, new_label) + return () + + +class DetRandomPadAug(DetAugmenter): + """Random padding augmenter. + + Parameters + ---------- + aspect_ratio_range : tuple of floats, default=(0.75, 1.33) + The padded area of the image must have an aspect ratio = width / height + within this range. + area_range : tuple of floats, default=(1.0, 3.0) + The padded area of the image must be larger than the original area + max_attempts : int, default=50 + Number of attempts at generating a padded region of the image of the + specified constraints. After max_attempts failures, return the original image. + pad_val: float or tuple of float, default=(128, 128, 128) + pixel value to be filled when padding is enabled. + """ + def __init__(self, aspect_ratio_range=(0.75, 1.33), area_range=(1.0, 3.0), + max_attempts=50, pad_val=(128, 128, 128)): + if not isinstance(pad_val, (list, tuple)): + assert isinstance(pad_val, numeric_types) + pad_val = (pad_val) + if not isinstance(aspect_ratio_range, (list, tuple)): + assert isinstance(aspect_ratio_range, numeric_types) + logging.info('Using fixed aspect ratio: %s in DetRandomPadAug', + str(aspect_ratio_range)) + aspect_ratio_range = (aspect_ratio_range, aspect_ratio_range) + if not isinstance(area_range, (tuple, list)): + assert isinstance(area_range, numeric_types) + logging.info('Using fixed area range: %s in DetRandomPadAug', area_range) + area_range = (area_range, area_range) + super(DetRandomPadAug, self).__init__(aspect_ratio_range=aspect_ratio_range, + area_range=area_range, max_attempts=max_attempts, + pad_val=pad_val) + self.pad_val = pad_val + self.aspect_ratio_range = aspect_ratio_range + self.area_range = area_range + self.max_attempts = max_attempts + self.enabled = False + if (area_range[1] <= 1.0 or area_range[0] > area_range[1]): + logging.warn('Skip DetRandomPadAug due to invalid parameters: %s', area_range) + elif (aspect_ratio_range[0] <= 0 or aspect_ratio_range[0] > aspect_ratio_range[1]): + logging.warn('Skip DetRandomPadAug due to invalid aspect_ratio_range: %s', + aspect_ratio_range) + else: + self.enabled = True + + def __call__(self, src, label): + """Augmenter body""" + height, width, _ = src.shape + pad = self._random_pad_proposal(label, height, width) + if pad: + x, y, w, h, label = pad + src = copyMakeBorder(src, y, h-y-height, x, w-x-width, 16, values=self.pad_val) + return (src, label) + + def _update_labels(self, label, pad_box, height, width): + """Update label according to padding region""" + out = label.copy() + out[:, (1, 3)] = (out[:, (1, 3)] * width + pad_box[0]) / pad_box[2] + out[:, (2, 4)] = (out[:, (2, 4)] * height + pad_box[1]) / pad_box[3] + return out + + def _random_pad_proposal(self, label, height, width): + """Generate random padding region""" + from math import sqrt + if not self.enabled or height <= 0 or width <= 0: + return () + min_area = self.area_range[0] * height * width + max_area = self.area_range[1] * height * width + for _ in range(self.max_attempts): + ratio = random.uniform(*self.aspect_ratio_range) + if ratio <= 0: + continue + h = int(round(sqrt(min_area / ratio))) + max_h = int(round(sqrt(max_area / ratio))) + if round(h * ratio) < width: + h = int((width + 0.499999) / ratio) + if h < height: + h = height + if h > max_h: + h = max_h + if h < max_h: + h = random.randint(h, max_h) + w = int(round(h * ratio)) + if (h - height) < 2 or (w - width) < 2: + continue # marginal padding is not helpful + + y = random.randint(0, max(0, h - height)) + x = random.randint(0, max(0, w - width)) + new_label = self._update_labels(label, (x, y, w, h), height, width) + return (x, y, w, h, new_label) + return () + + +def CreateMultiRandCropAugmenter(min_object_covered=0.1, aspect_ratio_range=(0.75, 1.33), + area_range=(0.05, 1.0), min_eject_coverage=0.3, + max_attempts=50, skip_prob=0): + """Helper function to create multiple random crop augmenters. + + Parameters + ---------- + min_object_covered : float or list of float, default=0.1 + The cropped area of the image must contain at least this fraction of + any bounding box supplied. The value of this parameter should be non-negative. + In the case of 0, the cropped area does not need to overlap any of the + bounding boxes supplied. + min_eject_coverage : float or list of float, default=0.3 + The minimum coverage of cropped sample w.r.t its original size. With this + constraint, objects that have marginal area after crop will be discarded. + aspect_ratio_range : tuple of floats or list of tuple of floats, default=(0.75, 1.33) + The cropped area of the image must have an aspect ratio = width / height + within this range. + area_range : tuple of floats or list of tuple of floats, default=(0.05, 1.0) + The cropped area of the image must contain a fraction of the supplied + image within in this range. + max_attempts : int or list of int, default=50 + Number of attempts at generating a cropped/padded region of the image of the + specified constraints. After max_attempts failures, return the original image. + + Examples + -------- + >>> # An example of creating multiple random crop augmenters + >>> min_object_covered = [0.1, 0.3, 0.5, 0.7, 0.9] # use 5 augmenters + >>> aspect_ratio_range = (0.75, 1.33) # use same range for all augmenters + >>> area_range = [(0.1, 1.0), (0.2, 1.0), (0.2, 1.0), (0.3, 0.9), (0.5, 1.0)] + >>> min_eject_coverage = 0.3 + >>> max_attempts = 50 + >>> aug = mx.image.det.CreateMultiRandCropAugmenter(min_object_covered=min_object_covered, + aspect_ratio_range=aspect_ratio_range, area_range=area_range, + min_eject_coverage=min_eject_coverage, max_attempts=max_attempts, + skip_prob=0) + >>> aug.dumps() # show some details + + """ + def align_parameters(params): + """Align parameters as pairs""" + out_params = [] + num = 1 + for p in params: + if not isinstance(p, list): + p = [p] + out_params.append(p) + num = max(num, len(p)) + # align for each param + for k, p in enumerate(out_params): + if len(p) != num: + assert len(p) == 1 + out_params[k] = p * num + return out_params + + aligned_params = align_parameters([min_object_covered, aspect_ratio_range, area_range, + min_eject_coverage, max_attempts]) + augs = [] + for moc, arr, ar, mec, ma in zip(*aligned_params): + augs.append(DetRandomCropAug(min_object_covered=moc, aspect_ratio_range=arr, + area_range=ar, min_eject_coverage=mec, max_attempts=ma)) + return DetRandomSelectAug(augs, skip_prob=skip_prob) + + +def CreateDetAugmenter(data_shape, resize=0, rand_crop=0, rand_pad=0, rand_gray=0, + rand_mirror=False, mean=None, std=None, brightness=0, contrast=0, + saturation=0, pca_noise=0, hue=0, inter_method=2, min_object_covered=0.1, + aspect_ratio_range=(0.75, 1.33), area_range=(0.05, 3.0), + min_eject_coverage=0.3, max_attempts=50, pad_val=(127, 127, 127)): + """Create augmenters for detection. + + Parameters + ---------- + data_shape : tuple of int + Shape for output data + resize : int + Resize shorter edge if larger than 0 at the begining + rand_crop : float + [0, 1], probability to apply random cropping + rand_pad : float + [0, 1], probability to apply random padding + rand_gray : float + [0, 1], probability to convert to grayscale for all channels + rand_mirror : bool + Whether to apply horizontal flip to image with probability 0.5 + mean : np.ndarray or None + Mean pixel values for [r, g, b] + std : np.ndarray or None + Standard deviations for [r, g, b] + brightness : float + Brightness jittering range (percent) + contrast : float + Contrast jittering range (percent) + saturation : float + Saturation jittering range (percent) + hue : float + Hue jittering range (percent) + pca_noise : float + Pca noise level (percent) + inter_method : int, default=2(Area-based) + Interpolation method for all resizing operations + + Possible values: + 0: Nearest Neighbors Interpolation. + 1: Bilinear interpolation. + 2: Area-based (resampling using pixel area relation). It may be a + preferred method for image decimation, as it gives moire-free + results. But when the image is zoomed, it is similar to the Nearest + Neighbors method. (used by default). + 3: Bicubic interpolation over 4x4 pixel neighborhood. + 4: Lanczos interpolation over 8x8 pixel neighborhood. + 9: Cubic for enlarge, area for shrink, bilinear for others + 10: Random select from interpolation method metioned above. + Note: + When shrinking an image, it will generally look best with AREA-based + interpolation, whereas, when enlarging an image, it will generally look best + with Bicubic (slow) or Bilinear (faster but still looks OK). + min_object_covered : float + The cropped area of the image must contain at least this fraction of + any bounding box supplied. The value of this parameter should be non-negative. + In the case of 0, the cropped area does not need to overlap any of the + bounding boxes supplied. + min_eject_coverage : float + The minimum coverage of cropped sample w.r.t its original size. With this + constraint, objects that have marginal area after crop will be discarded. + aspect_ratio_range : tuple of floats + The cropped area of the image must have an aspect ratio = width / height + within this range. + area_range : tuple of floats + The cropped area of the image must contain a fraction of the supplied + image within in this range. + max_attempts : int + Number of attempts at generating a cropped/padded region of the image of the + specified constraints. After max_attempts failures, return the original image. + pad_val: float + Pixel value to be filled when padding is enabled. pad_val will automatically + be subtracted by mean and divided by std if applicable. + + Examples + -------- + >>> # An example of creating multiple augmenters + >>> augs = mx.image.CreateDetAugmenter(data_shape=(3, 300, 300), rand_crop=0.5, + ... rand_pad=0.5, rand_mirror=True, mean=True, brightness=0.125, contrast=0.125, + ... saturation=0.125, pca_noise=0.05, inter_method=10, min_object_covered=[0.3, 0.5, 0.9], + ... area_range=(0.3, 3.0)) + >>> # dump the details + >>> for aug in augs: + ... aug.dumps() + """ + auglist = [] + + if resize > 0: + auglist.append(DetBorrowAug(ResizeAug(resize, inter_method))) + + if rand_crop > 0: + crop_augs = CreateMultiRandCropAugmenter(min_object_covered, aspect_ratio_range, + area_range, min_eject_coverage, + max_attempts, skip_prob=(1 - rand_crop)) + auglist.append(crop_augs) + + if rand_mirror > 0: + auglist.append(DetHorizontalFlipAug(0.5)) + + # apply random padding as late as possible to save computation + if rand_pad > 0: + pad_aug = DetRandomPadAug(aspect_ratio_range, + (1.0, area_range[1]), max_attempts, pad_val) + auglist.append(DetRandomSelectAug([pad_aug], 1 - rand_pad)) + + # force resize + auglist.append(DetBorrowAug(ForceResizeAug((data_shape[2], data_shape[1]), inter_method))) + + auglist.append(DetBorrowAug(CastAug())) + + if brightness or contrast or saturation: + auglist.append(DetBorrowAug(ColorJitterAug(brightness, contrast, saturation))) + + if hue: + auglist.append(DetBorrowAug(HueJitterAug(hue))) + + if pca_noise > 0: + eigval = np.array([55.46, 4.794, 1.148]) + eigvec = np.array([[-0.5675, 0.7192, 0.4009], + [-0.5808, -0.0045, -0.8140], + [-0.5836, -0.6948, 0.4203]]) + auglist.append(DetBorrowAug(LightingAug(pca_noise, eigval, eigvec))) + + if rand_gray > 0: + auglist.append(DetBorrowAug(RandomGrayAug(rand_gray))) + + if mean is True: + mean = np.array([123.68, 116.28, 103.53]) + elif mean is not None: + assert isinstance(mean, np.ndarray) and mean.shape[0] in [1, 3] + + if std is True: + std = np.array([58.395, 57.12, 57.375]) + elif std is not None: + assert isinstance(std, np.ndarray) and std.shape[0] in [1, 3] + + if mean is not None or std is not None: + auglist.append(DetBorrowAug(ColorNormalizeAug(mean, std))) + + return auglist + + +class ImageDetIter(ImageIter): + """Image iterator with a large number of augmentation choices for detection. + + Parameters + ---------- + aug_list : list or None + Augmenter list for generating distorted images + batch_size : int + Number of examples per batch. + data_shape : tuple + Data shape in (channels, height, width) format. + For now, only RGB image with 3 channels is supported. + path_imgrec : str + Path to image record file (.rec). + Created with tools/im2rec.py or bin/im2rec. + path_imglist : str + Path to image list (.lst). + Created with tools/im2rec.py or with custom script. + Format: Tab separated record of index, one or more labels and relative_path_from_root. + imglist: list + A list of images with the label(s). + Each item is a list [imagelabel: float or list of float, imgpath]. + path_root : str + Root folder of image files. + path_imgidx : str + Path to image index file. Needed for partition and shuffling when using .rec source. + shuffle : bool + Whether to shuffle all images at the start of each iteration or not. + Can be slow for HDD. + part_index : int + Partition index. + num_parts : int + Total number of partitions. + data_name : str + Data name for provided symbols. + label_name : str + Name for detection labels + kwargs : ... + More arguments for creating augmenter. See mx.image.CreateDetAugmenter. + """ + def __init__(self, batch_size, data_shape, + path_imgrec=None, path_imglist=None, path_root=None, path_imgidx=None, + shuffle=False, part_index=0, num_parts=1, aug_list=None, imglist=None, + data_name='data', label_name='label', **kwargs): + super(ImageDetIter, self).__init__(batch_size=batch_size, data_shape=data_shape, + path_imgrec=path_imgrec, path_imglist=path_imglist, + path_root=path_root, path_imgidx=path_imgidx, + shuffle=shuffle, part_index=part_index, + num_parts=num_parts, aug_list=[], imglist=imglist, + data_name=data_name, label_name=label_name) + + if aug_list is None: + self.auglist = CreateDetAugmenter(data_shape, **kwargs) + else: + self.auglist = aug_list + + # went through all labels to get the proper label shape + label_shape = self._estimate_label_shape() + self.provide_label = [(label_name, (self.batch_size, label_shape[0], label_shape[1]))] + self.label_shape = label_shape + + def _check_valid_label(self, label): + """Validate label and its shape.""" + if len(label.shape) != 2 or label.shape[1] < 5: + msg = "Label with shape (1+, 5+) required, %s received." % str(label) + raise RuntimeError(msg) + valid_label = np.where(np.logical_and(label[:, 0] >= 0, label[:, 3] > label[:, 1], + label[:, 4] > label[:, 2]))[0] + if valid_label.size < 1: + raise RuntimeError('Invalid label occurs.') + + def _estimate_label_shape(self): + """Helper function to estimate label shape""" + max_count = 0 + self.reset() + try: + while True: + label, _ = self.next_sample() + label = self._parse_label(label) + max_count = max(max_count, label.shape[0]) + except StopIteration: + pass + self.reset() + return (max_count, label.shape[1]) + + def _parse_label(self, label): + """Helper function to parse object detection label. + + Format for raw label: + n \t k \t ... \t [id \t xmin\t ymin \t xmax \t ymax \t ...] \t [repeat] + where n is the width of header, 2 or larger + k is the width of each object annotation, can be arbitrary, at least 5 + """ + if isinstance(label, nd.NDArray): + label = label.asnumpy() + raw = label.ravel() + if raw.size < 7: + raise RuntimeError("Label shape is invalid: " + str(raw.shape)) + header_width = int(raw[0]) + obj_width = int(raw[1]) + if (raw.size - header_width) % obj_width != 0: + msg = "Label shape %s inconsistent with annotation width %d." \ + %(str(raw.shape), obj_width) + raise RuntimeError(msg) + out = np.reshape(raw[header_width:], (-1, obj_width)) + # remove bad ground-truths + valid = np.where(np.logical_and(out[:, 3] > out[:, 1], out[:, 4] > out[:, 2]))[0] + if valid.size < 1: + raise RuntimeError('Encounter sample with no valid label.') + return out[valid, :] + + def reshape(self, data_shape=None, label_shape=None): + """Reshape iterator for data_shape or label_shape. + + Parameters + ---------- + data_shape : tuple or None + Reshape the data_shape to the new shape if not None + label_shape : tuple or None + Reshape label shape to new shape if not None + """ + if data_shape is not None: + self.check_data_shape(data_shape) + self.provide_data = [(self.provide_data[0][0], (self.batch_size,) + data_shape)] + if label_shape is not None: + self.check_label_shape(label_shape) + self.provide_label = [(self.provide_label[0][0], (self.batch_size,) + label_shape)] + + def next(self): + """Override the function for returning next batch.""" + batch_size = self.batch_size + c, h, w = self.data_shape + batch_data = nd.zeros((batch_size, c, h, w)) + batch_label = nd.empty(self.provide_label[0][1]) + batch_label[:] = -1 + i = 0 + try: + while i < batch_size: + label, s = self.next_sample() + data = self.imdecode(s) + try: + self.check_valid_image([data]) + label = self._parse_label(label) + data, label = self.augmentation_transform(data, label) + self._check_valid_label(label) + except RuntimeError as e: + logging.debug('Invalid image, skipping: %s', str(e)) + continue + for datum in [data]: + assert i < batch_size, 'Batch size must be multiples of augmenter output length' + batch_data[i] = self.postprocess_data(datum) + num_object = label.shape[0] + batch_label[i][0:num_object] = nd.array(label) + if num_object < batch_label[i].shape[0]: + batch_label[i][num_object:] = -1 + i += 1 + except StopIteration: + if not i: + raise StopIteration + + return io.DataBatch([batch_data], [batch_label], batch_size - i) + + def augmentation_transform(self, data, label): # pylint: disable=arguments-differ + """Override Transforms input data with specified augmentations.""" + for aug in self.auglist: + data, label = aug(data, label) + return (data, label) + + def check_label_shape(self, label_shape): + """Checks if the new label shape is valid""" + if not len(label_shape) == 2: + raise ValueError('label_shape should have length 2') + if label_shape[0] < self.label_shape[0]: + msg = 'Attempts to reduce label count from %d to %d, not allowed.' \ + % (self.label_shape[0], label_shape[0]) + raise ValueError(msg) + if label_shape[1] != self.provide_label[0][1][2]: + msg = 'label_shape object width inconsistent: %d vs %d.' \ + % (self.provide_label[0][1][2], label_shape[1]) + raise ValueError(msg) + + def draw_next(self, color=None, thickness=2, mean=None, std=None, clip=True, + waitKey=None, window_name='draw_next'): + """Display next image with bounding boxes drawn. + + Parameters + ---------- + color : tuple + Bounding box color in RGB, use None for random color + thickness : int + Bounding box border thickness + mean : True or numpy.ndarray + Compensate for the mean to have better visual effect + std : True or numpy.ndarray + Revert standard deviations + clip : bool + If true, clip to [0, 255] for better visual effect + waitKey : None or int + Hold the window for waitKey milliseconds if set, skip ploting if None + window_name : str + Plot window name if waitKey is set. + + Returns + ------- + numpy.ndarray + + Examples + -------- + >>> # use draw_next to get images with bounding boxes drawn + >>> iterator = mx.image.ImageDetIter(1, (3, 600, 600), path_imgrec='train.rec') + >>> for image in iterator.draw_next(waitKey=None): + ... # display image + >>> # or let draw_next display using cv2 module + >>> for image in iterator.draw_next(waitKey=0, window_name='disp'): + ... pass + """ + try: + import cv2 + except ImportError as e: + logging.warn('Unable to import cv2, skip drawing: %s', str(e)) + raise StopIteration + count = 0 + try: + while True: + label, s = self.next_sample() + data = self.imdecode(s) + try: + self.check_valid_image([data]) + label = self._parse_label(label) + except RuntimeError as e: + logging.debug('Invalid image, skipping: %s', str(e)) + continue + count += 1 + data, label = self.augmentation_transform(data, label) + image = data.asnumpy() + + # revert color_normalize + if std is True: + std = np.array([58.395, 57.12, 57.375]) + elif std is not None: + assert isinstance(std, np.ndarray) and std.shape[0] in [1, 3] + if std is not None: + image *= std + + if mean is True: + mean = np.array([123.68, 116.28, 103.53]) + elif mean is not None: + assert isinstance(mean, np.ndarray) and mean.shape[0] in [1, 3] + if mean is not None: + image += mean + + # swap RGB + image[:, :, (0, 1, 2)] = image[:, :, (2, 1, 0)] + if clip: + image = np.maximum(0, np.minimum(255, image)) + if color: + color = color[::-1] + image = image.astype(np.uint8) + height, width, _ = image.shape + for i in range(label.shape[0]): + x1 = int(label[i, 1] * width) + if x1 < 0: + continue + y1 = int(label[i, 2] * height) + x2 = int(label[i, 3] * width) + y2 = int(label[i, 4] * height) + bc = np.random.rand(3) * 255 if not color else color + cv2.rectangle(image, (x1, y1), (x2, y2), bc, thickness) + if waitKey is not None: + cv2.imshow(window_name, image) + cv2.waitKey(waitKey) + yield image + except StopIteration: + if not count: + raise StopIteration + + def sync_label_shape(self, it, verbose=False): + """Synchronize label shape with the input iterator. This is useful when + train/validation iterators have different label padding. + + Parameters + ---------- + it : ImageDetIter + The other iterator to synchronize + verbose : bool + Print verbose log if true + + Returns + ------- + ImageDetIter + The synchronized other iterator, the internal label shape is updated as well. + + Examples + -------- + >>> train_iter = mx.image.ImageDetIter(32, (3, 300, 300), path_imgrec='train.rec') + >>> val_iter = mx.image.ImageDetIter(32, (3, 300, 300), path.imgrec='val.rec') + >>> train_iter.label_shape + (30, 6) + >>> val_iter.label_shape + (25, 6) + >>> val_iter = train_iter.sync_label_shape(val_iter, verbose=False) + >>> train_iter.label_shape + (30, 6) + >>> val_iter.label_shape + (30, 6) + """ + assert isinstance(it, ImageDetIter), 'Synchronize with invalid iterator.' + train_label_shape = self.label_shape + val_label_shape = it.label_shape + assert train_label_shape[1] == val_label_shape[1], "object width mismatch." + max_count = max(train_label_shape[0], val_label_shape[0]) + if max_count > train_label_shape[0]: + self.reshape(None, (max_count, train_label_shape[1])) + if max_count > val_label_shape[0]: + it.reshape(None, (max_count, val_label_shape[1])) + if verbose and max_count > min(train_label_shape[0], val_label_shape[0]): + logging.info('Resized label_shape to (%d, %d).', max_count, train_label_shape[1]) + return it diff --git a/python/mxnet/image/image.py b/python/mxnet/image/image.py new file mode 100644 index 000000000000..2e40019971ac --- /dev/null +++ b/python/mxnet/image/image.py @@ -0,0 +1,1204 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +# pylint: disable=no-member, too-many-lines, redefined-builtin, protected-access, unused-import, invalid-name +# pylint: disable=too-many-arguments, too-many-locals, no-name-in-module, too-many-branches, too-many-statements +"""Read individual image files and perform augmentations.""" + +from __future__ import absolute_import, print_function + +import os +import random +import logging +import json +import numpy as np + +try: + import cv2 +except ImportError: + cv2 = None + +from ..base import numeric_types +from .. import ndarray as nd +from .. import _ndarray_internal as _internal +from .._ndarray_internal import _cvimresize as imresize +from .._ndarray_internal import _cvcopyMakeBorder as copyMakeBorder +from .. import io +from .. import recordio + + +def imread(filename, *args, **kwargs): + """Read and decode an image to an NDArray. + + Note: `imread` uses OpenCV (not the CV2 Python library). + MXNet must have been built with USE_OPENCV=1 for `imdecode` to work. + + Parameters + ---------- + filename : str + Name of the image file to be loaded. + flag : {0, 1}, default 1 + 1 for three channel color output. 0 for grayscale output. + to_rgb : bool, default True + True for RGB formatted output (MXNet default). + False for BGR formatted output (OpenCV default). + out : NDArray, optional + Output buffer. Use `None` for automatic allocation. + + Returns + ------- + NDArray + An `NDArray` containing the image. + + Example + ------- + >>> mx.img.imread("flower.jpg") + + + Set `flag` parameter to 0 to get grayscale output + + >>> mx.img.imdecode("flower.jpg", flag=0) + + + Set `to_rgb` parameter to 0 to get output in OpenCV format (BGR) + + >>> mx.img.imdecode(str_image, to_rgb=0) + + """ + return _internal._cvimread(filename, *args, **kwargs) + + +def imdecode(buf, *args, **kwargs): + """Decode an image to an NDArray. + + Note: `imdecode` uses OpenCV (not the CV2 Python library). + MXNet must have been built with USE_OPENCV=1 for `imdecode` to work. + + Parameters + ---------- + buf : str/bytes or numpy.ndarray + Binary image data as string or numpy ndarray. + flag : int, optional, default=1 + 1 for three channel color output. 0 for grayscale output. + to_rgb : int, optional, default=1 + 1 for RGB formatted output (MXNet default). 0 for BGR formatted output (OpenCV default). + out : NDArray, optional + Output buffer. Use `None` for automatic allocation. + + Returns + ------- + NDArray + An `NDArray` containing the image. + + Example + ------- + >>> with open("flower.jpg", 'rb') as fp: + ... str_image = fp.read() + ... + >>> image = mx.img.imdecode(str_image) + >>> image + + + Set `flag` parameter to 0 to get grayscale output + + >>> with open("flower.jpg", 'rb') as fp: + ... str_image = fp.read() + ... + >>> image = mx.img.imdecode(str_image, flag=0) + >>> image + + + Set `to_rgb` parameter to 0 to get output in OpenCV format (BGR) + + >>> with open("flower.jpg", 'rb') as fp: + ... str_image = fp.read() + ... + >>> image = mx.img.imdecode(str_image, to_rgb=0) + >>> image + + """ + if not isinstance(buf, nd.NDArray): + buf = nd.array(np.frombuffer(buf, dtype=np.uint8), dtype=np.uint8) + return _internal._cvimdecode(buf, *args, **kwargs) + + +def scale_down(src_size, size): + """Scales down crop size if it's larger than image size. + + If width/height of the crop is larger than the width/height of the image, + sets the width/height to the width/height of the image. + + Parameters + ---------- + src_size : tuple of int + Size of the image in (width, height) format. + size : tuple of int + Size of the crop in (width, height) format. + + Returns + ------- + tuple of int + A tuple containing the scaled crop size in (width, height) format. + + Example + -------- + >>> src_size = (640,480) + >>> size = (720,120) + >>> new_size = mx.img.scale_down(src_size, size) + >>> new_size + (640,106) + """ + w, h = size + sw, sh = src_size + if sh < h: + w, h = float(w * sh) / h, sh + if sw < w: + w, h = sw, float(h * sw) / w + return int(w), int(h) + + +def _get_interp_method(interp, sizes=()): + """Get the interpolation method for resize functions. + The major purpose of this function is to wrap a random interp method selection + and a auto-estimation method. + + Parameters + ---------- + interp : int + interpolation method for all resizing operations + + Possible values: + 0: Nearest Neighbors Interpolation. + 1: Bilinear interpolation. + 2: Area-based (resampling using pixel area relation). It may be a + preferred method for image decimation, as it gives moire-free + results. But when the image is zoomed, it is similar to the Nearest + Neighbors method. (used by default). + 3: Bicubic interpolation over 4x4 pixel neighborhood. + 4: Lanczos interpolation over 8x8 pixel neighborhood. + 9: Cubic for enlarge, area for shrink, bilinear for others + 10: Random select from interpolation method metioned above. + Note: + When shrinking an image, it will generally look best with AREA-based + interpolation, whereas, when enlarging an image, it will generally look best + with Bicubic (slow) or Bilinear (faster but still looks OK). + More details can be found in the documentation of OpenCV, please refer to + http://docs.opencv.org/master/da/d54/group__imgproc__transform.html. + sizes : tuple of int + (old_height, old_width, new_height, new_width), if None provided, auto(9) + will return Area(2) anyway. + + Returns + ------- + int + interp method from 0 to 4 + """ + if interp == 9: + if sizes: + assert len(sizes) == 4 + oh, ow, nh, nw = sizes + if nh > oh and nw > ow: + return 2 + elif nh < oh and nw < ow: + return 3 + else: + return 1 + else: + return 2 + if interp == 10: + return random.randint(0, 4) + if interp not in (0, 1, 2, 3, 4): + raise ValueError('Unknown interp method %d' % interp) + return interp + + +def resize_short(src, size, interp=2): + """Resizes shorter edge to size. + + Note: `resize_short` uses OpenCV (not the CV2 Python library). + MXNet must have been built with OpenCV for `resize_short` to work. + + Resizes the original image by setting the shorter edge to size + and setting the longer edge accordingly. + Resizing function is called from OpenCV. + + Parameters + ---------- + src : NDArray + The original image. + size : int + The length to be set for the shorter edge. + interp : int, optional, default=2 + Interpolation method used for resizing the image. + Possible values: + 0: Nearest Neighbors Interpolation. + 1: Bilinear interpolation. + 2: Area-based (resampling using pixel area relation). It may be a + preferred method for image decimation, as it gives moire-free + results. But when the image is zoomed, it is similar to the Nearest + Neighbors method. (used by default). + 3: Bicubic interpolation over 4x4 pixel neighborhood. + 4: Lanczos interpolation over 8x8 pixel neighborhood. + 9: Cubic for enlarge, area for shrink, bilinear for others + 10: Random select from interpolation method metioned above. + Note: + When shrinking an image, it will generally look best with AREA-based + interpolation, whereas, when enlarging an image, it will generally look best + with Bicubic (slow) or Bilinear (faster but still looks OK). + More details can be found in the documentation of OpenCV, please refer to + http://docs.opencv.org/master/da/d54/group__imgproc__transform.html. + + Returns + ------- + NDArray + An 'NDArray' containing the resized image. + + Example + ------- + >>> with open("flower.jpeg", 'rb') as fp: + ... str_image = fp.read() + ... + >>> image = mx.img.imdecode(str_image) + >>> image + + >>> size = 640 + >>> new_image = mx.img.resize_short(image, size) + >>> new_image + + """ + h, w, _ = src.shape + if h > w: + new_h, new_w = size * h // w, size + else: + new_h, new_w = size, size * w // h + return imresize(src, new_w, new_h, interp=_get_interp_method(interp, (h, w, new_h, new_w))) + + +def fixed_crop(src, x0, y0, w, h, size=None, interp=2): + """Crop src at fixed location, and (optionally) resize it to size. + + Parameters + ---------- + src : NDArray + Input image + x0 : int + Left boundary of the cropping area + y0 : int + Top boundary of the cropping area + w : int + Width of the cropping area + h : int + Height of the cropping area + size : tuple of (w, h) + Optional, resize to new size after cropping + interp : int, optional, default=2 + Interpolation method. See resize_short for details. + + Returns + ------- + NDArray + An `NDArray` containing the cropped image. + """ + out = nd.crop(src, begin=(y0, x0, 0), end=(y0 + h, x0 + w, int(src.shape[2]))) + if size is not None and (w, h) != size: + sizes = (h, w, size[1], size[0]) + out = imresize(out, *size, interp=_get_interp_method(interp, sizes)) + return out + + +def random_crop(src, size, interp=2): + """Randomly crop `src` with `size` (width, height). + Upsample result if `src` is smaller than `size`. + + Parameters + ---------- + src: Source image `NDArray` + size: Size of the crop formatted as (width, height). If the `size` is larger + than the image, then the source image is upsampled to `size` and returned. + interp: int, optional, default=2 + Interpolation method. See resize_short for details. + Returns + ------- + NDArray + An `NDArray` containing the cropped image. + Tuple + A tuple (x, y, width, height) where (x, y) is top-left position of the crop in the + original image and (width, height) are the dimensions of the cropped image. + + Example + ------- + >>> im = mx.nd.array(cv2.imread("flower.jpg")) + >>> cropped_im, rect = mx.image.random_crop(im, (100, 100)) + >>> print cropped_im + + >>> print rect + (20, 21, 100, 100) + """ + + h, w, _ = src.shape + new_w, new_h = scale_down((w, h), size) + + x0 = random.randint(0, w - new_w) + y0 = random.randint(0, h - new_h) + + out = fixed_crop(src, x0, y0, new_w, new_h, size, interp) + return out, (x0, y0, new_w, new_h) + + +def center_crop(src, size, interp=2): + """Crops the image `src` to the given `size` by trimming on all four + sides and preserving the center of the image. Upsamples if `src` is smaller + than `size`. + + .. note:: This requires MXNet to be compiled with USE_OPENCV. + + Parameters + ---------- + src : NDArray + Binary source image data. + size : list or tuple of int + The desired output image size. + interp : int, optional, default=2 + Interpolation method. See resize_short for details. + + Returns + ------- + NDArray + The cropped image. + Tuple + (x, y, width, height) where x, y are the positions of the crop in the + original image and width, height the dimensions of the crop. + + Example + ------- + >>> with open("flower.jpg", 'rb') as fp: + ... str_image = fp.read() + ... + >>> image = mx.image.imdecode(str_image) + >>> image + + >>> cropped_image, (x, y, width, height) = mx.image.center_crop(image, (1000, 500)) + >>> cropped_image + + >>> x, y, width, height + (1241, 910, 1000, 500) + """ + + h, w, _ = src.shape + new_w, new_h = scale_down((w, h), size) + + x0 = int((w - new_w) / 2) + y0 = int((h - new_h) / 2) + + out = fixed_crop(src, x0, y0, new_w, new_h, size, interp) + return out, (x0, y0, new_w, new_h) + + +def color_normalize(src, mean, std=None): + """Normalize src with mean and std. + + Parameters + ---------- + src : NDArray + Input image + mean : NDArray + RGB mean to be subtracted + std : NDArray + RGB standard deviation to be divided + + Returns + ------- + NDArray + An `NDArray` containing the normalized image. + """ + if mean is not None: + src -= mean + if std is not None: + src /= std + return src + + +def random_size_crop(src, size, min_area, ratio, interp=2): + """Randomly crop src with size. Randomize area and aspect ratio. + + Parameters + ---------- + src : NDArray + Input image + size : tuple of (int, int) + Size of the crop formatted as (width, height). + min_area : int + Minimum area to be maintained after cropping + ratio : tuple of (float, float) + Aspect ratio range as (min_aspect_ratio, max_aspect_ratio) + interp: int, optional, default=2 + Interpolation method. See resize_short for details. + Returns + ------- + NDArray + An `NDArray` containing the cropped image. + Tuple + A tuple (x, y, width, height) where (x, y) is top-left position of the crop in the + original image and (width, height) are the dimensions of the cropped image. + + """ + h, w, _ = src.shape + area = h * w + for _ in range(10): + target_area = random.uniform(min_area, 1.0) * area + new_ratio = random.uniform(*ratio) + + new_w = int(round(np.sqrt(target_area * new_ratio))) + new_h = int(round(np.sqrt(target_area / new_ratio))) + + if random.random() < 0.5: + new_h, new_w = new_w, new_h + + if new_w <= w and new_h <= h: + x0 = random.randint(0, w - new_w) + y0 = random.randint(0, h - new_h) + + out = fixed_crop(src, x0, y0, new_w, new_h, size, interp) + return out, (x0, y0, new_w, new_h) + + # fall back to center_crop + return center_crop(src, size, interp) + + +class Augmenter(object): + """Image Augmenter base class""" + def __init__(self, **kwargs): + self._kwargs = kwargs + for k, v in self._kwargs.items(): + if isinstance(v, nd.NDArray): + v = v.asnumpy() + if isinstance(v, np.ndarray): + v = v.tolist() + self._kwargs[k] = v + + def dumps(self): + """Saves the Augmenter to string + + Returns + ------- + str + JSON formatted string that describes the Augmenter. + """ + return json.dumps([self.__class__.__name__.lower(), self._kwargs]) + + def __call__(self, src): + """Abstract implementation body""" + raise NotImplementedError("Must override implementation.") + + +class ResizeAug(Augmenter): + """Make resize shorter edge to size augmenter. + + Parameters + ---------- + size : int + The length to be set for the shorter edge. + interp : int, optional, default=2 + Interpolation method. See resize_short for details. + """ + def __init__(self, size, interp=2): + super(ResizeAug, self).__init__(size=size, interp=interp) + self.size = size + self.interp = interp + + def __call__(self, src): + """Augmenter body""" + return resize_short(src, self.size, self.interp) + + +class ForceResizeAug(Augmenter): + """Force resize to size regardless of aspect ratio + + Parameters + ---------- + size : tuple of (int, int) + The desired size as in (width, height) + interp : int, optional, default=2 + Interpolation method. See resize_short for details. + """ + def __init__(self, size, interp=2): + super(ForceResizeAug, self).__init__(size=size, interp=interp) + self.size = size + self.interp = interp + + def __call__(self, src): + """Augmenter body""" + sizes = (src.shape[0], src.shape[1], self.size[1], self.size[0]) + return imresize(src, *self.size, interp=_get_interp_method(self.interp, sizes)) + + +class RandomCropAug(Augmenter): + """Make random crop augmenter + + Parameters + ---------- + size : int + The length to be set for the shorter edge. + interp : int, optional, default=2 + Interpolation method. See resize_short for details. + """ + def __init__(self, size, interp=2): + super(RandomCropAug, self).__init__(size=size, interp=interp) + self.size = size + self.interp = interp + + def __call__(self, src): + """Augmenter body""" + return random_crop(src, self.size, self.interp)[0] + + +class RandomSizedCropAug(Augmenter): + """Make random crop with random resizing and random aspect ratio jitter augmenter. + + Parameters + ---------- + size : tuple of (int, int) + Size of the crop formatted as (width, height). + min_area : int + Minimum area to be maintained after cropping + ratio : tuple of (float, float) + Aspect ratio range as (min_aspect_ratio, max_aspect_ratio) + interp: int, optional, default=2 + Interpolation method. See resize_short for details. + """ + def __init__(self, size, min_area, ratio, interp=2): + super(RandomSizedCropAug, self).__init__(size=size, min_area=min_area, + ratio=ratio, interp=interp) + self.size = size + self.min_area = min_area + self.ratio = ratio + self.interp = interp + + def __call__(self, src): + """Augmenter body""" + return random_size_crop(src, self.size, self.min_area, self.ratio, self.interp)[0] + + +class CenterCropAug(Augmenter): + """Make center crop augmenter. + + Parameters + ---------- + size : list or tuple of int + The desired output image size. + interp : int, optional, default=2 + Interpolation method. See resize_short for details. + """ + def __init__(self, size, interp=2): + super(CenterCropAug, self).__init__(size=size, interp=interp) + self.size = size + self.interp = interp + + def __call__(self, src): + """Augmenter body""" + return center_crop(src, self.size, self.interp)[0] + + +class RandomOrderAug(Augmenter): + """Apply list of augmenters in random order + + Parameters + ---------- + ts : list of augmenters + A series of augmenters to be applied in random order + """ + def __init__(self, ts): + super(RandomOrderAug, self).__init__() + self.ts = ts + + def dumps(self): + """Override the default to avoid duplicate dump.""" + return [self.__class__.__name__.lower(), [x.dumps() for x in self.ts]] + + def __call__(self, src): + """Augmenter body""" + random.shuffle(self.ts) + for t in self.ts: + src = t(src) + return src + + +class BrightnessJitterAug(Augmenter): + """Random brightness jitter augmentation. + + Parameters + ---------- + brightness : float + The brightness jitter ratio range, [0, 1] + """ + def __init__(self, brightness): + super(BrightnessJitterAug, self).__init__(brightness=brightness) + self.brightness = brightness + + def __call__(self, src): + """Augmenter body""" + alpha = 1.0 + random.uniform(-self.brightness, self.brightness) + src *= alpha + return src + + +class ContrastJitterAug(Augmenter): + """Random contrast jitter augmentation. + + Parameters + ---------- + contrast : float + The contrast jitter ratio range, [0, 1] + """ + def __init__(self, contrast): + super(ContrastJitterAug, self).__init__(contrast=contrast) + self.contrast = contrast + self.coef = nd.array([[[0.299, 0.587, 0.114]]]) + + def __call__(self, src): + """Augmenter body""" + alpha = 1.0 + random.uniform(-self.contrast, self.contrast) + gray = src * self.coef + gray = (3.0 * (1.0 - alpha) / gray.size) * nd.sum(gray) + src *= alpha + src += gray + return src + + +class SaturationJitterAug(Augmenter): + """Random saturation jitter augmentation. + + Parameters + ---------- + saturation : float + The saturation jitter ratio range, [0, 1] + """ + def __init__(self, saturation): + super(SaturationJitterAug, self).__init__(saturation=saturation) + self.saturation = saturation + self.coef = nd.array([[[0.299, 0.587, 0.114]]]) + + def __call__(self, src): + """Augmenter body""" + alpha = 1.0 + random.uniform(-self.saturation, self.saturation) + gray = src * self.coef + gray = nd.sum(gray, axis=2, keepdims=True) + gray *= (1.0 - alpha) + src *= alpha + src += gray + return src + + +class HueJitterAug(Augmenter): + """Random hue jitter augmentation. + + Parameters + ---------- + hue : float + The hue jitter ratio range, [0, 1] + """ + def __init__(self, hue): + super(HueJitterAug, self).__init__(hue=hue) + self.hue = hue + self.tyiq = np.array([[0.299, 0.587, 0.114], + [0.596, -0.274, -0.321], + [0.211, -0.523, 0.311]]) + self.ityiq = np.array([[1.0, 0.956, 0.621], + [1.0, -0.272, -0.647], + [1.0, -1.107, 1.705]]) + + def __call__(self, src): + """Augmenter body. + Using approximate linear transfomation described in: + https://beesbuzz.biz/code/hsv_color_transforms.php + """ + alpha = random.uniform(-self.hue, self.hue) + vsu = np.cos(alpha * np.pi) + vsw = np.sin(alpha * np.pi) + bt = np.array([[1.0, 0.0, 0.0], + [0.0, vsu, -vsw], + [0.0, vsw, vsu]]) + t = np.dot(np.dot(self.tyiq, bt), self.ityiq).T + src = nd.dot(src, nd.array(t)) + return src + + +class ColorJitterAug(RandomOrderAug): + """Apply random brightness, contrast and saturation jitter in random order. + + Parameters + ---------- + brightness : float + The brightness jitter ratio range, [0, 1] + contrast : float + The contrast jitter ratio range, [0, 1] + saturation : float + The saturation jitter ratio range, [0, 1] + """ + def __init__(self, brightness, contrast, saturation): + ts = [] + if brightness > 0: + ts.append(BrightnessJitterAug(brightness)) + if contrast > 0: + ts.append(ContrastJitterAug(contrast)) + if saturation > 0: + ts.append(SaturationJitterAug(saturation)) + super(ColorJitterAug, self).__init__(ts) + + +class LightingAug(Augmenter): + """Add PCA based noise. + + Parameters + ---------- + alphastd : float + Noise level + eigval : 3x1 np.array + Eigen values + eigvec : 3x3 np.array + Eigen vectors + """ + def __init__(self, alphastd, eigval, eigvec): + super(LightingAug, self).__init__(alphastd=alphastd, eigval=eigval, eigvec=eigvec) + self.alphastd = alphastd + self.eigval = eigval + self.eigvec = eigvec + + def __call__(self, src): + """Augmenter body""" + alpha = np.random.normal(0, self.alphastd, size=(3,)) + rgb = np.dot(self.eigvec * alpha, self.eigval) + src += nd.array(rgb) + return src + + +class ColorNormalizeAug(Augmenter): + """Mean and std normalization. + + Parameters + ---------- + mean : NDArray + RGB mean to be subtracted + std : NDArray + RGB standard deviation to be divided + """ + def __init__(self, mean, std): + super(ColorNormalizeAug, self).__init__(mean=mean, std=std) + self.mean = nd.array(mean) if mean is not None else None + self.std = nd.array(std) if std is not None else None + + def __call__(self, src): + """Augmenter body""" + return color_normalize(src, self.mean, self.std) + + +class RandomGrayAug(Augmenter): + """Randomly convert to gray image. + + Parameters + ---------- + p : float + Probability to convert to grayscale + """ + def __init__(self, p): + super(RandomGrayAug, self).__init__(p=p) + self.p = p + self.mat = nd.array([[0.21, 0.21, 0.21], + [0.72, 0.72, 0.72], + [0.07, 0.07, 0.07]]) + + def __call__(self, src): + """Augmenter body""" + if random.random() < self.p: + src = nd.dot(src, self.mat) + return src + + +class HorizontalFlipAug(Augmenter): + """Random horizontal flip. + + Parameters + ---------- + p : float + Probability to flip image horizontally + """ + def __init__(self, p): + super(HorizontalFlipAug, self).__init__(p=p) + self.p = p + + def __call__(self, src): + """Augmenter body""" + if random.random() < self.p: + src = nd.flip(src, axis=1) + return src + + +class CastAug(Augmenter): + """Cast to float32""" + def __init__(self): + super(CastAug, self).__init__(type='float32') + + def __call__(self, src): + """Augmenter body""" + src = src.astype(np.float32) + return src + + +def CreateAugmenter(data_shape, resize=0, rand_crop=False, rand_resize=False, rand_mirror=False, + mean=None, std=None, brightness=0, contrast=0, saturation=0, hue=0, + pca_noise=0, rand_gray=0, inter_method=2): + """Creates an augmenter list. + + Parameters + ---------- + data_shape : tuple of int + Shape for output data + resize : int + Resize shorter edge if larger than 0 at the begining + rand_crop : bool + Whether to enable random cropping other than center crop + rand_resize : bool + Whether to enable random sized cropping, require rand_crop to be enabled + rand_gray : float + [0, 1], probability to convert to grayscale for all channels, the number + of channels will not be reduced to 1 + rand_mirror : bool + Whether to apply horizontal flip to image with probability 0.5 + mean : np.ndarray or None + Mean pixel values for [r, g, b] + std : np.ndarray or None + Standard deviations for [r, g, b] + brightness : float + Brightness jittering range (percent) + contrast : float + Contrast jittering range (percent) + saturation : float + Saturation jittering range (percent) + hue : float + Hue jittering range (percent) + pca_noise : float + Pca noise level (percent) + inter_method : int, default=2(Area-based) + Interpolation method for all resizing operations + + Possible values: + 0: Nearest Neighbors Interpolation. + 1: Bilinear interpolation. + 2: Area-based (resampling using pixel area relation). It may be a + preferred method for image decimation, as it gives moire-free + results. But when the image is zoomed, it is similar to the Nearest + Neighbors method. (used by default). + 3: Bicubic interpolation over 4x4 pixel neighborhood. + 4: Lanczos interpolation over 8x8 pixel neighborhood. + 9: Cubic for enlarge, area for shrink, bilinear for others + 10: Random select from interpolation method metioned above. + Note: + When shrinking an image, it will generally look best with AREA-based + interpolation, whereas, when enlarging an image, it will generally look best + with Bicubic (slow) or Bilinear (faster but still looks OK). + + Examples + -------- + >>> # An example of creating multiple augmenters + >>> augs = mx.image.CreateAugmenter(data_shape=(3, 300, 300), rand_mirror=True, + ... mean=True, brightness=0.125, contrast=0.125, rand_gray=0.05, + ... saturation=0.125, pca_noise=0.05, inter_method=10) + >>> # dump the details + >>> for aug in augs: + ... aug.dumps() + """ + auglist = [] + + if resize > 0: + auglist.append(ResizeAug(resize, inter_method)) + + crop_size = (data_shape[2], data_shape[1]) + if rand_resize: + assert rand_crop + auglist.append(RandomSizedCropAug(crop_size, 0.08, (3.0 / 4.0, 4.0 / 3.0), inter_method)) + elif rand_crop: + auglist.append(RandomCropAug(crop_size, inter_method)) + else: + auglist.append(CenterCropAug(crop_size, inter_method)) + + if rand_mirror: + auglist.append(HorizontalFlipAug(0.5)) + + auglist.append(CastAug()) + + if brightness or contrast or saturation: + auglist.append(ColorJitterAug(brightness, contrast, saturation)) + + if hue: + auglist.append(HueJitterAug(hue)) + + if pca_noise > 0: + eigval = np.array([55.46, 4.794, 1.148]) + eigvec = np.array([[-0.5675, 0.7192, 0.4009], + [-0.5808, -0.0045, -0.8140], + [-0.5836, -0.6948, 0.4203]]) + auglist.append(LightingAug(pca_noise, eigval, eigvec)) + + if rand_gray > 0: + auglist.append(RandomGrayAug(rand_gray)) + + if mean is True: + mean = np.array([123.68, 116.28, 103.53]) + elif mean is not None: + assert isinstance(mean, np.ndarray) and mean.shape[0] in [1, 3] + + if std is True: + std = np.array([58.395, 57.12, 57.375]) + elif std is not None: + assert isinstance(std, np.ndarray) and std.shape[0] in [1, 3] + + if mean is not None or std is not None: + auglist.append(ColorNormalizeAug(mean, std)) + + return auglist + + +class ImageIter(io.DataIter): + """Image data iterator with a large number of augmentation choices. + This iterator supports reading from both .rec files and raw image files. + + To load input images from .rec files, use `path_imgrec` parameter and to load from raw image + files, use `path_imglist` and `path_root` parameters. + + To use data partition (for distributed training) or shuffling, specify `path_imgidx` parameter. + + Parameters + ---------- + batch_size : int + Number of examples per batch. + data_shape : tuple + Data shape in (channels, height, width) format. + For now, only RGB image with 3 channels is supported. + label_width : int, optional + Number of labels per example. The default label width is 1. + path_imgrec : str + Path to image record file (.rec). + Created with tools/im2rec.py or bin/im2rec. + path_imglist : str + Path to image list (.lst). + Created with tools/im2rec.py or with custom script. + Format: Tab separated record of index, one or more labels and relative_path_from_root. + imglist: list + A list of images with the label(s). + Each item is a list [imagelabel: float or list of float, imgpath]. + path_root : str + Root folder of image files. + path_imgidx : str + Path to image index file. Needed for partition and shuffling when using .rec source. + shuffle : bool + Whether to shuffle all images at the start of each iteration or not. + Can be slow for HDD. + part_index : int + Partition index. + num_parts : int + Total number of partitions. + data_name : str + Data name for provided symbols. + label_name : str + Label name for provided symbols. + kwargs : ... + More arguments for creating augmenter. See mx.image.CreateAugmenter. + """ + + def __init__(self, batch_size, data_shape, label_width=1, + path_imgrec=None, path_imglist=None, path_root=None, path_imgidx=None, + shuffle=False, part_index=0, num_parts=1, aug_list=None, imglist=None, + data_name='data', label_name='softmax_label', **kwargs): + super(ImageIter, self).__init__() + assert path_imgrec or path_imglist or (isinstance(imglist, list)) + num_threads = os.environ.get('MXNET_CPU_WORKER_NTHREADS', 1) + logging.info('Using %s threads for decoding...', str(num_threads)) + logging.info('Set enviroment variable MXNET_CPU_WORKER_NTHREADS to a' + ' larger number to use more threads.') + class_name = self.__class__.__name__ + if path_imgrec: + logging.info('%s: loading recordio %s...', + class_name, path_imgrec) + if path_imgidx: + self.imgrec = recordio.MXIndexedRecordIO(path_imgidx, path_imgrec, 'r') # pylint: disable=redefined-variable-type + self.imgidx = list(self.imgrec.keys) + else: + self.imgrec = recordio.MXRecordIO(path_imgrec, 'r') # pylint: disable=redefined-variable-type + self.imgidx = None + else: + self.imgrec = None + + if path_imglist: + logging.info('%s: loading image list %s...', class_name, path_imglist) + with open(path_imglist) as fin: + imglist = {} + imgkeys = [] + for line in iter(fin.readline, ''): + line = line.strip().split('\t') + label = nd.array([float(i) for i in line[1:-1]]) + key = int(line[0]) + imglist[key] = (label, line[-1]) + imgkeys.append(key) + self.imglist = imglist + elif isinstance(imglist, list): + logging.info('%s: loading image list...', class_name) + result = {} + imgkeys = [] + index = 1 + for img in imglist: + key = str(index) # pylint: disable=redefined-variable-type + index += 1 + if len(img) > 2: + label = nd.array(img[:-1]) + elif isinstance(img[0], numeric_types): + label = nd.array([img[0]]) + else: + label = nd.array(img[0]) + result[key] = (label, img[-1]) + imgkeys.append(str(key)) + self.imglist = result + else: + self.imglist = None + self.path_root = path_root + + self.check_data_shape(data_shape) + self.provide_data = [(data_name, (batch_size,) + data_shape)] + if label_width > 1: + self.provide_label = [(label_name, (batch_size, label_width))] + else: + self.provide_label = [(label_name, (batch_size,))] + self.batch_size = batch_size + self.data_shape = data_shape + self.label_width = label_width + + self.shuffle = shuffle + if self.imgrec is None: + self.seq = imgkeys + elif shuffle or num_parts > 1: + assert self.imgidx is not None + self.seq = self.imgidx + else: + self.seq = None + + if num_parts > 1: + assert part_index < num_parts + N = len(self.seq) + C = N // num_parts + self.seq = self.seq[part_index * C:(part_index + 1) * C] + if aug_list is None: + self.auglist = CreateAugmenter(data_shape, **kwargs) + else: + self.auglist = aug_list + self.cur = 0 + self.reset() + + def reset(self): + """Resets the iterator to the beginning of the data.""" + if self.shuffle: + random.shuffle(self.seq) + if self.imgrec is not None: + self.imgrec.reset() + self.cur = 0 + + def next_sample(self): + """Helper function for reading in next sample.""" + if self.seq is not None: + if self.cur >= len(self.seq): + raise StopIteration + idx = self.seq[self.cur] + self.cur += 1 + if self.imgrec is not None: + s = self.imgrec.read_idx(idx) + header, img = recordio.unpack(s) + if self.imglist is None: + return header.label, img + else: + return self.imglist[idx][0], img + else: + label, fname = self.imglist[idx] + return label, self.read_image(fname) + else: + s = self.imgrec.read() + if s is None: + raise StopIteration + header, img = recordio.unpack(s) + return header.label, img + + def next(self): + """Returns the next batch of data.""" + batch_size = self.batch_size + c, h, w = self.data_shape + batch_data = nd.empty((batch_size, c, h, w)) + batch_label = nd.empty(self.provide_label[0][1]) + i = 0 + try: + while i < batch_size: + label, s = self.next_sample() + data = self.imdecode(s) + try: + self.check_valid_image(data) + except RuntimeError as e: + logging.debug('Invalid image, skipping: %s', str(e)) + continue + data = self.augmentation_transform(data) + assert i < batch_size, 'Batch size must be multiples of augmenter output length' + batch_data[i] = self.postprocess_data(data) + batch_label[i] = label + i += 1 + except StopIteration: + if not i: + raise StopIteration + + return io.DataBatch([batch_data], [batch_label], batch_size - i) + + def check_data_shape(self, data_shape): + """Checks if the input data shape is valid""" + if not len(data_shape) == 3: + raise ValueError('data_shape should have length 3, with dimensions CxHxW') + if not data_shape[0] == 3: + raise ValueError('This iterator expects inputs to have 3 channels.') + + def check_valid_image(self, data): + """Checks if the input data is valid""" + if len(data[0].shape) == 0: + raise RuntimeError('Data shape is wrong') + + def imdecode(self, s): + """Decodes a string or byte string to an NDArray. + See mx.img.imdecode for more details.""" + return imdecode(s) + + def read_image(self, fname): + """Reads an input image `fname` and returns the decoded raw bytes. + + Example usage: + ---------- + >>> dataIter.read_image('Face.jpg') # returns decoded raw bytes. + """ + with open(os.path.join(self.path_root, fname), 'rb') as fin: + img = fin.read() + return img + + def augmentation_transform(self, data): + """Transforms input data with specified augmentation.""" + for aug in self.auglist: + data = aug(data) + return data + + def postprocess_data(self, datum): + """Final postprocessing step before image is loaded into the batch.""" + return nd.transpose(datum, axes=(2, 0, 1)) diff --git a/python/mxnet/initializer.py b/python/mxnet/initializer.py index 5cc2ede3f3ed..78afa2dbd29a 100755 --- a/python/mxnet/initializer.py +++ b/python/mxnet/initializer.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + """Weight initializer.""" from __future__ import absolute_import, print_function @@ -5,11 +22,13 @@ import logging import warnings import json +from math import sqrt import numpy as np from .base import string_types from .ndarray import NDArray, load from . import random from . import registry +from . import ndarray # inherit str for backward compatibility class InitDesc(str): @@ -35,6 +54,44 @@ class Initializer(object): """The base class of an initializer.""" def __init__(self, **kwargs): self._kwargs = kwargs + self._verbose = False + self._print_func = None + + def set_verbosity(self, verbose=False, print_func=None): + """Switch on/off verbose mode + + Parameters + ---------- + verbose : bool + switch on/off verbose mode + print_func : function + A function that computes statistics of initialized arrays. + Takes an `NDArray` and returns an `str`. Defaults to mean + absolute value str((|x|/size(x)).asscalar()). + """ + self._verbose = verbose + if print_func is None: + def asum_stat(x): + """returns |x|/size(x), async execution.""" + return str((ndarray.norm(x)/sqrt(x.size)).asscalar()) + print_func = asum_stat + self._print_func = print_func + return self + + def _verbose_print(self, desc, init, arr): + """Internal verbose print function + + Parameters + ---------- + desc : InitDesc or str + name of the array + init : str + initializer pattern + arr : NDArray + initialized array + """ + if self._verbose and self._print_func: + logging.info('Initialized %s as %s: %s', desc, init, self._print_func(arr)) def dumps(self): """Saves the initializer to string @@ -79,17 +136,22 @@ def __call__(self, desc, arr): if init: # when calling Variable initializer create(init)._init_weight(desc, arr) + self._verbose_print(desc, init, arr) else: # register nnvm::FSetInputVariableAttrs in the backend for new patterns # don't add new cases here. if desc.endswith('weight'): self._init_weight(desc, arr) + self._verbose_print(desc, 'weight', arr) elif desc.endswith('bias'): self._init_bias(desc, arr) + self._verbose_print(desc, 'bias', arr) elif desc.endswith('gamma'): self._init_gamma(desc, arr) + self._verbose_print(desc, 'gamma', arr) elif desc.endswith('beta'): self._init_beta(desc, arr) + self._verbose_print(desc, 'beta', arr) else: self._init_default(desc, arr) @@ -519,9 +581,12 @@ def __init__(self, rnd_type="uniform", factor_type="avg", magnitude=3): self.magnitude = float(magnitude) - def _init_weight(self, _, arr): + def _init_weight(self, name, arr): shape = arr.shape hw_scale = 1. + if len(shape) < 2: + raise ValueError('Xavier initializer cannot be applied to vector {0}. It requires at' + ' least 2D.'.format(name)) if len(shape) > 2: hw_scale = np.prod(shape[2:]) fan_in, fan_out = shape[1] * hw_scale, shape[0] * hw_scale @@ -591,10 +656,11 @@ class LSTMBias(Initializer): Parameters ---------- - forget_bias: float, bias for the forget gate. - Jozefowicz et al. 2015 recommends setting this to 1.0. + forget_bias: float, default 1.0 + bias for the forget gate. Jozefowicz et al. 2015 recommends + setting this to 1.0. """ - def __init__(self, forget_bias): + def __init__(self, forget_bias=1.0): super(LSTMBias, self).__init__(forget_bias=forget_bias) self.forget_bias = forget_bias @@ -613,7 +679,7 @@ class FusedRNN(Initializer): Parameters ---------- init : Initializer - intializer applied to unpacked weights. Fall back to global + initializer applied to unpacked weights. Fall back to global initializer if None. num_hidden : int should be the same with arguments passed to FusedRNNCell. @@ -640,7 +706,7 @@ def __init__(self, init, num_hidden, num_layers, mode, bidirectional=False, forg self._bidirectional = bidirectional self._forget_bias = forget_bias - def _init_weight(self, desc, arr): + def _init_weight(self, desc, arr): # pylint: disable=arguments-differ from .rnn import rnn_cell cell = rnn_cell.FusedRNNCell(self._num_hidden, self._num_layers, self._mode, self._bidirectional, diff --git a/python/mxnet/io.py b/python/mxnet/io.py index 28e220d16ed5..0404e34ea36c 100644 --- a/python/mxnet/io.py +++ b/python/mxnet/io.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + """Data iterators for common data formats.""" from __future__ import absolute_import from collections import OrderedDict, namedtuple @@ -6,6 +23,10 @@ import ctypes import logging import threading +try: + import h5py +except ImportError: + h5py = None import numpy as np from .base import _LIB from .base import c_array, c_str, mx_uint, py_str @@ -25,7 +46,7 @@ class DataDesc(namedtuple('DataDesc', ['name', 'shape'])): that the first axis is number of examples in the batch(N), C is number of channels, H is the height and W is the width of the image. - for sequential data, by default `layout` is set to ``NTC`` where + For sequential data, by default `layout` is set to ``NTC``, where N is number of examples in the batch, T the temporal axis representing time and C is the number of channels. @@ -42,7 +63,7 @@ class DataDesc(namedtuple('DataDesc', ['name', 'shape'])): layout : str, optional Data layout. """ - def __new__(cls, name, shape, dtype=mx_real_t, layout='NCHW'): + def __new__(cls, name, shape, dtype=mx_real_t, layout='NCHW'): # pylint: disable=super-on-old-class ret = super(cls, DataDesc).__new__(cls, name, shape) ret.dtype = dtype ret.layout = layout @@ -465,7 +486,8 @@ def _init_data(data, allow_empty, default_name): if data is None: data = [] - if isinstance(data, (np.ndarray, NDArray)): + if isinstance(data, (np.ndarray, NDArray, h5py.Dataset) + if h5py else (np.ndarray, NDArray)): data = [data] if isinstance(data, list): if not allow_empty: @@ -476,20 +498,20 @@ def _init_data(data, allow_empty, default_name): data = OrderedDict( # pylint: disable=redefined-variable-type [('_%d_%s' % (i, default_name), d) for i, d in enumerate(data)]) if not isinstance(data, dict): - raise TypeError("Input must be NDArray, numpy.ndarray, " + \ + raise TypeError("Input must be NDArray, numpy.ndarray, h5py.Dataset " + \ "a list of them or dict with them as values") for k, v in data.items(): - if not isinstance(v, NDArray): + if not isinstance(v, (NDArray, h5py.Dataset) if h5py else NDArray): try: data[k] = array(v) except: raise TypeError(("Invalid type '%s' for %s, " % (type(v), k)) + \ - "should be NDArray or numpy.ndarray") + "should be NDArray, numpy.ndarray or h5py.Dataset") return list(data.items()) class NDArrayIter(DataIter): - """Returns an iterator for ``mx.nd.NDArray`` or ``numpy.ndarray``. + """Returns an iterator for ``mx.nd.NDArray``, ``numpy.ndarray`` or ``h5py.Dataset``. Example usage: ---------- @@ -562,6 +584,7 @@ class NDArrayIter(DataIter): Batch size of data. shuffle: bool, optional Whether to shuffle the data. + Only supported if no h5py.Dataset inputs are used. last_batch_handle : str, optional How to handle the last batch. This parameter can be 'pad', 'discard' or 'roll_over'. 'roll_over' is intended for training and can cause problems @@ -579,30 +602,29 @@ def __init__(self, data, label=None, batch_size=1, shuffle=False, self.data = _init_data(data, allow_empty=False, default_name=data_name) self.label = _init_data(label, allow_empty=True, default_name=label_name) + self.idx = np.arange(self.data[0][1].shape[0]) # shuffle data if shuffle: - idx = np.arange(self.data[0][1].shape[0]) - np.random.shuffle(idx) - self.data = [(k, array(v.asnumpy()[idx], v.context)) for k, v in self.data] - self.label = [(k, array(v.asnumpy()[idx], v.context)) for k, v in self.label] + np.random.shuffle(self.idx) + self.data = [(k, array(v.asnumpy()[self.idx], v.context)) + if not (isinstance(v, h5py.Dataset) + if h5py else False) else (k, v) + for k, v in self.data] + self.label = [(k, array(v.asnumpy()[self.idx], v.context)) + if not (isinstance(v, h5py.Dataset) + if h5py else False) else (k, v) + for k, v in self.label] # batching if last_batch_handle == 'discard': new_n = self.data[0][1].shape[0] - self.data[0][1].shape[0] % batch_size - data_dict = OrderedDict(self.data) - label_dict = OrderedDict(self.label) - for k, _ in self.data: - data_dict[k] = data_dict[k][:new_n] - for k, _ in self.label: - label_dict[k] = label_dict[k][:new_n] - self.data = data_dict.items() - self.label = label_dict.items() + self.idx = self.idx[:new_n] self.data_list = [x[1] for x in self.data] + [x[1] for x in self.label] self.num_source = len(self.data_list) - self.num_data = self.data_list[0].shape[0] + self.num_data = self.idx.shape[0] assert self.num_data >= batch_size, \ - "batch_size need to be smaller than data size." + "batch_size needs to be smaller than data size." self.cursor = -batch_size self.batch_size = batch_size self.last_batch_handle = last_batch_handle @@ -648,10 +670,37 @@ def _getdata(self, data_source): """Load data from underlying arrays, internal use only.""" assert(self.cursor < self.num_data), "DataIter needs reset." if self.cursor + self.batch_size <= self.num_data: - return [x[1][self.cursor:self.cursor+self.batch_size] for x in data_source] + return [ + # np.ndarray or NDArray case + x[1][self.cursor:self.cursor + self.batch_size] + if isinstance(x[1], (np.ndarray, NDArray)) else + # h5py (only supports indices in increasing order) + array(x[1][sorted(self.idx[ + self.cursor:self.cursor + self.batch_size])][[ + list(self.idx[self.cursor: + self.cursor + self.batch_size]).index(i) + for i in sorted(self.idx[ + self.cursor:self.cursor + self.batch_size]) + ]]) for x in data_source + ] else: pad = self.batch_size - self.num_data + self.cursor - return [concatenate([x[1][self.cursor:], x[1][:pad]]) for x in data_source] + return [ + # np.ndarray or NDArray case + concatenate([x[1][self.cursor:], x[1][:pad]]) + if isinstance(x[1], (np.ndarray, NDArray)) else + # h5py (only supports indices in increasing order) + concatenate([ + array(x[1][sorted(self.idx[self.cursor:])][[ + list(self.idx[self.cursor:]).index(i) + for i in sorted(self.idx[self.cursor:]) + ]]), + array(x[1][sorted(self.idx[:pad])][[ + list(self.idx[:pad]).index(i) + for i in sorted(self.idx[:pad]) + ]]) + ]) for x in data_source + ] def getdata(self): return self._getdata(self.data) @@ -670,10 +719,28 @@ def getpad(self): class MXDataIter(DataIter): """A python wrapper a C++ data iterator. + This iterator is the Python wrapper to all native C++ data iterators, such + as `CSVIter, `ImageRecordIter`, `MNISTIter`, etc. When initializing + `CSVIter` for example, you will get an `MXDataIter` instance to use in your + Python code. Calls to `next`, `reset`, etc will be delegated to the + underlying C++ data iterators. + + Usually you don't need to interact with `MXDataIter` directly unless you are + implementing your own data iterators in C++. To do that, please refer to + examples under the `src/io` folder. + Parameters ---------- - handle : DataIterHandle + handle : DataIterHandle, required The handle to the underlying C++ Data Iterator. + data_name : str, optional + Data name. Default to "data". + label_name : str, optional + Label name. Default to "softmax_label". + + See Also + -------- + src/io : The underlying C++ data iterator implementation, e.g., `CSVIter`. """ def __init__(self, handle, data_name='data', label_name='softmax_label', **_): super(MXDataIter, self).__init__() @@ -692,7 +759,6 @@ def __init__(self, handle, data_name='data', label_name='softmax_label', **_): self.provide_label = [DataDesc(label_name, label.shape, label.dtype)] self.batch_size = data.shape[0] - def __del__(self): check_call(_LIB.MXDataIterFree(self.handle)) diff --git a/python/mxnet/kvstore.py b/python/mxnet/kvstore.py index 7ea7c748dbdf..fd0091182aea 100644 --- a/python/mxnet/kvstore.py +++ b/python/mxnet/kvstore.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # coding: utf-8 """ Key value store interface of MXNet for parameter synchronization.""" from __future__ import absolute_import @@ -11,30 +28,26 @@ from . import optimizer as opt def _ctype_key_value(keys, vals): - """ - Returns ctype arrays for the key-value args. For internal use. - """ - if isinstance(keys, int): - if isinstance(vals, NDArray): - return (c_array(ctypes.c_int, [keys]), - c_array(NDArrayHandle, [vals.handle])) - else: - for value in vals: - assert(isinstance(value, NDArray)) - return (c_array(ctypes.c_int, [keys] * len(vals)), - c_array(NDArrayHandle, [value.handle for value in vals])) - else: + if isinstance(keys, (tuple, list)): assert(len(keys) == len(vals)) - for k in keys: - assert(isinstance(k, int)) c_keys = [] c_vals = [] for key, val in zip(keys, vals): c_key_i, c_val_i = _ctype_key_value(key, val) c_keys += c_key_i c_vals += c_val_i - return (c_array(ctypes.c_int, c_keys), c_array(NDArrayHandle, c_vals)) - + return (c_array(ctypes.c_char_p, c_keys), c_array(NDArrayHandle, c_vals)) + names = [] + keys = str(keys) + if isinstance(vals, NDArray): + names.append(c_str(keys)) + return (c_array(ctypes.c_char_p, names), + c_array(NDArrayHandle, [vals.handle])) + else: + for value in vals: + assert(isinstance(value, NDArray)) + return (c_array(ctypes.c_char_p, [c_str(keys)] * len(vals)), + c_array(NDArrayHandle, [value.handle for value in vals])) def _updater_wrapper(updater): """A wrapper for the user-defined handle.""" @@ -74,7 +87,7 @@ def init(self, key, value): Parameters ---------- - key : int or sequence of int + key : str or sequence of str The keys. value : NDArray or sequence of NDArray Values corresponding to the keys. @@ -84,20 +97,19 @@ def init(self, key, value): >>> # init a single key-value pair >>> shape = (2,3) >>> kv = mx.kv.create('local') - >>> kv.init(3, mx.nd.ones(shape)*2) + >>> kv.init('3', mx.nd.ones(shape)*2) >>> a = mx.nd.zeros(shape) - >>> kv.pull(3, out=a) + >>> kv.pull('3', out=a) >>> print a.asnumpy() [[ 2. 2. 2.] [ 2. 2. 2.]] >>> # init a list of key-value pairs - >>> keys = [5, 7, 9] + >>> keys = ['5', '7', '9'] >>> kv.init(keys, [mx.nd.ones(shape)]*len(keys)) """ ckeys, cvals = _ctype_key_value(key, value) - check_call(_LIB.MXKVStoreInit( - self.handle, mx_uint(len(ckeys)), ckeys, cvals)) + check_call(_LIB.MXKVStoreInitEx(self.handle, mx_uint(len(ckeys)), ckeys, cvals)) def push(self, key, value, priority=0): """ Pushes a single or a sequence of key-value pairs into the store. @@ -110,7 +122,7 @@ def push(self, key, value, priority=0): Parameters ---------- - key : int or list of int + key : str or list of str Keys. value : NDArray or list of NDArray or list of list of NDArray @@ -124,8 +136,8 @@ def push(self, key, value, priority=0): Examples -------- >>> # push a single key-value pair - >>> kv.push(3, mx.nd.ones(shape)*8) - >>> kv.pull(3, out=a) # pull out the value + >>> kv.push('3', mx.nd.ones(shape)*8) + >>> kv.pull('3', out=a) # pull out the value >>> print a.asnumpy() [[ 8. 8. 8.] [ 8. 8. 8.]] @@ -133,8 +145,8 @@ def push(self, key, value, priority=0): >>> # aggregate the value and the push >>> gpus = [mx.gpu(i) for i in range(4)] >>> b = [mx.nd.ones(shape, gpu) for gpu in gpus] - >>> kv.push(3, b) - >>> kv.pull(3, out=a) + >>> kv.push('3', b) + >>> kv.pull('3', out=a) >>> print a.asnumpy() [[ 4. 4. 4.] [ 4. 4. 4.]] @@ -157,10 +169,11 @@ def push(self, key, value, priority=0): [ 4. 4. 4.]] """ ckeys, cvals = _ctype_key_value(key, value) - check_call(_LIB.MXKVStorePush( + check_call(_LIB.MXKVStorePushEx( self.handle, mx_uint(len(ckeys)), ckeys, cvals, ctypes.c_int(priority))) + def pull(self, key, out=None, priority=0): """ Pulls a single value or a sequence of values from the store. @@ -190,21 +203,21 @@ def pull(self, key, out=None, priority=0): -------- >>> # pull a single key-value pair >>> a = mx.nd.zeros(shape) - >>> kv.pull(3, out=a) + >>> kv.pull('3', out=a) >>> print a.asnumpy() [[ 2. 2. 2.] [ 2. 2. 2.]] >>> # pull into multiple devices >>> b = [mx.nd.ones(shape, gpu) for gpu in gpus] - >>> kv.pull(3, out=b) + >>> kv.pull('3', out=b) >>> print b[1].asnumpy() [[ 2. 2. 2.] [ 2. 2. 2.]] >>> # pull a list of key-value pairs. >>> # On single device - >>> keys = [5, 7, 9] + >>> keys = ['5', '7', '9'] >>> b = [mx.nd.zeros(shape)]*len(keys) >>> kv.pull(keys, out=b) >>> print b[1].asnumpy() @@ -219,7 +232,7 @@ def pull(self, key, out=None, priority=0): """ assert(out is not None) ckeys, cvals = _ctype_key_value(key, out) - check_call(_LIB.MXKVStorePull( + check_call(_LIB.MXKVStorePullEx( self.handle, mx_uint(len(ckeys)), ckeys, cvals, ctypes.c_int(priority))) @@ -248,7 +261,7 @@ def set_optimizer(self, optimizer): >>> grad = mx.nd.ones(shape) >>> kv.push(3, grad) >>> kv.pull(3, out = weight) - >>> # weight is updated via gradient descient + >>> # weight is updated via gradient descent >>> weight.asnumpy() array([[-0.01, -0.01], [-0.01, -0.01]], dtype=float32) @@ -348,13 +361,13 @@ def _set_updater(self, updater): ... print "update on key: %d" % key ... stored += input * 2 >>> kv._set_updater(update) - >>> kv.pull(3, out=a) + >>> kv.pull('3', out=a) >>> print a.asnumpy() [[ 4. 4. 4.] [ 4. 4. 4.]] - >>> kv.push(3, mx.nd.ones(shape)) + >>> kv.push('3', mx.nd.ones(shape)) update on key: 3 - >>> kv.pull(3, out=a) + >>> kv.pull('3', out=a) >>> print a.asnumpy() [[ 6. 6. 6.] [ 6. 6. 6.]] diff --git a/python/mxnet/kvstore_server.py b/python/mxnet/kvstore_server.py index c6d0b073f824..1bb995a45ca8 100644 --- a/python/mxnet/kvstore_server.py +++ b/python/mxnet/kvstore_server.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # coding: utf-8 """A server node for the key value store.""" from __future__ import absolute_import diff --git a/python/mxnet/libinfo.py b/python/mxnet/libinfo.py index 5689a106c4eb..7da0dcfc8d2d 100644 --- a/python/mxnet/libinfo.py +++ b/python/mxnet/libinfo.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # coding: utf-8 """Information about mxnet.""" from __future__ import absolute_import @@ -44,4 +61,4 @@ def find_lib_path(): # current version -__version__ = "0.9.5" +__version__ = "0.11.0" diff --git a/python/mxnet/log.py b/python/mxnet/log.py index 46d97f6c390c..6dcaedbe6fe7 100644 --- a/python/mxnet/log.py +++ b/python/mxnet/log.py @@ -1,4 +1,22 @@ #!/usr/bin/env python + +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # -*- coding: utf-8 -*- # pylint: disable= protected-access, invalid-name """Logging utilities.""" diff --git a/python/mxnet/lr_scheduler.py b/python/mxnet/lr_scheduler.py index ec410d9d5093..e27c1d47dbb6 100644 --- a/python/mxnet/lr_scheduler.py +++ b/python/mxnet/lr_scheduler.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + """Scheduling learning rate.""" import logging import math @@ -22,7 +39,7 @@ def __call__(self, num_update): The ``num_update`` is the upper bound of the number of updates applied to every weight. - Assume the optimizer has udpated *i*-th weight by *k_i* times, namely + Assume the optimizer has updated *i*-th weight by *k_i* times, namely ``optimizer.update(i, weight_i)`` is called by *k_i* times. Then:: num_update = max([k_i for all i]) diff --git a/python/mxnet/metric.py b/python/mxnet/metric.py index 88ce97198000..00cc2da61f3c 100644 --- a/python/mxnet/metric.py +++ b/python/mxnet/metric.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # coding: utf-8 # pylint: disable=no-member, too-many-lines @@ -81,12 +98,12 @@ def update_dict(self, label, pred): if self.output_names is not None: pred = [pred[name] for name in self.output_names] else: - pred = pred.values() + pred = list(pred.values()) if self.label_names is not None: label = [label[name] for name in self.label_names] else: - label = label.values() + label = list(label.values()) self.update(label, pred) @@ -251,7 +268,7 @@ def get_metric(self, index): return ValueError("Metric index {} is out of range 0 and {}".format( index, len(self.metrics))) - def update_dict(self, labels, preds): + def update_dict(self, labels, preds): # pylint: disable=arguments-differ if self.label_names is not None: labels = OrderedDict([i for i in labels.items() if i[0] in self.label_names]) @@ -461,7 +478,7 @@ def update(self, labels, preds): class F1(EvalMetric): """Computes the F1 score of a binary classification problem. - The F1 score is equvalent to weighted average of the precision and recall, + The F1 score is equivalent to weighted average of the precision and recall, where the best value is 1.0 and the worst value is 0.0. The formula for F1 score is:: F1 = 2 * (precision * recall) / (precision + recall) @@ -634,7 +651,7 @@ def update(self, labels, preds): label = label.as_in_context(pred.context).reshape((label.size,)) pred = ndarray.pick(pred, label.astype(dtype='int32'), axis=self.axis) if self.ignore_label is not None: - ignore = label == self.ignore_label + ignore = (label == self.ignore_label).astype(pred.dtype) num -= ndarray.sum(ignore).asscalar() pred = pred*(1-ignore) + ignore loss -= ndarray.sum(ndarray.log(ndarray.maximum(1e-10, pred))).asscalar() @@ -837,10 +854,14 @@ def update(self, labels, preds): class CrossEntropy(EvalMetric): """Computes Cross Entropy loss. - The cross entropy is given by + The cross entropy over a batch of sample size :math:`N` is given by .. math:: - -y\\log \\hat{y} + (1-y)\\log (1-\\hat{y}) + -\\sum_{n=1}^{N}\\sum_{k=1}^{K}t_{nk}\\log (y_{nk}), + + where :math:`t_{nk}=1` if and only if sample :math:`n` belongs to class :math:`k`. + :math:`y_{nk}` denotes the probability of sample :math:`n` belonging to + class :math:`k`. Parameters ---------- @@ -897,6 +918,60 @@ def update(self, labels, preds): self.num_inst += label.shape[0] +@register +@alias('pearsonr') +class PearsonCorrelation(EvalMetric): + """Computes Pearson correlation. + + The pearson correlation is given by + + .. math:: + \\frac{cov(y, \\hat{y})}{\\sigma{y}\\sigma{\\hat{y}}} + + Parameters + ---------- + name : str + Name of this metric instance for display. + output_names : list of str, or None + Name of predictions that should be used when updating with update_dict. + By default include all predictions. + label_names : list of str, or None + Name of labels that should be used when updating with update_dict. + By default include all labels. + + Examples + -------- + >>> predicts = [mx.nd.array([[0.3, 0.7], [0, 1.], [0.4, 0.6]])] + >>> labels = [mx.nd.array([[1, 0], [0, 1], [0, 1]])] + >>> pr = mx.metric.PearsonCorrelation() + >>> pr.update(labels, predicts) + >>> print pr.get() + ('pearson-correlation', 0.42163704544016178) + """ + def __init__(self, name='pearsonr', + output_names=None, label_names=None): + super(PearsonCorrelation, self).__init__( + name, output_names=output_names, label_names=label_names) + + def update(self, labels, preds): + """Updates the internal evaluation result. + + Parameters + ---------- + labels : list of `NDArray` + The labels of the data. + preds : list of `NDArray` + Predicted values. + """ + check_label_shapes(labels, preds) + for label, pred in zip(labels, preds): + check_label_shapes(label, pred, 1) + label = label.asnumpy() + pred = pred.asnumpy() + self.sum_metric += numpy.corrcoef(pred.ravel(), label.ravel())[0, 1] + self.num_inst += 1 + + @register class Loss(EvalMetric): """Dummy metric for directly printing loss. diff --git a/python/mxnet/misc.py b/python/mxnet/misc.py index b158981ecf97..13b7dc23b5f6 100644 --- a/python/mxnet/misc.py +++ b/python/mxnet/misc.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # pylint: disable=invalid-name """Learning rate scheduler.""" diff --git a/python/mxnet/model.py b/python/mxnet/model.py index 5eddfac47981..01b3fa50e18f 100644 --- a/python/mxnet/model.py +++ b/python/mxnet/model.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # pylint: disable=fixme, invalid-name, too-many-arguments, too-many-locals, too-many-lines # pylint: disable=too-many-branches, too-many-statements """MXNet model module""" @@ -62,7 +79,7 @@ def _create_kvstore(kvstore, num_device, arg_params): kv = None else: kv = kvs.create(kvstore) - if kvstore is 'local': + if kvstore == 'local': # automatically select a proper local max_size = max(np.prod(param.shape) for param in arg_params.values()) @@ -80,34 +97,37 @@ def _initialize_kvstore(kvstore, param_arrays, arg_params, param_names, update_on_kvstore): """Initialize kvstore""" for idx, param_on_devs in enumerate(param_arrays): - kvstore.init(idx, arg_params[param_names[idx]]) + name = param_names[idx] + kvstore.init(name, arg_params[name]) if update_on_kvstore: - kvstore.pull(idx, param_on_devs, priority=-idx) + kvstore.pull(name, param_on_devs, priority=-idx) -def _update_params_on_kvstore(param_arrays, grad_arrays, kvstore): +def _update_params_on_kvstore(param_arrays, grad_arrays, kvstore, param_names): """Perform update of param_arrays from grad_arrays on kvstore.""" for index, pair in enumerate(zip(param_arrays, grad_arrays)): arg_list, grad_list = pair if grad_list[0] is None: continue + name = param_names[index] # push gradient, priority is negative index - kvstore.push(index, grad_list, priority=-index) + kvstore.push(name, grad_list, priority=-index) # pull back the weights - kvstore.pull(index, arg_list, priority=-index) + kvstore.pull(name, arg_list, priority=-index) def _update_params(param_arrays, grad_arrays, updater, num_device, - kvstore=None): + kvstore=None, param_names=None): """Perform update of param_arrays from grad_arrays not on kvstore.""" for index, pair in enumerate(zip(param_arrays, grad_arrays)): arg_list, grad_list = pair if grad_list[0] is None: continue if kvstore: + name = param_names[index] # push gradient, priority is negative index - kvstore.push(index, grad_list, priority=-index) + kvstore.push(name, grad_list, priority=-index) # pull back the sum gradients, to the same locations. - kvstore.pull(index, grad_list, priority=-index) + kvstore.pull(name, grad_list, priority=-index) for k, p in enumerate(zip(arg_list, grad_list)): # faked an index here, to make optimizer create diff # state for the same index but on diff devs, TODO(mli) @@ -245,13 +265,14 @@ def _train_multi_device(symbol, ctx, arg_names, param_names, aux_names, if update_on_kvstore: _update_params_on_kvstore(executor_manager.param_arrays, executor_manager.grad_arrays, - kvstore) + kvstore, executor_manager.param_names) else: _update_params(executor_manager.param_arrays, executor_manager.grad_arrays, updater=updater, num_device=len(ctx), - kvstore=kvstore) + kvstore=kvstore, + param_names=executor_manager.param_names) if monitor is not None: monitor.toc_print() @@ -909,7 +930,7 @@ def create(symbol, X, y=None, ctx=None, ``ceil(num_train_examples / batch_size)``. optimizer : str or Optimizer, optional The name of the chosen optimizer, or an optimizer object, used for training. - initializier : initializer function, optional + initializer : initializer function, optional The initialization scheme used. eval_data : DataIter or numpy.ndarray pair If `eval_set` is ``numpy.ndarray`` pair, it should @@ -925,7 +946,7 @@ def create(symbol, X, y=None, ctx=None, A callback that is invoked at end of each batch for print purposes. kvstore: KVStore or str, optional The KVStore or a string kvstore type: 'local', 'dist_sync', 'dis_async'. - Defaults to 'local', often no need to change for single machiine. + Defaults to 'local', often no need to change for single machine. logger : logging logger, optional When not specified, default logger will be used. work_load_list : list of float or int, optional diff --git a/python/mxnet/module/__init__.py b/python/mxnet/module/__init__.py index 916413782a17..32ecbb9c8be3 100644 --- a/python/mxnet/module/__init__.py +++ b/python/mxnet/module/__init__.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + """A module is like a FeedForward model. But we would like to make it easier to compose, similar to Torch modules. """ diff --git a/python/mxnet/module/base_module.py b/python/mxnet/module/base_module.py index 6e0c92731246..3123462f9c7c 100644 --- a/python/mxnet/module/base_module.py +++ b/python/mxnet/module/base_module.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # pylint: disable=fixme, too-many-arguments, too-many-locals, too-many-public-methods, too-many-branches """`BaseModule` defines an API for modules.""" @@ -12,23 +29,7 @@ from ..model import BatchEndParam from ..initializer import Uniform from ..io import DataDesc - - -def _as_list(obj): - """A utility function that treat the argument as a list. - - Parameters - ---------- - obj : object - - Returns - ------- - If `obj` is a list, return it. Otherwise, return `[obj]` as a single-element list. - """ - if isinstance(obj, list): - return obj - else: - return [obj] +from ..base import _as_list def _check_input_names(symbol, names, typename, throw): @@ -55,7 +56,7 @@ def _check_input_names(symbol, names, typename, throw): def _check_names_match(data_names, data_shapes, name, throw): """Check that input names matches input data descriptors.""" actual = [x[0] for x in data_shapes] - if data_names != actual: + if sorted(data_names) != sorted(actual): msg = "Data provided by %s_shapes don't match names specified by %s_names (%s vs. %s)"%( name, name, str(data_shapes), str(data_names)) if throw: @@ -161,18 +162,16 @@ class BaseModule(object): Examples -------- - An example of creating a mxnet module:: - >>> import mxnet as mx - - >>> data = mx.symbol.Variable('data') - >>> fc1 = mx.symbol.FullyConnected(data, name='fc1', num_hidden=128) - >>> act1 = mx.symbol.Activation(fc1, name='relu1', act_type="relu") - >>> fc2 = mx.symbol.FullyConnected(act1, name = 'fc2', num_hidden = 64) - >>> act2 = mx.symbol.Activation(fc2, name='relu2', act_type="relu") - >>> fc3 = mx.symbol.FullyConnected(act2, name='fc3', num_hidden=10) - >>> out = mx.symbol.SoftmaxOutput(fc3, name = 'softmax') - - >>> mod = mx.mod.Module(out) + >>> # An example of creating a mxnet module. + >>> import mxnet as mx + >>> data = mx.symbol.Variable('data') + >>> fc1 = mx.symbol.FullyConnected(data, name='fc1', num_hidden=128) + >>> act1 = mx.symbol.Activation(fc1, name='relu1', act_type="relu") + >>> fc2 = mx.symbol.FullyConnected(act1, name = 'fc2', num_hidden = 64) + >>> act2 = mx.symbol.Activation(fc2, name='relu2', act_type="relu") + >>> fc3 = mx.symbol.FullyConnected(act2, name='fc3', num_hidden=10) + >>> out = mx.symbol.SoftmaxOutput(fc3, name = 'softmax') + >>> mod = mx.mod.Module(out) """ def __init__(self, logger=logging): self.logger = logger @@ -221,11 +220,11 @@ def score(self, eval_data, eval_metric, num_batch=None, batch_end_callback=None, Examples -------- - An example of using score for prediction:: - >>> # Evaluate accuracy on val_dataiter - >>> metric = mx.metric.Accuracy() - >>> mod.score(val_dataiter, metric) - >>> mod.score(val_dataiter, ['mse', 'acc']) + >>> # An example of using score for prediction. + >>> # Evaluate accuracy on val_dataiter + >>> metric = mx.metric.Accuracy() + >>> mod.score(val_dataiter, metric) + >>> mod.score(val_dataiter, ['mse', 'acc']) """ assert self.binded and self.params_initialized @@ -270,9 +269,9 @@ def iter_predict(self, eval_data, num_batch=None, reset=True): Example Usage: ---------- >>> for pred, i_batch, batch in module.iter_predict(eval_data): - >>> # pred is a list of outputs from the module - >>> # i_batch is a integer - >>> # batch is the data batch from the data iterator + ... # pred is a list of outputs from the module + ... # i_batch is a integer + ... # batch is the data batch from the data iterator Parameters ---------- @@ -336,9 +335,9 @@ def predict(self, eval_data, num_batch=None, merge_batches=True, reset=True, Examples -------- - An example of using `predict` for prediction:: - >>> # Predict on the first 10 batches of val_dataiter - >>> mod.predict(eval_data=val_dataiter, num_batch=10) + >>> # An example of using `predict` for prediction. + >>> # Predict on the first 10 batches of val_dataiter + >>> mod.predict(eval_data=val_dataiter, num_batch=10) """ assert self.binded and self.params_initialized @@ -446,14 +445,14 @@ def fit(self, train_data, eval_data=None, eval_metric='acc', Examples -------- - An example of using fit for training:: - >>> #Assume training dataIter and validation dataIter are ready - >>> #Assume loading a previously checkpointed model - >>> sym, arg_params, aux_params = mx.model.load_checkpoint(model_prefix, 3) - >>> mod.fit(train_data=train_dataiter, eval_data=val_dataiter, optimizer='sgd', - optimizer_params={'learning_rate':0.01, 'momentum': 0.9}, - arg_params=arg_params, aux_params=aux_params, - eval_metric='acc', num_epoch=10, begin_epoch=3) + >>> # An example of using fit for training. + >>> # Assume training dataIter and validation dataIter are ready + >>> # Assume loading a previously checkpointed model + >>> sym, arg_params, aux_params = mx.model.load_checkpoint(model_prefix, 3) + >>> mod.fit(train_data=train_dataiter, eval_data=val_dataiter, optimizer='sgd', + ... optimizer_params={'learning_rate':0.01, 'momentum': 0.9}, + ... arg_params=arg_params, aux_params=aux_params, + ... eval_metric='acc', num_epoch=10, begin_epoch=3) """ assert num_epoch is not None, 'please specify number of epochs' @@ -573,7 +572,7 @@ def output_shapes(self): # Parameters of a module ################################################################################ def get_params(self): - """Get parameters, those are potentially copies of the the actual parameters used + """Gets parameters, those are potentially copies of the the actual parameters used to do computation on the device. Returns @@ -583,17 +582,17 @@ def get_params(self): Examples -------- - An example of getting module parameters:: - >>> print mod.get_params() - ({'fc2_weight': , 'fc1_weight': , - 'fc3_bias': , 'fc3_weight': , - 'fc2_bias': , 'fc1_bias': }, {}) + >>> # An example of getting module parameters. + >>> print mod.get_params() + ({'fc2_weight': , 'fc1_weight': , + 'fc3_bias': , 'fc3_weight': , + 'fc2_bias': , 'fc1_bias': }, {}) """ raise NotImplementedError() def init_params(self, initializer=Uniform(0.01), arg_params=None, aux_params=None, - allow_missing=False, force_init=False): - """Initialize the parameters and auxiliary states. + allow_missing=False, force_init=False, allow_extra=False): + """Initializes the parameters and auxiliary states. Parameters ---------- @@ -610,16 +609,21 @@ def init_params(self, initializer=Uniform(0.01), arg_params=None, aux_params=Non called to fill those missing params. force_init : bool If ``True``, `force_init` will force re-initialize even if already initialized. + allow_extra : boolean, optional + Whether allow extra parameters that are not needed by symbol. + If this is True, no error will be thrown when arg_params or aux_params + contain extra parameters that is not needed by the executor. Examples -------- - An example of initializing module parameters:: - >>> mod.init_params() + >>> # An example of initializing module parameters. + >>> mod.init_params() """ raise NotImplementedError() - def set_params(self, arg_params, aux_params, allow_missing=False, force_init=True): - """Assign parameter and aux state values. + def set_params(self, arg_params, aux_params, allow_missing=False, force_init=True, + allow_extra=False): + """Assigns parameter and aux state values. Parameters ---------- @@ -632,18 +636,23 @@ def set_params(self, arg_params, aux_params, allow_missing=False, force_init=Tru called to fill those missing params. force_init : bool If ``True``, will force re-initialize even if already initialized. + allow_extra : boolean, optional + Whether allow extra parameters that are not needed by symbol. + If this is True, no error will be thrown when arg_params or aux_params + contain extra parameters that is not needed by the executor. Examples -------- - An example of setting module parameters:: - >>> sym, arg_params, aux_params = mx.model.load_checkpoint(model_prefix, n_epoch_load) - >>> mod.set_params(arg_params=arg_params, aux_params=aux_params) + >>> # An example of setting module parameters. + >>> sym, arg_params, aux_params = mx.model.load_checkpoint(model_prefix, n_epoch_load) + >>> mod.set_params(arg_params=arg_params, aux_params=aux_params) """ self.init_params(initializer=None, arg_params=arg_params, aux_params=aux_params, - allow_missing=allow_missing, force_init=force_init) + allow_missing=allow_missing, force_init=force_init, + allow_extra=allow_extra) def save_params(self, fname): - """Save model parameters to file. + """Saves model parameters to file. Parameters ---------- @@ -652,8 +661,8 @@ def save_params(self, fname): Examples -------- - An example of saving module parameters:: - >>> mod.save_params('myfile') + >>> # An example of saving module parameters. + >>> mod.save_params('myfile') """ arg_params, aux_params = self.get_params() save_dict = {('arg:%s' % k) : v.as_in_context(cpu()) for k, v in arg_params.items()} @@ -661,7 +670,7 @@ def save_params(self, fname): ndarray.save(fname, save_dict) def load_params(self, fname): - """Load model parameters from file. + """Loads model parameters from file. Parameters ---------- @@ -670,8 +679,8 @@ def load_params(self, fname): Examples -------- - An example of loading module parameters - >>> mod.load_params('myfile') + >>> # An example of loading module parameters. + >>> mod.load_params('myfile') """ save_dict = ndarray.load(fname) arg_params = {} @@ -687,7 +696,7 @@ def load_params(self, fname): self.set_params(arg_params, aux_params) def get_states(self, merge_multi_context=True): - """Get states from all devices + """Gets states from all devices If `merge_multi_context` is ``True``, returns output of form ``[out1, out2]``. Otherwise, it returns output of the form @@ -711,7 +720,7 @@ def get_states(self, merge_multi_context=True): return [] def set_states(self, states=None, value=None): - """Set value for states. Only one of states & value can be specified. + """Sets value for states. Only one of states & value can be specified. Parameters ---------- @@ -725,14 +734,14 @@ def set_states(self, states=None, value=None): assert not states and not value def install_monitor(self, mon): - """Install monitor on all executors.""" + """Installs monitor on all executors.""" raise NotImplementedError() ################################################################################ # Computations ################################################################################ def prepare(self, data_batch): - '''Prepare the module for processing a data batch. + '''Prepares the module for processing a data batch. Usually involves switching bucket and reshaping. @@ -743,7 +752,11 @@ def prepare(self, data_batch): pass def forward(self, data_batch, is_train=None): - """Forward computation. + """Forward computation. It supports data batches with different shapes, such as + different batch sizes or different image sizes. + If reshaping of data batch relates to modification of symbol or module, such as + changing image layout ordering or switching from training to predicting, module + rebinding is required. Parameters ---------- @@ -754,18 +767,25 @@ def forward(self, data_batch, is_train=None): Examples -------- - An example of forward computation:: - >>> from collections import namedtuple - >>> Batch = namedtuple('Batch', ['data']) - - >>> mod.bind(data_shapes=[('data', (1, 10, 10))]) - >>> mod.init_params() - - >>> data1 = [mx.nd.ones([1, 10, 10])] - >>> mod.forward(Batch(data1)) - >>> print mod.get_outputs()[0].asnumpy() - [[ 0.09999977 0.10000153 0.10000716 0.10000195 0.09999853 0.09999743 - 0.10000272 0.10000113 0.09999088 0.09999888]] + >>> import mxnet as mx + >>> from collections import namedtuple + >>> Batch = namedtuple('Batch', ['data']) + >>> data = mx.sym.Variable('data') + >>> out = data * 2 + >>> mod = mx.mod.Module(symbol=out, label_names=None) + >>> mod.bind(data_shapes=[('data', (1, 10))]) + >>> mod.init_params() + >>> data1 = [mx.nd.ones((1, 10))] + >>> mod.forward(Batch(data1)) + >>> print mod.get_outputs()[0].asnumpy() + [[ 2. 2. 2. 2. 2. 2. 2. 2. 2. 2.]] + >>> # Forward with data batch of different shape + >>> data2 = [mx.nd.ones((3, 5))] + >>> mod.forward(Batch(data2)) + >>> print mod.get_outputs()[0].asnumpy() + [[ 2. 2. 2. 2. 2.] + [ 2. 2. 2. 2. 2.] + [ 2. 2. 2. 2. 2.]] """ raise NotImplementedError() @@ -781,18 +801,18 @@ def backward(self, out_grads=None): Examples -------- - An example of backward computation:: - >>> mod.backward() - >>> print mod.get_input_grads()[0].asnumpy() - [[[ 1.10182791e-05 5.12257748e-06 4.01927764e-06 8.32566820e-06 - -1.59775993e-06 7.24269375e-06 7.28067835e-06 -1.65902311e-05 - 5.46342608e-06 8.44196393e-07] - ...]] + >>> # An example of backward computation. + >>> mod.backward() + >>> print mod.get_input_grads()[0].asnumpy() + [[[ 1.10182791e-05 5.12257748e-06 4.01927764e-06 8.32566820e-06 + -1.59775993e-06 7.24269375e-06 7.28067835e-06 -1.65902311e-05 + 5.46342608e-06 8.44196393e-07] + ...]] """ raise NotImplementedError() def get_outputs(self, merge_multi_context=True): - """Get outputs of the previous forward computation. + """Gets outputs of the previous forward computation. If `merge_multi_context` is ``True``, it is like ``[out1, out2]``. Otherwise, it returns out put of form ``[[out1_dev1, out1_dev2], [out2_dev1, out2_dev2]]``. @@ -814,15 +834,15 @@ def get_outputs(self, merge_multi_context=True): Examples -------- - An example of getting forward output:: - >>> print mod.get_outputs()[0].asnumpy() - [[ 0.09999977 0.10000153 0.10000716 0.10000195 0.09999853 0.09999743 - 0.10000272 0.10000113 0.09999088 0.09999888]] + >>> # An example of getting forward output. + >>> print mod.get_outputs()[0].asnumpy() + [[ 0.09999977 0.10000153 0.10000716 0.10000195 0.09999853 0.09999743 + 0.10000272 0.10000113 0.09999088 0.09999888]] """ raise NotImplementedError() def get_input_grads(self, merge_multi_context=True): - """Get the gradients to the inputs, computed in the previous backward computation. + """Gets the gradients to the inputs, computed in the previous backward computation. If `merge_multi_context` is ``True``, it is like ``[grad1, grad2]``. Otherwise, it is like ``[[grad1_dev1, grad1_dev2], [grad2_dev1, grad2_dev2]]``. All the output @@ -844,12 +864,12 @@ def get_input_grads(self, merge_multi_context=True): Examples -------- - An example of getting input gradients:: - >>> print mod.get_input_grads()[0].asnumpy() - [[[ 1.10182791e-05 5.12257748e-06 4.01927764e-06 8.32566820e-06 - -1.59775993e-06 7.24269375e-06 7.28067835e-06 -1.65902311e-05 - 5.46342608e-06 8.44196393e-07] - ...]] + >>> # An example of getting input gradients. + >>> print mod.get_input_grads()[0].asnumpy() + [[[ 1.10182791e-05 5.12257748e-06 4.01927764e-06 8.32566820e-06 + -1.59775993e-06 7.24269375e-06 7.28067835e-06 -1.65902311e-05 + 5.46342608e-06 8.44196393e-07] + ...]] """ raise NotImplementedError() @@ -859,16 +879,16 @@ def update(self): Examples -------- - An example of updating module parameters:: - >>> mod.init_optimizer(kvstore='local', optimizer='sgd', - >>> optimizer_params=(('learning_rate', 0.01), )) - >>> mod.backward() - >>> mod.update() - >>> print mod.get_params()[0]['fc3_weight'].asnumpy() - [[ 5.86930104e-03 5.28078526e-03 -8.88729654e-03 -1.08308345e-03 - 6.13054074e-03 4.27560415e-03 1.53817423e-03 4.62131854e-03 - 4.69872449e-03 -2.42400169e-03 9.94111411e-04 1.12386420e-03 - ...]] + >>> # An example of updating module parameters. + >>> mod.init_optimizer(kvstore='local', optimizer='sgd', + ... optimizer_params=(('learning_rate', 0.01), )) + >>> mod.backward() + >>> mod.update() + >>> print mod.get_params()[0]['fc3_weight'].asnumpy() + [[ 5.86930104e-03 5.28078526e-03 -8.88729654e-03 -1.08308345e-03 + 6.13054074e-03 4.27560415e-03 1.53817423e-03 4.62131854e-03 + 4.69872449e-03 -2.42400169e-03 9.94111411e-04 1.12386420e-03 + ...]] """ raise NotImplementedError() @@ -885,9 +905,9 @@ def update_metric(self, eval_metric, labels): Examples -------- - An example of updating evaluation metric:: - >>> mod.forward(data_batch) - >>> mod.update_metric(metric, data_batch.label) + >>> # An example of updating evaluation metric. + >>> mod.forward(data_batch) + >>> mod.update_metric(metric, data_batch.label) """ raise NotImplementedError() @@ -928,10 +948,10 @@ def bind(self, data_shapes, label_shapes=None, for_training=True, Examples -------- - An example of binding symbols:: - >>> mod.bind(data_shapes=[('data', (1, 10, 10))]) - >>> #Assume train_iter is already created. - >>> mod.bind(data_shapes=train_iter.provide_data, label_shapes=train_iter.provide_label) + >>> # An example of binding symbols. + >>> mod.bind(data_shapes=[('data', (1, 10, 10))]) + >>> # Assume train_iter is already created. + >>> mod.bind(data_shapes=train_iter.provide_data, label_shapes=train_iter.provide_label) """ raise NotImplementedError() @@ -954,8 +974,8 @@ def init_optimizer(self, kvstore='local', optimizer='sgd', Examples -------- - An example of initializing optimizer:: - >>> mod.init_optimizer(optimizer='sgd', optimizer_params=(('learning_rate', 0.005),)) + >>> # An example of initializing optimizer. + >>> mod.init_optimizer(optimizer='sgd', optimizer_params=(('learning_rate', 0.005),)) """ raise NotImplementedError() @@ -964,7 +984,7 @@ def init_optimizer(self, kvstore='local', optimizer='sgd', ################################################################################ @property def symbol(self): - """Get the symbol associated with this module. + """Gets the symbol associated with this module. Except for `Module`, for other types of modules (e.g. `BucketingModule`), this property might not be a constant throughout its life time. Some modules might diff --git a/python/mxnet/module/bucketing_module.py b/python/mxnet/module/bucketing_module.py index 836514f66926..6f7ab52ad503 100644 --- a/python/mxnet/module/bucketing_module.py +++ b/python/mxnet/module/bucketing_module.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # pylint: disable=too-many-instance-attributes, too-many-arguments, protected-access # pylint: disable=too-many-public-methods """A `BucketingModule` implement the `BaseModule` API, and allows multiple @@ -97,6 +114,7 @@ def output_names(self): @property def data_shapes(self): """Get data shapes. + Returns ------- A list of `(name, shape)` pairs. @@ -107,18 +125,21 @@ def data_shapes(self): @property def label_shapes(self): """Get label shapes. + Returns ------- - A list of `(name, shape)` pairs. The return value could be ``None`` if - the module does not need labels, or if the module is not bound for - training (in this case, label information is not available). + A list of `(name, shape)` pairs. + The return value could be ``None`` if the module does not need labels, + or if the module is not bound for training (in this case, label information + is not available). """ assert self.binded return self._curr_module.label_shapes @property def output_shapes(self): - """Get output shapes. + """Gets output shapes. + Returns ------- A list of `(name, shape)` pairs. @@ -127,11 +148,12 @@ def output_shapes(self): return self._curr_module.output_shapes def get_params(self): - """Get current parameters. + """Gets current parameters. + Returns ------- - `(arg_params, aux_params)`, each a dictionary mapping names to parameters - (`NDArray`). + `(arg_params, aux_params)` + A pair of dictionaries each mapping parameter names to NDArray values. """ assert self.binded and self.params_initialized self._curr_module._params_dirty = self._params_dirty @@ -139,8 +161,9 @@ def get_params(self): self._params_dirty = False return params - def set_params(self, arg_params, aux_params, allow_missing=False, force_init=True): - """Assign parameter and aux state values. + def set_params(self, arg_params, aux_params, allow_missing=False, force_init=True, + allow_extra=False): + """Assigns parameters and aux state values. Parameters ---------- @@ -153,13 +176,16 @@ def set_params(self, arg_params, aux_params, allow_missing=False, force_init=Tru called to fill those missing params. force_init : bool If true, will force re-initialize even if already initialized. + allow_extra : boolean, optional + Whether allow extra parameters that are not needed by symbol. + If this is True, no error will be thrown when arg_params or aux_params + contain extra parameters that is not needed by the executor. Examples -------- - An example of setting module parameters:: - >>> sym, arg_params, aux_params = \ - >>> mx.model.load_checkpoint(model_prefix, n_epoch_load) - >>> mod.set_params(arg_params=arg_params, aux_params=aux_params) + >>> # An example of setting module parameters. + >>> sym, arg_params, aux_params = mx.model.load_checkpoint(model_prefix, n_epoch_load) + >>> mod.set_params(arg_params=arg_params, aux_params=aux_params) """ if not allow_missing: self.init_params(initializer=None, arg_params=arg_params, aux_params=aux_params, @@ -172,15 +198,15 @@ def set_params(self, arg_params, aux_params, allow_missing=False, force_init=Tru return self._curr_module.set_params(arg_params, aux_params, allow_missing=allow_missing, - force_init=force_init) + force_init=force_init, allow_extra=allow_extra) # because we didn't update self._arg_params, they are dirty now. self._params_dirty = True self.params_initialized = True def init_params(self, initializer=Uniform(0.01), arg_params=None, aux_params=None, - allow_missing=False, force_init=False): - """Initialize parameters. + allow_missing=False, force_init=False, allow_extra=False): + """Initializes parameters. Parameters ---------- @@ -196,18 +222,22 @@ def init_params(self, initializer=Uniform(0.01), arg_params=None, aux_params=Non In this case, missing values will be filled with `initializer`. force_init : bool Defaults to ``False``. + allow_extra : boolean, optional + Whether allow extra parameters that are not needed by symbol. + If this is True, no error will be thrown when arg_params or aux_params + contain extra parameters that is not needed by the executor. """ if self.params_initialized and not force_init: return assert self.binded, 'call bind before initializing the parameters' self._curr_module.init_params(initializer=initializer, arg_params=arg_params, aux_params=aux_params, allow_missing=allow_missing, - force_init=force_init) + force_init=force_init, allow_extra=allow_extra) self._params_dirty = False self.params_initialized = True def get_states(self, merge_multi_context=True): - """Get states from all devices + """Gets states from all devices. Parameters ---------- @@ -219,15 +249,16 @@ def get_states(self, merge_multi_context=True): Returns ------- - If `merge_multi_context` is ``True``, it is like ``[out1, out2]``. Otherwise, it - is like ``[[out1_dev1, out1_dev2], [out2_dev1, out2_dev2]]``. All the output - elements are `NDArray`. + list of NDArrays or list of list of NDArrays + If `merge_multi_context` is ``True``, it is like ``[out1, out2]``. Otherwise, it + is like ``[[out1_dev1, out1_dev2], [out2_dev1, out2_dev2]]``. All the output + elements are `NDArray`. """ assert self.binded and self.params_initialized return self._curr_module.get_states(merge_multi_context=merge_multi_context) def set_states(self, states=None, value=None): - """Set value for states. Only one of states & value can be specified. + """Sets value for states. Only one of states & values can be specified. Parameters ---------- @@ -303,7 +334,7 @@ def bind(self, data_shapes, label_shapes=None, for_training=True, self.set_params(arg_params, aux_params) def switch_bucket(self, bucket_key, data_shapes, label_shapes=None): - """Switch to a different bucket. This will change ``self.curr_module``. + """Switches to a different bucket. This will change ``self.curr_module``. Parameters ---------- @@ -338,7 +369,7 @@ def switch_bucket(self, bucket_key, data_shapes, label_shapes=None): def init_optimizer(self, kvstore='local', optimizer='sgd', optimizer_params=(('learning_rate', 0.01),), force_init=False): - """Install and initialize optimizers. + """Installs and initializes optimizers. Parameters ---------- @@ -367,12 +398,12 @@ def init_optimizer(self, kvstore='local', optimizer='sgd', self.optimizer_initialized = True def prepare(self, data_batch): - '''Prepare a data batch for forward. + """Prepares a data batch for forward. Parameters ---------- data_batch : DataBatch - ''' + """ # perform bind if haven't done so assert self.binded and self.params_initialized bucket_key = data_batch.bucket_key @@ -403,7 +434,7 @@ def backward(self, out_grads=None): self._curr_module.backward(out_grads=out_grads) def update(self): - """Update parameters according to installed optimizer and the gradient computed + """Updates parameters according to installed optimizer and the gradient computed in the previous forward-backward cycle. """ assert self.binded and self.params_initialized and self.optimizer_initialized @@ -411,7 +442,7 @@ def update(self): self._curr_module.update() def get_outputs(self, merge_multi_context=True): - """Get outputs from a previous forward computation. + """Gets outputs from a previous forward computation. Parameters ---------- @@ -423,15 +454,16 @@ def get_outputs(self, merge_multi_context=True): Returns ------- - If `merge_multi_context` is ``True``, it is like ``[out1, out2]``. Otherwise, it - is like ``[[out1_dev1, out1_dev2], [out2_dev1, out2_dev2]]``. All the output - elements are numpy arrays. + list of numpy arrays or list of list of numpy arrays + If `merge_multi_context` is ``True``, it is like ``[out1, out2]``. Otherwise, it + is like ``[[out1_dev1, out1_dev2], [out2_dev1, out2_dev2]]``. All the output + elements are numpy arrays. """ assert self.binded and self.params_initialized return self._curr_module.get_outputs(merge_multi_context=merge_multi_context) def get_input_grads(self, merge_multi_context=True): - """Get the gradients with respect to the inputs of the module. + """Gets the gradients with respect to the inputs of the module. Parameters ---------- @@ -443,15 +475,16 @@ def get_input_grads(self, merge_multi_context=True): Returns ------- - If `merge_multi_context` is ``True``, it is like ``[grad1, grad2]``. Otherwise, it - is like ``[[grad1_dev1, grad1_dev2], [grad2_dev1, grad2_dev2]]``. All the output - elements are `NDArray`. + list of NDArrays or list of list of NDArrays + If `merge_multi_context` is ``True``, it is like ``[grad1, grad2]``. Otherwise, it + is like ``[[grad1_dev1, grad1_dev2], [grad2_dev1, grad2_dev2]]``. All the output + elements are `NDArray`. """ assert self.binded and self.params_initialized and self.inputs_need_grad return self._curr_module.get_input_grads(merge_multi_context=merge_multi_context) def update_metric(self, eval_metric, labels): - """Evaluate and accumulate evaluation metric on outputs of the last forward computation. + """Evaluates and accumulates evaluation metric on outputs of the last forward computation. Parameters ---------- @@ -469,7 +502,7 @@ def symbol(self): return self._curr_module.symbol def install_monitor(self, mon): - """ Install monitor on all executors """ + """Installs monitor on all executors """ assert self.binded for mod in self._buckets.values(): mod.install_monitor(mon) diff --git a/python/mxnet/module/executor_group.py b/python/mxnet/module/executor_group.py old mode 100644 new mode 100755 index 177f5c5ed478..0f3c079f8fcb --- a/python/mxnet/module/executor_group.py +++ b/python/mxnet/module/executor_group.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # pylint: disable=too-many-instance-attributes,too-many-locals # pylint: disable=too-many-branches,too-many-statements,too-many-arguments """Executor group is a convenient tool for managing a group of executors.""" @@ -5,8 +22,6 @@ import logging from collections import OrderedDict -import numpy as np - from .. import context as ctx from .. import ndarray as nd from ..io import DataDesc @@ -15,7 +30,7 @@ def _load_general(data, targets, major_axis): """Load a list of arrays into a list of arrays specified by slices.""" - for d_src, d_targets, axis in zip(data, targets, major_axis): + for d_src, d_targets, axis in zip(data, targets, major_axis): # pylint: disable=too-many-nested-blocks if isinstance(d_targets, nd.NDArray): d_src.copyto(d_targets) elif isinstance(d_src, (list, tuple)): @@ -26,17 +41,22 @@ def _load_general(data, targets, major_axis): if axis >= 0: # copy slice shape = d_src.shape - begin = np.zeros(len(shape), dtype=int) - end = np.array(shape) - begin[axis] = slice_idx.start - end[axis] = slice_idx.stop + do_crop = (slice_idx.start != 0 or shape[axis] != slice_idx.stop) # pylint: disable=no-member,protected-access - if d_src.context == d_dst.context: - nd.crop(d_src, begin=tuple(begin), end=tuple(end), out=d_dst) + if do_crop: + if axis == 0: + d_src[slice_idx.start:slice_idx.stop].copyto(d_dst) + else: + if d_src.context == d_dst.context: + nd.slice_axis(d_src, axis=axis, begin=slice_idx.start, + end=slice_idx.stop, out=d_dst) + else: + # on different device, crop and then do cross device copy + d_dst_copy = nd.slice_axis(d_src, axis=axis, begin=slice_idx.start, + end=slice_idx.stop) + d_dst_copy.copyto(d_dst) else: - # on different device, crop and then do cross device copy - d_dst_copy = nd.crop(d_src, begin=tuple(begin), end=tuple(end)) - d_dst_copy.copyto(d_dst) + d_src.copyto(d_dst) # pylint: enable=no-member,protected-access else: d_src.copyto(d_dst) @@ -108,8 +128,8 @@ class DataParallelExecutorGroup(object): shared_group : DataParallelExecutorGroup Defaults to ``None``. This is used in bucketing. When not ``None``, it should be a executor group corresponding to a different bucket. In other words, it will correspond to a different - symbol but with the same set of parameters (e.g. unrolled RNNs with different lengths). - In this case, many memory will be shared. + symbol with the same set of parameters (e.g. unrolled RNNs with different lengths). + In this case the memory regions of the parameters will be shared. logger : Logger Default is `logging`. fixed_param_names: list of str @@ -327,7 +347,7 @@ def reshape(self, data_shapes, label_shapes): self._default_execs = [i for i in self.execs] self.bind_exec(data_shapes, label_shapes, reshape=True) - def set_params(self, arg_params, aux_params): + def set_params(self, arg_params, aux_params, allow_extra=False): """Assign, i.e. copy parameters to all the executors. Parameters @@ -336,9 +356,13 @@ def set_params(self, arg_params, aux_params): A dictionary of name to `NDArray` parameter mapping. aux_params : dict A dictionary of name to `NDArray` auxiliary variable mapping. + allow_extra : boolean, optional + Whether allow extra parameters that are not needed by symbol. + If this is True, no error will be thrown when arg_params or aux_params + contain extra parameters that is not needed by the executor. """ for exec_ in self.execs: - exec_.copy_params_from(arg_params, aux_params) + exec_.copy_params_from(arg_params, aux_params, allow_extra_params=allow_extra) def get_params(self, arg_params, aux_params): """ Copy data from each executor to `arg_params` and `aux_params`. @@ -559,6 +583,7 @@ def update_metric(self, eval_metric, labels): def _bind_ith_exec(self, i, data_shapes, label_shapes, shared_group): """Internal utility function to bind the i-th executor. + This function utilizes simple_bind python interface. """ shared_exec = None if shared_group is None else shared_group.execs[i] context = self.contexts[i] @@ -568,85 +593,14 @@ def _bind_ith_exec(self, i, data_shapes, label_shapes, shared_group): if label_shapes is not None: input_shapes.update(dict(label_shapes)) - arg_shapes, _, aux_shapes = self.symbol.infer_shape(**input_shapes) - assert arg_shapes is not None, "shape inference failed" - input_types = {x.name: x.dtype for x in data_shapes} if label_shapes is not None: input_types.update({x.name: x.dtype for x in label_shapes}) - arg_types, _, aux_types = self.symbol.infer_type(**input_types) - assert arg_types is not None, "type inference failed" - - arg_arrays = [] - grad_arrays = {} if self.for_training else None - - def _get_or_reshape(name, shared_data_arrays, arg_shape, arg_type, context, logger): - """Internal helper to get a memory block or re-use by re-shaping.""" - if name in shared_data_arrays: - arg_arr = shared_data_arrays[name] - if np.prod(arg_arr.shape) >= np.prod(arg_shape): - # nice, we can directly re-use this data blob - assert arg_arr.dtype == arg_type - arg_arr = arg_arr.reshape(arg_shape) - else: - logger.warning(('bucketing: data "%s" has a shape %s' % (name, arg_shape)) + - (', which is larger than already allocated ') + - ('shape %s' % (arg_arr.shape,)) + - ('. Need to re-allocate. Consider putting ') + - ('default_bucket_key to') + - (' be the bucket taking the largest input for better ') + - ('memory sharing.')) - arg_arr = nd.zeros(arg_shape, context, dtype=arg_type) - - # replace existing shared array because the new one is bigger - shared_data_arrays[name] = arg_arr - else: - arg_arr = nd.zeros(arg_shape, context, dtype=arg_type) - shared_data_arrays[name] = arg_arr - - return arg_arr - - # create or borrow arguments and gradients - for j in range(len(self.arg_names)): - name = self.arg_names[j] - if name in self.param_names: # model parameters - if shared_exec is None: - arg_arr = nd.zeros(arg_shapes[j], context, dtype=arg_types[j]) - if self.grad_req[name] != 'null': - grad_arr = nd.zeros(arg_shapes[j], context, dtype=arg_types[j]) - grad_arrays[name] = grad_arr - else: - arg_arr = shared_exec.arg_dict[name] - assert arg_arr.shape == arg_shapes[j] - assert arg_arr.dtype == arg_types[j] - if self.grad_req[name] != 'null': - grad_arrays[name] = shared_exec.grad_dict[name] - else: # data, label, or states - arg_arr = _get_or_reshape(name, shared_data_arrays, arg_shapes[j], arg_types[j], - context, self.logger) - - # data might also need grad if inputs_need_grad is True - if self.grad_req[name] != 'null': - grad_arrays[name] = _get_or_reshape('grad of ' + name, shared_data_arrays, - arg_shapes[j], arg_types[j], context, - self.logger) - - arg_arrays.append(arg_arr) - - # create or borrow aux variables - if shared_exec is None: - aux_arrays = [nd.zeros(s, context, dtype=t) for s, t in zip(aux_shapes, aux_types)] - else: - for j, arr in enumerate(shared_exec.aux_arrays): - assert aux_shapes[j] == arr.shape - assert aux_types[j] == arr.dtype - aux_arrays = shared_exec.aux_arrays[:] - - executor = self.symbol.bind(ctx=context, args=arg_arrays, - args_grad=grad_arrays, aux_states=aux_arrays, - grad_req=self.grad_req, shared_exec=shared_exec) - # Get the total bytes allocated for this executor + executor = self.symbol.simple_bind(ctx=context, grad_req=self.grad_req, + type_dict=input_types, shared_arg_names=self.param_names, + shared_exec=shared_exec, + shared_buffer=shared_data_arrays, **input_shapes) self._total_exec_bytes += int(executor.debug_str().split('\n')[-3].split()[1]) return executor diff --git a/python/mxnet/module/module.py b/python/mxnet/module/module.py index db0ee0b53f40..058edd57eb3d 100644 --- a/python/mxnet/module/module.py +++ b/python/mxnet/module/module.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # pylint: disable=too-many-instance-attributes, too-many-arguments, protected-access, too-many-branches # pylint: disable=too-many-public-methods """A `Module` implement the `BaseModule` API by wrapping a `Symbol` and one or @@ -15,6 +32,7 @@ from ..model import _create_kvstore, _initialize_kvstore, _update_params, _update_params_on_kvstore from ..model import load_checkpoint from ..initializer import Uniform, InitDesc +from ..io import DataDesc from .base_module import BaseModule, _check_input_names, _parse_data_desc @@ -40,7 +58,7 @@ class Module(BaseModule): Default ``None``, indicating no network parameters are fixed. state_names : list of str states are similar to data and label, but not provided by data iterator. - Instead they are initialized to 0 and can be set by set_states() + Instead they are initialized to 0 and can be set by `set_states()`. """ def __init__(self, symbol, data_names=('data',), label_names=('softmax_label',), logger=logging, context=ctx.cpu(), work_load_list=None, @@ -94,7 +112,7 @@ def __init__(self, symbol, data_names=('data',), label_names=('softmax_label',), @staticmethod def load(prefix, epoch, load_optimizer_states=False, **kwargs): - """Create a model from previously saved checkpoint. + """Creates a model from previously saved checkpoint. Parameters ---------- @@ -132,17 +150,17 @@ def load(prefix, epoch, load_optimizer_states=False, **kwargs): return mod def save_checkpoint(self, prefix, epoch, save_optimizer_states=False): - """Save current progress to checkpoint. - Use mx.callback.module_checkpoint as epoch_end_callback to save during training. + """Saves current progress to checkpoint. + Use `mx.callback.module_checkpoint` as `epoch_end_callback` to save during training. Parameters ---------- prefix : str - The file prefix to checkpoint to + The file prefix to checkpoint to. epoch : int - The current epoch number + The current epoch number. save_optimizer_states : bool - Whether to save optimizer states for continue training + Whether to save optimizer states to continue training. """ self._symbol.save('%s-symbol.json'%prefix) param_name = '%s-%04d.params' % (prefix, epoch) @@ -177,7 +195,7 @@ def output_names(self): @property def data_shapes(self): - """Get data shapes. + """Gets data shapes. Returns ------- @@ -188,11 +206,12 @@ def data_shapes(self): @property def label_shapes(self): - """Get label shapes. + """Gets label shapes. Returns ------- - A list of `(name, shape)` pairs. The return value could be ``None`` if + A list of `(name, shape)` pairs. + The return value could be ``None`` if the module does not need labels, or if the module is not bound for training (in this case, label information is not available). """ @@ -201,7 +220,7 @@ def label_shapes(self): @property def output_shapes(self): - """Get output shapes. + """Gets output shapes. Returns ------- @@ -211,11 +230,12 @@ def output_shapes(self): return self._exec_group.get_output_shapes() def get_params(self): - """Get current parameters. + """Gets current parameters. + Returns ------- - `(arg_params, aux_params)`, each a dictionary of name to parameters (in - `NDArray`) mapping. + `(arg_params, aux_params)` + A pair of dictionaries each mapping parameter names to NDArray values. """ assert self.binded and self.params_initialized @@ -224,15 +244,15 @@ def get_params(self): return (self._arg_params, self._aux_params) def init_params(self, initializer=Uniform(0.01), arg_params=None, aux_params=None, - allow_missing=False, force_init=False): - """Initialize the parameters and auxiliary states. + allow_missing=False, force_init=False, allow_extra=False): + """Initializes the parameters and auxiliary states. Parameters ---------- initializer : Initializer Called to initialize parameters if needed. arg_params : dict - If not None, should be a dictionary of existing arg_params. Initialization + If not ``None``, should be a dictionary of existing arg_params. Initialization will be copied from that. aux_params : dict If not ``None``, should be a dictionary of existing aux_params. Initialization @@ -242,6 +262,10 @@ def init_params(self, initializer=Uniform(0.01), arg_params=None, aux_params=Non called to fill those missing params. force_init : bool If ``True``, will force re-initialize even if already initialized. + allow_extra : boolean, optional + Whether allow extra parameters that are not needed by symbol. + If this is True, no error will be thrown when arg_params or aux_params + contain extra parameters that is not needed by the executor. """ if self.params_initialized and not force_init: warnings.warn("Parameters already initialized and force_init=False. " @@ -279,10 +303,12 @@ def _impl(name, arr, cache): self._params_dirty = False # copy the initialized parameters to devices - self._exec_group.set_params(self._arg_params, self._aux_params) + self._exec_group.set_params(self._arg_params, self._aux_params, + allow_extra=allow_extra) - def set_params(self, arg_params, aux_params, allow_missing=False, force_init=True): - """Assign parameter and aux state values. + def set_params(self, arg_params, aux_params, allow_missing=False, force_init=True, + allow_extra=False): + """Assigns parameter and aux state values. Parameters ---------- @@ -295,17 +321,20 @@ def set_params(self, arg_params, aux_params, allow_missing=False, force_init=Tru called to fill those missing params. force_init : bool If ``True``, will force re-initialize even if already initialized. - + allow_extra : boolean, optional + Whether allow extra parameters that are not needed by symbol. + If this is True, no error will be thrown when arg_params or aux_params + contain extra parameters that is not needed by the executor. Examples -------- - An example of setting module parameters:: - >>> sym, arg_params, aux_params = \ - mx.model.load_checkpoint(model_prefix, n_epoch_load) - >>> mod.set_params(arg_params=arg_params, aux_params=aux_params) + >>> # An example of setting module parameters. + >>> sym, arg_params, aux_params = mx.model.load_checkpoint(model_prefix, n_epoch_load) + >>> mod.set_params(arg_params=arg_params, aux_params=aux_params) """ if not allow_missing: self.init_params(initializer=None, arg_params=arg_params, aux_params=aux_params, - allow_missing=allow_missing, force_init=force_init) + allow_missing=allow_missing, force_init=force_init, + allow_extra=allow_extra) return if self.params_initialized and not force_init: @@ -313,7 +342,7 @@ def set_params(self, arg_params, aux_params, allow_missing=False, force_init=Tru "set_params call ignored.", stacklevel=2) return - self._exec_group.set_params(arg_params, aux_params) + self._exec_group.set_params(arg_params, aux_params, allow_extra=allow_extra) # because we didn't update self._arg_params, they are dirty now. self._params_dirty = True @@ -322,7 +351,7 @@ def set_params(self, arg_params, aux_params, allow_missing=False, force_init=Tru def bind(self, data_shapes, label_shapes=None, for_training=True, inputs_need_grad=False, force_rebind=False, shared_module=None, grad_req='write'): - """Bind the symbols to construct executors. This is necessary before one + """Binds the symbols to construct executors. This is necessary before one can perform computation with the module. Parameters @@ -374,6 +403,7 @@ def bind(self, data_shapes, label_shapes=None, for_training=True, assert isinstance(shared_module, Module) and \ shared_module.binded and shared_module.params_initialized shared_group = shared_module._exec_group + assert len(shared_group.execs) == len(self._context) else: shared_group = None @@ -413,7 +443,7 @@ def bind(self, data_shapes, label_shapes=None, for_training=True, def reshape(self, data_shapes, label_shapes=None): - """Reshape the module for new input shapes. + """Reshapes the module for new input shapes. Parameters ---------- @@ -524,7 +554,11 @@ def borrow_optimizer(self, shared_module): self.optimizer_initialized = True def forward(self, data_batch, is_train=None): - """Forward computation. + """Forward computation. It supports data batches with different shapes, such as + different batch sizes or different image sizes. + If reshaping of data batch relates to modification of symbol or module, such as + changing image layout ordering or switching from training to predicting, module + rebinding is required. See Also ---------- @@ -538,6 +572,27 @@ def forward(self, data_batch, is_train=None): Default is ``None``, which means ``is_train`` takes the value of ``self.for_training``. """ assert self.binded and self.params_initialized + + curr_data_shapes = tuple(i.shape for i in self._data_shapes) + new_data_shapes = tuple(i.shape for i in data_batch.data) + + if curr_data_shapes != new_data_shapes: + if hasattr(data_batch, "provide_data") and data_batch.provide_data: + new_dshape = data_batch.provide_data + else: + new_dshape = [DataDesc(i.name, shape, i.dtype, i.layout) \ + for i, shape in zip(self._data_shapes, new_data_shapes)] + + if hasattr(data_batch, "provide_label") and data_batch.provide_label: + new_lshape = data_batch.provide_label + elif hasattr(data_batch, "label") and data_batch.label: + new_lshape = [DataDesc(i.name, j.shape, i.dtype, i.layout) \ + for i, j in zip(self._label_shapes, data_batch.label)] + else: + new_lshape = None + + self.reshape(new_dshape, new_lshape) + self._exec_group.forward(data_batch, is_train) def backward(self, out_grads=None): @@ -571,13 +626,14 @@ def update(self): if self._update_on_kvstore: _update_params_on_kvstore(self._exec_group.param_arrays, self._exec_group.grad_arrays, - self._kvstore) + self._kvstore, self._exec_group.param_names) else: _update_params(self._exec_group.param_arrays, self._exec_group.grad_arrays, updater=self._updater, num_device=len(self._context), - kvstore=self._kvstore) + kvstore=self._kvstore, + param_names=self._exec_group.param_names) def get_outputs(self, merge_multi_context=True): """Gets outputs of the previous forward computation. @@ -718,6 +774,6 @@ def load_optimizer_states(self, fname): self._updater.set_states(open(fname, 'rb').read()) def install_monitor(self, mon): - """ Installs monitor on all executors. """ + """Installs monitor on all executors. """ assert self.binded self._exec_group.install_monitor(mon) diff --git a/python/mxnet/module/python_module.py b/python/mxnet/module/python_module.py index d9b103c40bb3..2d4343c80c72 100644 --- a/python/mxnet/module/python_module.py +++ b/python/mxnet/module/python_module.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # pylint: disable=too-many-instance-attributes, too-many-arguments """Provide some handy classes for user to implement a simple computation module in Python easily. @@ -77,19 +94,19 @@ def output_shapes(self): # Parameters of a module ################################################################################ def get_params(self): - """Get parameters, those are potentially copies of the the actual parameters used - to do computation on the device. + """Gets parameters, those are potentially copies of the the actual parameters used + to do computation on the device. Subclass should override this method if contains + parameters. Returns ------- - ``({}, {})``, a pair of empty dict. Subclass should override this method if - contains parameters. + ``({}, {})``, a pair of empty dict. """ return (dict(), dict()) def init_params(self, initializer=Uniform(0.01), arg_params=None, aux_params=None, - allow_missing=False, force_init=False): - """Initialize the parameters and auxiliary states. By default this function + allow_missing=False, force_init=False, allow_extra=False): + """Initializes the parameters and auxiliary states. By default this function does nothing. Subclass should override this method if contains parameters. Parameters @@ -107,19 +124,23 @@ def init_params(self, initializer=Uniform(0.01), arg_params=None, aux_params=Non called to fill those missing params. force_init : bool If ``True``, will force re-initialize even if already initialized. + allow_extra : boolean, optional + Whether allow extra parameters that are not needed by symbol. + If this is True, no error will be thrown when arg_params or aux_params + contain extra parameters that is not needed by the executor. """ pass def update(self): - """Update parameters according to the installed optimizer and the gradients computed + """Updates parameters according to the installed optimizer and the gradients computed in the previous forward-backward batch. Currently we do nothing here. Subclass should override this method if contains parameters. """ pass def update_metric(self, eval_metric, labels): - """Evaluate and accumulate evaluation metric on outputs of the last forward computation. - ubclass should override this method if needed. + """Evaluates and accumulates evaluation metric on outputs of the last forward computation. + Subclass should override this method if needed. Parameters ---------- @@ -141,7 +162,7 @@ def update_metric(self, eval_metric, labels): def bind(self, data_shapes, label_shapes=None, for_training=True, inputs_need_grad=False, force_rebind=False, shared_module=None, grad_req='write'): - """Bind the symbols to construct executors. This is necessary before one + """Binds the symbols to construct executors. This is necessary before one can perform computation with the module. Parameters @@ -197,8 +218,8 @@ def _compute_output_shapes(self): def init_optimizer(self, kvstore='local', optimizer='sgd', optimizer_params=(('learning_rate', 0.01),), force_init=False): - """Install and initialize optimizers. By default we do nothing. Subclass - should + """Installs and initializes optimizers. By default we do nothing. Subclass should + override this method if needed. Parameters ---------- @@ -253,7 +274,7 @@ def __init__(self, name='pyloss', data_names=('data',), label_names=('softmax_la self._grad_func = grad_func def _compute_output_shapes(self): - """Compute the shapes of outputs. As a loss module with outputs, we simply + """Computes the shapes of outputs. As a loss module with outputs, we simply output whatever we receive as inputs (i.e. the scores). """ return [(self._name + '_output', self._data_shapes[0][1])] @@ -278,7 +299,7 @@ def forward(self, data_batch, is_train=None): self._labels = data_batch.label[0] def get_outputs(self, merge_multi_context=True): - """Get outputs of the previous forward computation. As a output loss module, + """Gets outputs of the previous forward computation. As a output loss module, we treat the inputs to this module as scores, and simply return them. Parameters @@ -323,7 +344,7 @@ def _backward_impl(self): raise NotImplementedError() def get_input_grads(self, merge_multi_context=True): - """Get the gradients to the inputs, computed in the previous backward computation. + """Gets the gradients to the inputs, computed in the previous backward computation. Parameters ---------- @@ -334,5 +355,5 @@ def get_input_grads(self, merge_multi_context=True): return [self._scores_grad] def install_monitor(self, mon): - """Install monitor on all executors.""" + """Installs monitor on all executors.""" raise NotImplementedError() diff --git a/python/mxnet/module/sequential_module.py b/python/mxnet/module/sequential_module.py index 27842206f938..642a398c08d4 100644 --- a/python/mxnet/module/sequential_module.py +++ b/python/mxnet/module/sequential_module.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # pylint: disable=too-many-arguments, too-many-locals, too-many-instance-attributes """`SequentialModule` is a container module that chains a number of modules together.""" @@ -11,9 +28,11 @@ class SequentialModule(BaseModule): """A SequentialModule is a container module that can chain multiple modules together. - Note building a computation graph with this kind of imperative container is less - flexible and less efficient than the symbolic graph. So this should be only used as a - handy utility. + .. note:: + + Building a computation graph with this kind of imperative container is less + flexible and less efficient than the symbolic graph. So, this should be only used as a + handy utility. """ META_TAKE_LABELS = 'take_labels' @@ -31,7 +50,7 @@ def __init__(self, logger=logging): if x.startswith('META_')]) def add(self, module, **kwargs): - """Add a module to the chain. + """Adds a module to the chain. Parameters ---------- @@ -55,10 +74,10 @@ def add(self, module, **kwargs): Examples -------- - An example of addinging two modules to a chain:: - >>> seq_mod = mx.mod.SequentialModule() - >>> seq_mod.add(mod1) - >>> seq_mod.add(mod2) + >>> # An example of addinging two modules to a chain. + >>> seq_mod = mx.mod.SequentialModule() + >>> seq_mod.add(mod1) + >>> seq_mod.add(mod2) """ self._modules.append(module) @@ -92,7 +111,7 @@ def output_names(self): @property def data_shapes(self): - """Get data shapes. + """Gets data shapes. Returns ------- @@ -105,7 +124,7 @@ def data_shapes(self): @property def label_shapes(self): - """Get label shapes. + """Gets label shapes. Returns ------- @@ -119,7 +138,7 @@ def label_shapes(self): @property def output_shapes(self): - """Get output shapes. + """Gets output shapes. Returns ------- @@ -131,12 +150,12 @@ def output_shapes(self): return self._modules[-1].output_shapes def get_params(self): - """Get current parameters. + """Gets current parameters. Returns ------- (arg_params, aux_params) - each a dictionary of name to parameters (in `NDArray`) mapping. This + A pair of dictionaries each mapping parameter names to NDArray values. This is a merged dictionary of all the parameters in the modules. """ assert self.binded and self.params_initialized @@ -152,8 +171,8 @@ def get_params(self): return (arg_params, aux_params) def init_params(self, initializer=Uniform(0.01), arg_params=None, aux_params=None, - allow_missing=False, force_init=False): - """Initialize parameters. + allow_missing=False, force_init=False, allow_extra=False): + """Initializes parameters. Parameters ---------- @@ -169,6 +188,10 @@ def init_params(self, initializer=Uniform(0.01), arg_params=None, aux_params=Non In this case, missing values will be filled with `initializer`. force_init : bool Default ``False``. + allow_extra : boolean, optional + Whether allow extra parameters that are not needed by symbol. + If this is True, no error will be thrown when arg_params or aux_params + contain extra parameters that is not needed by the executor. """ if self.params_initialized and not force_init: return @@ -177,7 +200,7 @@ def init_params(self, initializer=Uniform(0.01), arg_params=None, aux_params=Non for module in self._modules: module.init_params(initializer=initializer, arg_params=arg_params, aux_params=aux_params, allow_missing=allow_missing, - force_init=force_init) + force_init=force_init, allow_extra=allow_extra) # make sure we do not have duplicated parameter names def _check_name(known_names, new_names, modules, i): @@ -201,7 +224,7 @@ def _check_name(known_names, new_names, modules, i): def bind(self, data_shapes, label_shapes=None, for_training=True, inputs_need_grad=False, force_rebind=False, shared_module=None, grad_req='write'): - """Bind the symbols to construct executors. This is necessary before one + """Binds the symbols to construct executors. This is necessary before one can perform computation with the module. Parameters @@ -274,7 +297,7 @@ def bind(self, data_shapes, label_shapes=None, for_training=True, def init_optimizer(self, kvstore='local', optimizer='sgd', optimizer_params=(('learning_rate', 0.01),), force_init=False): - """Install and initialize optimizers. + """Installs and initializes optimizers. Parameters ---------- @@ -343,7 +366,7 @@ def backward(self, out_grads=None): out_grads = module.get_input_grads() def update(self): - """Update parameters according to installed optimizer and the gradient computed + """Updates parameters according to installed optimizer and the gradient computed in the previous forward-backward cycle. """ assert self.binded and self.params_initialized and self.optimizer_initialized @@ -352,7 +375,7 @@ def update(self): module.update() def get_outputs(self, merge_multi_context=True): - """Get outputs from a previous forward computation. + """Gets outputs from a previous forward computation. Parameters ---------- @@ -373,7 +396,7 @@ def get_outputs(self, merge_multi_context=True): return self._modules[-1].get_outputs(merge_multi_context=merge_multi_context) def get_input_grads(self, merge_multi_context=True): - """Get the gradients with respect to the inputs of the module. + """Gets the gradients with respect to the inputs of the module. Parameters ---------- @@ -385,7 +408,7 @@ def get_input_grads(self, merge_multi_context=True): Returns ------- - list of NDArray or list of list of NDArray + list of NDArrays or list of list of NDArrays If `merge_multi_context` is ``True``, it is like ``[grad1, grad2]``. Otherwise, it is like ``[[grad1_dev1, grad1_dev2], [grad2_dev1, grad2_dev2]]``. All the output elements are `NDArray`. @@ -394,7 +417,7 @@ def get_input_grads(self, merge_multi_context=True): return self._modules[0].get_input_grads(merge_multi_context=merge_multi_context) def update_metric(self, eval_metric, labels): - """Evaluate and accumulate evaluation metric on outputs of the last forward computation. + """Evaluates and accumulates evaluation metric on outputs of the last forward computation. Parameters ---------- @@ -410,7 +433,7 @@ def update_metric(self, eval_metric, labels): module.update_metric(eval_metric, labels) def install_monitor(self, mon): - """ Install monitor on all executors.""" + """Installs monitor on all executors.""" assert self.binded for module in self._modules: module.install_monitor(mon) diff --git a/python/mxnet/monitor.py b/python/mxnet/monitor.py index 15be41d585a8..e3185a1281af 100644 --- a/python/mxnet/monitor.py +++ b/python/mxnet/monitor.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # coding: utf-8 # pylint: disable=protected-access, logging-format-interpolation, invalid-name, no-member, too-many-branches """Monitor outputs, weights, and gradients for debugging.""" diff --git a/python/mxnet/name.py b/python/mxnet/name.py index 8003073f4d8a..966d38280ef7 100644 --- a/python/mxnet/name.py +++ b/python/mxnet/name.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # coding: utf-8 """Automatic naming support for symbolic API.""" from __future__ import absolute_import diff --git a/python/mxnet/ndarray.py b/python/mxnet/ndarray.py index b087e315ebc0..42f0ff5e87cf 100644 --- a/python/mxnet/ndarray.py +++ b/python/mxnet/ndarray.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # coding: utf-8 # pylint: disable= too-many-lines, redefined-builtin, protected-access # pylint: disable=import-error, no-name-in-module, undefined-variable @@ -17,43 +34,62 @@ import operator import numpy as np -from .base import _LIB, string_types, numeric_types -from .base import c_array, py_str, c_str, mx_real_t -from .base import mx_uint, NDArrayHandle, check_call +from .base import _LIB, string_types, numeric_types, integer_types +from .base import c_array, py_str, c_str, mx_real_t, _Null # pylint: disable=unused-import +from .base import mx_uint, NDArrayHandle, check_call, OpHandle from .base import ctypes2buffer from .context import Context from . import _ndarray_internal as _internal +from .ndarray_doc import _build_doc + -# Use different verison of SymbolBase +# Use different version of SymbolBase # When possible, use cython to speedup part of computation. +# pylint: disable=unused-import try: if int(_os.environ.get("MXNET_ENABLE_CYTHON", True)) == 0: - from ._ctypes.ndarray import NDArrayBase, _init_ndarray_module + from ._ctypes.ndarray import NDArrayBase, _set_ndarray_class + from ._ctypes.ndarray import CachedOp, _imperative_invoke elif _sys.version_info >= (3, 0): - from ._cy3.ndarray import NDArrayBase, _init_ndarray_module + from ._cy3.ndarray import NDArrayBase, _set_ndarray_class, _imperative_invoke + from ._cy3.ndarray import CachedOp, _imperative_invoke else: - from ._cy2.ndarray import NDArrayBase, _init_ndarray_module + from ._cy2.ndarray import NDArrayBase, _set_ndarray_class, _imperative_invoke + from ._cy2.ndarray import CachedOp, _imperative_invoke except ImportError: if int(_os.environ.get("MXNET_ENFORCE_CYTHON", False)) != 0: raise ImportError("Cython Module cannot be loaded but MXNET_ENFORCE_CYTHON=1") - from ._ctypes.ndarray import NDArrayBase, _init_ndarray_module - + from ._ctypes.ndarray import NDArrayBase, _set_ndarray_class, _imperative_invoke + from ._ctypes.ndarray import CachedOp, _imperative_invoke +# pylint: enable=unused-import # pylint: disable= no-member _DTYPE_NP_TO_MX = { + None : -1, np.float32 : 0, np.float64 : 1, np.float16 : 2, np.uint8 : 3, - np.int32 : 4 + np.int32 : 4, + np.int8 : 5, + np.int64 : 6, } _DTYPE_MX_TO_NP = { + -1 : None, 0 : np.float32, 1 : np.float64, 2 : np.float16, 3 : np.uint8, - 4 : np.int32 + 4 : np.int32, + 5 : np.int8, + 6 : np.int64, +} + +_GRAD_REQ_MAP = { + 'null': 0, + 'write': 1, + 'add': 3 } # pylint: enable= no-member @@ -105,12 +141,15 @@ class NDArray(NDArrayBase): """ __slots__ = [] + # make numpy functions return NDArray instead of numpy object array + __array_priority__ = 1000.0 # pylint: disable= no-member, undefined-variable def __repr__(self): """Returns a string representation of the array.""" shape_info = 'x'.join(['%d' % x for x in self.shape]) - return '<%s %s @%s>' % (self.__class__.__name__, - shape_info, self.context) + return '\n%s\n<%s %s @%s>' % (str(self.asnumpy()), + self.__class__.__name__, + shape_info, self.context) def __add__(self, other): """x.__add__(y) <=> x+y <=> mx.nd.add(x, y) """ @@ -199,6 +238,25 @@ def __rtruediv__(self, other): def __itruediv__(self, other): return self.__idiv__(other) + def __mod__(self, other): + """x.__mod__(y) <=> x%y <=> mx.nd.modulo(x, y) """ + return modulo(self, other) + + def __rmod__(self, other): + """x.__rmod__(y) <=> y%x <=> mx.nd.modulo(y, x) """ + return modulo(other, self) + + def __imod__(self, other): + """x.__rmod__(y) <=> x%=y """ + if not self.writable: + raise ValueError('trying to take modulo from a readonly NDArray') + if isinstance(other, NDArray): + return broadcast_mod(self, other, out=self) + elif isinstance(other, numeric_types): + return _internal._mod_scalar(self, float(other), out=self) + else: + raise TypeError('type %s not supported' % str(type(other))) + def __pow__(self, other): """x.__pow__(y) <=> x**y <=> mx.nd.power(x,y) """ return power(self, other) @@ -232,9 +290,15 @@ def __le__(self, other): return lesser_equal(self, other) def __bool__(self): - raise ValueError("The truth value of an NDArray with more than one element is ambiguous.") + raise ValueError("The truth value of an NDArray is ambiguous. " \ + "Please convert to number with asscalar() first.") + __nonzero__ = __bool__ + def __len__(self): + """Number of element along the first axis.""" + return self.shape[0] + def __getstate__(self): handle = self.handle this = {'handle' : None} @@ -301,14 +365,14 @@ def __setitem__(self, key, value): """ # pylint: disable=too-many-branches if not self.writable: - raise ValueError('Failed to assign to a readonly NDArray') - if isinstance(key, int): + raise ValueError('Cannot assign to readonly NDArray') + if isinstance(key, integer_types): sliced_arr = self._at(key) sliced_arr[:] = value return - if isinstance(key, py_slice): + elif isinstance(key, py_slice): if key.step is not None: - raise ValueError('NDArray only supports continuous slicing on axis 0') + raise ValueError('NDArray only supports slicing with step size 1') if key.start is not None or key.stop is not None: sliced_arr = self._slice(key.start, key.stop) sliced_arr[:] = value @@ -321,45 +385,73 @@ def __setitem__(self, key, value): elif isinstance(value, (np.ndarray, np.generic)): self._sync_copyfrom(value) else: - raise TypeError('type %s not supported' % str(type(value))) - if isinstance(key, tuple): + raise TypeError( + 'NDArray does not support assignment with %s of type %s'%( + str(value), str(type(value)))) + elif isinstance(key, tuple): # multi-dimension indexing my_shape = self.shape - assert len(key) == len(my_shape) - for slice_i in key: - assert isinstance(slice_i, (py_slice, int)) + assert len(key) <= len(my_shape), \ + "Indexing dimensions exceed array dimensions, %d vs %d"%( + len(key), len(my_shape)) begin = [0 for _ in my_shape] end = [x for x in my_shape] + expand = [] for i, slice_i in enumerate(key): - if isinstance(slice_i, int): + if isinstance(slice_i, integer_types): assert slice_i < my_shape[i] begin[i] = slice_i end[i] = slice_i + 1 - if isinstance(slice_i, py_slice): + expand.append(i) + elif isinstance(slice_i, py_slice): # only support continuous slicing - assert slice_i.step is None + assert slice_i.step is None, \ + "NDArray only supports slicing with step size 1." begin[i] = slice_i.start or 0 end[i] = slice_i.stop or my_shape[i] assert begin[i] < end[i] assert end[i] <= my_shape[i] - begin = tuple(begin) - end = tuple(end) + else: + raise ValueError( + "NDArray does not support slicing with key %s of type %s."%( + str(slice_i), str(type(slice_i)))) + if isinstance(value, NDArray): value = value.as_in_context(self.context) - _internal._crop_assign(self, value, out=self, - begin=begin, end=end) + self._slice_assign(value, begin, end, expand) elif isinstance(value, numeric_types): _internal._crop_assign_scalar(self, out=self, begin=begin, end=end, scalar=value) elif isinstance(value, (np.ndarray, np.generic)): - value = array(value, ctx=self.context) - _internal._crop_assign(self, value, out=self, - begin=begin, end=end) + value = array(value, ctx=self.context, dtype=self.dtype) + self._slice_assign(value, begin, end, expand) else: - raise TypeError('type %s not supported' % str(type(value))) + raise TypeError( + 'NDArray does not support assignment with %s of type %s'%( + str(value), str(type(value)))) + else: + raise ValueError( + "NDArray does not support slicing with key %s of type %s."%( + str(key), str(type(key)))) # pylint: enable=too-many-branches + def _slice_assign(self, value, begin, end, expand): + vshape = list(value.shape) + if expand and len(vshape) != len(begin): + if len(expand) + len(vshape) != len(begin): + sshape = [e - b for e, b in zip(end, begin)] + for i in reversed(expand): + sshape.pop(i) + raise ValueError( + "Cannot assign NDArray with shape %s to NDArray slice with " \ + "shape %s"%(str(vshape), str(sshape))) + for i in expand: + vshape.insert(i, 1) + value = value.reshape(vshape) + _internal._crop_assign(self, value, out=self, + begin=begin, end=end) + def __getitem__(self, key): """x.__getitem__(i) <=> x[i] @@ -385,22 +477,50 @@ def __getitem__(self, key): [ 3., 4., 5.]], dtype=float32) """ # multi-dimensional slicing is not supported yet - if isinstance(key, int): + if isinstance(key, integer_types): if key > self.shape[0] - 1: raise IndexError( 'index {} is out of bounds for axis 0 with size {}'.format( key, self.shape[0])) return self._at(key) - if isinstance(key, py_slice): + elif isinstance(key, py_slice): if key.step is not None: - raise ValueError('NDArray only supports continuous slicing on axis 0') + raise ValueError("NDArray only supports slicing with step size 1.") if key.start is not None or key.stop is not None: return self._slice(key.start, key.stop) else: return self - if isinstance(key, tuple): - raise ValueError('Multi-dimension indexing is not supported') - + elif isinstance(key, tuple): + shape = self.shape + oshape = [] + begin = [] + end = [] + assert len(shape) >= len(key), \ + "Slicing dimensions exceeds array dimensions, %d vs %d"%( + len(key), len(shape)) + i = -1 + for i, slice_i in enumerate(key): + if isinstance(slice_i, integer_types): + begin.append(slice_i) + end.append(slice_i+1) + elif isinstance(slice_i, py_slice): + if slice_i.step is not None: + raise ValueError("NDArray only supports slicing with step size 1.") + begin.append(0 if slice_i.start is None else slice_i.start) + end.append(shape[i] if slice_i.stop is None else slice_i.stop) + oshape.append(end[i] - begin[i]) + else: + raise ValueError( + "NDArray does not support slicing with key %s of type %s."%( + str(slice_i), str(type(slice_i)))) + oshape.extend(shape[i+1:]) + if len(oshape) == 0: + oshape.append(1) + return slice(self, begin, end).reshape(oshape) + else: + raise ValueError( + "NDArray does not support slicing with key %s of type %s."%( + str(key), str(type(key)))) def _sync_copyfrom(self, source_array): """Performs a synchronized copy from the `source_array` to the current array. @@ -661,7 +781,7 @@ def shape(self): def size(self): """Number of elements in the array. - Equivalent to the product of the array’s dimensions. + Equivalent to the product of the array's dimensions. Examples -------- @@ -672,7 +792,10 @@ def size(self): >>> np.prod(x.shape) 30 """ - return np.prod(self.shape) + size = 1 + for i in self.shape: + size *= i + return size @property def context(self): @@ -697,7 +820,7 @@ def context(self): @property def dtype(self): - """Data-type of the array’s elements. + """Data-type of the array's elements. Returns ------- @@ -746,6 +869,24 @@ def T(self): return transpose(self) # pylint: enable= invalid-name, undefined-variable + @property + def _fresh_grad(self): + """Whether this array's corresponding gradient array + (registered via `autograd.mark_variables`) has been + updated by `autograd.backward` since last reset. + + `_fresh_grad` need to be manually set to False + after consuming gradient (usually after updating this + array). + """ + out = ctypes.c_int() + check_call(_LIB.MXNDArrayGetGradState(self.handle, ctypes.byref(out))) + return out.value + + @_fresh_grad.setter + def _fresh_grad(self, state): + check_call(_LIB.MXNDArraySetGradState(self.handle, ctypes.c_int(state))) + def asnumpy(self): """Returns a ``numpy.ndarray`` object with value copied from this array. @@ -901,7 +1042,65 @@ def as_in_context(self, context): return self return self.copyto(context) -_init_ndarray_module(NDArray, "mxnet") + def attach_grad(self, grad_req='write'): + """Attach a gradient buffer to this NDArray, so that `backward` + can compute gradient with respect to it. + + Parameters + ---------- + grad_req : {'write', 'add', 'null'} + How gradient will be accumulated. + - 'write': gradient will be overwritten on every backward. + - 'add': gradient will be added to existing value on every backward. + - 'null': do not compute gradient for this NDArray. + """ + grad = zeros_like(self) # pylint: disable=undefined-variable + grad_req = _GRAD_REQ_MAP[grad_req] + check_call(_LIB.MXAutogradMarkVariables( + 1, ctypes.pointer(self.handle), + ctypes.pointer(mx_uint(grad_req)), + ctypes.pointer(grad.handle))) + + @property + def grad(self): + """Returns gradient buffer attached to this NDArray.""" + hdl = NDArrayHandle() + check_call(_LIB.MXNDArrayGetGrad(self.handle, ctypes.byref(hdl))) + if hdl.value is None: + return None + return NDArray(hdl) + + def detach(self): + """Returns a new NDArray, detached from the current graph.""" + hdl = NDArrayHandle() + check_call(_LIB.MXNDArrayDetach(self.handle, ctypes.byref(hdl))) + return NDArray(hdl) + + def backward(self, out_grad=None, retain_graph=False, train_mode=True): + """Compute the gradients of this NDArray w.r.t variables. + + Parameters + ---------- + out_grad : NDArray, optional + Gradient with respect to head. + retain_graph : bool, optional + Whether to retain the computaion graph for another backward + pass on the same graph. By default the computaion history + is cleared. + train_mode : bool, optional + Whether to compute gradient for training or inference. + """ + if out_grad is None: + ograd_handles = [NDArrayHandle(0)] + else: + ograd_handles = [out_grad.handle] + + check_call(_LIB.MXAutogradBackwardEx( + 1, c_array(NDArrayHandle, [self.handle]), + c_array(NDArrayHandle, ograd_handles), + ctypes.c_int(retain_graph), + ctypes.c_int(train_mode))) + def onehot_encode(indices, out): """One-hot encoding indices into matrix out. @@ -940,7 +1139,7 @@ def empty(shape, ctx=None, dtype=mx_real_t): >>> mx.nd.empty((1,2), mx.gpu(0), 'float16') """ - if isinstance(shape, int): + if isinstance(shape, integer_types): shape = (shape, ) if ctx is None: ctx = Context.default_ctx @@ -957,6 +1156,8 @@ def zeros(shape, ctx=None, dtype=mx_real_t, **kwargs): An optional device context (default is the current default context). dtype : str or numpy.dtype, optional An optional value type (default is `float32`). + out : NDArray, optional + The output NDArray (default is `None`). Returns ------- @@ -976,7 +1177,7 @@ def zeros(shape, ctx=None, dtype=mx_real_t, **kwargs): if ctx is None: ctx = Context.default_ctx # pylint: disable= no-member, protected-access - return _internal._zeros(shape=shape, ctx=ctx, dtype=dtype) + return _internal._zeros(shape=shape, ctx=ctx, dtype=dtype, **kwargs) # pylint: enable= no-member, protected-access def ones(shape, ctx=None, dtype=mx_real_t, **kwargs): @@ -991,6 +1192,8 @@ def ones(shape, ctx=None, dtype=mx_real_t, **kwargs): Defaults to the current default context (``mxnet.Context.default_ctx``). dtype : str or numpy.dtype, optional An optional value type (default is `float32`). + out : NDArray, optional + The output NDArray (default is `None`). Returns ------- @@ -1010,10 +1213,10 @@ def ones(shape, ctx=None, dtype=mx_real_t, **kwargs): if ctx is None: ctx = Context.default_ctx # pylint: disable= no-member, protected-access - return _internal._ones(shape=shape, ctx=ctx, dtype=dtype) + return _internal._ones(shape=shape, ctx=ctx, dtype=dtype, **kwargs) # pylint: enable= no-member, protected-access -def full(shape, val, ctx=None, dtype=mx_real_t): +def full(shape, val, ctx=None, dtype=mx_real_t, out=None): """Returns a new array of given shape and type, filled with the given value `val`. Parameters @@ -1026,6 +1229,8 @@ def full(shape, val, ctx=None, dtype=mx_real_t): Device context (default is the current default context). dtype : `str` or `numpy.dtype`, optional The data type of the returned `NDArray`. The default datatype is `float32`. + out : NDArray, optional + The output NDArray (default is `None`). Returns ------- @@ -1041,9 +1246,9 @@ def full(shape, val, ctx=None, dtype=mx_real_t): >>> mx.nd.full((1, 2), 2.0, dtype='float16').asnumpy() array([[ 2., 2.]], dtype=float16) """ - arr = empty(shape, ctx, dtype) - arr[:] = val - return arr + out = empty(shape, ctx, dtype) if out is None else out + out[:] = val + return out def array(source_array, ctx=None, dtype=None): @@ -1463,6 +1668,62 @@ def divide(lhs, rhs): _internal._rdiv_scalar) # pylint: enable= no-member, protected-access +def modulo(lhs, rhs): + """Returns element-wise modulo of the input arrays with broadcasting. + + Equivalent to ``lhs % rhs`` and ``mx.nd.broadcast_mod(lhs, rhs)``. + + .. note:: + + If the corresponding dimensions of two arrays have the same size or one of them has size 1, + then the arrays are broadcastable to a common shape. + + Parameters + ---------- + lhs : scalar or array + First array in modulo. + rhs : scalar or array + Second array in modulo. + The arrays to be taken modulo. If ``lhs.shape != rhs.shape``, they must be + broadcastable to a common shape. + + Returns + ------- + NDArray + The element-wise modulo of the input arrays. + + Examples + -------- + >>> x = mx.nd.ones((2,3))*6 + >>> y = mx.nd.ones((2,1))*4 + >>> x.asnumpy() + array([[ 6., 6., 6.], + [ 6., 6., 6.]], dtype=float32) + >>> y.asnumpy() + array([[ 4.], + [ 4.]], dtype=float32) + >>> x%5 + + >>> (x%5).asnumpy() + array([[ 1., 1., 1.], + [ 1., 1., 1.]], dtype=float32) + >>> (x%y).asnumpy() + array([[ 2., 2., 2.], + [ 2., 2., 2.]], dtype=float32) + >>> mx.nd.modulo(x,y).asnumpy() + array([[ 2., 2., 2.], + [ 2., 2., 2.]], dtype=float32) + """ + # pylint: disable= no-member, protected-access + return _ufunc_helper( + lhs, + rhs, + broadcast_mod, + operator.mod, + _internal._mod_scalar, + _internal._rmod_scalar) + # pylint: enable= no-member, protected-access + def power(base, exp): """Returns result of first array elements raised to powers from second array, element-wise with broadcasting. @@ -2048,6 +2309,7 @@ def negative(arr): """ return multiply(arr, -1.0) + def load(fname): """Loads an array from file. @@ -2095,7 +2357,7 @@ def save(fname, data): ---------- fname : str The filename. - data : list of ``NDArray` or dict of str to ``NDArray`` + data : ``NDArray``, list of ``NDArray` or dict of str to ``NDArray`` The data to save. Examples @@ -2109,6 +2371,8 @@ def save(fname, data): >>> mx.nd.load('my_dict') {'y': , 'x': } """ + if isinstance(data, NDArray): + data = [data] handles = [] if isinstance(data, dict): keys = [] @@ -2120,12 +2384,15 @@ def save(fname, data): keys.append(c_str(key)) handles.append(val.handle) keys = c_array(ctypes.c_char_p, keys) - else: + elif isinstance(data, list): for val in data: if not isinstance(val, NDArray): raise TypeError('save only accept dict str->NDArray or list of NDArray') handles.append(val.handle) keys = None + else: + raise ValueError("data needs to either be a NDArray, dict of str, NDArray pairs " + "or a list of NDarrays.") check_call(_LIB.MXNDArraySave(c_str(fname), mx_uint(len(handles)), c_array(NDArrayHandle, handles), @@ -2229,5 +2496,160 @@ def imdecode(str_img, clip_rect=(0, 0, 0, 0), out=None, index=0, channels=3, mea str_img=str_img, out=out) + +# pylint: disable=too-many-locals, invalid-name +def _make_ndarray_function(handle, name): + """Create a NDArray function from the FunctionHandle.""" + real_name = ctypes.c_char_p() + desc = ctypes.c_char_p() + num_args = mx_uint() + arg_names = ctypes.POINTER(ctypes.c_char_p)() + arg_types = ctypes.POINTER(ctypes.c_char_p)() + arg_descs = ctypes.POINTER(ctypes.c_char_p)() + key_var_num_args = ctypes.c_char_p() + ret_type = ctypes.c_char_p() + + check_call(_LIB.MXSymbolGetAtomicSymbolInfo( + handle, ctypes.byref(real_name), ctypes.byref(desc), + ctypes.byref(num_args), + ctypes.byref(arg_names), + ctypes.byref(arg_types), + ctypes.byref(arg_descs), + ctypes.byref(key_var_num_args), + ctypes.byref(ret_type))) + narg = int(num_args.value) + arg_names = [py_str(arg_names[i]) for i in range(narg)] + arg_types = [py_str(arg_types[i]) for i in range(narg)] + func_name = name + key_var_num_args = py_str(key_var_num_args.value) + ret_type = py_str(ret_type.value) if ret_type.value is not None else '' + doc_str = _build_doc(func_name, + py_str(desc.value), + arg_names, + arg_types, + [py_str(arg_descs[i]) for i in range(narg)], + key_var_num_args, + ret_type) + + dtype_name = None + arr_name = None + ndsignature = [] + signature = [] + ndarg_names = [] + kwarg_names = [] + for i in range(narg): + name, atype = arg_names[i], arg_types[i] + if name == 'dtype': + dtype_name = name + signature.append('%s=_Null'%name) + elif atype.startswith('NDArray') or atype.startswith('Symbol'): + assert not arr_name, \ + "Op can only have one argument with variable " \ + "size and it must be the last argument." + if atype.endswith('[]'): + ndsignature.append('*%s'%name) + arr_name = name + else: + ndsignature.append('%s=None'%name) + ndarg_names.append(name) + else: + signature.append('%s=_Null'%name) + kwarg_names.append(name) + signature.append('out=None') + signature.append('name=None') + signature.append('**kwargs') + signature = ndsignature + signature + + code = [] + if arr_name: + code.append(""" +def %s(*%s, **kwargs):"""%(func_name, arr_name)) + code.append(""" + ndargs = [] + for i in {}: + assert isinstance(i, NDArrayBase), \\ + "Positional arguments must have NDArray type, " \\ + "but got %s"%str(i) + ndargs.append(i)""".format(arr_name)) + if dtype_name is not None: + code.append(""" + if '%s' in kwargs: + kwargs['%s'] = np.dtype(kwargs['%s']).name"""%( + dtype_name, dtype_name, dtype_name)) + code.append(""" + _ = kwargs.pop('name', None) + out = kwargs.pop('out', None) + keys = list(kwargs.keys()) + vals = list(kwargs.values())""") + else: + code.append(""" +def %s(%s): + ndargs = [] + keys = list(kwargs.keys()) + vals = list(kwargs.values())"""%(func_name, ', '.join(signature))) + # NDArray args + for name in ndarg_names: # pylint: disable=redefined-argument-from-local + code.append(""" + if {name} is not None: + assert isinstance({name}, NDArrayBase), \\ + "Argument {name} must have NDArray type, but got %s"%str({name}) + ndargs.append({name})""".format(name=name)) + # kwargs + for name in kwarg_names: # pylint: disable=redefined-argument-from-local + code.append(""" + if %s is not _Null: + keys.append('%s') + vals.append(%s)"""%(name, name, name)) + # dtype + if dtype_name is not None: + code.append(""" + if %s is not _Null: + keys.append('%s') + vals.append(np.dtype(%s).name)"""%(dtype_name, dtype_name, dtype_name)) + + code.append(""" + return _imperative_invoke(%d, ndargs, keys, vals, out)"""%( + handle.value)) + + local = {} + exec(''.join(code), None, local) # pylint: disable=exec-used + ndarray_function = local[func_name] + ndarray_function.__name__ = func_name + ndarray_function.__doc__ = doc_str + ndarray_function.__module__ = 'mxnet.ndarray' + return ndarray_function + + +# pylint: enable=too-many-locals, invalid-name +def _init_ndarray_module(ndarray_class, root_namespace): + """List and add all the ndarray functions to current module.""" + _set_ndarray_class(ndarray_class) + plist = ctypes.POINTER(ctypes.c_char_p)() + size = ctypes.c_uint() + + check_call(_LIB.MXListAllOpNames(ctypes.byref(size), + ctypes.byref(plist))) + op_names = [] + for i in range(size.value): + op_names.append(py_str(plist[i])) + + module_obj = _sys.modules["%s.ndarray" % root_namespace] + module_internal = _sys.modules["%s._ndarray_internal" % root_namespace] + module_contrib = _sys.modules["%s.contrib.ndarray" % root_namespace] + for name in op_names: + hdl = OpHandle() + check_call(_LIB.NNGetOpHandle(c_str(name), ctypes.byref(hdl))) + function = _make_ndarray_function(hdl, name) + if function.__name__.startswith('_contrib_'): + function.__name__ = function.__name__[9:] + function.__module__ = 'mxnet.contrib.ndarray' + setattr(module_contrib, function.__name__, function) + elif function.__name__.startswith('_'): + setattr(module_internal, function.__name__, function) + else: + setattr(module_obj, function.__name__, function) + +_init_ndarray_module(NDArray, "mxnet") + # from .base import add_fileline_to_docstring # add_fileline_to_docstring(__name__) diff --git a/python/mxnet/ndarray_doc.py b/python/mxnet/ndarray_doc.py index 9cc4545b9fe3..0c51036d8208 100644 --- a/python/mxnet/ndarray_doc.py +++ b/python/mxnet/ndarray_doc.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # coding: utf-8 # pylint: disable=unused-argument, too-many-arguments """Extra symbol documents""" diff --git a/python/mxnet/notebook/__init__.py b/python/mxnet/notebook/__init__.py index 71a30e3f037d..d605d7483330 100644 --- a/python/mxnet/notebook/__init__.py +++ b/python/mxnet/notebook/__init__.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # pylint: disable=invalid-name, missing-docstring, no-init, old-style-class, multiple-statements """MXNet notebook: an easy to use visualization platform""" diff --git a/python/mxnet/notebook/callback.py b/python/mxnet/notebook/callback.py index d73b0233befd..56321b715b40 100644 --- a/python/mxnet/notebook/callback.py +++ b/python/mxnet/notebook/callback.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # pylint: disable=fixme, invalid-name, missing-docstring, no-init, old-style-class, multiple-statements # pylint: disable=arguments-differ, too-many-arguments, no-member """Visualization callback function @@ -90,14 +107,14 @@ def eval_df(self): """The dataframe with evaluation data. This has validation scores calculated at the end of each epoch. """ - return self._dataframes['train'] + return self._dataframes['eval'] @property def epoch_df(self): """The dataframe with epoch data. This has timing information. """ - return self._dataframes['train'] + return self._dataframes['epoch'] @property def all_dataframes(self): diff --git a/python/mxnet/operator.py b/python/mxnet/operator.py index a08e764088a5..1337bbccc3c8 100644 --- a/python/mxnet/operator.py +++ b/python/mxnet/operator.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # coding: utf-8 # pylint: disable=invalid-name, protected-access, too-many-arguments, no-self-use, too-many-locals, broad-except """numpy interface for operators.""" @@ -9,9 +26,9 @@ from ctypes import CFUNCTYPE, POINTER, Structure, pointer from ctypes import c_void_p, c_int, c_char, c_char_p, cast, c_bool -from .base import _LIB, check_call +from .base import _LIB, check_call, MXCallbackList from .base import c_array, c_str, mx_uint, mx_float, ctypes2numpy_shared, NDArrayHandle, py_str -from . import symbol +from . import symbol, context from .ndarray import NDArray, _DTYPE_NP_TO_MX, _DTYPE_MX_TO_NP c_int_p = POINTER(c_int) @@ -448,7 +465,7 @@ class CustomOpProp(object): The default declare_backward_dependency function. Use this value to determine whether this operator needs gradient input. """ - def __init__(self, need_top_grad=False): + def __init__(self, need_top_grad=True): self.need_top_grad_ = need_top_grad def infer_shape(self, in_shape): @@ -471,7 +488,7 @@ def infer_shape(self, in_shape): List of aux shapes calculated from in_shape, in the same order as declared in list_auxiliary_states. """ - return in_shape, [in_shape[0]], [] + return in_shape, (in_shape[0],)*len(self.list_outputs()), () def infer_type(self, in_type): """infer_type interface. override to create new operators @@ -577,15 +594,6 @@ def register(reg_name): """Register a subclass of CustomOpProp to the registry with name reg_name.""" def do_register(prop_cls): """Register a subclass of CustomOpProp to the registry.""" - - class MXCallbackList(Structure): - """Structure that holds Callback information. Passed to CustomOpProp.""" - _fields_ = [ - ('num_callbacks', c_int), - ('callbacks', POINTER(CFUNCTYPE(c_int))), - ('contexts', POINTER(c_void_p)) - ] - fb_functype = CFUNCTYPE(c_int, c_int, POINTER(c_void_p), POINTER(c_int), POINTER(c_int), c_int, c_void_p) del_functype = CFUNCTYPE(c_int, c_void_p) @@ -626,9 +634,15 @@ def infer_shape_entry(num_tensor, tensor_dims, ishape, oshape, ashape = ret else: raise AssertionError("infer_shape must return 2 or 3 lists") - assert len(oshape) == n_out - assert len(ishape) == n_in - assert len(ashape) == n_aux + assert len(oshape) == n_out, \ + "InferShape Error: expecting %d entries in returned output " \ + "shapes, got %d."%(n_out, len(oshape)) + assert len(ishape) == n_in, \ + "InferShape Error: expecting %d entries in returned input " \ + "shapes, got %d."%(n_in, len(ishape)) + assert len(ashape) == n_aux, \ + "InferShape Error: expecting %d entries in returned aux state " \ + "shapes, got %d."%(n_aux, len(ashape)) rshape = list(ishape) + list(oshape) + list(ashape) for i in range(n_in+n_out+n_aux): tensor_shapes[i] = cast(c_array(mx_uint, rshape[i]), POINTER(mx_uint)) @@ -657,9 +671,15 @@ def infer_type_entry(num_tensor, tensor_types, _): itype, otype, atype = ret else: raise AssertionError("infer_type must return 2 or 3 lists") - assert len(otype) == n_out - assert len(itype) == n_in - assert len(atype) == n_aux + assert len(otype) == n_out, \ + "InferType Error: expecting %d entries in returned output " \ + "shapes, got %d."%(n_out, len(otype)) + assert len(itype) == n_in, \ + "InferType Error: expecting %d entries in returned input " \ + "shapes, got %d."%(n_in, len(itype)) + assert len(atype) == n_aux, \ + "InferType Error: expecting %d entries in returned aux state " \ + "shapes, got %d."%(n_aux, len(atype)) rtype = list(itype) + list(otype) + list(atype) for i, dtype in enumerate(rtype): tensor_types[i] = _DTYPE_NP_TO_MX[dtype] @@ -734,6 +754,9 @@ def declare_backward_dependency_entry(out_grad, in_data, out_data, num_dep, deps def create_operator_entry(ctx, num_inputs, shapes, ndims, dtypes, ret, _): """C Callback for CustomOpProp::CreateOperator""" try: + ctx = py_str(ctx) + sep = ctx.find('(') + ctx = context.Context(ctx[:sep], int(ctx[sep+1:-1])) ndims = [ndims[i] for i in range(num_inputs)] shapes = [[shapes[i][j] for j in range(ndims[i])] for i in range(num_inputs)] dtypes = [dtypes[i] for i in range(num_inputs)] @@ -753,9 +776,10 @@ def forward_entry(num_ndarray, ndarraies, tags, reqs, is_train, _): NDArrayHandle), writable=False)) reqs = [req_enum[reqs[i]] for i in range(len(tensors[1]))] - op.forward(is_train=is_train, req=reqs, - in_data=tensors[0], out_data=tensors[1], - aux=tensors[4]) + with ctx: + op.forward(is_train=is_train, req=reqs, + in_data=tensors[0], out_data=tensors[1], + aux=tensors[4]) except Exception: print('Error in CustomOp.forward: %s' % traceback.format_exc()) return False @@ -776,10 +800,11 @@ def backward_entry(num_ndarray, ndarraies, tags, reqs, is_train, _): NDArrayHandle), writable=False)) reqs = [req_enum[reqs[i]] for i in range(len(tensors[2]))] - op.backward(req=reqs, - in_data=tensors[0], out_data=tensors[1], - in_grad=tensors[2], out_grad=tensors[3], - aux=tensors[4]) + with ctx: + op.backward(req=reqs, + in_data=tensors[0], out_data=tensors[1], + in_grad=tensors[2], out_grad=tensors[3], + aux=tensors[4]) except Exception: print('Error in CustomOp.backward: %s' % traceback.format_exc()) return False diff --git a/python/mxnet/optimizer.py b/python/mxnet/optimizer.py index d2d394076e89..1ef9cc845036 100644 --- a/python/mxnet/optimizer.py +++ b/python/mxnet/optimizer.py @@ -1,9 +1,29 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + """Weight updating functions.""" import math import pickle import logging -from .ndarray import NDArray, zeros, clip, sqrt, sign -from .ndarray import sgd_update, sgd_mom_update, adam_update, rmsprop_update, rmspropalex_update +import warnings +import numpy +from .ndarray import (NDArray, zeros, clip, sqrt, sign, array, maximum, abs as NDabs) +from .ndarray import (sgd_update, sgd_mom_update, adam_update, rmsprop_update, rmspropalex_update, + mp_sgd_update, mp_sgd_mom_update) from .random import normal @@ -36,11 +56,12 @@ class Optimizer(object): The Symbol this optimizer is applying to. begin_num_update : int, optional - The initial number of updates + The initial number of updates. """ def __init__(self, rescale_grad=1., param_idx2name=None, wd=0., clip_gradient=None, learning_rate=0.01, - lr_scheduler=None, sym=None, begin_num_update=0): + lr_scheduler=None, sym=None, begin_num_update=0, + param_dict=None): self.rescale_grad = rescale_grad self.lr = learning_rate self.lr_scheduler = lr_scheduler @@ -61,6 +82,7 @@ def __init__(self, rescale_grad=1., param_idx2name=None, wd=0., 'param_idx2name should be a dict of param indexes to names.' self.idx2name = param_idx2name.copy() self.sym = sym + self.param_dict = param_dict if param_dict else {} self.set_lr_mult({}) self.set_wd_mult({}) @@ -69,7 +91,7 @@ def __init__(self, rescale_grad=1., param_idx2name=None, wd=0., @staticmethod def register(klass): - """Register a new optimizer. + """Registers a new optimizer. Once an optimizer is registered, we can create an instance of this optimizer with `create_optimizer` later. @@ -97,11 +119,9 @@ def register(klass): @staticmethod def create_optimizer(name, **kwargs): - """Instantiate an optimizer with a given name and kwargs. + """Instantiates an optimizer with a given name and kwargs. - Notes - ----- - We can use the alias `create` for ``Optimizer.create_optimizer`` + .. note:: We can use the alias `create` for ``Optimizer.create_optimizer``. Parameters ---------- @@ -133,7 +153,7 @@ def create_optimizer(name, **kwargs): def create_state(self, index, weight): - """Create auxiliary state for a given weight + """Creates auxiliary state for a given weight. Some optimizers require additional states, e.g. as momentum, in addition to gradients in order to update weights. This function creates state @@ -172,7 +192,7 @@ def update(self, index, weight, grad, state): raise NotImplementedError() def set_lr_scale(self, args_lrscale): # pylint: disable=unused-argument - """[DEPRECATED] set lr scale. Use set_lr_mult instead.""" + """[DEPRECATED] Sets lr scale. Use set_lr_mult instead.""" raise DeprecationWarning def set_lr_mult(self, args_lr_mult): @@ -246,9 +266,10 @@ def set_wd_mult(self, args_wd_mult): self.wd_mult.update(args_wd_mult) def _update_count(self, index): - """Update num_update + """Updates num_update. - Parameters: + Parameters + ---------- index : int The index to be updated. """ @@ -258,7 +279,7 @@ def _update_count(self, index): self.num_update = max(self._index_update_count[index], self.num_update) def _get_lr(self, index): - """Get the learning rate given the index of the weight. + """Gets the learning rate given the index of the weight. Parameters ---------- @@ -275,14 +296,16 @@ def _get_lr(self, index): else: lr = self.lr - if index in self.lr_mult: + if index in self.param_dict: + lr *= self.param_dict[index].lr_mult + elif index in self.lr_mult: lr *= self.lr_mult[index] elif index in self.idx2name: lr *= self.lr_mult.get(self.idx2name[index], 1.0) return lr def _get_wd(self, index): - """get weight decay for index. + """Gets weight decay for index. Returns 0 for non-weights if the name of weights are provided for `__init__`. Parameters @@ -296,7 +319,9 @@ def _get_wd(self, index): Weight decay for this index. """ wd = self.wd - if index in self.wd_mult: + if index in self.param_dict: + wd *= self.param_dict[index].wd_mult + elif index in self.wd_mult: wd *= self.wd_mult[index] elif index in self.idx2name: wd *= self.wd_mult.get(self.idx2name[index], 1.0) @@ -318,22 +343,40 @@ class SGD(Optimizer): :class:`~mxnet.ndarray.sgd_mom_update`. This optimizer accepts the following parameters in addition to those accepted - by :class:`.Optimizer`: + by :class:`.Optimizer`. Parameters ---------- momentum : float, optional The momentum value. + multi_precision: bool, optional + Flag to control the internal precision of the optimizer. + ``False`` results in using the same precision as the weights (default), + ``True`` makes internal 32-bit copy of the weights and applies gradients + in 32-bit precision even if actual weights used in the model have lower precision. + Turning this on can improve convergence and accuracy when training with float16. """ - def __init__(self, momentum=0.0, **kwargs): + def __init__(self, momentum=0.0, multi_precision=False, **kwargs): super(SGD, self).__init__(**kwargs) self.momentum = momentum + self.multi_precision = multi_precision def create_state(self, index, weight): - if self.momentum == 0.0: - return None - else: - return zeros(weight.shape, weight.context, dtype=weight.dtype) + momentum = None + weight_master_copy = None + if self.multi_precision and weight.dtype == numpy.float16: + weight_master_copy = array(weight, ctx=weight.context, dtype=numpy.float32) + if self.momentum != 0.0: + momentum = zeros(weight.shape, weight.context, dtype=numpy.float32) + return (momentum, weight_master_copy) + if weight.dtype == numpy.float16 and not self.multi_precision: + warnings.warn("Accumulating with float16 in optimizer can lead to " + "poor accuracy or slow convergence. " + "Consider using multi_precision=True option of the " + "SGD optimizer") + if self.momentum != 0.0: + momentum = zeros(weight.shape, weight.context, dtype=weight.dtype) + return momentum def update(self, index, weight, grad, state): assert(isinstance(weight, NDArray)) @@ -347,23 +390,33 @@ def update(self, index, weight, grad, state): kwargs['momentum'] = self.momentum if self.clip_gradient: kwargs['clip_gradient'] = self.clip_gradient + use_multi_precision = isinstance(state, (list, tuple)) - if state is not None: - sgd_mom_update(weight, grad, state, out=weight, + if not use_multi_precision: + if state is not None: + sgd_mom_update(weight, grad, state, out=weight, + lr=lr, wd=wd, **kwargs) + else: + sgd_update(weight, grad, out=weight, lr=lr, wd=wd, **kwargs) else: - sgd_update(weight, grad, out=weight, - lr=lr, wd=wd, **kwargs) + if state[0] is not None: + mp_sgd_mom_update(weight, grad, state[0], state[1], out=weight, + lr=lr, wd=wd, **kwargs) + else: + mp_sgd_update(weight, grad, state[1], out=weight, + lr=lr, wd=wd, **kwargs) @register class DCASGD(Optimizer): - """The DCASGD optimizer + """The DCASGD optimizer. - This class implements the optimizer described in *Asynchronous Stochastic Gradient Descent with - Delay Compensation for Distributed Deep Learning*, available at https://arxiv.org/abs/1609.08326 + This class implements the optimizer described in *Asynchronous Stochastic Gradient Descent + with Delay Compensation for Distributed Deep Learning*, + available at https://arxiv.org/abs/1609.08326. This optimizer accepts the following parameters in addition to those accepted - by :class:`.Optimizer`: + by :class:`.Optimizer`. Parameters ---------- @@ -402,11 +455,11 @@ def update(self, index, weight, grad, state): if mom: mom[:] *= self.momentum mom[:] += -lr * (grad + wd * weight + self.lamda \ - * grad * grad * (weight - previous_weight)) + * grad * grad * (weight - previous_weight)) else: assert(self.momentum == 0.0) mom = -lr * (grad + wd * weight + self.lamda \ - * grad * grad * (weight - previous_weight)) + * grad * grad * (weight - previous_weight)) previous_weight[:] = weight weight[:] += mom @@ -414,7 +467,7 @@ def update(self, index, weight, grad, state): class NAG(SGD): """Nesterov accelerated SGD. - This optimizer updates each weight by: + This optimizer updates each weight by:: state = momentum * state + grad + wd * weight weight = weight - (lr * (grad + momentum * state)) @@ -452,7 +505,7 @@ class SGLD(Optimizer): This class implements the optimizer described in the paper *Stochastic Gradient Riemannian Langevin Dynamics on the Probability Simplex*, available at - https://papers.nips.cc/paper/4883-stochastic-gradient-riemannian-langevin-dynamics-on-the-probability-simplex.pdf + https://papers.nips.cc/paper/4883-stochastic-gradient-riemannian-langevin-dynamics-on-the-probability-simplex.pdf. """ def __init__(self, **kwargs): @@ -477,7 +530,7 @@ def update(self, index, weight, grad, state): @register # pylint: disable=invalid-name class ccSGD(SGD): - """[Deprecated] Same as sgd. Left here for backward compatibility.""" + """[DEPRECATED] Same as `SGD`. Left here for backward compatibility.""" def __init__(self, *args, **kwargs): super(ccSGD, self).__init__(*args, **kwargs) @@ -486,10 +539,10 @@ class Adam(Optimizer): """The Adam optimizer. This class implements the optimizer described in *Adam: A Method for - Stochastic Optimization*, available at http://arxiv.org/abs/1412.6980 + Stochastic Optimization*, available at http://arxiv.org/abs/1412.6980. This optimizer accepts the following parameters in addition to those accepted - by :class:`.Optimizer`: + by :class:`.Optimizer`. For details of the update algorithm, see :class:`ndarray.adam_update`. @@ -536,14 +589,14 @@ def update(self, index, weight, grad, state): @register class AdaGrad(Optimizer): - """AdaGrad optimizer + """AdaGrad optimizer. - This calss implements the AdaGrad optiizer described in *Adaptive Subgradient + This class implements the AdaGrad optimizer described in *Adaptive Subgradient Methods for Online Learning and Stochastic Optimization*, and available at - http://www.jmlr.org/papers/volume12/duchi11a/duchi11a.pdf + http://www.jmlr.org/papers/volume12/duchi11a/duchi11a.pdf. This optimizer accepts the following parameters in addition to those accepted - by :class:`.Optimizer`: + by :class:`.Optimizer`. Parameters ---------- @@ -580,14 +633,14 @@ class RMSProp(Optimizer): If ``centered=False``, we follow http://www.cs.toronto.edu/~tijmen/csc321/slides/lecture_slides_lec6.pdf by Tieleman & Hinton, 2012. - For details of the update algorithm see :class:`~mxnet.ndarray.rmsprop_update` + For details of the update algorithm see :class:`~mxnet.ndarray.rmsprop_update`. If ``centered=True``, we follow http://arxiv.org/pdf/1308.0850v5.pdf (38)-(45) by Alex Graves, 2013. - For details of the update algorithm see :class:`~mxnet.ndarray.rmspropalex_update` + For details of the update algorithm see :class:`~mxnet.ndarray.rmspropalex_update`. This optimizer accepts the following parameters in addition to those accepted - by :class:`.Optimizer`: + by :class:`.Optimizer`. Parameters ---------- @@ -602,7 +655,7 @@ class RMSProp(Optimizer): ``True`` will use Graves's version of `RMSProp`, ``False`` will use Tieleman & Hinton's version of `RMSProp`. clip_weights : float, optional - Clips weights into range ``[-clip_weights, clip_weights]`` + Clips weights into range ``[-clip_weights, clip_weights]``. """ def __init__(self, learning_rate=0.001, gamma1=0.9, gamma2=0.9, epsilon=1e-8, centered=False, clip_weights=None, **kwargs): @@ -652,10 +705,10 @@ class AdaDelta(Optimizer): """The AdaDelta optimizer. This class implements AdaDelta, an optimizer described in *ADADELTA: An adaptive - learning rate method*, available at https://arxiv.org/abs/1212.5701 + learning rate method*, available at https://arxiv.org/abs/1212.5701. This optimizer accepts the following parameters in addition to those accepted - by :class:`.Optimizer`: + by :class:`.Optimizer`. Parameters ---------- @@ -698,21 +751,22 @@ def update(self, index, weight, grad, state): #pylint: disable=invalid-name @register class Ftrl(Optimizer): - """ - Reference:Ad Click Prediction: a View from the Trenches + """The Ftrl optimizer. + + Referenced from *Ad Click Prediction: a View from the Trenches*, available at + http://dl.acm.org/citation.cfm?id=2488200. Parameters ---------- lamda1 : float, optional L1 regularization coefficient. - learning_rate : float, optional The initial learning rate. - beta : float, optional Per-coordinate learning rate correlation parameter. - eta_{t,i}=frac{learning_rate}{beta+sqrt{sum_{s=1^}tg_{s,i}^t} - + eta : + .. math:: + \\eta_{t,i} = \\frac{learningrate}{\\beta+\\sqrt{\\sum_{s=1}^tg_{s,i}^t}} """ def __init__(self, lamda1=0.01, learning_rate=0.1, beta=1, **kwargs): @@ -737,7 +791,7 @@ def update(self, index, weight, grad, state): if self.clip_gradient is not None: grad = clip(grad, -self.clip_gradient, self.clip_gradient) - # accumulated g and delta initlization + # accumulated g and delta initialization dn, n = state #update dn, n @@ -746,19 +800,137 @@ def update(self, index, weight, grad, state): # update weight weight[:] = (sign(dn) * self.lamda1 - dn) / \ - ((self.beta + sqrt(n)) / lr + wd) * (NDArray.abs(dn) > self.lamda1) + ((self.beta + sqrt(n)) / lr + wd) * (NDabs(dn) > self.lamda1) + +@register +class Adamax(Optimizer): + """The AdaMax optimizer. + + It is a variant of Adam based on the infinity norm + available at http://arxiv.org/abs/1412.6980 Section 7. + + This optimizer accepts the following parameters in addition to those accepted + by :class:`.Optimizer`. + + Parameters + ---------- + beta1 : float, optional + Exponential decay rate for the first moment estimates. + beta2 : float, optional + Exponential decay rate for the second moment estimates. + """ + def __init__(self, learning_rate=0.002, beta1=0.9, beta2=0.999, **kwargs): + super(Adamax, self).__init__(learning_rate=learning_rate, **kwargs) + self.beta1 = beta1 + self.beta2 = beta2 + + def create_state(self, index, weight): + return (zeros(weight.shape, weight.context, dtype=weight.dtype), # mean + zeros(weight.shape, weight.context, dtype=weight.dtype)) # variance + + def update(self, index, weight, grad, state): + assert(isinstance(weight, NDArray)) + assert(isinstance(grad, NDArray)) + lr = self._get_lr(index) + wd = self._get_wd(index) + self._update_count(index) + + t = self._index_update_count[index] + lr /= (1. - self.beta1**t) + + # preprocess grad + grad = grad * self.rescale_grad + wd * weight + if self.clip_gradient is not None: + grad = clip(grad, -self.clip_gradient, self.clip_gradient) + + # update m_t and u_t + m_t, u_t = state + m_t[:] = self.beta1 * m_t + (1. - self.beta1) * grad + u_t[:] = maximum(self.beta2 * u_t, NDabs(grad)) + + # update weight + weight[:] -= lr * m_t / u_t + +@register +class Nadam(Optimizer): + """The Nesterov Adam optimizer. + + Much like Adam is essentially RMSprop with momentum, + Nadam is Adam RMSprop with Nesterov momentum available + at http://cs229.stanford.edu/proj2015/054_report.pdf. + + This optimizer accepts the following parameters in addition to those accepted + by :class:`.Optimizer`. + + Parameters + ---------- + beta1 : float, optional + Exponential decay rate for the first moment estimates. + beta2 : float, optional + Exponential decay rate for the second moment estimates. + epsilon : float, optional + Small value to avoid division by 0. + schedule_decay : float, optional + Exponential decay rate for the momentum schedule + """ + def __init__(self, learning_rate=0.001, beta1=0.9, beta2=0.999, epsilon=1e-8, + schedule_decay=0.004, **kwargs): + super(Nadam, self).__init__(learning_rate=learning_rate, **kwargs) + self.beta1 = beta1 + self.beta2 = beta2 + self.epsilon = epsilon + self.schedule_decay = schedule_decay + self.m_schedule = 1. + + def create_state(self, index, weight): + return (zeros(weight.shape, weight.context, dtype=weight.dtype), # mean + zeros(weight.shape, weight.context, dtype=weight.dtype)) # variance + + def update(self, index, weight, grad, state): + assert(isinstance(weight, NDArray)) + assert(isinstance(grad, NDArray)) + lr = self._get_lr(index) + wd = self._get_wd(index) + self._update_count(index) + + t = self._index_update_count[index] + + # preprocess grad + grad *= self.rescale_grad + wd * weight + if self.clip_gradient is not None: + grad = clip(grad, -self.clip_gradient, self.clip_gradient) + + # warming momentum schedule + momentum_t = self.beta1 * (1. - 0.5 * (pow(0.96, t * self.schedule_decay))) + momentum_t_1 = self.beta1 * (1. - 0.5 * (pow(0.96, (t + 1) * self.schedule_decay))) + self.m_schedule = self.m_schedule * momentum_t + m_schedule_next = self.m_schedule * momentum_t_1 + + # update m_t and v_t + m_t, v_t = state + m_t[:] = self.beta1 * m_t + (1. - self.beta1) * grad + v_t[:] = self.beta2 * v_t + (1. - self.beta2) * grad * grad + + grad_prime = grad / (1. - self.m_schedule) + m_t_prime = m_t / (1. - m_schedule_next) + v_t_prime = v_t / (1. - pow(self.beta2, t)) + m_t_bar = (1. - momentum_t) * grad_prime + momentum_t_1 * m_t_prime + + # update weight + weight[:] -= lr * m_t_bar / (sqrt(v_t_prime) + self.epsilon) @register class Test(Optimizer): + """The Test optimizer""" def __init__(self, **kwargs): super(Test, self).__init__(**kwargs) def create_state(self, index, weight): - """Create a state to duplicate weight""" + """Creates a state to duplicate weight.""" return zeros(weight.shape, weight.context) def update(self, index, weight, grad, state): - """performs w += rescale_grad * grad""" + """Performs w += rescale_grad * grad.""" weight[:] += grad * self.rescale_grad state[:] = weight @@ -770,23 +942,42 @@ class Updater(object): def __init__(self, optimizer): self.optimizer = optimizer self.states = {} + self.states_synced = {} def __call__(self, index, grad, weight): - """Update weight given gradient and index.""" + """Updates weight given gradient and index.""" if index not in self.states: self.states[index] = self.optimizer.create_state(index, weight) + self.states_synced[index] = True + elif not self.states_synced[index]: + self.states[index] = \ + self.sync_state_context(self.states[index], weight.context) + self.states_synced[index] = True self.optimizer.update(index, weight, grad, self.states[index]) + def sync_state_context(self, state, context): + if isinstance(state, NDArray): + return state.as_in_context(context) + elif isinstance(state, (tuple, list)): + synced_state = (self.sync_state_context(i, context) for i in state) + if isinstance(state, tuple): + return tuple(synced_state) + else: + return list(synced_state) + else: + return state + def set_states(self, states): - """Set updater states.""" + """Sets updater states.""" self.states = pickle.loads(states) + self.states_synced = dict.fromkeys(self.states.keys(), False) def get_states(self): - """Get updater states.""" + """Gets updater states.""" return pickle.dumps(self.states) def get_updater(optimizer): - """Return a clossure of the updater needed for kvstore. + """Returns a closure of the updater needed for kvstore. Parameters ---------- @@ -796,6 +987,6 @@ def get_updater(optimizer): Returns ------- updater: function - The clossure of the updater. + The closure of the updater. """ return Updater(optimizer) diff --git a/python/mxnet/profiler.py b/python/mxnet/profiler.py index 0d4b8fb979ba..7356ed0fb8d0 100644 --- a/python/mxnet/profiler.py +++ b/python/mxnet/profiler.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # coding: utf-8 # pylint: disable=fixme, invalid-name, too-many-arguments, too-many-locals, too-many-lines # pylint: disable=too-many-branches, too-many-statements diff --git a/python/mxnet/random.py b/python/mxnet/random.py index 91c2f5035ffa..29b250d980ce 100644 --- a/python/mxnet/random.py +++ b/python/mxnet/random.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # coding: utf-8 # pylint: disable=no-member, protected-access, unused-import, no-name-in-module """Random number interface of MXNet.""" diff --git a/python/mxnet/recordio.py b/python/mxnet/recordio.py index 3ffa4cf9b5e7..39f442b6aefc 100644 --- a/python/mxnet/recordio.py +++ b/python/mxnet/recordio.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + """Read and write for the RecordIO data format.""" from __future__ import absolute_import from collections import namedtuple @@ -296,9 +313,23 @@ def pack(header, s): ---------- header : IRHeader Header of the image record. - ``header.label`` can be a number or an array. + ``header.label`` can be a number or an array. See more detail in ``IRHeader``. + s : str + Raw image string to be packed. + + Returns + ------- s : str - string to pack + The packed string. + + Examples + -------- + >>> label = 4 # label can also be a 1-D array, for example: label = [1,2,3] + >>> id = 2574 + >>> header = mx.recordio.IRHeader(0, label, id, 0) + >>> with open(path, 'r') as file: + ... s = file.read() + >>> packed_s = mx.recordio.pack(header, s) """ header = IRHeader(*header) if isinstance(header.label, numbers.Number): @@ -324,6 +355,14 @@ def unpack(s): Header of the image record. s : str Unpacked string. + + Examples + -------- + >>> record = mx.recordio.MXRecordIO('test.rec', 'r') + >>> item = record.read() + >>> header, s = mx.recordio.unpack(item) + >>> header + HEADER(flag=0, label=14.0, id=20129312, id2=0) """ header = IRHeader(*struct.unpack(_IR_FORMAT, s[:_IR_SIZE])) s = s[_IR_SIZE:] @@ -340,7 +379,7 @@ def unpack_img(s, iscolor=-1): s : str String buffer from ``MXRecordIO.read``. iscolor : int - image format option for ``cv2.imdecode``. + Image format option for ``cv2.imdecode``. Returns ------- @@ -348,6 +387,26 @@ def unpack_img(s, iscolor=-1): Header of the image record. img : numpy.ndarray Unpacked image. + + Examples + -------- + >>> record = mx.recordio.MXRecordIO('test.rec', 'r') + >>> item = record.read() + >>> header, img = mx.recordio.unpack_img(item) + >>> header + HEADER(flag=0, label=14.0, id=20129312, id2=0) + >>> img + array([[[ 23, 27, 45], + [ 28, 32, 50], + ..., + [ 36, 40, 59], + [ 35, 39, 58]], + ..., + [[ 91, 92, 113], + [ 97, 98, 119], + ..., + [168, 169, 167], + [166, 167, 165]]], dtype=uint8) """ header, s = unpack(s) img = np.fromstring(s, dtype=np.uint8) @@ -362,9 +421,9 @@ def pack_img(header, img, quality=95, img_fmt='.jpg'): ---------- header : IRHeader Header of the image record. - ``header.label`` can be a number or an array. + ``header.label`` can be a number or an array. See more detail in ``IRHeader``. img : numpy.ndarray - image to pack + Image to be packed. quality : int Quality for JPEG encoding in range 1-100, or compression for PNG encoding in range 1-9. img_fmt : str @@ -374,6 +433,14 @@ def pack_img(header, img, quality=95, img_fmt='.jpg'): ------- s : str The packed string. + + Examples + -------- + >>> label = 4 # label can also be a 1-D array, for example: label = [1,2,3] + >>> id = 2574 + >>> header = mx.recordio.IRHeader(0, label, id, 0) + >>> img = cv2.imread('test.jpg') + >>> packed_s = mx.recordio.pack_img(header, img) """ assert cv2 is not None jpg_formats = ['.JPG', '.JPEG'] diff --git a/python/mxnet/registry.py b/python/mxnet/registry.py index fdd095e1ebb5..4a4f22fa142b 100644 --- a/python/mxnet/registry.py +++ b/python/mxnet/registry.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # coding: utf-8 # pylint: disable=no-member diff --git a/python/mxnet/rnn/__init__.py b/python/mxnet/rnn/__init__.py index 99b0a2da0329..dbf382ecc411 100644 --- a/python/mxnet/rnn/__init__.py +++ b/python/mxnet/rnn/__init__.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # coding: utf-8 # pylint: disable=wildcard-import """Recurrent neural network module.""" diff --git a/python/mxnet/rnn/io.py b/python/mxnet/rnn/io.py index cfbce25a0e5b..ab51b09c5710 100644 --- a/python/mxnet/rnn/io.py +++ b/python/mxnet/rnn/io.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # coding: utf-8 # pylint: disable=too-many-arguments, too-many-locals """Definition of various recurrent neural network cells.""" @@ -7,7 +24,7 @@ import random import numpy as np -from ..io import DataIter, DataBatch +from ..io import DataIter, DataBatch, DataDesc from .. import ndarray def encode_sentences(sentences, vocab=None, invalid_label=-1, invalid_key='\n', start_label=0): @@ -24,8 +41,8 @@ def encode_sentences(sentences, vocab=None, invalid_label=-1, invalid_key='\n', Optional input Vocabulary invalid_label : int, default -1 Index for invalid token, like - invalid_key : str, default '\n' - Key for invalid token. Use '\n' for end + invalid_key : str, default '\\n' + Key for invalid token. Use '\\n' for end of sentence by default. start_label : int lowest index. @@ -85,7 +102,7 @@ class BucketSentenceIter(DataIter): """ def __init__(self, sentences, batch_size, buckets=None, invalid_label=-1, data_name='data', label_name='softmax_label', dtype='float32', - layout='NTC'): + layout='NT'): super(BucketSentenceIter, self).__init__() if not buckets: buckets = [i for i, j in enumerate(np.bincount([len(s) for s in sentences])) @@ -116,14 +133,23 @@ def __init__(self, sentences, batch_size, buckets=None, invalid_label=-1, self.nddata = [] self.ndlabel = [] self.major_axis = layout.find('N') + self.layout = layout self.default_bucket_key = max(buckets) if self.major_axis == 0: - self.provide_data = [(data_name, (batch_size, self.default_bucket_key))] - self.provide_label = [(label_name, (batch_size, self.default_bucket_key))] + self.provide_data = [DataDesc( + name=self.data_name, shape=(batch_size, self.default_bucket_key), + layout=self.layout)] + self.provide_label = [DataDesc( + name=self.label_name, shape=(batch_size, self.default_bucket_key), + layout=self.layout)] elif self.major_axis == 1: - self.provide_data = [(data_name, (self.default_bucket_key, batch_size))] - self.provide_label = [(label_name, (self.default_bucket_key, batch_size))] + self.provide_data = [DataDesc( + name=self.data_name, shape=(self.default_bucket_key, batch_size), + layout=self.layout)] + self.provide_label = [DataDesc( + name=self.label_name, shape=(self.default_bucket_key, batch_size), + layout=self.layout)] else: raise ValueError("Invalid layout %s: Must by NT (batch major) or TN (time major)") @@ -166,5 +192,9 @@ def next(self): return DataBatch([data], [label], pad=0, bucket_key=self.buckets[i], - provide_data=[(self.data_name, data.shape)], - provide_label=[(self.label_name, label.shape)]) + provide_data=[DataDesc( + name=self.data_name, shape=data.shape, + layout=self.layout)], + provide_label=[DataDesc( + name=self.label_name, shape=label.shape, + layout=self.layout)]) diff --git a/python/mxnet/rnn/rnn.py b/python/mxnet/rnn/rnn.py index 6a1213b272b0..47307c55b042 100644 --- a/python/mxnet/rnn/rnn.py +++ b/python/mxnet/rnn/rnn.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # coding: utf-8 # pylint: disable=too-many-arguments, no-member """Functions for constructing recurrent neural networks.""" @@ -18,7 +35,7 @@ def save_rnn_checkpoint(cells, prefix, epoch, symbol, arg_params, aux_params): Parameters ---------- - cells : RNNCells or list of RNNCells + cells : RNNCell or list of RNNCells The RNN cells used by this symbol. prefix : str Prefix of model name. @@ -48,7 +65,7 @@ def load_rnn_checkpoint(cells, prefix, epoch): Parameters ---------- - cells : RNNCells or list of RNNCells + cells : RNNCell or list of RNNCells The RNN cells used by this symbol. prefix : str Prefix of model name. @@ -83,8 +100,8 @@ def do_rnn_checkpoint(cells, prefix, period=1): Parameters ---------- - cells : subclass of BaseRNNCell - RNN cells used by this module. + cells : RNNCell or list of RNNCells + The RNN cells used by this symbol. prefix : str The file prefix to checkpoint to period : int diff --git a/python/mxnet/rnn/rnn_cell.py b/python/mxnet/rnn/rnn_cell.py index 9ffa9342ee1d..1c3452041494 100644 --- a/python/mxnet/rnn/rnn_cell.py +++ b/python/mxnet/rnn/rnn_cell.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # coding: utf-8 # pylint: disable=no-member, invalid-name, protected-access, no-self-use # pylint: disable=too-many-branches, too-many-arguments, no-self-use @@ -6,6 +23,7 @@ from __future__ import print_function import warnings +import functools from .. import symbol, init, ndarray from ..base import string_types, numeric_types @@ -96,7 +114,7 @@ class BaseRNNCell(object): Prefix for names of layers (this prefix is also used for names of weights if `params` is None i.e. if `params` are being created and not reused) - params : RNNParams or None, optional + params : RNNParams, default None. Container for weight sharing between cells. A new RNNParams container is created if `params` is None. """ @@ -113,7 +131,7 @@ def __init__(self, prefix='', params=None): self.reset() def reset(self): - """Reset before re-using the cell for another graph""" + """Reset before re-using the cell for another graph.""" self._init_counter = -1 self._counter = -1 @@ -277,7 +295,7 @@ def unroll(self, length, inputs, begin_state=None, layout='NTC', merge_outputs=N Parameters ---------- length : int - number of steps to unroll + Number of steps to unroll. inputs : Symbol, list of Symbol, or None If `inputs` is a single Symbol (usually the output of Embedding symbol), it should have shape @@ -287,7 +305,7 @@ def unroll(self, length, inputs, begin_state=None, layout='NTC', merge_outputs=N If `inputs` is a list of symbols (usually output of previous unroll), they should all have shape (batch_size, ...). - begin_state : nested list of Symbol, optional + begin_state : nested list of Symbol, default None Input states created by `begin_state()` or output state of another cell. Created from `begin_state()` if None. @@ -300,7 +318,7 @@ def unroll(self, length, inputs, begin_state=None, layout='NTC', merge_outputs=N and return a single symbol with shape (batch_size, length, ...) if layout == 'NTC', or (length, batch_size, ...) if layout == 'TNC'. - If None, output whatever is faster + If None, output whatever is faster. Returns ------- @@ -339,20 +357,18 @@ def _get_activation(self, inputs, activation, **kwargs): class RNNCell(BaseRNNCell): - """Simple recurrent neural network cell + """Simple recurrent neural network cell. Parameters ---------- num_hidden : int - number of units in output symbol + Number of units in output symbol. activation : str or Symbol, default 'tanh' - type of activation function + Type of activation function. Options are 'relu' and 'tanh'. prefix : str, default 'rnn_' - prefix for name of layers - (and name of weight if params is None) - params : RNNParams or None - container for weight sharing between cells. - created if None. + Prefix for name of layers (and name of weight if params is None). + params : RNNParams, default None + Container for weight sharing between cells. Created if None. """ def __init__(self, num_hidden, activation='tanh', prefix='rnn_', params=None): super(RNNCell, self).__init__(prefix=prefix, params=params) @@ -392,13 +408,11 @@ class LSTMCell(BaseRNNCell): Parameters ---------- num_hidden : int - number of units in output symbol + Number of units in output symbol. prefix : str, default 'lstm_' - prefix for name of layers - (and name of weight if params is None) - params : RNNParams or None - container for weight sharing between cells. - created if None. + Prefix for name of layers (and name of weight if params is None). + params : RNNParams, default None + Container for weight sharing between cells. Created if None. forget_bias : bias added to forget gate, default 1.0. Jozefowicz et al. 2015 recommends setting this to 1.0 """ @@ -457,13 +471,11 @@ class GRUCell(BaseRNNCell): Parameters ---------- num_hidden : int - number of units in output symbol + Number of units in output symbol. prefix : str, default 'gru_' - prefix for name of layers - (and name of weight if params is None) - params : RNNParams or None - container for weight sharing between cells. - created if None. + Prefix for name of layers (and name of weight if params is None). + params : RNNParams, default None + Container for weight sharing between cells. Created if None. """ def __init__(self, num_hidden, prefix='gru_', params=None): super(GRUCell, self).__init__(prefix=prefix, params=params) @@ -525,6 +537,26 @@ class FusedRNNCell(BaseRNNCell): Parameters ---------- + num_hidden : int + Number of units in output symbol. + num_layers : int, default 1 + Number of layers in the cell. + mode : str, default 'lstm' + Type of RNN. options are 'rnn_relu', 'rnn_tanh', 'lstm', 'gru'. + bidirectional : bool, default False + Whether to use bidirectional unroll. The output dimension size is doubled if bidrectional. + dropout : float, default 0. + Fraction of the input that gets dropped out during training time. + get_next_state : bool, default False + Whether to return the states that can be used as starting states next time. + forget_bias : bias added to forget gate, default 1.0. + Jozefowicz et al. 2015 recommends setting this to 1.0 + prefix : str, default '$mode_' such as 'lstm_' + Prefix for names of layers + (this prefix is also used for names of weights if `params` is None + i.e. if `params` are being created and not reused) + params : RNNParams, default None + Container for weight sharing between cells. Created if None. """ def __init__(self, num_hidden, num_layers=1, mode='lstm', bidirectional=False, dropout=0., get_next_state=False, forget_bias=1.0, @@ -658,11 +690,15 @@ def unroll(self, length, inputs, begin_state=None, layout='NTC', merge_outputs=N mode=self._mode, name=self._prefix+'rnn', **states) + attr = {'__layout__' : 'LNC'} if not self._get_next_state: outputs, states = rnn, [] elif self._mode == 'lstm': + rnn[1]._set_attr(**attr) + rnn[2]._set_attr(**attr) outputs, states = rnn[0], [rnn[1], rnn[2]] else: + rnn[1]._set_attr(**attr) outputs, states = rnn[0], [rnn[1]] if axis == 1: @@ -707,13 +743,12 @@ def unfuse(self): class SequentialRNNCell(BaseRNNCell): - """Sequantially stacking multiple RNN cells + """Sequantially stacking multiple RNN cells. Parameters ---------- - params : RNNParams or None - container for weight sharing between cells. - created if None. + params : RNNParams, default None + Container for weight sharing between cells. Created if None. """ def __init__(self, params=None): super(SequentialRNNCell, self).__init__(prefix='', params=params) @@ -725,7 +760,9 @@ def add(self, cell): Parameters ---------- - cell : rnn cell + cell : BaseRNNCell + The cell to be appended. During unroll, previous cell's output (or raw inputs if + no previous cell) is used as the input to this cell. """ self._cells.append(cell) if self._override_cell_params: @@ -739,7 +776,7 @@ def add(self, cell): def state_info(self): return _cells_state_info(self._cells) - def begin_state(self, **kwargs): + def begin_state(self, **kwargs): # pylint: disable=arguments-differ assert not self._modified, \ "After applying modifier cells (e.g. ZoneoutCell) the base " \ "cell cannot be called directly. Call the modifier cell instead." @@ -790,8 +827,14 @@ class DropoutCell(BaseRNNCell): Parameters ---------- dropout : float - percentage of elements to drop out, which + Percentage of elements to drop out, which is 1 - percentage to retain. + prefix : str, default 'dropout_' + Prefix for names of layers + (this prefix is also used for names of weights if `params` is None + i.e. if `params` are being created and not reused) + params : RNNParams, default None + Container for weight sharing between cells. Created if None. """ def __init__(self, dropout, prefix='dropout_', params=None): super(DropoutCell, self).__init__(prefix, params) @@ -841,7 +884,7 @@ def params(self): def state_info(self): return self.base_cell.state_info - def begin_state(self, init_sym=symbol.zeros, **kwargs): + def begin_state(self, init_sym=symbol.zeros, **kwargs): # pylint: disable=arguments-differ assert not self._modified, \ "After applying modifier cells (e.g. DropoutCell) the base " \ "cell cannot be called directly. Call the modifier cell instead." @@ -861,7 +904,17 @@ def __call__(self, inputs, states): class ZoneoutCell(ModifierCell): - """Apply Zoneout on base cell""" + """Apply Zoneout on base cell. + + Parameters + ---------- + base_cell : BaseRNNCell + Cell on whose states to perform zoneout. + zoneout_outputs : float, default 0. + Fraction of the output that gets dropped out during training time. + zoneout_states : float, default 0. + Fraction of the states that gets dropped out during training time. + """ def __init__(self, base_cell, zoneout_outputs=0., zoneout_states=0.): assert not isinstance(base_cell, FusedRNNCell), \ "FusedRNNCell doesn't support zoneout. " \ @@ -886,7 +939,7 @@ def __call__(self, inputs, states): next_output, next_states = cell(inputs, states) mask = lambda p, like: symbol.Dropout(symbol.ones_like(like), p=p) - prev_output = self.prev_output if self.prev_output else symbol.zeros((0, 0)) + prev_output = self.prev_output if self.prev_output is not None else symbol.zeros((0, 0)) output = (symbol.where(mask(p_outputs, next_output), next_output, prev_output) if p_outputs != 0. else next_output) @@ -899,10 +952,15 @@ def __call__(self, inputs, states): class ResidualCell(ModifierCell): - """ - Adds residual connection as described in Wu et al, 2016 + """Adds residual connection as described in Wu et al, 2016 (https://arxiv.org/abs/1609.08144). + Output of the cell is output of the base cell plus input. + + Parameters + ---------- + base_cell : BaseRNNCell + Cell on whose outputs to add residual connection. """ def __init__(self, base_cell): @@ -913,9 +971,29 @@ def __call__(self, inputs, states): output = symbol.elemwise_add(output, inputs, name="%s_plus_residual" % output.name) return output, states + def unroll(self, length, inputs, begin_state=None, layout='NTC', merge_outputs=None): + self.reset() + + self.base_cell._modified = False + outputs, states = self.base_cell.unroll(length, inputs=inputs, begin_state=begin_state, + layout=layout, merge_outputs=merge_outputs) + self.base_cell._modified = True + + merge_outputs = isinstance(outputs, symbol.Symbol) if merge_outputs is None else \ + merge_outputs + inputs, _ = _normalize_sequence(length, inputs, layout, merge_outputs) + if merge_outputs: + outputs = symbol.elemwise_add(outputs, inputs, name="%s_plus_residual" % outputs.name) + else: + outputs = [symbol.elemwise_add(output_sym, input_sym, + name="%s_plus_residual" % output_sym.name) + for output_sym, input_sym in zip(outputs, inputs)] + + return outputs, states + class BidirectionalCell(BaseRNNCell): - """Bidirectional RNN cell + """Bidirectional RNN cell. Parameters ---------- @@ -923,14 +1001,26 @@ class BidirectionalCell(BaseRNNCell): cell for forward unrolling r_cell : BaseRNNCell cell for backward unrolling + params : RNNParams, default None. + Container for weight sharing between cells. + A new RNNParams container is created if `params` is None. output_prefix : str, default 'bi_' prefix for name of output """ def __init__(self, l_cell, r_cell, params=None, output_prefix='bi_'): super(BidirectionalCell, self).__init__('', params=params) + self._output_prefix = output_prefix self._override_cell_params = params is not None + + if self._override_cell_params: + assert l_cell._own_params and r_cell._own_params, \ + "Either specify params for BidirectionalCell " \ + "or child cells, not both." + l_cell.params._params.update(self.params._params) + r_cell.params._params.update(self.params._params) + self.params._params.update(l_cell.params._params) + self.params._params.update(r_cell.params._params) self._cells = [l_cell, r_cell] - self._output_prefix = output_prefix def unpack_weights(self, args): return _cells_unpack_weights(self._cells, args) @@ -945,7 +1035,7 @@ def __call__(self, inputs, states): def state_info(self): return _cells_state_info(self._cells) - def begin_state(self, **kwargs): + def begin_state(self, **kwargs): # pylint: disable=arguments-differ assert not self._modified, \ "After applying modifier cells (e.g. DropoutCell) the base " \ "cell cannot be called directly. Call the modifier cell instead." @@ -996,3 +1086,335 @@ def unroll(self, length, inputs, begin_state=None, layout='NTC', merge_outputs=N states = [l_states, r_states] return outputs, states + + +class BaseConvRNNCell(BaseRNNCell): + """Abstract base class for Convolutional RNN cells""" + def __init__(self, input_shape, num_hidden, + h2h_kernel, h2h_dilate, + i2h_kernel, i2h_stride, + i2h_pad, i2h_dilate, + i2h_weight_initializer, h2h_weight_initializer, + i2h_bias_initializer, h2h_bias_initializer, + activation, prefix='', params=None, conv_layout='NCHW'): + super(BaseConvRNNCell, self).__init__(prefix=prefix, params=params) + # Convolution setting + self._h2h_kernel = h2h_kernel + assert (self._h2h_kernel[0] % 2 == 1) and (self._h2h_kernel[1] % 2 == 1), \ + "Only support odd number, get h2h_kernel= %s" % str(h2h_kernel) + self._h2h_pad = (h2h_dilate[0] * (h2h_kernel[0] - 1) // 2, + h2h_dilate[1] * (h2h_kernel[1] - 1) // 2) + self._h2h_dilate = h2h_dilate + self._i2h_kernel = i2h_kernel + self._i2h_stride = i2h_stride + self._i2h_pad = i2h_pad + self._i2h_dilate = i2h_dilate + + self._num_hidden = num_hidden + self._input_shape = input_shape + self._conv_layout = conv_layout + self._activation = activation + + # Infer state shape + data = symbol.Variable('data') + self._state_shape = symbol.Convolution(data=data, + num_filter=self._num_hidden, + kernel=self._i2h_kernel, + stride=self._i2h_stride, + pad=self._i2h_pad, + dilate=self._i2h_dilate, + layout=conv_layout) + self._state_shape = self._state_shape.infer_shape(data=input_shape)[1][0] + self._state_shape = (0, ) + self._state_shape[1:] + + # Get params + self._iW = self.params.get('i2h_weight', init=i2h_weight_initializer) + self._hW = self.params.get('h2h_weight', init=h2h_weight_initializer) + self._iB = self.params.get('i2h_bias', init=i2h_bias_initializer) + self._hB = self.params.get('h2h_bias', init=h2h_bias_initializer) + + @property + def _num_gates(self): + return len(self._gate_names) + + @property + def state_info(self): + return [{'shape': self._state_shape, '__layout__': self._conv_layout}, + {'shape': self._state_shape, '__layout__': self._conv_layout}] + + def _conv_forward(self, inputs, states, name): + + i2h = symbol.Convolution(name='%si2h'%name, + data=inputs, + num_filter=self._num_hidden*self._num_gates, + kernel=self._i2h_kernel, + stride=self._i2h_stride, + pad=self._i2h_pad, + dilate=self._i2h_dilate, + weight=self._iW, + bias=self._iB, + layout=self._conv_layout) + + h2h = symbol.Convolution(name='%sh2h'%name, + data=states[0], + num_filter=self._num_hidden*self._num_gates, + kernel=self._h2h_kernel, + dilate=self._h2h_dilate, + pad=self._h2h_pad, + stride=(1, 1), + weight=self._hW, + bias=self._hB, + layout=self._conv_layout) + return i2h, h2h + + def __call__(self, inputs, states): + raise NotImplementedError("BaseConvRNNCell is abstract class for convolutional RNN") + +class ConvRNNCell(BaseConvRNNCell): + """Convolutional RNN cells + + Parameters + ---------- + input_shape : tuple of int + Shape of input in single timestep. + num_hidden : int + Number of units in output symbol. + h2h_kernel : tuple of int, default (3, 3) + Kernel of Convolution operator in state-to-state transitions. + h2h_dilate : tuple of int, default (1, 1) + Dilation of Convolution operator in state-to-state transitions. + i2h_kernel : tuple of int, default (3, 3) + Kernel of Convolution operator in input-to-state transitions. + i2h_stride : tuple of int, default (1, 1) + Stride of Convolution operator in input-to-state transitions. + i2h_pad : tuple of int, default (1, 1) + Pad of Convolution operator in input-to-state transitions. + i2h_dilate : tuple of int, default (1, 1) + Dilation of Convolution operator in input-to-state transitions. + i2h_weight_initializer : str or Initializer + Initializer for the input weights matrix, used for the convolution + transformation of the inputs. + h2h_weight_initializer : str or Initializer + Initializer for the recurrent weights matrix, used for the convolution + transformation of the recurrent state. + i2h_bias_initializer : str or Initializer, default zeros + Initializer for the bias vector. + h2h_bias_initializer : str or Initializer, default zeros + Initializer for the bias vector. + activation : str or Symbol, + default functools.partial(symbol.LeakyReLU, act_type='leaky', slope=0.2) + Type of activation function. + prefix : str, default 'ConvRNN_' + Prefix for name of layers (and name of weight if params is None). + params : RNNParams, default None + Container for weight sharing between cells. Created if None. + conv_layout : str, , default 'NCHW' + Layout of ConvolutionOp + """ + def __init__(self, input_shape, num_hidden, + h2h_kernel=(3, 3), h2h_dilate=(1, 1), + i2h_kernel=(3, 3), i2h_stride=(1, 1), + i2h_pad=(1, 1), i2h_dilate=(1, 1), + i2h_weight_initializer=None, h2h_weight_initializer=None, + i2h_bias_initializer='zeros', h2h_bias_initializer='zeros', + activation=functools.partial(symbol.LeakyReLU, act_type='leaky', slope=0.2), + prefix='ConvRNN_', params=None, conv_layout='NCHW'): + super(ConvRNNCell, self).__init__(input_shape=input_shape, num_hidden=num_hidden, + h2h_kernel=h2h_kernel, h2h_dilate=h2h_dilate, + i2h_kernel=i2h_kernel, i2h_stride=i2h_stride, + i2h_pad=i2h_pad, i2h_dilate=i2h_dilate, + i2h_weight_initializer=i2h_weight_initializer, + h2h_weight_initializer=h2h_weight_initializer, + i2h_bias_initializer=i2h_bias_initializer, + h2h_bias_initializer=h2h_bias_initializer, + activation=activation, prefix=prefix, + params=params, conv_layout=conv_layout) + + @property + def _gate_names(self): + return ('',) + + def __call__(self, inputs, states): + self._counter += 1 + name = '%st%d_'%(self._prefix, self._counter) + i2h, h2h = self._conv_forward(inputs, states, name) + output = self._get_activation(i2h + h2h, self._activation, + name='%sout'%name) + return output, [output] + + +class ConvLSTMCell(BaseConvRNNCell): + """Convolutional LSTM network cell. + + Reference: + Xingjian et al. NIPS2015 + + Parameters + ---------- + input_shape : tuple of int + Shape of input in single timestep. + num_hidden : int + Number of units in output symbol. + h2h_kernel : tuple of int, default (3, 3) + Kernel of Convolution operator in state-to-state transitions. + h2h_dilate : tuple of int, default (1, 1) + Dilation of Convolution operator in state-to-state transitions. + i2h_kernel : tuple of int, default (3, 3) + Kernel of Convolution operator in input-to-state transitions. + i2h_stride : tuple of int, default (1, 1) + Stride of Convolution operator in input-to-state transitions. + i2h_pad : tuple of int, default (1, 1) + Pad of Convolution operator in input-to-state transitions. + i2h_dilate : tuple of int, default (1, 1) + Dilation of Convolution operator in input-to-state transitions. + i2h_weight_initializer : str or Initializer + Initializer for the input weights matrix, used for the convolution + transformation of the inputs. + h2h_weight_initializer : str or Initializer + Initializer for the recurrent weights matrix, used for the convolution + transformation of the recurrent state. + i2h_bias_initializer : str or Initializer, default zeros + Initializer for the bias vector. + h2h_bias_initializer : str or Initializer, default zeros + Initializer for the bias vector. + activation : str or Symbol + default functools.partial(symbol.LeakyReLU, act_type='leaky', slope=0.2) + Type of activation function. + prefix : str, default 'ConvLSTM_' + Prefix for name of layers (and name of weight if params is None). + params : RNNParams, default None + Container for weight sharing between cells. Created if None. + conv_layout : str, , default 'NCHW' + Layout of ConvolutionOp + """ + def __init__(self, input_shape, num_hidden, + h2h_kernel=(3, 3), h2h_dilate=(1, 1), + i2h_kernel=(3, 3), i2h_stride=(1, 1), + i2h_pad=(1, 1), i2h_dilate=(1, 1), + i2h_weight_initializer=None, h2h_weight_initializer=None, + i2h_bias_initializer='zeros', h2h_bias_initializer='zeros', + activation=functools.partial(symbol.LeakyReLU, act_type='leaky', slope=0.2), + prefix='ConvLSTM_', params=None, + conv_layout='NCHW'): + super(ConvLSTMCell, self).__init__(input_shape=input_shape, num_hidden=num_hidden, + h2h_kernel=h2h_kernel, h2h_dilate=h2h_dilate, + i2h_kernel=i2h_kernel, i2h_stride=i2h_stride, + i2h_pad=i2h_pad, i2h_dilate=i2h_dilate, + i2h_weight_initializer=i2h_weight_initializer, + h2h_weight_initializer=h2h_weight_initializer, + i2h_bias_initializer=i2h_bias_initializer, + h2h_bias_initializer=h2h_bias_initializer, + activation=activation, prefix=prefix, + params=params, conv_layout=conv_layout) + + @property + def _gate_names(self): + return ['_i', '_f', '_c', '_o'] + + def __call__(self, inputs, states): + self._counter += 1 + name = '%st%d_'%(self._prefix, self._counter) + i2h, h2h = self._conv_forward(inputs, states, name) + gates = i2h + h2h + slice_gates = symbol.SliceChannel(gates, num_outputs=4, axis=self._conv_layout.find('C'), + name="%sslice"%name) + in_gate = symbol.Activation(slice_gates[0], act_type="sigmoid", + name='%si'%name) + forget_gate = symbol.Activation(slice_gates[1], act_type="sigmoid", + name='%sf'%name) + in_transform = self._get_activation(slice_gates[2], self._activation, + name='%sc'%name) + out_gate = symbol.Activation(slice_gates[3], act_type="sigmoid", + name='%so'%name) + next_c = symbol._internal._plus(forget_gate * states[1], in_gate * in_transform, + name='%sstate'%name) + next_h = symbol._internal._mul(out_gate, self._get_activation(next_c, self._activation), + name='%sout'%name) + + return next_h, [next_h, next_c] + +class ConvGRUCell(BaseConvRNNCell): + """Convolutional Gated Rectified Unit (GRU) network cell. + + Parameters + ---------- + input_shape : tuple of int + Shape of input in single timestep. + num_hidden : int + Number of units in output symbol. + h2h_kernel : tuple of int, default (3, 3) + Kernel of Convolution operator in state-to-state transitions. + h2h_dilate : tuple of int, default (1, 1) + Dilation of Convolution operator in state-to-state transitions. + i2h_kernel : tuple of int, default (3, 3) + Kernel of Convolution operator in input-to-state transitions. + i2h_stride : tuple of int, default (1, 1) + Stride of Convolution operator in input-to-state transitions. + i2h_pad : tuple of int, default (1, 1) + Pad of Convolution operator in input-to-state transitions. + i2h_dilate : tuple of int, default (1, 1) + Dilation of Convolution operator in input-to-state transitions. + i2h_weight_initializer : str or Initializer + Initializer for the input weights matrix, used for the convolution + transformation of the inputs. + h2h_weight_initializer : str or Initializer + Initializer for the recurrent weights matrix, used for the convolution + transformation of the recurrent state. + i2h_bias_initializer : str or Initializer, default zeros + Initializer for the bias vector. + h2h_bias_initializer : str or Initializer, default zeros + Initializer for the bias vector. + activation : str or Symbol, + default functools.partial(symbol.LeakyReLU, act_type='leaky', slope=0.2) + Type of activation function. + prefix : str, default 'ConvGRU_' + Prefix for name of layers (and name of weight if params is None). + params : RNNParams, default None + Container for weight sharing between cells. Created if None. + conv_layout : str, , default 'NCHW' + Layout of ConvolutionOp + """ + def __init__(self, input_shape, num_hidden, + h2h_kernel=(3, 3), h2h_dilate=(1, 1), + i2h_kernel=(3, 3), i2h_stride=(1, 1), + i2h_pad=(1, 1), i2h_dilate=(1, 1), + i2h_weight_initializer=None, h2h_weight_initializer=None, + i2h_bias_initializer='zeros', h2h_bias_initializer='zeros', + activation=functools.partial(symbol.LeakyReLU, act_type='leaky', slope=0.2), + prefix='ConvGRU_', params=None, conv_layout='NCHW'): + super(ConvGRUCell, self).__init__(input_shape=input_shape, num_hidden=num_hidden, + h2h_kernel=h2h_kernel, h2h_dilate=h2h_dilate, + i2h_kernel=i2h_kernel, i2h_stride=i2h_stride, + i2h_pad=i2h_pad, i2h_dilate=i2h_dilate, + i2h_weight_initializer=i2h_weight_initializer, + h2h_weight_initializer=h2h_weight_initializer, + i2h_bias_initializer=i2h_bias_initializer, + h2h_bias_initializer=h2h_bias_initializer, + activation=activation, prefix=prefix, + params=params, conv_layout=conv_layout) + + @property + def _gate_names(self): + return ['_r', '_z', '_o'] + + def __call__(self, inputs, states): + self._counter += 1 + seq_idx = self._counter + name = '%st%d_' % (self._prefix, seq_idx) + i2h, h2h = self._conv_forward(inputs, states, name) + + i2h_r, i2h_z, i2h = symbol.SliceChannel(i2h, num_outputs=3, name="%s_i2h_slice" % name) + h2h_r, h2h_z, h2h = symbol.SliceChannel(h2h, num_outputs=3, name="%s_h2h_slice" % name) + + reset_gate = symbol.Activation(i2h_r + h2h_r, act_type="sigmoid", + name="%s_r_act" % name) + update_gate = symbol.Activation(i2h_z + h2h_z, act_type="sigmoid", + name="%s_z_act" % name) + + next_h_tmp = self._get_activation(i2h + reset_gate * h2h, self._activation, + name="%s_h_act" % name) + + next_h = symbol._internal._plus((1. - update_gate) * next_h_tmp, update_gate * states[0], + name='%sout' % name) + + return next_h, [next_h] diff --git a/python/mxnet/rtc.py b/python/mxnet/rtc.py index 759fc3d30042..9da38c6aaaf5 100644 --- a/python/mxnet/rtc.py +++ b/python/mxnet/rtc.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + """Interface to runtime cuda kernel compile module.""" from __future__ import absolute_import diff --git a/python/mxnet/symbol.py b/python/mxnet/symbol.py index 4632f7d71b17..14cb3811deeb 100644 --- a/python/mxnet/symbol.py +++ b/python/mxnet/symbol.py @@ -1,8 +1,29 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # coding: utf-8 # pylint: disable=invalid-name, protected-access, too-many-arguments, too-many-lines # pylint: disable=import-error, no-name-in-module """Symbolic configuration API of MXNet.""" from __future__ import absolute_import as _abs +try: + from __builtin__ import slice as py_slice +except ImportError: + from builtins import slice as py_slice import ctypes import warnings @@ -13,30 +34,35 @@ import numpy as _numpy from .base import _LIB, numeric_types -from .base import c_array, c_str, mx_uint, py_str, string_types, mx_real_t -from .base import NDArrayHandle, ExecutorHandle, SymbolHandle -from .base import check_call, MXNetError -from .context import Context, cpu -from .ndarray import NDArray, zeros as _nd_zeros, _DTYPE_NP_TO_MX, _DTYPE_MX_TO_NP +from .base import c_array, c_str, mx_uint, py_str, string_types +from .base import NDArrayHandle, ExecutorHandle, SymbolHandle, OpHandle +from .base import check_call, MXNetError, NotImplementedForSymbol, _Null # pylint: disable=unused-import +from .context import Context +from .ndarray import NDArray, _DTYPE_NP_TO_MX, _DTYPE_MX_TO_NP, _GRAD_REQ_MAP +from .name import NameManager # pylint: disable=unused-import from .executor import Executor from . import _symbol_internal as _internal from .attribute import AttrScope +from .symbol_doc import _build_doc # Use different version of SymbolBase # When possible, use cython to speedup part of computation. try: if int(_os.environ.get("MXNET_ENABLE_CYTHON", True)) == 0: - from ._ctypes.symbol import SymbolBase, _init_symbol_module + from ._ctypes.symbol import SymbolBase, _set_symbol_class + from ._ctypes.symbol import _symbol_creator # pylint: disable=unused-import elif _sys.version_info >= (3, 0): - from ._cy3.symbol import SymbolBase, _init_symbol_module + from ._cy3.symbol import SymbolBase, _set_symbol_class + from ._cy3.symbol import _symbol_creator # pylint: disable=unused-import else: - from ._cy2.symbol import SymbolBase, _init_symbol_module + from ._cy2.symbol import SymbolBase, _set_symbol_class + from ._cy2.symbol import _symbol_creator # pylint: disable=unused-import except ImportError: if int(_os.environ.get("MXNET_ENFORCE_CYTHON", False)) != 0: raise ImportError("Cython Module cannot be loaded but MXNET_ENFORCE_CYTHON=1") - from ._ctypes.symbol import SymbolBase, _init_symbol_module + from ._ctypes.symbol import SymbolBase, _set_symbol_class + from ._ctypes.symbol import _symbol_creator # pylint: disable=unused-import -_GRAD_REQ_MAP = {'null': 0, 'write': 1, 'add': 3} class Symbol(SymbolBase): """Symbol is symbolic graph of the mxnet.""" @@ -45,7 +71,7 @@ class Symbol(SymbolBase): __slots__ = [] def __repr__(self): - """Get a string representation of the symbol.""" + """Gets a string representation of the symbol.""" name = self.name if name is None: name = ', '.join([i.name for i in self]) @@ -88,6 +114,9 @@ def __add__(self, other): else: raise TypeError('type %s not supported' % str(type(other))) + def __iadd__(self, other): + raise NotImplementedForSymbol(self.__iadd__, '+=', other, 1) + def __radd__(self, other): return self.__add__(other) @@ -103,6 +132,9 @@ def __sub__(self, other): else: raise TypeError('type %s not supported' % str(type(other))) + def __isub__(self, other): + raise NotImplementedForSymbol(self.__isub__, '-=', other) + def __rsub__(self, other): """x.__rsub__(y) <=> y-x @@ -133,6 +165,9 @@ def __mul__(self, other): else: raise TypeError('type %s not supported' % str(type(other))) + def __imul__(self, other): + raise NotImplementedForSymbol(self.__imul__, '*=', other) + def __rmul__(self, other): return self.__mul__(other) @@ -166,12 +201,48 @@ def __rdiv__(self, other): else: raise TypeError('type %s not supported' % str(type(other))) + def __mod__(self, other): + """x.__mod__(y) <=> x%y + + Scalar input is supported. + Broadcasting is not supported. Use `broadcast_mod` instead. """ + if isinstance(other, Symbol): + return _internal._Mod(self, other) + if isinstance(other, Number): + return _internal._ModScalar(self, scalar=other) + else: + raise TypeError('type %s not supported' % str(type(other))) + + def __rmod__(self, other): + """x.__rmod__(y) <=> y%x + + Only `NDArray` is supported for now. + + Example usage: + ---------- + >>> x = mx.nd.ones((2,3))*3 + >>> y = mx.nd.ones((2,3)) + >>> x.__rmod__(y).asnumpy() + array([[ 1., 1., 1., + [ 1., 1., 1., dtype=float32) + """ + if isinstance(other, Number): + return _internal._RModScalar(self, scalar=other) + else: + raise TypeError('type %s not supported' % str(type(other))) + + def __idiv__(self, other): + raise NotImplementedForSymbol(self.__idiv__, '/=', other) + def __truediv__(self, other): return self.__div__(other) def __rtruediv__(self, other): return self.__rdiv__(other) + def __itruediv__(self, other): + raise NotImplementedForSymbol(self.__itruediv__, '/=', other) + def __pow__(self, other): """x.__pow__(y) <=> x**y @@ -184,6 +255,9 @@ def __pow__(self, other): else: raise TypeError('type %s not supported' % str(type(other))) + def __rpow__(self, other): + raise NotImplementedForSymbol(self.__rpow__, 'y**x', other) + def __neg__(self): """x.__neg__() <=> -x @@ -404,7 +478,7 @@ def _compose(self, *args, **kwargs): num_args = len(args) + len(kwargs) if len(kwargs) != 0: - keys = c_array(ctypes.c_char_p, [c_str(key) for key in kwargs.keys()]) + keys = c_array(ctypes.c_char_p, [c_str(key) for key in kwargs]) args = c_array(SymbolHandle, [s.handle for s in kwargs.values()]) else: keys = None @@ -431,9 +505,16 @@ def __getitem__(self, index): Indexing key """ + output_names = self.list_outputs() + if isinstance(index, py_slice): + start = 0 if index.start is None else index.start + stop = len(output_names) if index.stop is None else index.stop + step = 1 if index.step is None else index.step + return Group([self[i] for i in range(start, stop, step)]) + if isinstance(index, string_types): idx = None - for i, name in enumerate(self.list_outputs()): + for i, name in enumerate(output_names): if name == index: if idx is not None: raise ValueError('There are multiple outputs with name \"%s\"' % index) @@ -441,9 +522,10 @@ def __getitem__(self, index): if idx is None: raise ValueError('Cannot find output that matches name \"%s\"' % index) index = idx + if not isinstance(index, int): raise TypeError('Symbol only support integer index to fetch i-th output') - if index >= (len(self.list_outputs())): + if index >= len(output_names): # Important, python determines the end by this exception raise IndexError handle = SymbolHandle() @@ -453,7 +535,7 @@ def __getitem__(self, index): @property def name(self): - """Get name string from the symbol, this function only works for non-grouped symbol. + """Gets name string from the symbol, this function only works for non-grouped symbol. Returns ------- @@ -520,7 +602,7 @@ def list_attr(self, recursive=False): pairs = ctypes.POINTER(ctypes.c_char_p)() f_handle = _LIB.MXSymbolListAttrShallow check_call(f_handle(self.handle, ctypes.byref(size), ctypes.byref(pairs))) - return {py_str(pairs[i*2]): py_str(pairs[i*2+1]) for i in range(size.value)} + return {py_str(pairs[i * 2]): py_str(pairs[i * 2 + 1]) for i in range(size.value)} def attr_dict(self): """Recursively gets all attributes from the symbol and its children. @@ -546,8 +628,8 @@ def attr_dict(self): check_call(f_handle(self.handle, ctypes.byref(size), ctypes.byref(pairs))) ret = {} for i in range(size.value): - name, key = py_str(pairs[i*2]).split('$') - val = py_str(pairs[i*2+1]) + name, key = py_str(pairs[i * 2]).split('$') + val = py_str(pairs[i * 2 + 1]) if name not in ret: ret[name] = {} ret[name][key] = val @@ -699,7 +781,7 @@ def list_auxiliary_states(self): Returns ------- - aux_states : list of string + aux_states : list of str List of the auxiliary states in input symbol. Notes @@ -715,6 +797,30 @@ def list_auxiliary_states(self): self.handle, ctypes.byref(size), ctypes.byref(sarr))) return [py_str(sarr[i]) for i in range(size.value)] + def list_inputs(self): + """Lists all arguments and auxiliary states of this Symbol. + + Returns + ------- + inputs : list of str + List of all inputs. + + Examples + -------- + >>> bn = mx.sym.BatchNorm(name='bn') + >>> bn.list_arguments() + ['bn_data', 'bn_gamma', 'bn_beta'] + >>> bn.list_auxiliary_states() + ['bn_moving_mean', 'bn_moving_var'] + >>> bn.list_inputs() + ['bn_data', 'bn_gamma', 'bn_beta', 'bn_moving_mean', 'bn_moving_var'] + """ + size = ctypes.c_uint() + sarr = ctypes.POINTER(ctypes.c_char_p)() + check_call(_LIB.NNSymbolListInputNames( + self.handle, 0, ctypes.byref(size), ctypes.byref(sarr))) + return [py_str(sarr[i]) for i in range(size.value)] + def infer_type(self, *args, **kwargs): """Infers the type of all arguments and all outputs, given the known types for some arguments. @@ -770,7 +876,7 @@ def infer_type(self, *args, **kwargs): if s is not None: s = _numpy.dtype(s).type if s not in _DTYPE_NP_TO_MX: - raise TypeError('Argument need to be one of '+str(_DTYPE_NP_TO_MX)) + raise TypeError('Argument need to be one of ' + str(_DTYPE_NP_TO_MX)) sdata.append(_DTYPE_NP_TO_MX[s]) else: sdata.append(-1) @@ -879,7 +985,7 @@ def infer_shape(self, *args, **kwargs): if len(unknowns) >= 10: unknowns.append('...') break - unknowns.append('%s: %s'%(name, str(shape))) + unknowns.append('%s: %s' % (name, str(shape))) warnings.warn( "Cannot decide shape for the following arguments " + "(0s in shape means unknown dimensions). " + @@ -953,19 +1059,22 @@ def _infer_shape_impl(self, partial, *args, **kwargs): indptr = [0] if len(args) != 0: keys = None - for s in args: + for i, s in enumerate(args): if s is not None: if not isinstance(s, tuple): - raise TypeError('Arguments must be shapes (tuple)') + raise TypeError("Arguments need to be shapes (tuple), " + "but argument %d is %s." % (i, type(s))) sdata.extend(s) indptr.append(len(sdata)) else: keys = [] for k, v in kwargs.items(): - if isinstance(v, tuple): - keys.append(c_str(k)) - sdata.extend(v) - indptr.append(len(sdata)) + if not isinstance(v, tuple): + raise TypeError("Arguments need to be shapes (tuple), " + "but '%s' is %s." % (k, type(v))) + keys.append(c_str(k)) + sdata.extend(v) + indptr.append(len(sdata)) arg_shape_size = mx_uint() arg_shape_ndim = ctypes.POINTER(mx_uint)() arg_shape_data = ctypes.POINTER(ctypes.POINTER(mx_uint))() @@ -1006,7 +1115,7 @@ def _infer_shape_impl(self, partial, *args, **kwargs): return (arg_shapes, out_shapes, aux_shapes) else: return (None, None, None) - # pylint: enable=too-many-locals + # pylint: enable=too-many-locals def debug_str(self): """Gets a debug string of symbol. @@ -1154,12 +1263,10 @@ def _get_ndarray_inputs(arg_key, args, arg_names, allow_missing): raise TypeError('Only accept list of NDArrays or dict of str to NDArray') return c_array(NDArrayHandle, arg_handles), arg_arrays - def simple_bind(self, ctx, - grad_req='write', - type_dict=None, - group2ctx=None, - **kwargs): - """Binds current symbol to get an executor, allocate all the arguments needed. + def simple_bind(self, ctx, grad_req='write', type_dict=None, group2ctx=None, + shared_arg_names=None, shared_exec=None, shared_buffer=None, **kwargs): + """Bind current symbol to get an executor, allocate all the arguments needed. + Allows specifying data types. This function simplifies the binding procedure. You need to specify only input data shapes. Before binding the executor, the function allocates arguments and auxiliary states @@ -1169,7 +1276,7 @@ def simple_bind(self, ctx, ---------- >>> x = mx.sym.Variable('x') >>> y = mx.sym.FullyConnected(x, num_hidden=4) - >>> exe = y.simple_bind(mx.cpu(), x=(5,4), grad_req=[]) + >>> exe = y.simple_bind(mx.cpu(), x=(5,4), grad_req='null') >>> exe.forward() [] >>> exe.outputs[0].asnumpy() @@ -1202,6 +1309,19 @@ def simple_bind(self, ctx, group2ctx : Dict of string to mx.Context The dict mapping the `ctx_group` attribute to the context assignment. + shared_arg_names : List of string + The argument names whose `NDArray` of shared_exec can be reused for initializing + the current executor. + + shared_exec : Executor + The executor whose arg_arrays, arg_arrays, grad_arrays, and aux_arrays can be + reused for initializing the current executor. + + shared_buffer : Dict of string to `NDArray` + The dict mapping argument names to the `NDArray` that can be reused for initializing + the current executor. This buffer will be checked for reuse if one argument name + of the current executor is not found in `shared_arg_names`. + kwargs : Dict of str->shape Input shape dictionary, name->shape @@ -1210,47 +1330,173 @@ def simple_bind(self, ctx, executor : mxnet.Executor The generated executor """ - # pylint: disable=too-many-locals - if type_dict is None: - attrs = self.attr_dict() - type_dict = {k: mx_real_t for k in self.list_arguments() - if k not in attrs or '__dtype__' not in attrs[k]} - arg_shapes, _, aux_shapes = self.infer_shape(**kwargs) - arg_types, _, aux_types = self.infer_type(**type_dict) - - if arg_shapes is None or arg_types is None: - raise ValueError("Input node is not complete") - + num_provided_arg_types = 0 + provided_arg_type_names = ctypes.POINTER(ctypes.c_char_p)() # provided type argument names + provided_arg_type_data = ctypes.POINTER(mx_uint)() # provided types + if type_dict is not None: + provided_arg_type_names = [] + provided_arg_type_data = [] + for k, v in type_dict.items(): + v = _numpy.dtype(v).type + if v in _DTYPE_NP_TO_MX: + provided_arg_type_names.append(c_str(k)) + provided_arg_type_data.append(ctypes.c_int(_DTYPE_NP_TO_MX[v])) + num_provided_arg_types = mx_uint(len(provided_arg_type_names)) + provided_arg_type_names = c_array(ctypes.c_char_p, provided_arg_type_names) + provided_arg_type_data = c_array(ctypes.c_int, provided_arg_type_data) + + provided_arg_shape_data = [] # shape data + # argument shape index in sdata, + # e.g. [sdata[indptr[0]], sdata[indptr[1]]) is the shape of the first arg + provided_arg_shape_idx = [0] + provided_arg_shape_names = [] # provided argument names + for k, v in kwargs.items(): + # if k not in listed_arguments and k not in listed_aux_states: + # raise ValueError('arg name %s is not valid', k) + if isinstance(v, tuple): + provided_arg_shape_names.append(c_str(k)) + provided_arg_shape_data.extend(v) + provided_arg_shape_idx.append(len(provided_arg_shape_data)) + + provided_req_type_list_len = 0 + provided_grad_req_types = ctypes.POINTER(ctypes.c_char_p)() + provided_grad_req_names = ctypes.POINTER(ctypes.c_char_p)() + if grad_req is not None: + if isinstance(grad_req, string_types): + # use provided_req_type_list_len = 0 to indicate this situation + provided_req_type_list_len = 0 + provided_grad_req_types = [c_str(grad_req)] + elif isinstance(grad_req, list): + if len(grad_req) == 0: + raise RuntimeError('grad_req in simple_bind cannot be an empty list') + provided_grad_req_types = [c_str(item) for item in grad_req] + provided_req_type_list_len = len(provided_grad_req_types) + elif isinstance(grad_req, dict): + if len(grad_req) == 0: + raise RuntimeError('grad_req in simple_bind cannot be an empty dict') + provided_grad_req_names = [] + provided_grad_req_types = [] + for k, v in grad_req.items(): + provided_grad_req_names.append(c_str(k)) + provided_grad_req_types.append(c_str(v)) + provided_grad_req_names = c_array(ctypes.c_char_p, provided_grad_req_names) + provided_req_type_list_len = len(provided_grad_req_types) + provided_grad_req_types = c_array(ctypes.c_char_p, provided_grad_req_types) + + num_ctx_map_keys = mx_uint(0) + ctx_map_keys = ctypes.POINTER(ctypes.c_char_p)() + ctx_map_dev_types = ctypes.POINTER(ctypes.c_int)() + ctx_map_dev_ids = ctypes.POINTER(ctypes.c_int)() if group2ctx is not None: - attr_dict = self.attr_dict() - arg_ctx = [group2ctx.get(attr_dict[name]['__ctx_group__'], ctx) \ - if name in attr_dict and '__ctx_group__' in attr_dict[name] \ - else ctx for name in self.list_arguments()] - aux_ctx = [group2ctx.get(attr_dict[name]['__ctx_group__'], ctx) \ - if name in attr_dict and '__ctx_group__' in attr_dict[name] \ - else ctx for name in self.list_auxiliary_states()] - else: - arg_ctx = [ctx] * len(arg_shapes) - aux_ctx = [ctx] * len(aux_shapes) - - # alloc space - arg_ndarrays = [ - _nd_zeros(shape, dev, dtype=dtype) - for dtype, dev, shape in zip(arg_types, arg_ctx, arg_shapes)] - if grad_req != 'null': - grad_ndarrays = {} - for name, shape, dev, dtype in zip( - self.list_arguments(), arg_shapes, arg_ctx, arg_types): - if not isinstance(grad_req, dict) or grad_req[name] != 'null': - grad_ndarrays[name] = _nd_zeros(shape, dev, dtype=dtype) + ctx_map_keys = [] + ctx_map_dev_types = [] + ctx_map_dev_ids = [] + for key, val in group2ctx.items(): + ctx_map_keys.append(c_str(key)) + ctx_map_dev_types.append(ctypes.c_int(val.device_typeid)) + ctx_map_dev_ids.append(ctypes.c_int(val.device_id)) + num_ctx_map_keys = mx_uint(len(ctx_map_keys)) + ctx_map_keys = c_array(ctypes.c_char_p, ctx_map_keys) + ctx_map_dev_types = c_array(ctypes.c_int, ctx_map_dev_types) + ctx_map_dev_ids = c_array(ctypes.c_int, ctx_map_dev_ids) + + # prepare param names + shared_arg_name_list = [] + if shared_arg_names is not None: + if not isinstance(shared_arg_names, list): + raise ValueError('shared_arg_names in simple_bind must be a list or None') + shared_arg_name_list = [c_str(name) for name in shared_arg_names] + + # prepare shared_buffer + if shared_buffer is None: + shared_buffer_len = ctypes.c_int(-1) + shared_buffer_names = ctypes.POINTER(ctypes.c_char_p)() + shared_buffer_handles = ctypes.POINTER(NDArrayHandle)() else: - grad_ndarrays = None + if not isinstance(shared_buffer, dict): + raise ValueError('shared_buffer in simple_bind must be dict or None') + shared_buffer_names = [] + shared_buffer_handles = [] + for k, v in shared_buffer.items(): + shared_buffer_names.append(c_str(k)) + shared_buffer_handles.append(v.handle) + shared_buffer_names = c_array(ctypes.c_char_p, shared_buffer_names) + shared_buffer_len = ctypes.c_int(len(shared_buffer_handles)) + shared_buffer_handles = c_array(NDArrayHandle, shared_buffer_handles) + updated_shared_buffer_names = ctypes.POINTER(ctypes.c_char_p)() + updated_shared_buffer_handles = ctypes.POINTER(NDArrayHandle)() + + # prepare shared_exec_handle + shared_exec_handle = shared_exec.handle if shared_exec is not None else ExecutorHandle() + + # prepare current executor handle + exe_handle = ExecutorHandle() + + # prepare current executor's in_args, arg_grads, and aux_states + num_in_args = ctypes.c_uint() + in_arg_handles = ctypes.POINTER(NDArrayHandle)() + arg_grad_handles = ctypes.POINTER(NDArrayHandle)() + num_aux_states = ctypes.c_uint() + aux_state_handles = ctypes.POINTER(NDArrayHandle)() - aux_ndarrays = [_nd_zeros(shape, dev, dtype=dtype) - for shape, dev, dtype in zip(aux_shapes, aux_ctx, aux_types)] - executor = self.bind(ctx, arg_ndarrays, - grad_ndarrays, grad_req, aux_ndarrays, - group2ctx=group2ctx) + try: + check_call(_LIB.MXExecutorSimpleBind(self.handle, + ctypes.c_int(ctx.device_typeid), + ctypes.c_int(ctx.device_id), + num_ctx_map_keys, + ctx_map_keys, + ctx_map_dev_types, + ctx_map_dev_ids, + mx_uint(provided_req_type_list_len), + provided_grad_req_names, + provided_grad_req_types, + mx_uint(len(provided_arg_shape_names)), + c_array(ctypes.c_char_p, provided_arg_shape_names), + c_array(mx_uint, provided_arg_shape_data), + c_array(mx_uint, provided_arg_shape_idx), + num_provided_arg_types, + provided_arg_type_names, + provided_arg_type_data, + mx_uint(len(shared_arg_name_list)), + c_array(ctypes.c_char_p, shared_arg_name_list), + ctypes.byref(shared_buffer_len), + shared_buffer_names, + shared_buffer_handles, + ctypes.byref(updated_shared_buffer_names), + ctypes.byref(updated_shared_buffer_handles), + ctypes.byref(num_in_args), + ctypes.byref(in_arg_handles), + ctypes.byref(arg_grad_handles), + ctypes.byref(num_aux_states), + ctypes.byref(aux_state_handles), + shared_exec_handle, + ctypes.byref(exe_handle))) + except MXNetError as e: + error_msg = "simple_bind error. Arguments:\n" + for k, v in kwargs.items(): + error_msg += "%s: %s\n" % (k, v) + error_msg += "%s" % e + raise RuntimeError(error_msg) + + # update shared_buffer + if shared_buffer is not None: + for i in range(shared_buffer_len.value): + k = py_str(updated_shared_buffer_names[i]) + v = NDArray(NDArrayHandle(updated_shared_buffer_handles[i])) + shared_buffer[k] = v + + # create in_args, arg_grads, and aux_states for the current executor + arg_arrays = [NDArray(NDArrayHandle(in_arg_handles[i])) for i in range(num_in_args.value)] + grad_arrays = [NDArray(NDArrayHandle(arg_grad_handles[i])) + if arg_grad_handles[i] is not None + else None for i in range(num_in_args.value)] + aux_arrays = [NDArray(NDArrayHandle(aux_state_handles[i])) + for i in range(num_aux_states.value)] + + executor = Executor(exe_handle, self, ctx, grad_req, group2ctx) + executor.arg_arrays = arg_arrays + executor.grad_arrays = grad_arrays + executor.aux_arrays = aux_arrays return executor def bind(self, ctx, args, args_grad=None, grad_req='write', @@ -1411,8 +1657,8 @@ def bind(self, ctx, args, args_grad=None, grad_req='write', executor.aux_arrays = aux_states return executor - def grad(self, wrt): - """Get the autodiff of current symbol. + def gradient(self, wrt): + """Gets the autodiff of current symbol. This function can only be used if current symbol is a loss function. @@ -1435,9 +1681,10 @@ def grad(self, wrt): c_wrt, ctypes.byref(handle))) return Symbol(handle) + # pylint: enable= no-member - def eval(self, ctx=cpu(), **kwargs): + def eval(self, ctx=None, **kwargs): """Evaluates a symbol given arguments. The `eval` method combines a call to `bind` (which returns an executor) @@ -1473,6 +1720,8 @@ def eval(self, ctx=cpu(), **kwargs): evaluated on given args. When called on a single symbol (not a group), the result will be a list with one element. """ + if ctx is None: + ctx = Context.default_ctx return self.bind(ctx, kwargs).forward() def reshape(self, shape): @@ -1494,6 +1743,29 @@ def reshape(self, shape): """ return reshape(self, shape=shape) + def wait_to_read(self): + raise NotImplementedForSymbol(self.wait_to_read, None) + + def asnumpy(self): + raise NotImplementedForSymbol(self.asnumpy, None) + + def asscalar(self): + raise NotImplementedForSymbol(self.asscalar, None) + + def astype(self): + raise NotImplementedForSymbol(self.astype, None) + + def copy(self): + raise NotImplementedForSymbol(self.copy, None) + + def as_in_context(self): + raise NotImplementedForSymbol(self.as_in_context, None) + + def detach(self): + raise NotImplementedForSymbol(self.detach, None) + + def backward(self): + raise NotImplementedForSymbol(self.backward, None) def var(name, attr=None, shape=None, lr_mult=None, wd_mult=None, dtype=None, init=None, **kwargs): """Creates a symbolic variable with specified name. @@ -1559,9 +1831,11 @@ def var(name, attr=None, shape=None, lr_mult=None, wd_mult=None, dtype=None, ini ret._set_attr(**attr) return ret + # for back compatibility Variable = var + def Group(symbols): """Creates a symbol that contains a collection of other symbols, grouped together. @@ -1651,9 +1925,6 @@ def load_json(json_str): return Symbol(handle) -# Initialize the atomic symbol in startups -_init_symbol_module(Symbol, "mxnet") - # pylint: disable=no-member # pylint: disable=redefined-builtin def pow(base, exp): @@ -1793,9 +2064,9 @@ def minimum(left, right): # pylint: disable=no-member # pylint: disable=redefined-builtin def hypot(left, right): - """Given the "legs" of a right triangle, return its hypotenuse. + """Given the "legs" of a right triangle, returns its hypotenuse. - Equivalent to "sqrt(left**2 + right**2)", element-wise. + Equivalent to :math:`\\sqrt(left^2 + right^2)`, element-wise. Both inputs can be Symbol or scalar number. Broadcasting is not supported. Parameters @@ -1836,7 +2107,7 @@ def hypot(left, right): def zeros(shape, dtype=None, **kwargs): - """Return a new symbol of given shape and type, filled with zeros. + """Returns a new symbol of given shape and type, filled with zeros. Parameters ---------- @@ -1856,7 +2127,7 @@ def zeros(shape, dtype=None, **kwargs): def ones(shape, dtype=None, **kwargs): - """Return a new symbol of given shape and type, filled with ones. + """Returns a new symbol of given shape and type, filled with ones. Parameters ---------- @@ -1875,8 +2146,30 @@ def ones(shape, dtype=None, **kwargs): return _internal._ones(shape=shape, dtype=dtype, **kwargs) +def full(shape, val, dtype=None, **kwargs): + """Returns a new array of given shape and type, filled with the given value `val`. + + Parameters + ---------- + shape : int or sequence of ints + Shape of the new array. + val : scalar + Fill value. + dtype : str or numpy.dtype, optional + The value type of the inner value, default to ``np.float32``. + + Returns + ------- + out : Symbol + The created Symbol + """ + if dtype is None: + dtype = _numpy.float32 + return _internal._MulScalar(ones(shape=shape, dtype=dtype, **kwargs), scalar=val) + + def arange(start, stop=None, step=1.0, repeat=1, name=None, dtype=None): - """Return evenly spaced values within a given interval. + """Returns evenly spaced values within a given interval. Parameters ---------- @@ -1901,3 +2194,189 @@ def arange(start, stop=None, step=1.0, repeat=1, name=None, dtype=None): dtype = _numpy.float32 return _internal._arange(start=start, stop=stop, step=step, repeat=repeat, name=name, dtype=dtype) + + +def _make_atomic_symbol_function(handle, name): + """Create an atomic symbol function by handle and function name.""" + real_name = ctypes.c_char_p() + desc = ctypes.c_char_p() + num_args = mx_uint() + arg_names = ctypes.POINTER(ctypes.c_char_p)() + arg_types = ctypes.POINTER(ctypes.c_char_p)() + arg_descs = ctypes.POINTER(ctypes.c_char_p)() + key_var_num_args = ctypes.c_char_p() + ret_type = ctypes.c_char_p() + + check_call(_LIB.MXSymbolGetAtomicSymbolInfo( + handle, ctypes.byref(real_name), ctypes.byref(desc), + ctypes.byref(num_args), + ctypes.byref(arg_names), + ctypes.byref(arg_types), + ctypes.byref(arg_descs), + ctypes.byref(key_var_num_args), + ctypes.byref(ret_type))) + narg = int(num_args.value) + arg_names = [py_str(arg_names[i]) for i in range(narg)] + arg_types = [py_str(arg_types[i]) for i in range(narg)] + func_name = name + key_var_num_args = py_str(key_var_num_args.value) + ret_type = py_str(ret_type.value) if ret_type.value is not None else '' + doc_str = _build_doc(func_name, + py_str(desc.value), + arg_names, + arg_types, + [py_str(arg_descs[i]) for i in range(narg)], + key_var_num_args, + ret_type) + + dtype_name = None + arr_name = None + ndsignature = [] + signature = [] + ndarg_names = [] + kwarg_names = [] + for i in range(narg): + name, atype = arg_names[i], arg_types[i] + if name == 'dtype': + dtype_name = name + signature.append('%s=_Null'%name) + elif atype.startswith('NDArray') or atype.startswith('Symbol'): + assert not arr_name, \ + "Op can only have one argument with variable " \ + "size and it must be the last argument." + if atype.endswith('[]'): + ndsignature.append('*%s'%name) + arr_name = name + else: + ndsignature.append('%s=None'%name) + ndarg_names.append(name) + else: + signature.append('%s=_Null'%name) + kwarg_names.append(name) + #signature.append('is_train=False') + signature.append('name=None') + signature.append('attr=None') + signature.append('out=None') + signature.append('**kwargs') + signature = ndsignature + signature + + code = [] + if arr_name: + code.append(""" +def %s(*%s, **kwargs):"""%(func_name, arr_name)) + code.append(""" + sym_args = [] + for i in {}: + assert isinstance(i, SymbolBase), \\ + "Positional arguments must be Symbol instances, " \\ + "but got %s"%str(i) + sym_args.append(i)""".format(arr_name)) + if dtype_name is not None: + code.append(""" + if '%s' in kwargs: + kwargs['%s'] = _numpy.dtype(kwargs['%s']).name"""%( + dtype_name, dtype_name, dtype_name)) + code.append(""" + attr = kwargs.pop('attr', None) + kwargs.update(AttrScope.current.get(attr)) + name = kwargs.pop('name', None) + name = NameManager.current.get(name, '%s') + _ = kwargs.pop('out', None) + keys = [] + vals = [] + sym_kwargs = dict() + for k, v in kwargs.items(): + if isinstance(v, SymbolBase): + sym_kwargs[k] = v + else: + keys.append(k) + vals.append(v)"""%(func_name.lower())) + if key_var_num_args: + code.append(""" + if '%s' not in kwargs: + keys.append('%s') + vals.append(len(sym_args) + len(sym_kwargs))"""%( + key_var_num_args, key_var_num_args)) + + code.append(""" + return _symbol_creator(%d, sym_args, sym_kwargs, keys, vals, name)"""%( + handle.value)) + else: + code.append(""" +def %s(%s): + kwargs.update(AttrScope.current.get(attr)) + sym_kwargs = dict() + keys = [] + vals = []"""%(func_name, ', '.join(signature))) + code.append(""" + for k, v in kwargs.items(): + if isinstance(v, SymbolBase): + sym_kwargs[k] = v + else: + keys.append(k) + vals.append(v)""") + # NDArray args + for name in ndarg_names: # pylint: disable=redefined-argument-from-local + code.append(""" + if {name} is not None: + assert isinstance({name}, SymbolBase), \\ + "Argument {name} must be Symbol instances, but got %s"%str({name}) + sym_kwargs['{name}'] = {name}""".format(name=name)) + # kwargs + for name in kwarg_names: # pylint: disable=redefined-argument-from-local + code.append(""" + if %s is not _Null: + keys.append('%s') + vals.append(%s)"""%(name, name, name)) + # dtype + if dtype_name is not None: + code.append(""" + if %s is not _Null: + keys.append('%s') + vals.append(_numpy.dtype(%s).name)"""%(dtype_name, dtype_name, dtype_name)) + + code.append(""" + name = NameManager.current.get(name, '%s') + return _symbol_creator(%d, None, sym_kwargs, keys, vals, name)"""%( + func_name.lower(), handle.value)) + + local = {} + exec(''.join(code), None, local) # pylint: disable=exec-used + symbol_function = local[func_name] + symbol_function.__name__ = func_name + symbol_function.__doc__ = doc_str + symbol_function.__module__ = 'mxnet.symbol' + return symbol_function + + +def _init_symbol_module(symbol_class, root_namespace): + """List and add all the atomic symbol functions to current module.""" + _set_symbol_class(symbol_class) + plist = ctypes.POINTER(ctypes.c_char_p)() + size = ctypes.c_uint() + + check_call(_LIB.MXListAllOpNames(ctypes.byref(size), + ctypes.byref(plist))) + op_names = [] + for i in range(size.value): + op_names.append(py_str(plist[i])) + + module_obj = _sys.modules["%s.symbol" % root_namespace] + module_internal = _sys.modules["%s._symbol_internal" % root_namespace] + module_contrib = _sys.modules["%s.contrib.symbol" % root_namespace] + for name in op_names: + hdl = OpHandle() + check_call(_LIB.NNGetOpHandle(c_str(name), ctypes.byref(hdl))) + function = _make_atomic_symbol_function(hdl, name) + if function.__name__.startswith('_contrib_'): + function.__name__ = function.__name__[9:] + function.__module__ = 'mxnet.contrib.symbol' + setattr(module_contrib, function.__name__, function) + elif function.__name__.startswith('_'): + setattr(module_internal, function.__name__, function) + else: + setattr(module_obj, function.__name__, function) + + +# Initialize the atomic symbol in startups +_init_symbol_module(Symbol, "mxnet") diff --git a/python/mxnet/symbol_doc.py b/python/mxnet/symbol_doc.py index dff5383e2682..3cb1997584d2 100644 --- a/python/mxnet/symbol_doc.py +++ b/python/mxnet/symbol_doc.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # coding: utf-8 # pylint: disable=unused-argument, too-many-arguments """Extra symbol documents diff --git a/python/mxnet/test_utils.py b/python/mxnet/test_utils.py index 6b836f5d5d84..c5587f8d80a8 100644 --- a/python/mxnet/test_utils.py +++ b/python/mxnet/test_utils.py @@ -1,13 +1,34 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + """Tools for testing.""" # pylint: disable=too-many-lines from __future__ import absolute_import, print_function, division import time +import gzip +import struct import traceback import numbers import subprocess +import sys import os import errno import logging +from contextlib import contextmanager import numpy as np import numpy.testing as npt import mxnet as mx @@ -943,9 +964,6 @@ def download(url, fname=None, dirname=None, overwrite=False): """ if fname is None: fname = url.split('/')[-1] - if not overwrite and os.path.exists(fname): - logging.info("%s exists, skip to downloada", fname) - return fname if dirname is None: dirname = os.path.dirname(fname) @@ -960,6 +978,10 @@ def download(url, fname=None, dirname=None, overwrite=False): if exc.errno != errno.EEXIST: raise OSError('failed to create ' + dirname) + if not overwrite and os.path.exists(fname): + logging.info("%s exists, skipping download", fname) + return fname + r = requests.get(url, stream=True) assert r.status_code == 200, "failed to open %s" % url with open(fname, 'wb') as f: @@ -1018,3 +1040,45 @@ def set_env_var(key, val, default_val=""): prev_val = os.environ.get(key, default_val) os.environ[key] = val return prev_val + +def same_array(array1, array2): + """Check whether two NDArrays sharing the same memory block + + Parameters + ---------- + + array1 : NDArray + First NDArray to be checked + array2 : NDArray + Second NDArray to be checked + + Returns + ------- + bool + Whether two NDArrays share the same memory + """ + array1[:] += 1 + if not same(array1.asnumpy(), array2.asnumpy()): + array1[:] -= 1 + return False + array1[:] -= 1 + return same(array1.asnumpy(), array2.asnumpy()) + +@contextmanager +def discard_stderr(): + """ + Discards error output of a routine if invoked as: + + with discard_stderr(): + ... + """ + + try: + stderr_fileno = sys.stderr.fileno() + old_stderr = os.dup(stderr_fileno) + bit_bucket = open(os.devnull, 'w') + os.dup2(bit_bucket.fileno(), stderr_fileno) + yield + finally: + os.dup2(old_stderr, stderr_fileno) + bit_bucket.close() diff --git a/python/mxnet/torch.py b/python/mxnet/torch.py index 765c96bbfb60..b7fce6d5c8fd 100644 --- a/python/mxnet/torch.py +++ b/python/mxnet/torch.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # coding: utf-8 """Interface for NDArray functions executed by torch backend. Install Torch and compile with USE_TORCH=1 to use this module.""" diff --git a/python/mxnet/visualization.py b/python/mxnet/visualization.py index 10a24241c730..4dbf680c2e3a 100644 --- a/python/mxnet/visualization.py +++ b/python/mxnet/visualization.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # coding: utf-8 # pylint: disable=invalid-name, too-many-locals, fixme # pylint: disable=too-many-branches, too-many-statements @@ -240,13 +257,8 @@ def looks_like_weight(name): return True if name.endswith("_bias"): return True - if name.endswith("_var"): - return True - if name.endswith("_beta"): - return True - if name.endswith("_gamma"): - return True - if name.endswith("_mean"): + if name.endswith("_beta") or name.endswith("_gamma") or \ + name.endswith("_moving_var") or name.endswith("_moving_mean"): return True return False @@ -322,7 +334,6 @@ def looks_like_weight(name): params = input_node["attr"] if "num_outputs" in params: key += str(int(params["num_outputs"]) - 1) - params["num_outputs"] = int(params["num_outputs"]) - 1 shape = shape_dict[key][1:] label = "x".join([str(x) for x in shape]) attr["label"] = label diff --git a/python/setup.py b/python/setup.py index d56ae7517a79..14c8121d35ee 100644 --- a/python/setup.py +++ b/python/setup.py @@ -1,15 +1,40 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # pylint: disable=invalid-name, exec-used """Setup mxnet package.""" from __future__ import absolute_import import os import sys # need to use distutils.core for correct placement of cython dll +kwargs = {} if "--inplace" in sys.argv: from distutils.core import setup from distutils.extension import Extension else: from setuptools import setup from setuptools.extension import Extension + kwargs = {'install_requires': ['numpy', 'requests', 'graphviz'], 'zip_safe': False} +from setuptools import find_packages + +with_cython = False +if '--with-cython' in sys.argv: + with_cython = True + sys.argv.remove('--with-cython') # We can not import `mxnet.info.py` in setup.py directly since mxnet/__init__.py # Will be invoked which introduces dependences @@ -24,7 +49,8 @@ def config_cython(): """Try to configure cython and return cython configuration""" - return [] # disable cython due to some users have compile errors. + if not with_cython: + return [] # pylint: disable=unreachable if os.name == 'nt': print("WARNING: Cython is not supported on Windows, will compile without cython module") @@ -65,14 +91,8 @@ def config_cython(): setup(name='mxnet', version=__version__, description=open(os.path.join(CURRENT_DIR, 'README.md')).read(), - install_requires=[ - 'numpy', - ], - zip_safe=False, - packages=[ - 'mxnet', 'mxnet.module', 'mxnet._ctypes', 'mxnet.rnn', - 'mxnet._cy2', 'mxnet._cy3', 'mxnet.notebook', 'mxnet.contrib' - ], + packages=find_packages(), data_files=[('mxnet', [LIB_PATH[0]])], url='https://github.com/dmlc/mxnet', - ext_modules=config_cython()) + ext_modules=config_cython(), + **kwargs) diff --git a/scala-package/assembly/linux-x86_64-cpu/pom.xml b/scala-package/assembly/linux-x86_64-cpu/pom.xml index 8872b774890f..2c25e6856fd3 100644 --- a/scala-package/assembly/linux-x86_64-cpu/pom.xml +++ b/scala-package/assembly/linux-x86_64-cpu/pom.xml @@ -6,13 +6,11 @@ ml.dmlc.mxnet mxnet-full-parent_2.11 - 0.9.5-SNAPSHOT + ${project.version} ../pom.xml - ml.dmlc.mxnet mxnet-full_2.11-linux-x86_64-cpu - 0.9.5-SNAPSHOT MXNet Scala Package - Full Linux-x86_64 CPU-only jar diff --git a/scala-package/assembly/linux-x86_64-gpu/pom.xml b/scala-package/assembly/linux-x86_64-gpu/pom.xml index f8e4900926da..892851281655 100644 --- a/scala-package/assembly/linux-x86_64-gpu/pom.xml +++ b/scala-package/assembly/linux-x86_64-gpu/pom.xml @@ -6,13 +6,11 @@ ml.dmlc.mxnet mxnet-full-parent_2.11 - 0.9.5-SNAPSHOT + ${project.version} ../pom.xml - ml.dmlc.mxnet mxnet-full_2.11-linux-x86_64-gpu - 0.9.5-SNAPSHOT MXNet Scala Package - Full Linux-x86_64 GPU jar diff --git a/scala-package/assembly/osx-x86_64-cpu/pom.xml b/scala-package/assembly/osx-x86_64-cpu/pom.xml index 401443c6f0a7..e3f433f673e4 100644 --- a/scala-package/assembly/osx-x86_64-cpu/pom.xml +++ b/scala-package/assembly/osx-x86_64-cpu/pom.xml @@ -6,13 +6,11 @@ ml.dmlc.mxnet mxnet-full-parent_2.11 - 0.9.5-SNAPSHOT + ${project.version} ../pom.xml - ml.dmlc.mxnet mxnet-full_2.11-osx-x86_64-cpu - 0.9.5-SNAPSHOT MXNet Scala Package - Full OSX-x86_64 CPU-only jar diff --git a/scala-package/assembly/pom.xml b/scala-package/assembly/pom.xml index 0c93b4b38f94..52a2cc42228f 100644 --- a/scala-package/assembly/pom.xml +++ b/scala-package/assembly/pom.xml @@ -6,13 +6,11 @@ ml.dmlc.mxnet mxnet-parent_2.11 - 0.9.5-SNAPSHOT + ${project.version} ../pom.xml - ml.dmlc.mxnet mxnet-full-parent_2.11 - 0.9.5-SNAPSHOT MXNet Scala Package - Full Parent pom diff --git a/scala-package/core/pom.xml b/scala-package/core/pom.xml index 2f7fe2bb6f79..51e8a3596b1a 100644 --- a/scala-package/core/pom.xml +++ b/scala-package/core/pom.xml @@ -6,13 +6,11 @@ ml.dmlc.mxnet mxnet-parent_2.11 - 0.9.5-SNAPSHOT + ${project.version} ../pom.xml - ml.dmlc.mxnet mxnet-core_2.11 - 0.9.5-SNAPSHOT MXNet Scala Package - Core diff --git a/scala-package/core/scripts/get_cifar_data.sh b/scala-package/core/scripts/get_cifar_data.sh index eba3a27805fa..9ec1c39a4f99 100755 --- a/scala-package/core/scripts/get_cifar_data.sh +++ b/scala-package/core/scripts/get_cifar_data.sh @@ -1,5 +1,23 @@ #!/bin/bash +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + + set -e if [ ! -z "$MXNET_DATA_DIR" ]; then diff --git a/scala-package/core/scripts/get_mnist_data.sh b/scala-package/core/scripts/get_mnist_data.sh index a4cfe11e4b5a..97e151bf8333 100755 --- a/scala-package/core/scripts/get_mnist_data.sh +++ b/scala-package/core/scripts/get_mnist_data.sh @@ -1,5 +1,23 @@ #!/bin/bash +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + + set -e if [ ! -z "$MXNET_DATA_DIR" ]; then diff --git a/scala-package/core/src/main/scala/ml/dmlc/mxnet/DType.scala b/scala-package/core/src/main/scala/ml/dmlc/mxnet/DType.scala index 2ea09f4bee83..bfe757d5cfad 100644 --- a/scala-package/core/src/main/scala/ml/dmlc/mxnet/DType.scala +++ b/scala-package/core/src/main/scala/ml/dmlc/mxnet/DType.scala @@ -19,11 +19,11 @@ package ml.dmlc.mxnet object DType extends Enumeration { type DType = Value - val Float32 = Value(0) - val Float64 = Value(1) - val Float16 = Value(2) - val UInt8 = Value(3) - val Int32 = Value(4) + val Float32 = Value(0, "float32") + val Float64 = Value(1, "float64") + val Float16 = Value(2, "float16") + val UInt8 = Value(3, "uint8") + val Int32 = Value(4, "int32") private[mxnet] def numOfBytes(dtype: DType): Int = { dtype match { case DType.UInt8 => 1 diff --git a/scala-package/core/src/main/scala/ml/dmlc/mxnet/KVStore.scala b/scala-package/core/src/main/scala/ml/dmlc/mxnet/KVStore.scala index 32e0acec1572..94dd25497a9e 100644 --- a/scala-package/core/src/main/scala/ml/dmlc/mxnet/KVStore.scala +++ b/scala-package/core/src/main/scala/ml/dmlc/mxnet/KVStore.scala @@ -83,13 +83,13 @@ class KVStore(private[mxnet] val handle: KVStoreHandle) { * @param keys The keys. * @param values The values. */ - def init(keys: Array[Int], values: Array[NDArray]): Unit = { + def init(keys: Array[String], values: Array[NDArray]): Unit = { require(keys.length == values.length, "len(keys) != len(values)") val valuePtrs = values.map(_.handle) - checkCall(_LIB.mxKVStoreInit(handle, keys.length, keys, valuePtrs)) + checkCall(_LIB.mxKVStoreInitEx(handle, keys.length, keys, valuePtrs)) } - def init(key: Int, value: NDArray): Unit = { + def init(key: String, value: NDArray): Unit = { init(Array(key), Array(value)) } @@ -107,24 +107,24 @@ class KVStore(private[mxnet] val handle: KVStoreHandle) { * The higher the priority, the faster this action is likely * to be executed before other push actions. */ - def push(keys: Array[Int], values: Array[NDArray], priority: Int): Unit = { + def push(keys: Array[String], values: Array[NDArray], priority: Int): Unit = { require(keys.length == values.length, "len(keys) != len(values)") val valuePtrs = values.map(_.handle) - checkCall(_LIB.mxKVStorePush(handle, keys.length, keys, valuePtrs, priority)) + checkCall(_LIB.mxKVStorePushEx(handle, keys.length, keys, valuePtrs, priority)) } - def push(keys: Array[Int], values: Array[NDArray]): Unit = push(keys, values, 0) + def push(keys: Array[String], values: Array[NDArray]): Unit = push(keys, values, 0) - def push(key: Int, value: NDArray, priority: Int = 0): Unit = { + def push(key: String, value: NDArray, priority: Int = 0): Unit = { push(Array(key), Array(value), priority) } - def push(key: Int, values: Array[NDArray], priority: Int): Unit = { + def push(key: String, values: Array[NDArray], priority: Int): Unit = { val keys = Array.fill(values.length)(key) push(keys, values, priority) } - def push(key: Int, values: Array[NDArray]): Unit = { + def push(key: String, values: Array[NDArray]): Unit = { push(key, values, 0) } @@ -143,24 +143,24 @@ class KVStore(private[mxnet] val handle: KVStoreHandle) { * The higher the priority, the faster this action is likely * to be executed before other push actions. */ - def pull(keys: Array[Int], outs: Array[NDArray], priority: Int): Unit = { + def pull(keys: Array[String], outs: Array[NDArray], priority: Int): Unit = { require(keys.length == outs.length, "len(keys) != len(outs)") val outPtrs = outs.map(_.handle) - checkCall(_LIB.mxKVStorePull(handle, keys.length, keys, outPtrs, priority)) + checkCall(_LIB.mxKVStorePullEx(handle, keys.length, keys, outPtrs, priority)) } - def pull(keys: Array[Int], outs: Array[NDArray]): Unit = pull(keys, outs, 0) + def pull(keys: Array[String], outs: Array[NDArray]): Unit = pull(keys, outs, 0) - def pull(key: Int, out: NDArray, priority: Int = 0): Unit = { + def pull(key: String, out: NDArray, priority: Int = 0): Unit = { pull(Array(key), Array(out), priority) } - def pull(key: Int, outs: Array[NDArray], priority: Int): Unit = { + def pull(key: String, outs: Array[NDArray], priority: Int): Unit = { val keys = Array.fill(outs.length)(key) pull(keys, outs, priority) } - def pull(key: Int, outs: Array[NDArray]): Unit = { + def pull(key: String, outs: Array[NDArray]): Unit = { pull(key, outs, 0) } diff --git a/scala-package/core/src/main/scala/ml/dmlc/mxnet/LibInfo.scala b/scala-package/core/src/main/scala/ml/dmlc/mxnet/LibInfo.scala index f776117df8ed..a943e314055f 100644 --- a/scala-package/core/src/main/scala/ml/dmlc/mxnet/LibInfo.scala +++ b/scala-package/core/src/main/scala/ml/dmlc/mxnet/LibInfo.scala @@ -117,16 +117,30 @@ private[mxnet] class LibInfo { len: MXUint, keys: Array[Int], values: Array[NDArrayHandle]): Int + @native def mxKVStoreInitEx(handle: KVStoreHandle, + len: MXUint, + keys: Array[String], + values: Array[NDArrayHandle]): Int @native def mxKVStorePush(handle: KVStoreHandle, len: MXUint, keys: Array[Int], values: Array[NDArrayHandle], priority: Int): Int + @native def mxKVStorePushEx(handle: KVStoreHandle, + len: MXUint, + keys: Array[String], + values: Array[NDArrayHandle], + priority: Int): Int @native def mxKVStorePull(handle: KVStoreHandle, len: MXUint, keys: Array[Int], outs: Array[NDArrayHandle], priority: Int): Int + @native def mxKVStorePullEx(handle: KVStoreHandle, + len: MXUint, + keys: Array[String], + outs: Array[NDArrayHandle], + priority: Int): Int @native def mxKVStoreSetUpdater(handle: KVStoreHandle, updaterFunc: MXKVStoreUpdater): Int @native def mxKVStoreIsWorkerNode(isWorker: RefInt): Int @native def mxKVStoreGetType(handle: KVStoreHandle, kvType: RefString): Int @@ -186,6 +200,12 @@ private[mxnet] class LibInfo { paramVals: Array[String], symHandleRef: SymbolHandleRef): Int @native def mxSymbolSetAttr(handle: SymbolHandle, key: String, value: String): Int + @native def mxSymbolListAttrShallow(handle: SymbolHandle, + outSize: MXUintRef, + out: ArrayBuffer[String]): Int + @native def mxSymbolListAttr(handle: SymbolHandle, + outSize: MXUintRef, + out: ArrayBuffer[String]): Int @native def mxSymbolCompose(handle: SymbolHandle, name: String, keys: Array[String], diff --git a/scala-package/core/src/main/scala/ml/dmlc/mxnet/Model.scala b/scala-package/core/src/main/scala/ml/dmlc/mxnet/Model.scala index 69fe68255274..81ff1cfb3c7d 100644 --- a/scala-package/core/src/main/scala/ml/dmlc/mxnet/Model.scala +++ b/scala-package/core/src/main/scala/ml/dmlc/mxnet/Model.scala @@ -163,9 +163,10 @@ object Model { require(paramArrays.length == paramNames.length) for (idx <- 0 until paramArrays.length) { val paramOnDevs = paramArrays(idx) - kvStore.init(idx, argParams(paramNames(idx))) + val name = paramNames(idx) + kvStore.init(name, argParams(paramNames(idx))) if (updateOnKVStore) { - kvStore.pull(idx, paramOnDevs, -idx) + kvStore.pull(name, paramOnDevs, -idx) } } } @@ -173,13 +174,15 @@ object Model { // Perform update of param_arrays from grad_arrays on kvstore private[mxnet] def updateParamsOnKVStore(paramArrays: IndexedSeq[Array[NDArray]], gradArrays: IndexedSeq[Array[NDArray]], - kvStore: Option[KVStore]): Unit = { + kvStore: Option[KVStore], + paramNames: IndexedSeq[String]): Unit = { (paramArrays zip gradArrays).zipWithIndex.foreach { case ((argList, gradList), index) => if (gradList != null) { + val name = paramNames(index) // push gradient, priority is negative index - kvStore.foreach(_.push(index, gradList, -index)) + kvStore.foreach(_.push(name, gradList, -index)) // pull back the weights - kvStore.foreach(_.pull(index, argList, -index)) + kvStore.foreach(_.pull(name, argList, -index)) } } } @@ -189,14 +192,16 @@ object Model { gradArrays: IndexedSeq[Array[NDArray]], updater: MXKVStoreUpdater, numDevice: Int, + paramNames: IndexedSeq[String], kvStore: Option[KVStore] = None) { (paramArrays zip gradArrays).zipWithIndex.foreach { case ((argList, gradList), index) => if (gradList != null) { kvStore.foreach(kv => { + val name = paramNames(index) // push gradient, priority is negative index - kv.push(index, gradList, -index) + kv.push(name, gradList, -index) // pull back the sum gradients, to the same locations. - kv.pull(index, gradList, -index) + kv.pull(name, gradList, -index) }) (argList zip gradList).zipWithIndex.foreach { case ((w: NDArray, g: NDArray), k: Int) => // faked an index here, to make optimizer create diff @@ -295,11 +300,12 @@ object Model { if (updateOnKVStore) { updateParamsOnKVStore(executorManager.paramArrays, executorManager.gradArrays, - kvStore) + kvStore, executorManager.paramNames) } else { updateParams(executorManager.paramArrays, executorManager.gradArrays, updaterLocal, ctx.length, + executorManager.paramNames, kvStore) } monitor.foreach(_.tocPrint()) diff --git a/scala-package/core/src/main/scala/ml/dmlc/mxnet/NDArray.scala b/scala-package/core/src/main/scala/ml/dmlc/mxnet/NDArray.scala index fa9488c00c79..5314dc4a1896 100644 --- a/scala-package/core/src/main/scala/ml/dmlc/mxnet/NDArray.scala +++ b/scala-package/core/src/main/scala/ml/dmlc/mxnet/NDArray.scala @@ -291,6 +291,89 @@ object NDArray { NDArray.genericNDArrayFunctionInvoke("_minimum_scalar", Seq(lhs, rhs)) } + /** + * Returns the result of element-wise **equal to** (==) comparison operation with broadcasting. + * For each element in input arrays, return 1(true) if corresponding elements are same, + * otherwise return 0(false). + */ + def equal(lhs: NDArray, rhs: NDArray): NDArray = { + NDArray.genericNDArrayFunctionInvoke("broadcast_equal", Seq(lhs, rhs)) + } + + def equal(lhs: NDArray, rhs: Float): NDArray = { + NDArray.genericNDArrayFunctionInvoke("_equal_scalar", Seq(lhs, rhs)) + } + + /** + * Returns the result of element-wise **not equal to** (!=) comparison operation + * with broadcasting. + * For each element in input arrays, return 1(true) if corresponding elements are different, + * otherwise return 0(false). + */ + def notEqual(lhs: NDArray, rhs: NDArray): NDArray = { + NDArray.genericNDArrayFunctionInvoke("broadcast_not_equal", Seq(lhs, rhs)) + } + + def notEqual(lhs: NDArray, rhs: Float): NDArray = { + NDArray.genericNDArrayFunctionInvoke("_not_equal_scalar", Seq(lhs, rhs)) + } + + /** + * Returns the result of element-wise **greater than** (>) comparison operation + * with broadcasting. + * For each element in input arrays, return 1(true) if lhs elements are greater than rhs, + * otherwise return 0(false). + */ + def greater(lhs: NDArray, rhs: NDArray): NDArray = { + NDArray.genericNDArrayFunctionInvoke("broadcast_greater", Seq(lhs, rhs)) + } + + def greater(lhs: NDArray, rhs: Float): NDArray = { + NDArray.genericNDArrayFunctionInvoke("_greater_scalar", Seq(lhs, rhs)) + } + + /** + * Returns the result of element-wise **greater than or equal to** (>=) comparison + * operation with broadcasting. + * For each element in input arrays, return 1(true) if lhs elements are greater than equal to rhs, + * otherwise return 0(false). + */ + def greaterEqual(lhs: NDArray, rhs: NDArray): NDArray = { + NDArray.genericNDArrayFunctionInvoke("broadcast_greater_equal", Seq(lhs, rhs)) + } + + def greaterEqual(lhs: NDArray, rhs: Float): NDArray = { + NDArray.genericNDArrayFunctionInvoke("_greater_equal_scalar", Seq(lhs, rhs)) + } + + /** + * Returns the result of element-wise **lesser than** (<) comparison operation + * with broadcasting. + * For each element in input arrays, return 1(true) if lhs elements are less than rhs, + * otherwise return 0(false). + */ + def lesser(lhs: NDArray, rhs: NDArray): NDArray = { + NDArray.genericNDArrayFunctionInvoke("broadcast_lesser", Seq(lhs, rhs)) + } + + def lesser(lhs: NDArray, rhs: Float): NDArray = { + NDArray.genericNDArrayFunctionInvoke("_lesser_scalar", Seq(lhs, rhs)) + } + + /** + * Returns the result of element-wise **lesser than or equal to** (<=) comparison + * operation with broadcasting. + * For each element in input arrays, return 1(true) if lhs elements are + * lesser than equal to rhs, otherwise return 0(false). + */ + def lesserEqual(lhs: NDArray, rhs: NDArray): NDArray = { + NDArray.genericNDArrayFunctionInvoke("broadcast_lesser_equal", Seq(lhs, rhs)) + } + + def lesserEqual(lhs: NDArray, rhs: Float): NDArray = { + NDArray.genericNDArrayFunctionInvoke("_lesser_equal_scalar", Seq(lhs, rhs)) + } + /** * Create a new NDArray that copies content from source_array. * @param sourceArr Source data to create NDArray from. @@ -304,6 +387,27 @@ object NDArray { arr } + /** + * Returns evenly spaced values within a given interval. + * Values are generated within the half-open interval [`start`, `stop`). In other + * words, the interval includes `start` but excludes `stop`. + * @param start Start of interval. The default start value is 0. + * @param stop End of interval. + * @param step Spacing between values. The default step size is 1. + * @param repeat Number of times to repeat each element. The default repeat count is 1. + * @param ctx Device context. Default context is the current default context. + * @param dType The data type of the `NDArray`. The default datatype is `DType.Float32`. + * @return NDArray of evenly spaced values in the specified range. + */ + def arange(start: Float, stop: Option[Float] = None, step: Float = 1.0f, + repeat: Int = 1, ctx: Context = Context.defaultCtx, + dType: DType = Base.MX_REAL_TYPE): NDArray = { + val params = Map("start" -> start, "step" -> step, + "repeat" -> repeat, "ctx" -> ctx.toString, "dtype" -> dType.toString()) + val fParams = if (stop == None) params else params ++ Map("stop" -> stop.get) + NDArray.genericNDArrayFunctionInvoke("_arange", Seq(), fParams)(0) + } + /** * Concatenate a list of NDArrays along the specified dimension. * @param arrays Arrays to be concatenate. @@ -749,6 +853,78 @@ class NDArray private[mxnet](private[mxnet] val handle: NDArrayHandle, this } + def **(other: NDArray): NDArray = { + NDArray.power(this, other) + } + + def **(other: Float): NDArray = { + NDArray.power(this, other) + } + + def **=(other: NDArray): NDArray = { + NDArray.genericNDArrayFunctionInvoke("_power", Seq(this, other), Map("out" -> this)) + } + + def **=(other: Float): NDArray = { + NDArray.genericNDArrayFunctionInvoke("_power_scalar", Seq(this, other), Map("out" -> this)) + } + + def >(other: NDArray): NDArray = { + NDArray.greater(this, other) + } + + def >(other: Float): NDArray = { + NDArray.greater(this, other) + } + + def >=(other: NDArray): NDArray = { + NDArray.greaterEqual(this, other) + } + + def >=(other: Float): NDArray = { + NDArray.greaterEqual(this, other) + } + + def <(other: NDArray): NDArray = { + NDArray.lesser(this, other) + } + + def <(other: Float): NDArray = { + NDArray.lesser(this, other) + } + + def <=(other: NDArray): NDArray = { + NDArray.lesserEqual(this, other) + } + + def <=(other: Float): NDArray = { + NDArray.lesserEqual(this, other) + } + + def %(other: NDArray): NDArray = { + NDArray.genericNDArrayFunctionInvoke("_mod", Seq(this, other)) + } + + def %(other: Float): NDArray = { + NDArray.genericNDArrayFunctionInvoke("_mod_scalar", Seq(this, other)) + } + + def %=(other: NDArray): NDArray = { + if (!writable) { + throw new IllegalArgumentException("trying to take modulo from a readonly NDArray") + } + NDArray.genericNDArrayFunctionInvoke("_mod", Seq(this, other), Map("out" -> this)) + this + } + + def %=(other: Float): NDArray = { + if (!writable) { + throw new IllegalArgumentException("trying to take modulo from a readonly NDArray") + } + NDArray.genericNDArrayFunctionInvoke("_mod_scalar", Seq(this, other), Map("out" -> this)) + this + } + /** * Return a copied flat java array of current array (row-major). * @return A copy of array content. @@ -880,6 +1056,41 @@ private[mxnet] class NDArrayConversions(val value: Float) { def /(other: NDArrayFuncReturn): NDArray = { NDArray.genericNDArrayFunctionInvoke("_rdiv_scalar", Seq(other.head, value)) } + + def **(other: NDArray): NDArray = { + NDArray.power(value, other) + } + def **(other: NDArrayFuncReturn): NDArray = { + NDArray.power(value, other.head) + } + + def >(other: NDArray): NDArray = { + NDArray.lesser(other, value) + } + def >(other: NDArrayFuncReturn): NDArray = { + NDArray.lesser(other.head, value) + } + + def >=(other: NDArray): NDArray = { + NDArray.lesserEqual(other, value) + } + def >=(other: NDArrayFuncReturn): NDArray = { + NDArray.lesserEqual(other.head, value) + } + + def <(other: NDArray): NDArray = { + NDArray.greater(other, value) + } + def <(other: NDArrayFuncReturn): NDArray = { + NDArray.greater(other.head, value) + } + + def <=(other: NDArray): NDArray = { + NDArray.greaterEqual(other, value) + } + def <=(other: NDArrayFuncReturn): NDArray = { + NDArray.greaterEqual(other.head, value) + } } private case class NDArrayFunction(handle: NDArrayHandle, arguments: List[String]) @@ -927,6 +1138,16 @@ private[mxnet] class NDArrayFuncReturn(private[mxnet] val arr: Array[NDArray]) { def *=(other: NDArray): NDArray = head *= other def *=(other: Float): NDArray = head *= other def /(other: NDArray): NDArray = head / other + def **(other: NDArray): NDArray = head ** other + def **(other: Float): NDArray = head ** other + def >(other: NDArray): NDArray = head > other + def >(other: Float): NDArray = head > other + def >=(other: NDArray): NDArray = head >= other + def >=(other: Float): NDArray = head >= other + def <(other: NDArray): NDArray = head < other + def <(other: Float): NDArray = head < other + def <=(other: NDArray): NDArray = head <= other + def <=(other: Float): NDArray = head <= other def toArray: Array[Float] = head.toArray def toScalar: Float = head.toScalar def copyTo(other: NDArray): NDArray = head.copyTo(other) diff --git a/scala-package/core/src/main/scala/ml/dmlc/mxnet/Optimizer.scala b/scala-package/core/src/main/scala/ml/dmlc/mxnet/Optimizer.scala index 128fc9b53296..27db5656d7d7 100644 --- a/scala-package/core/src/main/scala/ml/dmlc/mxnet/Optimizer.scala +++ b/scala-package/core/src/main/scala/ml/dmlc/mxnet/Optimizer.scala @@ -20,6 +20,7 @@ package ml.dmlc.mxnet import java.io._ import scala.collection.mutable +import scala.util.Either object Optimizer { def getUpdater(optimizer: Optimizer): MXKVStoreUpdater = { @@ -103,7 +104,10 @@ object Optimizer { } abstract class Optimizer extends Serializable { - protected var lrScale: mutable.Map[Int, Float] = mutable.HashMap.empty[Int, Float] + protected val lrMult: mutable.Map[Either[Int, String], Float] = + mutable.HashMap.empty[Either[Int, String], Float] + protected val wdMult: mutable.Map[Either[Int, String], Float] = + mutable.HashMap.empty[Either[Int, String], Float] protected var numUpdate: Int = 0 protected val indexUpdateCount: mutable.Map[Int, Int] = mutable.HashMap.empty[Int, Int] @@ -136,8 +140,62 @@ abstract class Optimizer extends Serializable { def deserializeState(bytes: Array[Byte]): AnyRef // Set individual learning rate scale for parameters - def setLrScale(lrScale: Map[Int, Float]) { - this.lrScale = mutable.Map(lrScale.toSeq: _*) + @deprecated("Use setLrMult instead.") + def setLrScale(lrScale: Map[Int, Float]): Unit = { + val argsLrScale: Map[Either[Int, String], Float] = lrScale.map { case (k, v) => Left(k) -> v } + setLrMult(argsLrScale) + } + + /** + * Sets an individual learning rate multiplier for each parameter. + * If you specify a learning rate multiplier for a parameter, then + * the learning rate for the parameter will be set as the product of + * the global learning rate and its multiplier. + * note:: The default learning rate multiplier of a `Variable` + * can be set with `lr_mult` argument in the constructor. + * @param argsLrMult: Map[Either[Int, String], Float] + * For each of its key-value entries, the learning rate multipler for the + * parameter specified in the key will be set as the given value. + * + * You can specify the parameter with either its name or its index. + * If you use the name, you should also call the `setSymbol` method first, + * and the name you specified in the key of `argsLrMult` should match + * the name of the parameter in the `sym` you pass to `setSymbol` method. + * If you use the index, it should correspond to the index of the parameter + * used in the `update` method. + * + * Specifying a parameter by its index is only supported for backward + * compatibility, and we recommend to use the name instead. + */ + def setLrMult(argsLrMult: Map[Either[Int, String], Float]): Unit = { + argsLrMult.foreach { case (k, v) => this.lrMult(k) = v } + } + + /** + * Sets an individual weight decay multiplier for each parameter. + * + * By default, the weight decay multipler is set as 0 for all + * parameters whose name don't end with ``_weight`` or ``_gamma``, if + * you call the `setIdx2Name` method to set idx2name. + * + * note:: The default weight decay multiplier for a `Variable` + * can be set with its `wd_mult` argument in the constructor. + * @param argsWdMult: Map[Either[Int, String], Float] + * For each of its key-value entries, the learning rate multipler for the + * parameter specified in the key will be set as the given value. + * + * You can specify the parameter with either its name or its index. + * If you use the name, you should also call the `setSymbol` method first, + * and the name you specified in the key of `argsWdMult` should match + * the name of the parameter in the `sym` you pass to `setSymbol` method. + * If you use the index, it should correspond to the index of the parameter + * used in the `update` method. + * + * Specifying a parameter by its index is only supported for backward + * compatibility, and we recommend to use the name instead. + */ + def setWdMult(argsWdMult: Map[Either[Int, String], Float]): Unit = { + argsWdMult.foreach { case (k, v) => this.wdMult(k) = v } } def setArgNames(argNames: Seq[String]): Unit = { @@ -160,14 +218,30 @@ abstract class Optimizer extends Serializable { this.rescaleGrad = rescaleGrad } - // TODO def setSymbol(sym: Symbol): Unit = { this.symbol = sym + if (this.symbol != null) { + val attr = this.symbol.attrMap + for (name <- this.symbol.listArguments()) { + if (attr.contains(name) && attr(name).contains("__lr_mult__")) { + this.lrMult(Right(name)) = attr(name)("__lr_mult__").toFloat + } + if (attr.contains(name) && attr(name).contains("__wd_mult__")) { + this.wdMult(Right(name)) = attr(name)("__wd_mult__").toFloat + } + } + } } - // TODO: Special treat weight decay in parameters. def setIdx2Name(paramIdx2Name: Map[Int, String]): Unit = { this.idx2name = paramIdx2Name + if (this.idx2name != null) { + for (n <- this.idx2name.values) { + if (!(n.endsWith("_weight") || n.endsWith("_gamma"))) { + this.wdMult(Right(n)) = 0f + } + } + } } /** @@ -180,8 +254,20 @@ abstract class Optimizer extends Serializable { numUpdate = Math.max(count, numUpdate) } + // Gets the learning rate given the index of the weight. + protected def getLr(index: Int, lr: Float): Float = { + var llr = lr + if (this.lrMult.contains(Left(index))) { + llr *= this.lrMult(Left(index)) + } else if (this.idx2name != null && this.idx2name.contains(index)) { + llr *= this.lrMult.getOrElse(Right(this.idx2name(index)), 1.0f) + } + llr + } + + // Gets weight decay for index. protected def getWd(index: Int, wd: Float): Float = { - if (specialized) { + var lwd = if (specialized) { if (this.weightSet.contains(index)) { wd } else { @@ -190,6 +276,12 @@ abstract class Optimizer extends Serializable { } else { wd } + if (this.wdMult.contains(Left(index))) { + lwd *= this.wdMult(Left(index)) + } else if (this.idx2name != null && this.idx2name.contains(index)) { + lwd *= this.wdMult.getOrElse(Right(this.idx2name(index)), 1.0f) + } + lwd } } diff --git a/scala-package/core/src/main/scala/ml/dmlc/mxnet/Symbol.scala b/scala-package/core/src/main/scala/ml/dmlc/mxnet/Symbol.scala index beb793a25713..d8da1c67c252 100644 --- a/scala-package/core/src/main/scala/ml/dmlc/mxnet/Symbol.scala +++ b/scala-package/core/src/main/scala/ml/dmlc/mxnet/Symbol.scala @@ -70,6 +70,26 @@ class Symbol private(private[mxnet] val handle: SymbolHandle) { Symbol.createFromListedSymbols("_DivScalar")(Array(this), Map("scalar" -> other.toString)) } + def **(other: Symbol): Symbol = Symbol.pow(this, other) + def **[@specialized(Int, Float, Double) V](other: V): Symbol = Symbol.pow(this, other) + + def >(other: Symbol): Symbol = Symbol.greater(this, other) + def >[@specialized(Int, Float, Double) V](other: V): Symbol = Symbol.greater(this, other) + + def >=(other: Symbol): Symbol = Symbol.greaterEqual(this, other) + def >=[@specialized(Int, Float, Double) V](other: V): Symbol = Symbol.greaterEqual(this, other) + + def <(other: Symbol): Symbol = Symbol.lesser(this, other) + def <[@specialized(Int, Float, Double) V](other: V): Symbol = Symbol.lesser(this, other) + + def <=(other: Symbol): Symbol = Symbol.lesserEqual(this, other) + def <=[@specialized(Int, Float, Double) V](other: V): Symbol = Symbol.lesserEqual(this, other) + + def %(other: Symbol): Symbol = Symbol.createFromListedSymbols("_Mod")(Array(this, other)) + def %[@specialized(Int, Float, Double) V](other: V): Symbol = { + Symbol.createFromListedSymbols("_ModScalar")(Array(this), Map("scalar" -> other.toString)) + } + override def clone(): Symbol = { val clonedHandle = new SymbolHandleRef checkCall(_LIB.mxSymbolCopy(handle, clonedHandle)) @@ -312,6 +332,39 @@ class Symbol private(private[mxnet] val handle: SymbolHandle) { } } + /** + * Gets all attributes from the symbol. + * @return Map[String, String], mapping attribute keys to values. + */ + def listAttr(): Map[String, String] = { + val outSize = new MXUintRef + val out = ArrayBuffer[String]() + checkCall(_LIB.mxSymbolListAttrShallow(handle, outSize, out)) + (0 until outSize.value).map(i => out(i * 2) -> out(i * 2 + 1)).toMap + } + + /** + * Recursively gets all attributes from the symbol and its children. + * @return Map[Map[String, String]], There is a key in the returned + * dict for every child with non-empty attribute set. For each symbol, + * the name of the symbol is its key in the dict and the correspond value + * is that symbol's attribute list (itself a dictionary). + */ + def attrMap(): Map[String, Map[String, String]] = { + val outSize = new MXUintRef + val out = ArrayBuffer[String]() + checkCall(_LIB.mxSymbolListAttr(handle, outSize, out)) + val result = { + val tmp = out.toArray.grouped(2).map{ strs => + val nk = strs(0).split('$') + (nk(0), nk(1), strs(1)) + }.toArray + val grouped = tmp.groupBy(_._1) + grouped.map { case (name, kvs) => name -> kvs.map(x => (x._2, x._3)).toMap } + } + result + } + /** * Save symbol into file. * You can also use pickle to do the job if you only work on python. @@ -819,17 +872,134 @@ object Symbol { createFromListedSymbols("_MinimumScalar")(Array(right), Map("scalar" -> left.toString)) } + def equal(left: Symbol, right: Symbol): Symbol = { + createFromListedSymbols("_equal")(Array(left, right)) + } + + def equal[@specialized(Int, Float, Double) V](left: Symbol, right: V): Symbol = { + createFromListedSymbols("_equal_scalar")(Array(left), Map("scalar" -> right.toString)) + } + + def equal[@specialized(Int, Float, Double) V](left: V, right: Symbol): Symbol = { + createFromListedSymbols("_equal_scalar")(Array(right), Map("scalar" -> left.toString)) + } + + def notEqual(left: Symbol, right: Symbol): Symbol = { + createFromListedSymbols("_not_equal")(Array(left, right)) + } + + def notEqual[@specialized(Int, Float, Double) V](left: Symbol, right: V): Symbol = { + createFromListedSymbols("_not_equal_scalar")(Array(left), Map("scalar" -> right.toString)) + } + + def notEqual[@specialized(Int, Float, Double) V](left: V, right: Symbol): Symbol = { + createFromListedSymbols("_not_equal_scalar")(Array(right), Map("scalar" -> left.toString)) + } + + def greater(left: Symbol, right: Symbol): Symbol = { + createFromListedSymbols("_greater")(Array(left, right)) + } + + def greater[@specialized(Int, Float, Double) V](left: Symbol, right: V): Symbol = { + createFromListedSymbols("_greater_scalar")(Array(left), Map("scalar" -> right.toString)) + } + + def greaterEqual(left: Symbol, right: Symbol): Symbol = { + createFromListedSymbols("_greater_equal")(Array(left, right)) + } + + def greaterEqual[@specialized(Int, Float, Double) V](left: Symbol, right: V): Symbol = { + createFromListedSymbols("_greater_equal_scalar")(Array(left), Map("scalar" -> right.toString)) + } + + def lesser(left: Symbol, right: Symbol): Symbol = { + createFromListedSymbols("_lesser")(Array(left, right)) + } + + def lesser[@specialized(Int, Float, Double) V](left: Symbol, right: V): Symbol = { + createFromListedSymbols("_lesser_scalar")(Array(left), Map("scalar" -> right.toString)) + } + + def lesserEqual(left: Symbol, right: Symbol): Symbol = { + createFromListedSymbols("_lesser_equal")(Array(left, right)) + } + + def lesserEqual[@specialized(Int, Float, Double) V](left: Symbol, right: V): Symbol = { + createFromListedSymbols("_lesser_equal_scalar")(Array(left), Map("scalar" -> right.toString)) + } + + /** + * Returns a new symbol of given shape and type, filled with zeros. + */ + def zeros(shape: Shape, dType: DType = Base.MX_REAL_TYPE, ctx: Context = null): Symbol = { + val params = Map("shape" -> shape.toString, "dtype" -> dType.toString()) + val fParams = if (ctx == null) params else params ++ Map("ctx" -> ctx.toString) + createSymbolGeneral("_zeros", null, null, Array.empty[Symbol], fParams) + } + + /** + * Returns a new symbol of given shape and type, filled with ones. + */ + def ones(shape: Shape, dType: DType = Base.MX_REAL_TYPE, ctx: Context = null): Symbol = { + val params = Map("shape" -> shape.toString, "dtype" -> dType.toString()) + val fParams = if (ctx == null) params else params ++ Map("ctx" -> ctx.toString) + createSymbolGeneral("_ones", null, null, Array.empty[Symbol], fParams) + } + + /** + * Returns evenly spaced values within a given interval. + * @param start Start of interval. The default start value is 0. + * @param stop End of interval. + * @param step Spacing between values. The default step size is 1. + * @param repeat Number of times to repeat each element. The default repeat count is 1. + * @param dType The data type of the `NDArray`. The default datatype is `DType.Float32`. + * @return Symbol The created Symbol. + */ + def arange(start: Float, stop: Option[Float] = None, step: Float = 1.0f, + repeat: Int = 1, name: String = null, dType: DType = Base.MX_REAL_TYPE): Symbol = { + val params = Map("start" -> start, "step" -> step, + "repeat" -> repeat, "dtype" -> dType.toString()) + val fParams = if (stop == None) params else params ++ Map("stop" -> stop.get) + createSymbolGeneral("_arange", name, null, Array.empty[Symbol], fParams) + } + + // TODO(depeng) support setting initialization pattern /** * Create a symbolic variable with specified name. * @param name Name of the variable. * @param attr Additional attributes to set on the variable. - * @return The created variable symbol. + * @param shape + * The shape of a variable. If specified, this will be used during the shape inference. + * If one has specified a different shape for this variable using a keyword argument + * when calling shape inference, this shape information will be ignored. + * @param lrMult The learning rate multiplier for input variable. + * @param wdMult Weight decay multiplier for input variable. + * @param dType The dtype for input variable. If not specified, this value will be inferred. + * @param init Initializer for this variable to (optionally) override the default initializer. + * @param kwargs Additional attributes which must start and end with double underscores. + * @return A symbol corresponding to an input to the computation graph. */ - def Variable(name: String, attr: Map[String, String] = null): Symbol = { + def Variable(name: String, attr: Map[String, String] = null, shape: Shape = null, + lrMult: Option[Float] = None, wdMult: Option[Float] = None, dType: DType = null, + kwargs: Map[String, String] = Map.empty[String, String]): Symbol = { val handle = new SymbolHandleRef checkCall(_LIB.mxSymbolCreateVariable(name, handle)) val sym = new Symbol(handle.value) - sym.setAttr(AttrScope.current.get(Option(attr))) + val tmpAttr = scala.collection.mutable.Map[String, String]() + if (shape != null) tmpAttr += "__shape__" -> shape.toString + if (lrMult != None) tmpAttr += "__lr_mult__" -> lrMult.get.toString + if (wdMult != None) tmpAttr += "__wd_mult__" -> wdMult.get.toString + if (dType != null) tmpAttr += "__dtype__" -> dType.id.toString + for ((k, v) <- kwargs) { + require(k.startsWith("__") && k.endsWith("__"), + s"Attribute name=$k is not supported. " + + "Additional attributes must start and end with double underscores, e.g, __yourattr__") + tmpAttr += k -> v + } + if (attr != null) { + attr.foreach { case (k, v) => tmpAttr += k -> v } + } + sym.setAttr(AttrScope.current.get(Option(tmpAttr.toMap))) sym } @@ -1112,6 +1282,31 @@ class SymbolConversions[@specialized(Int, Float, Double) V](val value: V) { Symbol.createFromListedSymbols("_RDivScalar")( Array(other), Map("scalar" -> value.toString)) } + + def **(other: Symbol): Symbol = { + Symbol.pow(value, other) + } + + def >(other: Symbol): Symbol = { + other < value + } + + def >=(other: Symbol): Symbol = { + other <= value + } + + def <(other: Symbol): Symbol = { + other > value + } + + def <=(other: Symbol): Symbol = { + other >= value + } + + def %(other: Symbol): Symbol = { + Symbol.createFromListedSymbols("_RModScalar")( + Array(other), Map("scalar" -> value.toString)) + } } trait SymbolGenerator { diff --git a/scala-package/core/src/main/scala/ml/dmlc/mxnet/Visualization.scala b/scala-package/core/src/main/scala/ml/dmlc/mxnet/Visualization.scala index 5af6564a78d6..49c66a9f8aed 100644 --- a/scala-package/core/src/main/scala/ml/dmlc/mxnet/Visualization.scala +++ b/scala-package/core/src/main/scala/ml/dmlc/mxnet/Visualization.scala @@ -172,7 +172,7 @@ object Visualization { * @param shape Map of shapes, str -> shape, given input shapes * @param nodeAttrs Map of node's attributes * for example: - * nodeAttrs = Map("shape" -> "oval", "fixedsize" -> "fasle") + * nodeAttrs = Map("shape" -> "oval", "fixedsize" -> "false") * means to plot the network in "oval" * @param hideWeights * if true (default) then inputs with names like `*_weight` @@ -216,8 +216,9 @@ object Visualization { // Internal helper to figure out if node should be hidden with hide_weights def looksLikeWeight(name: String): Boolean = { - if (name.endsWith("_weight") || name.endsWith("_bias")) true - else false + if (name.endsWith("_weight") || name.endsWith("_bias") + || name.endsWith("_beta") || name.endsWith("_gamma") + || name.endsWith("_moving_var") || name.endsWith("_moving_mean")) { true } else { false } } // make nodes diff --git a/scala-package/core/src/main/scala/ml/dmlc/mxnet/module/BaseModule.scala b/scala-package/core/src/main/scala/ml/dmlc/mxnet/module/BaseModule.scala index c1cb91de56f5..0a73e1afcde1 100644 --- a/scala-package/core/src/main/scala/ml/dmlc/mxnet/module/BaseModule.scala +++ b/scala-package/core/src/main/scala/ml/dmlc/mxnet/module/BaseModule.scala @@ -121,6 +121,7 @@ abstract class BaseModule { private[module] var auxParams: Map[String, NDArray] = null // High Level API + def getSymbol: Symbol = this.symbol // A convenient function that calls both `forward` and `backward`. def forwardBackward(dataBatch: DataBatch): Unit = { @@ -259,7 +260,7 @@ abstract class BaseModule { /** * Get parameters, those are potentially copies of the the actual parameters used * to do computation on the device. - * @return `(arg_params, aux_params)`, a pair of dictionary of name to value mapping. + * @return `(argParams, auxParams)`, a pair of dictionary of name to value mapping. */ def getParams: (Map[String, NDArray], Map[String, NDArray]) @@ -267,41 +268,52 @@ abstract class BaseModule { * Initialize the parameters and auxiliary states. * @param initializer : Initializer * Called to initialize parameters if needed. - * arg_params : dict + * argParams : dict * If not None, should be a dictionary of existing arg_params. Initialization * will be copied from that. - * aux_params : dict + * auxParams : dict * If not None, should be a dictionary of existing aux_params. Initialization * will be copied from that. - * allow_missing : bool + * allowMissing : bool * If true, params could contain missing values, and the initializer will be * called to fill those missing params. - * force_init : bool + * forceInit : bool * If true, will force re-initialize even if already initialized. + * allowExtra : bool + * Whether allow extra parameters that are not needed by symbol. + * If this is True, no error will be thrown when argParams or auxParams + * contain extra parameters that is not needed by the executor. */ def initParams(initializer: Initializer = new Uniform(0.01f), argParams: Map[String, NDArray] = null, auxParams: Map[String, NDArray] = null, - allowMissing: Boolean = false, forceInit: Boolean = false): Unit + allowMissing: Boolean = false, + forceInit: Boolean = false, + allowExtra: Boolean = false): Unit /** * Assign parameter and aux state values. - * arg_params : dict + * argParams : dict * Dictionary of name to value (`NDArray`) mapping. - * aux_params : dict + * auxParams : dict * Dictionary of name to value (`NDArray`) mapping. - * allow_missing : bool + * allowMissing : bool * If true, params could contain missing values, and the initializer will be * called to fill those missing params. - * force_init : bool + * forceInit : bool * If true, will force re-initialize even if already initialized. + * allowExtra : bool + * Whether allow extra parameters that are not needed by symbol. + * If this is True, no error will be thrown when argParams or auxParams + * contain extra parameters that is not needed by the executor. */ def setParams(argParams: Map[String, NDArray], auxParams: Map[String, NDArray], allowMissing: Boolean = false, - forceInit: Boolean = true): Unit = { - initParams(initializer = null, argParams = argParams, auxParams = auxParams, - allowMissing = allowMissing, forceInit = forceInit) + forceInit: Boolean = true, + allowExtra: Boolean = false): Unit = { + initParams(initializer = null, argParams, auxParams, + allowMissing, forceInit, allowExtra) } /** diff --git a/scala-package/core/src/main/scala/ml/dmlc/mxnet/module/DataParallelExecutorGroup.scala b/scala-package/core/src/main/scala/ml/dmlc/mxnet/module/DataParallelExecutorGroup.scala index 2e724c6dc9ce..ea78962d00e8 100644 --- a/scala-package/core/src/main/scala/ml/dmlc/mxnet/module/DataParallelExecutorGroup.scala +++ b/scala-package/core/src/main/scala/ml/dmlc/mxnet/module/DataParallelExecutorGroup.scala @@ -297,6 +297,7 @@ class DataParallelExecutorGroup private[module]( private var batchSize: Int = -1 private var slices: Array[(Int, Int)] = null + private var _defaultExecs: Array[Executor] = null private var execs: Array[Executor] = null private var dataArrays: Seq[Array[((Int, Int), NDArray)]] = null private var labelArrays: Option[Seq[Array[((Int, Int), NDArray)]]] = None @@ -305,8 +306,8 @@ class DataParallelExecutorGroup private[module]( private[module] var auxArrays: IndexedSeq[Array[NDArray]] = null private var inputGradArrays: IndexedSeq[Array[NDArray]] = null - private val dataLayouts = decideSlices(dataShapes) - private val labelLayouts = + private var dataLayouts = decideSlices(dataShapes) + private var labelLayouts = // call it to make sure labels has the same batch size as data if (labelShapes != None) decideSlices(labelShapes.get) else null @@ -349,12 +350,30 @@ class DataParallelExecutorGroup private[module]( * @param dataShapes DataDesc for input data. * @param labelShapes DataDesc for input labels. * @param sharedGroup + * @param reshape */ def bindExec(dataShapes: Seq[DataDesc], labelShapes: Option[Seq[DataDesc]], - sharedGroup: Option[DataParallelExecutorGroup]): Unit = { - execs = (0 until contexts.length).map(i => - bindIthExec(i, dataShapes, labelShapes, sharedGroup) - ).toArray + sharedGroup: Option[DataParallelExecutorGroup], reshape: Boolean = false): Unit = { + this.batchSize = -1 + dataLayouts = decideSlices(dataShapes) + labelLayouts = { + // call it to make sure labels has the same batch size as data + if (labelShapes != None) decideSlices(labelShapes.get) + else null + } + if (reshape) { + (0 until contexts.length).foreach { i => + val dataShapesSliced = slicedShape(dataShapes, i, dataLayouts) + val labelShapesSliced = labelShapes.map(slicedShape(_, i, labelLayouts)) + val inputShapes + = dataShapesSliced.toMap ++ labelShapesSliced.getOrElse(Map.empty[String, Shape]) + execs(i) = _defaultExecs(i).reshape(allowUpSizing = true, kwargs = inputShapes) + } + } else { + execs = (0 until contexts.length).map(i => + bindIthExec(i, dataShapes, labelShapes, sharedGroup) + ).toArray + } // convenient data structures dataArrays = dataShapes.map(dataDesc => @@ -399,13 +418,31 @@ class DataParallelExecutorGroup private[module]( auxArrays = (0 until auxNames.length).map(i => execs.map(_.auxArrays(i))) } + /** + * Reshape executors. + * @param dataShapes + * @param labelShapes + */ + def reshape(dataShapes: Seq[DataDesc], labelShapes: Option[Seq[DataDesc]]): Unit = { + if (!(dataShapes == this.dataShapes && labelShapes == this.labelShapes)) { + if (this._defaultExecs == null) { + this._defaultExecs = this.execs.map(x => x) + } + this.bindExec(dataShapes, labelShapes, None, reshape = true) + } + } + /** * Assign, i.e. copy parameters to all the executors. * @param argParams A dictionary of name to `NDArray` parameter mapping. * @param auxParams A dictionary of name to `NDArray` auxiliary variable mapping. + * @param allowExtra hether allow extra parameters that are not needed by symbol. + * If this is True, no error will be thrown when argParams or auxParams + * contain extra parameters that is not needed by the executor. */ - def setParams(argParams: Map[String, NDArray], auxParams: Map[String, NDArray]): Unit = { - execs.foreach(_.copyParamsFrom(argParams, auxParams)) + def setParams(argParams: Map[String, NDArray], auxParams: Map[String, NDArray], + allowExtra: Boolean = false): Unit = { + execs.foreach(_.copyParamsFrom(argParams, auxParams, allowExtraParams = allowExtra)) } /** diff --git a/scala-package/core/src/main/scala/ml/dmlc/mxnet/module/Module.scala b/scala-package/core/src/main/scala/ml/dmlc/mxnet/module/Module.scala index f0b8da0ecfcb..b9cc07826504 100644 --- a/scala-package/core/src/main/scala/ml/dmlc/mxnet/module/Module.scala +++ b/scala-package/core/src/main/scala/ml/dmlc/mxnet/module/Module.scala @@ -107,11 +107,16 @@ class Module(symbolVar: Symbol, * @param allowMissing If true, params could contain missing values, * and the initializer will be called to fill those missing params. * @param forceInit If true, will force re-initialize even if already initialized. + * @param allowExtra Whether allow extra parameters that are not needed by symbol. + * If this is True, no error will be thrown when argParams or auxParams + * contain extra parameters that is not needed by the executor. */ override def initParams(initializer: Initializer = new Uniform(0.01f), argParams: Map[String, NDArray] = null, auxParams: Map[String, NDArray] = null, - allowMissing: Boolean = false, forceInit: Boolean = false): Unit = { + allowMissing: Boolean = false, + forceInit: Boolean = false, + allowExtra: Boolean = false): Unit = { if (paramsInitialized && !forceInit) { return } @@ -141,7 +146,7 @@ class Module(symbolVar: Symbol, this.paramsDirty = false // copy the initialized parameters to devices - this.execGroup.setParams(this.argParams, this.auxParams) + this.execGroup.setParams(this.argParams, this.auxParams, allowExtra = allowExtra) } // Internal helper for parameter initialization @@ -261,6 +266,46 @@ class Module(symbolVar: Symbol, } } + /** + * Check that input names matches input data descriptors. + */ + @throws(classOf[IllegalArgumentException]) + private def _checkNamesMatch(dataNames: IndexedSeq[String], dataShapes: IndexedSeq[DataDesc], + name: String, throwEx: Boolean): Unit = { + val actual = dataShapes.map(_.name) + if (dataNames.sorted != actual.sorted) { + val msg = s"Data provided by ${name}_shapes don't match names specified by " + + s"${name}_names (${dataShapes.mkString(", ")} vs. ${dataNames.mkString(", ")})" + if (throwEx) throw new IllegalArgumentException(msg) + else logger.warn(msg) + } + } + + /** + * parse data_attrs into DataDesc format and check that names match + */ + @throws(classOf[IllegalArgumentException]) + private def _parseDataDesc(dataNames: IndexedSeq[String], labelNames: IndexedSeq[String], + dataShapes: IndexedSeq[DataDesc], labelShapes: Option[IndexedSeq[DataDesc]]): + (IndexedSeq[DataDesc], Option[IndexedSeq[DataDesc]]) = { + _checkNamesMatch(dataNames, dataShapes, "data", true) + if (labelShapes != None) _checkNamesMatch(labelNames, labelShapes.get, "label", false) + (dataShapes, labelShapes) + } + + /** + * Reshapes the module for new input shapes. + * @param dataShapes Typically is `dataIter.provideData`. + * @param labelShapes Typically is `dataIter.provideLabel`. + */ + def reshape(dataShapes: IndexedSeq[DataDesc], + labelShapes: Option[IndexedSeq[DataDesc]] = None): Unit = { + require(this.binded) + val (tdataShapes, tlabelShapes) = this._parseDataDesc( + this.dataNames, this.labelNames, dataShapes, labelShapes) + this.execGroup.reshape(tdataShapes, tlabelShapes) + } + /** * Install and initialize optimizers. * @param kvstore @@ -344,6 +389,26 @@ class Module(symbolVar: Symbol, */ def forward(dataBatch: DataBatch, isTrain: Option[Boolean] = None): Unit = { require(binded && paramsInitialized) + val currDataShapes = this.dataShapes.map(_.shape) + val newDataShapes = dataBatch.data.map(_.shape) + if (currDataShapes != newDataShapes) { + val newDShapes: IndexedSeq[DataDesc] = + if (dataBatch.provideData != null) dataBatch.provideData + else { + this.dataShapes.zip(newDataShapes).map { case (i, shape) => + DataDesc(i.name, shape, i.dtype, i.layout) + } + } + val newLShapes: Option[IndexedSeq[DataDesc]] = + if (dataBatch.provideLabel != null) Some(dataBatch.provideLabel) + else if (dataBatch.label != null && dataBatch.label.length > 0 + && this.labelShapes != null) { + Some(this.labelShapes.zip(dataBatch.label).map { case (i, j) => + DataDesc(i.name, j.shape, i.dtype, i.layout) + }) + } else None + this.reshape(newDShapes, newLShapes) + } execGroup.forward(dataBatch, isTrain) } @@ -365,11 +430,11 @@ class Module(symbolVar: Symbol, paramsDirty = true if (updateOnKVStore) { Model.updateParamsOnKVStore(execGroup.paramArrays, - execGroup.gradArrays, kvstore) + execGroup.gradArrays, kvstore, execGroup.paramNames) } else { require(updater != None) Model.updateParams(execGroup.paramArrays, - execGroup.gradArrays, updater.orNull, contexts.length, kvstore) + execGroup.gradArrays, updater.orNull, contexts.length, execGroup.paramNames, kvstore) } } diff --git a/scala-package/core/src/main/scala/ml/dmlc/mxnet/module/SequentialModule.scala b/scala-package/core/src/main/scala/ml/dmlc/mxnet/module/SequentialModule.scala index dfa63ebac629..a77041de5b0a 100644 --- a/scala-package/core/src/main/scala/ml/dmlc/mxnet/module/SequentialModule.scala +++ b/scala-package/core/src/main/scala/ml/dmlc/mxnet/module/SequentialModule.scala @@ -144,11 +144,16 @@ class SequentialModule extends BaseModule { * @param allowMissing If true, params could contain missing values, * and the initializer will be called to fill those missing params. * @param forceInit If true, will force re-initialize even if already initialized. + * @param allowExtra Whether allow extra parameters that are not needed by symbol. + * If this is True, no error will be thrown when argParams or auxParams + * contain extra parameters that is not needed by the executor. */ override def initParams(initializer: Initializer = new Uniform(0.01f), argParams: Map[String, NDArray] = null, auxParams: Map[String, NDArray] = null, - allowMissing: Boolean = false, forceInit: Boolean = false): Unit = { + allowMissing: Boolean = false, + forceInit: Boolean = false, + allowExtra: Boolean = false): Unit = { if (this.paramsInitialized && !forceInit) { return } @@ -156,7 +161,8 @@ class SequentialModule extends BaseModule { for (module <- this.modules) { module.initParams(initializer = initializer, argParams = argParams, - auxParams = auxParams, allowMissing = allowMissing, forceInit = forceInit) + auxParams = auxParams, allowMissing = allowMissing, + forceInit = forceInit, allowExtra = allowExtra) } // Internal function to help checking duplicated names, diff --git a/scala-package/core/src/main/scala/ml/dmlc/mxnet/optimizer/AdaGrad.scala b/scala-package/core/src/main/scala/ml/dmlc/mxnet/optimizer/AdaGrad.scala index 759b9468f7d8..c13fe2ab1dba 100644 --- a/scala-package/core/src/main/scala/ml/dmlc/mxnet/optimizer/AdaGrad.scala +++ b/scala-package/core/src/main/scala/ml/dmlc/mxnet/optimizer/AdaGrad.scala @@ -42,7 +42,7 @@ class AdaGrad(val learningRate: Float = 0.05f, rescaleGradient: Float = 1.0f, * The auxiliary state used in optimization. */ override def update(index: Int, weight: NDArray, grad: NDArray, state: AnyRef): Unit = { - val lr = this.learningRate + val lr = getLr(index, this.learningRate) val resdGrad = rescaleGradient * grad val history = state.asInstanceOf[NDArray] diff --git a/scala-package/core/src/main/scala/ml/dmlc/mxnet/optimizer/Adam.scala b/scala-package/core/src/main/scala/ml/dmlc/mxnet/optimizer/Adam.scala index 10f90ae1e2ff..f611192c0905 100644 --- a/scala-package/core/src/main/scala/ml/dmlc/mxnet/optimizer/Adam.scala +++ b/scala-package/core/src/main/scala/ml/dmlc/mxnet/optimizer/Adam.scala @@ -57,14 +57,15 @@ class Adam(val learningRate: Float = 0.002f, beta1: Float = 0.9f, beta2: Float = * The auxiliary state used in optimization. */ override def update(index: Int, weight: NDArray, grad: NDArray, state: AnyRef): Unit = { - val lr = + var lr = (if (lrScheduler != null) { val scheduledLr = lrScheduler(numUpdate) updateCount(index) scheduledLr } else { this.learningRate - }) * lrScale.getOrElse(index, 1f) + }) + lr = getLr(index, lr) val (mean, variance) = state.asInstanceOf[(NDArray, NDArray)] diff --git a/scala-package/core/src/main/scala/ml/dmlc/mxnet/optimizer/DCASGD.scala b/scala-package/core/src/main/scala/ml/dmlc/mxnet/optimizer/DCASGD.scala index 763c0346482f..5af4caa2e634 100644 --- a/scala-package/core/src/main/scala/ml/dmlc/mxnet/optimizer/DCASGD.scala +++ b/scala-package/core/src/main/scala/ml/dmlc/mxnet/optimizer/DCASGD.scala @@ -43,14 +43,15 @@ class DCASGD(val learningRate: Float = 0.01f, momentum: Float = 0.0f, * The auxiliary state used in optimization. */ override def update(index: Int, weight: NDArray, grad: NDArray, state: AnyRef): Unit = { - val lr = + var lr = (if (lrScheduler != null) { val scheduledLr = lrScheduler(numUpdate) updateCount(index) scheduledLr } else { this.learningRate - }) * lrScale.getOrElse(index, 1f) + }) + lr = getLr(index, lr) val wd = getWd(index, this.wd) var resdGrad = grad * this.rescaleGrad diff --git a/scala-package/core/src/main/scala/ml/dmlc/mxnet/optimizer/NAG.scala b/scala-package/core/src/main/scala/ml/dmlc/mxnet/optimizer/NAG.scala index f1ee4cba637a..2b2ce5f461d5 100644 --- a/scala-package/core/src/main/scala/ml/dmlc/mxnet/optimizer/NAG.scala +++ b/scala-package/core/src/main/scala/ml/dmlc/mxnet/optimizer/NAG.scala @@ -49,14 +49,15 @@ class NAG(val learningRate: Float = 0.01f, momentum: Float = 0.0f, */ override def update(index: Int, weight: NDArray, grad: NDArray, state: AnyRef): Unit = { // TODO(bing) implement wd_bias, wd_gamma, wd_beta (copy from python package) - val lr = + var lr = (if (lrScheduler != null) { val scheduledLr = lrScheduler(numUpdate) updateCount(index) scheduledLr } else { this.learningRate - }) * lrScale.getOrElse(index, 1f) + }) + lr = getLr(index, lr) val wd = getWd(index, this.wd) var resdGrad = grad * this.rescaleGrad diff --git a/scala-package/core/src/main/scala/ml/dmlc/mxnet/optimizer/RMSProp.scala b/scala-package/core/src/main/scala/ml/dmlc/mxnet/optimizer/RMSProp.scala index a001eb05f496..b1b6e4004126 100644 --- a/scala-package/core/src/main/scala/ml/dmlc/mxnet/optimizer/RMSProp.scala +++ b/scala-package/core/src/main/scala/ml/dmlc/mxnet/optimizer/RMSProp.scala @@ -46,7 +46,7 @@ class RMSProp(val learningRate: Float = 0.002f, rescaleGradient: Float = 1.0f, * The auxiliary state used in optimization. */ override def update(index: Int, weight: NDArray, grad: NDArray, state: AnyRef): Unit = { - val lr = this.learningRate * lrScale.getOrElse(index, 1f) + val lr = getLr(index, this.learningRate) val (n, g, delta) = state.asInstanceOf[(NDArray, NDArray, NDArray)] val wd = getWd(index, this.wd) diff --git a/scala-package/core/src/main/scala/ml/dmlc/mxnet/optimizer/SGD.scala b/scala-package/core/src/main/scala/ml/dmlc/mxnet/optimizer/SGD.scala index e77d519ca29d..d3099d53f063 100644 --- a/scala-package/core/src/main/scala/ml/dmlc/mxnet/optimizer/SGD.scala +++ b/scala-package/core/src/main/scala/ml/dmlc/mxnet/optimizer/SGD.scala @@ -41,14 +41,15 @@ class SGD(val learningRate: Float = 0.01f, momentum: Float = 0.0f, */ override def update(index: Int, weight: NDArray, grad: NDArray, state: AnyRef): Unit = { // TODO(bing) implement wd_bias, wd_gamma, wd_beta (copy from python package) - val lr = + var lr = (if (lrScheduler != null) { val scheduledLr = lrScheduler(numUpdate) updateCount(index) scheduledLr } else { this.learningRate - }) * lrScale.getOrElse(index, 1f) + }) + lr = getLr(index, lr) val wd = getWd(index, this.wd) var resdGrad = grad * this.rescaleGrad diff --git a/scala-package/core/src/main/scala/ml/dmlc/mxnet/optimizer/SGLD.scala b/scala-package/core/src/main/scala/ml/dmlc/mxnet/optimizer/SGLD.scala index 8a1d8dcecd7c..cb509f4a062f 100644 --- a/scala-package/core/src/main/scala/ml/dmlc/mxnet/optimizer/SGLD.scala +++ b/scala-package/core/src/main/scala/ml/dmlc/mxnet/optimizer/SGLD.scala @@ -47,14 +47,15 @@ class SGLD(val learningRate: Float = 0.01f, rescaleGradient: Float = 1.0f, * The auxiliary state used in optimization. */ override def update(index: Int, weight: NDArray, grad: NDArray, state: AnyRef): Unit = { - val lr = + var lr = (if (lrScheduler != null) { val scheduledLr = lrScheduler(numUpdate) updateCount(index) scheduledLr } else { this.learningRate - }) * lrScale.getOrElse(index, 1f) + }) + lr = getLr(index, lr) val wd = getWd(index, this.wd) var resdGrad = grad * this.rescaleGrad diff --git a/scala-package/core/src/test/scala/ml/dmlc/mxnet/KVStoreSuite.scala b/scala-package/core/src/test/scala/ml/dmlc/mxnet/KVStoreSuite.scala index f024e8d16e41..8df6d18e2a33 100644 --- a/scala-package/core/src/test/scala/ml/dmlc/mxnet/KVStoreSuite.scala +++ b/scala-package/core/src/test/scala/ml/dmlc/mxnet/KVStoreSuite.scala @@ -25,8 +25,8 @@ class KVStoreSuite extends FunSuite with BeforeAndAfterAll { val shape = Shape(2, 1) val ndArray = NDArray.zeros(shape) - kv.init(3, NDArray.ones(shape)) - kv.pull(3, ndArray) + kv.init("3", NDArray.ones(shape)) + kv.pull("3", ndArray) assert(ndArray.toArray === Array(1f, 1f)) } @@ -35,12 +35,34 @@ class KVStoreSuite extends FunSuite with BeforeAndAfterAll { val shape = Shape(2, 1) val ndArray = NDArray.zeros(shape) - kv.init(3, NDArray.ones(shape)) - kv.push(3, NDArray.ones(shape) * 4) - kv.pull(3, ndArray) + kv.init("3", NDArray.ones(shape)) + kv.push("3", NDArray.ones(shape) * 4) + kv.pull("3", ndArray) assert(ndArray.toArray === Array(4f, 4f)) } + test("test aggregate") { + val shape = Shape(4, 4) + val keys = Array("b", "c", "d") + val kv = KVStore.create() + kv.init("a", NDArray.zeros(shape)) + kv.init(keys, Array.fill(keys.length)(NDArray.zeros(shape))) + val numDevs = 4 + val devs = (0 until numDevs).map(Context.cpu(_)) + val vals = devs.map(d => NDArray.ones(shape, d)).toArray + kv.push("a", vals) + kv.pull("a", outs = vals) + assert(vals.map(v => v.toArray.map(x => x - numDevs).sum).sum == 0f) + + val valss = keys.map { k => + val tmpVals = devs.map(d => NDArray.ones(shape, d) * 2f).toArray + kv.push(k, tmpVals) + kv.pull(k, outs = tmpVals) + tmpVals + }.flatten + assert(valss.map(v => v.toArray.map(x => x - numDevs * 2f).sum).sum == 0f) + } + test("updater runs when push") { val kv = KVStore.create() val updater = new MXKVStoreUpdater { @@ -57,12 +79,12 @@ class KVStoreSuite extends FunSuite with BeforeAndAfterAll { val shape = Shape(2, 1) val ndArray = NDArray.zeros(shape) - kv.init(3, NDArray.ones(shape) * 4) - kv.pull(3, ndArray) + kv.init("3", NDArray.ones(shape) * 4) + kv.pull("3", ndArray) assert(ndArray.toArray === Array(4f, 4f)) - kv.push(3, NDArray.ones(shape)) - kv.pull(3, ndArray) + kv.push("3", NDArray.ones(shape)) + kv.pull("3", ndArray) assert(ndArray.toArray === Array(6f, 6f)) } diff --git a/scala-package/core/src/test/scala/ml/dmlc/mxnet/ModelParallelSuite.scala b/scala-package/core/src/test/scala/ml/dmlc/mxnet/ModelParallelSuite.scala index 6fb30731fdd5..e95ab09b5bd2 100644 --- a/scala-package/core/src/test/scala/ml/dmlc/mxnet/ModelParallelSuite.scala +++ b/scala-package/core/src/test/scala/ml/dmlc/mxnet/ModelParallelSuite.scala @@ -23,8 +23,11 @@ import org.scalatest.{BeforeAndAfterAll, FunSuite} class ModelParallelSuite extends FunSuite with BeforeAndAfterAll { test("chain") { val n = 2 + val ctx1 = Context.cpu(0) + val ctx2 = Context.cpu(1) val data1 = Symbol.Variable("data1") val data2 = Symbol.Variable("data2") + val data3 = Symbol.Variable("data3") var net: Symbol = null new AttrScope(Map("ctx_group" -> "dev1")).withScope { @@ -32,31 +35,28 @@ class ModelParallelSuite extends FunSuite with BeforeAndAfterAll { } new AttrScope(Map("ctx_group" -> "dev2")).withScope { - net = net + data1 + net = net + data3 } val shape = Shape(4, 5) - val (arr, arrGrad) = - new Context(Context.cpu(0)).withScope { - val arr = (0 until n).map(_ => NDArray.empty(shape)) - val arrGrad = (0 until n).map(_ => NDArray.empty(shape)) - (arr, arrGrad) - } + val arr = (0 until n + 1).map(_ => NDArray.empty(shape, ctx1)) + val arrGrad = (0 until n).map(_ => NDArray.empty(shape, ctx1)) :+ NDArray.empty(shape, ctx2) - val exec1 = net.bind(Context.cpu(), + val exec1 = net.bind(ctx1, args = arr, argsGrad = arrGrad, gradReq = "write", auxStates = Nil, - group2ctx = Map("dev1" -> Context.cpu(0), "dev2" -> Context.cpu(1)), + group2ctx = Map("dev1" -> ctx1, "dev2" -> ctx2), sharedExec = null) arr(0).set(1f) arr(1).set(2f) + arr(2).set(3f) - val arr2 = arr.map(_.copyTo(Context.cpu())) - val arrGrad2 = arrGrad.map(_.copyTo(Context.cpu())) - val exec2 = net.bind(Context.cpu(), args = arr2, argsGrad = arrGrad2) + val arr2 = arr.map(_.copyTo(ctx1)) + val arrGrad2 = arrGrad.map(_.copyTo(ctx1)) + val exec2 = net.bind(ctx1, args = arr2, argsGrad = arrGrad2) // Show the execution plan that involves copynode // scalastyle:off println @@ -65,14 +65,14 @@ class ModelParallelSuite extends FunSuite with BeforeAndAfterAll { exec1.forward() exec2.forward() - assert(reldiff(exec1.outputs(0).copyTo(Context.cpu()), - exec2.outputs(0).copyTo(Context.cpu())) < 1e-6f) + assert(reldiff(exec1.outputs(0).copyTo(ctx1), + exec2.outputs(0).copyTo(ctx1)) < 1e-6f) - val outGrad = NDArray.ones(shape, Context.cpu(1)) + val outGrad = NDArray.ones(shape, ctx2) exec1.backward(Array(outGrad)) - exec2.backward(Array(outGrad.copyTo(Context.cpu()))) + exec2.backward(Array(outGrad.copyTo(ctx1))) (arrGrad zip arrGrad2) foreach { case (a, b) => - assert(reldiff(a, b) < 1e-6f) + assert(reldiff(a.copyTo(ctx1), b) < 1e-6f) } } } diff --git a/scala-package/core/src/test/scala/ml/dmlc/mxnet/ModuleSuite.scala b/scala-package/core/src/test/scala/ml/dmlc/mxnet/ModuleSuite.scala new file mode 100644 index 000000000000..ab48ef7d1928 --- /dev/null +++ b/scala-package/core/src/test/scala/ml/dmlc/mxnet/ModuleSuite.scala @@ -0,0 +1,368 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +package ml.dmlc.mxnet + +import org.scalatest.{BeforeAndAfterAll, FunSuite} +import ml.dmlc.mxnet.CheckUtils._ +import ml.dmlc.mxnet.module._ +import ml.dmlc.mxnet.optimizer._ +import ml.dmlc.mxnet.io._ + +class ModuleSuite extends FunSuite with BeforeAndAfterAll { + test ("model dtype") { + val dType = DType.Float16 + val dShape = Shape(3, 8, 7) + + var sym = Symbol.Variable("data") + sym = Symbol.Activation(attr = Map("__layout__" -> "TNC"))()( + Map("data" -> sym, "act_type" -> "relu")) + + val mod = new Module(sym, IndexedSeq("data"), null, + contexts = Array(Context.cpu(0), Context.cpu(1))) + mod.bind(dataShapes = IndexedSeq(DataDesc("data", dShape, dType, "TNC"))) + mod.initParams() + mod.forward(new DataBatch( + data = IndexedSeq(NDArray.ones(dShape, dtype = dType)), + label = null, index = null, pad = 0)) + mod.backward(Array(NDArray.ones(dShape, dtype = dType))) + + assert(mod.getOutputs.flatten.forall(_.dtype == dType)) + } + + test ("module input_grads") { + val a = Symbol.Variable("a", kwargs = Map("__layout__" -> "NC")) + val b = Symbol.Variable("b", kwargs = Map("__layout__" -> "NC")) + var c = Symbol.Variable("c", kwargs = Map("__layout__" -> "NC")) + + import SymbolConversions._ + c = a + 2 * b + 3 * c + + val mod = new Module(c, IndexedSeq("b", "c", "a"), null, + contexts = Array(Context.cpu(0), Context.cpu(1))) + mod.bind(dataShapes = IndexedSeq( + DataDesc("b", Shape(5, 5)), + DataDesc("c", Shape(5, 5)), + DataDesc("a", Shape(5, 5))), + inputsNeedGrad = true + ) + mod.initParams() + mod.forward(new DataBatch( + data = IndexedSeq( + NDArray.ones(5, 5), NDArray.ones(5, 5), NDArray.ones(5, 5)), + label = null, index = null, pad = 0)) + mod.backward(Array(NDArray.ones(5, 5))) + + val inputGrads = mod.getInputGradsMerged() + val aGrad = inputGrads(0).toArray + val bGrad = inputGrads(1).toArray + val cGrad = inputGrads(2).toArray + + assert(aGrad.forall(_ == 1f)) + assert(bGrad.forall(_ == 2f)) + assert(cGrad.forall(_ == 3f)) + } + + test ("module layout") { + var sym = Symbol.Variable("data") + sym = Symbol.Activation(attr = Map("__layout__" -> "TNC"))()( + Map("data" -> sym, "act_type" -> "relu")) + + val dShape = Shape(3, 8, 7) + val mod = new Module(sym, IndexedSeq("data"), null, + contexts = Array(Context.cpu(0), Context.cpu(1))) + mod.bind(dataShapes = IndexedSeq(DataDesc("data", dShape, layout = "TNC"))) + mod.initParams() + mod.forward(new DataBatch( + data = IndexedSeq(NDArray.ones(dShape)), + label = null, index = null, pad = 0)) + mod.backward(Array(NDArray.ones(dShape))) + assert(mod.getOutputsMerged()(0).shape == dShape) + + val hdShape = Shape(3, 4, 7) + for (x <- mod.getOutputs) assert(x(0).shape == hdShape) + } + + test ("save load") { + def mapEqu(a: Map[String, NDArray], b: Map[String, NDArray]): Unit = { + assert(a.toSet == b.toSet) + for (k <- a.keys) assert(a(k) == b(k)) + } + + var sym = Symbol.Variable("data") + sym = Symbol.FullyConnected()()(Map("data" -> sym, "num_hidden" -> 100)) + + // single device + var mod = new Module(sym, IndexedSeq("data"), null) + mod.bind(dataShapes = IndexedSeq(DataDesc("data", Shape(10, 10)))) + mod.initParams() + mod.initOptimizer(optimizer = new SGD(learningRate = 0.1f, momentum = 0.9f)) + mod.update() + mod.saveCheckpoint("test", 0, saveOptStates = true) + + var mod2 = Module.loadCheckpoint("test", 0, loadOptimizerStates = true) + mod2.bind(dataShapes = IndexedSeq(DataDesc("data", Shape(10, 10)))) + mod2.initOptimizer(optimizer = new SGD(learningRate = 0.1f, momentum = 0.9f)) + assert(mod.getSymbol.toJson == mod2.getSymbol.toJson) + mapEqu(mod.getParams._1, mod2.getParams._1) + + // multi device + mod = new Module(sym, IndexedSeq("data"), null, + contexts = Array(Context.cpu(0), Context.cpu(1))) + mod.bind(dataShapes = IndexedSeq(DataDesc("data", Shape(10, 10)))) + mod.initParams() + mod.initOptimizer(optimizer = new SGD(learningRate = 0.1f, momentum = 0.9f)) + mod.update() + mod.saveCheckpoint("test", 0, saveOptStates = true) + + mod2 = Module.loadCheckpoint("test", 0, loadOptimizerStates = true) + mod2.bind(dataShapes = IndexedSeq(DataDesc("data", Shape(10, 10)))) + mod2.initOptimizer(optimizer = new SGD(learningRate = 0.1f, momentum = 0.9f)) + assert(mod.getSymbol.toJson == mod2.getSymbol.toJson) + mapEqu(mod.getParams._1, mod2.getParams._1) + } + + test ("module reshape") { + var sym = Symbol.Variable("data") + sym = Symbol.FullyConnected("fc")()(Map("data" -> sym, "num_hidden" -> 20)) + + var dShape = Shape(7, 20) + val mod = new Module(sym, IndexedSeq("data"), null, + contexts = Array(Context.cpu(0), Context.cpu(1))) + mod.bind(dataShapes = IndexedSeq(DataDesc("data", dShape))) + mod.initParams() + mod.initOptimizer(optimizer = new SGD(learningRate = 1f)) + + mod.forward(new DataBatch( + data = IndexedSeq(NDArray.ones(dShape)), + label = null, index = null, pad = 0)) + mod.backward(Array(NDArray.ones(dShape))) + mod.update() + assert(mod.getOutputsMerged()(0).shape == dShape) + assert(mod.getParams._1("fc_bias").toArray.forall(_ == -1f)) + + dShape = Shape(14, 20) + mod.reshape(IndexedSeq(DataDesc("data", dShape))) + mod.forward(new DataBatch( + data = IndexedSeq(NDArray.ones(dShape)), + label = null, index = null, pad = 0)) + mod.backward(Array(NDArray.ones(dShape))) + mod.update() + assert(mod.getOutputsMerged()(0).shape == dShape) + assert(mod.getParams._1("fc_bias").toArray.forall(x => (x - -3f) < 1e-3)) + } + + test ("module setParams") { + val data = NDArray.array(Array(0.05f, 0.1f), Shape(1, 2)) + val label = NDArray.array(Array(0.01f, 0.99f), Shape(1, 2)) + val trainData = new NDArrayIter( + IndexedSeq(data), IndexedSeq(label), labelName = "softmax_label") + + // symbols + var x = Symbol.Variable("data") + x = Symbol.FullyConnected(name = "fc_0")()(Map("data" -> x, "num_hidden" -> 2)) + x = Symbol.Activation(name = "act_0")()(Map("data" -> x, "act_type" -> "sigmoid")) + x = Symbol.FullyConnected(name = "fc_1")()(Map("data" -> x, "num_hidden" -> 2)) + x = Symbol.Activation(name = "act_1")()(Map("data" -> x, "act_type" -> "sigmoid")) + x = Symbol.LinearRegressionOutput(name = "softmax")()(Map("data" -> x, "grad_scale" -> 2)) + + // create module + val mod = new Module(x, contexts = Array(Context.cpu())) + mod.bind(dataShapes = trainData.provideData, + Option(trainData.provideLabel)) + val argParamsCorrect = Map( + "fc_0_weight" -> NDArray.array(Array(0.15f, 0.2f, 0.25f, 0.3f), Shape(2, 2)), + "fc_0_bias" -> NDArray.array(Array(0.35f, 0.35f), Shape(2)), + "fc_1_weight" -> NDArray.array(Array(0.4f, 0.45f, 0.5f, 0.55f), Shape(2, 2)), + "fc_1_bias" -> NDArray.array(Array(0.6f, 0.6f), Shape(2)) + ) + val argParamsMissing = Map( + "fc_0_weight" -> NDArray.array(Array(0.15f, 0.2f, 0.25f, 0.3f), Shape(2, 2)), + "fc_0_bias" -> NDArray.array(Array(0.35f, 0.35f), Shape(2)), + "fc_1_weight" -> NDArray.array(Array(0.4f, 0.45f, 0.5f, 0.55f), Shape(2, 2)) + ) + val argParamsExtra = Map( + "fc_0_weight" -> NDArray.array(Array(0.15f, 0.2f, 0.25f, 0.3f), Shape(2, 2)), + "fc_0_bias" -> NDArray.array(Array(0.35f, 0.35f), Shape(2)), + "fc_1_weight" -> NDArray.array(Array(0.4f, 0.45f, 0.5f, 0.55f), Shape(2, 2)), + "fc_1_bias" -> NDArray.array(Array(0.6f, 0.6f), Shape(2)), + "fc_2_weight" -> NDArray.array(Array(0.6f, 0.6f), Shape(2)) + ) + + mod.setParams(forceInit = true, argParams = argParamsCorrect, + auxParams = null) + + // test allow missing + mod.setParams(forceInit = true, argParams = argParamsMissing, + auxParams = null, allowMissing = true) + + // test allow extra + mod.setParams(forceInit = true, argParams = argParamsExtra, auxParams = null, + allowMissing = true, allowExtra = true) + } + + test ("monitor") { + // data iter + val data = NDArray.array(Array(0.05f, 0.1f), Shape(1, 2)) + val label = NDArray.array(Array(0.01f, 0.99f), Shape(1, 2)) + val trainData = new NDArrayIter( + IndexedSeq(data), IndexedSeq(label), labelName = "softmax_label") + + // symbols + var x = Symbol.Variable("data") + x = Symbol.FullyConnected(name = "fc_0")()(Map("data" -> x, "num_hidden" -> 2)) + x = Symbol.Activation(name = "act_0")()(Map("data" -> x, "act_type" -> "sigmoid")) + x = Symbol.FullyConnected(name = "fc_1")()(Map("data" -> x, "num_hidden" -> 2)) + x = Symbol.Activation(name = "act_1")()(Map("data" -> x, "act_type" -> "sigmoid")) + x = Symbol.LinearRegressionOutput(name = "softmax")()(Map("data" -> x, "grad_scale" -> 2)) + + // create monitor + def meanAbs(x: NDArray): NDArray = { + val sumAbs = NDArray.sum(NDArray.abs(x)) + sumAbs / x.shape.product + } + val mon = new Monitor(1, statFunc = meanAbs) + + // create module + val mod = new Module(x, contexts = Array(Context.cpu())) + mod.bind(dataShapes = trainData.provideData, + Option(trainData.provideLabel)) + mod.installMonitor(mon) + val argParams = Map( + "fc_0_weight" -> NDArray.array(Array(0.15f, 0.2f, 0.25f, 0.3f), Shape(2, 2)), + "fc_0_bias" -> NDArray.array(Array(0.35f, 0.35f), Shape(2)), + "fc_1_weight" -> NDArray.array(Array(0.4f, 0.45f, 0.5f, 0.55f), Shape(2, 2)), + "fc_1_bias" -> NDArray.array(Array(0.6f, 0.6f), Shape(2)) + ) + mod.initParams(argParams = argParams) + + val dataBatch = trainData.next() + mon.tic() + mod.forwardBackward(dataBatch) + val res = mon.toc() + val keys = Array("act_0", "act_1", "data", "fc_0", "fc_1", "softmax") + val monResultCounts = Array(0, 0, 0, 0, 0, 0) + assert(res.length == 21) + for ((n, k, v) <- res) { + var break = false + for ((key, idx) <- keys.zipWithIndex) { + if (!break && k.startsWith(key)) { + monResultCounts(idx) += 1 + break = true + } + } + } + assert(monResultCounts.zip(Array(2, 2, 1, 6, 6, 4)).forall(x => x._1 == x._2)) + } + + test ("forward reshape") { + val numClass = 10 + val data1 = Symbol.Variable("data1") + val data2 = Symbol.Variable("data2") + val conv1 = Symbol.Convolution()()(Map("data" -> data1, + "kernel" -> "(2, 2)", "num_filter" -> 2, "stride" -> "(2, 2)")) + val conv2 = Symbol.Convolution()()(Map("data" -> data2, + "kernel" -> "(3, 3)", "num_filter" -> 3, "stride" -> "(1, 1)")) + val pooling1 = Symbol.Pooling()()(Map("data" -> conv1, + "kernel" -> "(2, 2)", "pool_type" -> "avg", "stride" -> "(1, 1)")) + val pooling2 = Symbol.Pooling()()(Map("data" -> conv2, + "kernel" -> "(2, 2)", "pool_type" -> "max", "stride" -> "(1, 1)")) + val flatten1 = Symbol.flatten()()(Map("data" -> pooling1)) + val flatten2 = Symbol.flatten()()(Map("data" -> pooling2)) + val sum = Symbol.sum()()(Map("data" -> flatten1, "axis" -> 1)) + + Symbol.sum()()(Map("data" -> flatten2, "axis" -> 1)) + val fc = Symbol.FullyConnected()()( + Map("data" -> sum, "num_hidden" -> numClass)) + val sym = Symbol.SoftmaxOutput(name = "softmax")()(Map("data" -> fc)) + + var dShape1 = Shape(10, 3, 64, 64) + var dShape2 = Shape(10, 3, 32, 32) + var lShape = Shape(10) + + val mod = new Module(sym, IndexedSeq("data1", "data2")) + mod.bind(dataShapes = IndexedSeq( + DataDesc("data1", dShape1), DataDesc("data2", dShape2)), + labelShapes = Option(IndexedSeq(DataDesc("softmax_label", lShape))) + ) + mod.initParams() + mod.initOptimizer(optimizer = new SGD(learningRate = 0.01f)) + + // Train with original data shapes + var dataBatch = new DataBatch( + data = IndexedSeq( + NDArray.random_uniform(Map("low" -> 0, "high" -> 9, "shape" -> dShape1.toString()))(), + NDArray.random_uniform(Map("low" -> 5, "high" -> 15, "shape" -> dShape2.toString()))()), + label = IndexedSeq(NDArray.ones(lShape)), index = null, pad = 0) + mod.forward(dataBatch) + assert(mod.getOutputsMerged()(0).shape == Shape(lShape(0), numClass)) + mod.backward() + mod.update() + + dShape1 = Shape(3, 3, 64, 64) + dShape2 = Shape(3, 3, 32, 32) + lShape = Shape(3) + dataBatch = new DataBatch( + data = IndexedSeq( + NDArray.random_uniform(Map("low" -> 0, "high" -> 9, "shape" -> dShape1.toString()))(), + NDArray.random_uniform(Map("low" -> 5, "high" -> 15, "shape" -> dShape2.toString()))()), + label = IndexedSeq(NDArray.ones(lShape)), index = null, pad = 0) + mod.forward(dataBatch) + assert(mod.getOutputsMerged()(0).shape == Shape(lShape(0), numClass)) + mod.backward() + mod.update() + + dShape1 = Shape(20, 3, 64, 64) + dShape2 = Shape(20, 3, 32, 32) + lShape = Shape(20) + dataBatch = new DataBatch( + data = IndexedSeq( + NDArray.random_uniform(Map("low" -> 3, "high" -> 5, "shape" -> dShape1.toString()))(), + NDArray.random_uniform(Map("low" -> 10, "high" -> 25, "shape" -> dShape2.toString()))()), + label = IndexedSeq(NDArray.ones(lShape)), index = null, pad = 0) + mod.forward(dataBatch) + assert(mod.getOutputsMerged()(0).shape == Shape(lShape(0), numClass)) + mod.backward() + mod.update() + + // Train with both different batch size and data shapes + dShape1 = Shape(20, 3, 120, 120) + dShape2 = Shape(20, 3, 32, 64) + lShape = Shape(20) + dataBatch = new DataBatch( + data = IndexedSeq( + NDArray.random_uniform(Map("low" -> 0, "high" -> 9, "shape" -> dShape1.toString()))(), + NDArray.random_uniform(Map("low" -> 5, "high" -> 15, "shape" -> dShape2.toString()))()), + label = IndexedSeq(NDArray.ones(lShape)), index = null, pad = 0) + mod.forward(dataBatch) + assert(mod.getOutputsMerged()(0).shape == Shape(lShape(0), numClass)) + mod.backward() + mod.update() + + dShape1 = Shape(5, 3, 28, 40) + dShape2 = Shape(5, 3, 24, 16) + lShape = Shape(5) + dataBatch = new DataBatch( + data = IndexedSeq( + NDArray.random_uniform(Map("low" -> 0, "high" -> 9, "shape" -> dShape1.toString()))(), + NDArray.random_uniform(Map("low" -> 15, "high" -> 25, "shape" -> dShape2.toString()))()), + label = IndexedSeq(NDArray.ones(lShape)), index = null, pad = 0) + mod.forward(dataBatch) + assert(mod.getOutputsMerged()(0).shape == Shape(lShape(0), numClass)) + mod.backward() + mod.update() + } +} diff --git a/scala-package/core/src/test/scala/ml/dmlc/mxnet/NDArraySuite.scala b/scala-package/core/src/test/scala/ml/dmlc/mxnet/NDArraySuite.scala index d3033ddffcf3..e1d091d1cd01 100644 --- a/scala-package/core/src/test/scala/ml/dmlc/mxnet/NDArraySuite.scala +++ b/scala-package/core/src/test/scala/ml/dmlc/mxnet/NDArraySuite.scala @@ -161,6 +161,19 @@ class NDArraySuite extends FunSuite with BeforeAndAfterAll with Matchers { assert(res.toArray === Array(11f)) } + test("arange") { + for (i <- 0 until 5) { + val start = scala.util.Random.nextFloat() * 5 + val stop = start + scala.util.Random.nextFloat() * 100 + val step = scala.util.Random.nextFloat() * 4 + val repeat = (scala.util.Random.nextFloat() * 5).toInt + 1 + val result = (start until stop by step).flatMap(x => Array.fill[Float](repeat)(x)) + val range = NDArray.arange(start = start, stop = Some(stop), step = step, + repeat = repeat, ctx = Context.cpu(), dType = DType.Float32) + assert(CheckUtils.reldiff(result.toArray, range.toArray) <= 1e-5f) + } + } + test("power") { val arr = NDArray.array(Array(3f, 5f), shape = Shape(2, 1)) @@ -175,6 +188,101 @@ class NDArraySuite extends FunSuite with BeforeAndAfterAll with Matchers { val arrPower3 = NDArray.power(arr, arr) assert(arrPower3.shape === Shape(2, 1)) assert(arrPower3.toArray === Array(27f, 3125f)) + + val arrPower4 = arr ** 2f + assert(arrPower4.shape === Shape(2, 1)) + assert(arrPower4.toArray === Array(9f, 25f)) + + val arrPower5 = arr ** arr + assert(arrPower5.shape === Shape(2, 1)) + assert(arrPower5.toArray === Array(27f, 3125f)) + + arr **= 2f + assert(arr.shape === Shape(2, 1)) + assert(arr.toArray === Array(9f, 25f)) + + arr.set(Array(3f, 5f)) + arr **= arr + assert(arr.shape === Shape(2, 1)) + assert(arr.toArray === Array(27f, 3125f)) + } + + test("equal") { + val arr1 = NDArray.array(Array(1f, 2f, 3f, 5f), shape = Shape(2, 2)) + val arr2 = NDArray.array(Array(1f, 4f, 3f, 6f), shape = Shape(2, 2)) + + val arrEqual1 = NDArray.equal(arr1, arr2) + assert(arrEqual1.shape === Shape(2, 2)) + assert(arrEqual1.toArray === Array(1f, 0f, 1f, 0f)) + + val arrEqual2 = NDArray.equal(arr1, 3f) + assert(arrEqual2.shape === Shape(2, 2)) + assert(arrEqual2.toArray === Array(0f, 0f, 1f, 0f)) + } + + test("not_equal") { + val arr1 = NDArray.array(Array(1f, 2f, 3f, 5f), shape = Shape(2, 2)) + val arr2 = NDArray.array(Array(1f, 4f, 3f, 6f), shape = Shape(2, 2)) + + val arrEqual1 = NDArray.notEqual(arr1, arr2) + assert(arrEqual1.shape === Shape(2, 2)) + assert(arrEqual1.toArray === Array(0f, 1f, 0f, 1f)) + + val arrEqual2 = NDArray.notEqual(arr1, 3f) + assert(arrEqual2.shape === Shape(2, 2)) + assert(arrEqual2.toArray === Array(1f, 1f, 0f, 1f)) + } + + test("greater") { + val arr1 = NDArray.array(Array(1f, 2f, 4f, 5f), shape = Shape(2, 2)) + val arr2 = NDArray.array(Array(1f, 4f, 3f, 6f), shape = Shape(2, 2)) + + val arrEqual1 = arr1 > arr2 + assert(arrEqual1.shape === Shape(2, 2)) + assert(arrEqual1.toArray === Array(0f, 0f, 1f, 0f)) + + val arrEqual2 = arr1 > 2f + assert(arrEqual2.shape === Shape(2, 2)) + assert(arrEqual2.toArray === Array(0f, 0f, 1f, 1f)) + } + + test("greater_equal") { + val arr1 = NDArray.array(Array(1f, 2f, 4f, 5f), shape = Shape(2, 2)) + val arr2 = NDArray.array(Array(1f, 4f, 3f, 6f), shape = Shape(2, 2)) + + val arrEqual1 = arr1 >= arr2 + assert(arrEqual1.shape === Shape(2, 2)) + assert(arrEqual1.toArray === Array(1f, 0f, 1f, 0f)) + + val arrEqual2 = arr1 >= 2f + assert(arrEqual2.shape === Shape(2, 2)) + assert(arrEqual2.toArray === Array(0f, 1f, 1f, 1f)) + } + + test("lesser") { + val arr1 = NDArray.array(Array(1f, 2f, 4f, 5f), shape = Shape(2, 2)) + val arr2 = NDArray.array(Array(1f, 4f, 3f, 6f), shape = Shape(2, 2)) + + val arrEqual1 = arr1 < arr2 + assert(arrEqual1.shape === Shape(2, 2)) + assert(arrEqual1.toArray === Array(0f, 1f, 0f, 1f)) + + val arrEqual2 = arr1 < 2f + assert(arrEqual2.shape === Shape(2, 2)) + assert(arrEqual2.toArray === Array(1f, 0f, 0f, 0f)) + } + + test("lesser_equal") { + val arr1 = NDArray.array(Array(1f, 2f, 4f, 5f), shape = Shape(2, 2)) + val arr2 = NDArray.array(Array(1f, 4f, 3f, 6f), shape = Shape(2, 2)) + + val arrEqual1 = arr1 <= arr2 + assert(arrEqual1.shape === Shape(2, 2)) + assert(arrEqual1.toArray === Array(1f, 1f, 0f, 1f)) + + val arrEqual2 = arr1 <= 2f + assert(arrEqual2.shape === Shape(2, 2)) + assert(arrEqual2.toArray === Array(1f, 1f, 0f, 0f)) } test("choose_element_0index") { diff --git a/scala-package/core/src/test/scala/ml/dmlc/mxnet/OperatorSuite.scala b/scala-package/core/src/test/scala/ml/dmlc/mxnet/OperatorSuite.scala index 4a2ae75cc4b8..86f04366a938 100644 --- a/scala-package/core/src/test/scala/ml/dmlc/mxnet/OperatorSuite.scala +++ b/scala-package/core/src/test/scala/ml/dmlc/mxnet/OperatorSuite.scala @@ -37,7 +37,7 @@ class OperatorSuite extends FunSuite with BeforeAndAfterAll exec.forward() val forwardOutput = exec.outputs(0) val forwardOutputExpected = arr.reduce(_ + _) - assert(reldiff(forwardOutput, forwardOutputExpected) < 1e-6) + assert(reldiff(forwardOutput, forwardOutputExpected) < 2e-6) // backward val outGrad = Random.uniform(-10, 10, shape) @@ -214,12 +214,41 @@ class OperatorSuite extends FunSuite with BeforeAndAfterAll checkSymbolicBackward(test, Array(dataTmp), Array(NDArray.ones(shape) * 2), Array(npoutGrad)) } + test("ones") { + val ones = Symbol.ones(shape = Shape(2, 2)) + val exe = ones.simpleBind(ctx = Context.cpu(), gradReq = "write", shapeDict = Map()) + exe.forward(isTrain = false) + assert(CheckUtils.reldiff(Array(1f, 1f, 1f, 1f), exe.outputs.head.toArray) <= 1e-5f) + } + + test("zeros") { + val zeros = Symbol.zeros(shape = Shape(2, 2)) + val exe = zeros.simpleBind(ctx = Context.cpu(), gradReq = "write", shapeDict = Map()) + exe.forward(isTrain = false) + assert(Array(0f, 0f, 0f, 0f) === exe.outputs.head.toArray) + } + + test("arange") { + for (i <- 0 until 5) { + val start = scala.util.Random.nextFloat() * 5 + val stop = start + scala.util.Random.nextFloat() * 100 + val step = scala.util.Random.nextFloat() * 4 + val repeat = (scala.util.Random.nextFloat() * 5).toInt + 1 + val result = (start until stop by step).flatMap(x => Array.fill[Float](repeat)(x)) + val x = Symbol.arange(start = start, stop = Some(stop), step = step, repeat = repeat) + var exe = x.simpleBind(ctx = Context.cpu(), gradReq = "write", shapeDict = Map()) + exe.forward(isTrain = false) + assert(exe.gradArrays.length == 0) + assert(CheckUtils.reldiff(result.toArray, exe.outputs.head.toArray) <= 1e-4f) + } + } + test("scalar pow") { val data = Symbol.Variable("data") val shape = Shape(1, 1) val dataTmp = NDArray.ones(shape) * 3 val dataTmpPowered = NDArray.ones(shape) * 9 - val test = Symbol.pow(data, 2) + val test = data ** 2 // TODO: check numeric gradient checkSymbolicForward(test, Array(dataTmp), Array(dataTmpPowered)) checkSymbolicBackward(test, Array(dataTmp), Array(NDArray.ones(shape)), Array(dataTmp * 2)) @@ -234,7 +263,7 @@ class OperatorSuite extends FunSuite with BeforeAndAfterAll val exp = Symbol.Variable("exp") val expTmp = NDArray.ones(shape) * 3 - val test = Symbol.pow(data, exp) + val test = data ** exp // TODO: check numeric gradient checkSymbolicForward(test, Seq(dataTmp, expTmp), Seq(NDArray.ones(shape) * 8)) @@ -249,7 +278,8 @@ class OperatorSuite extends FunSuite with BeforeAndAfterAll test("pow fn") { val shape = Shape(3, 4) val exp = Symbol.Variable("exp") - val y = Symbol.pow(2, exp) + import SymbolConversions._ + val y = 2 ** exp val x = NDArray.ones(shape) * 3 // TODO: check numeric gradient checkSymbolicForward(y, Seq(x), Seq(NDArray.ones(shape) * 8)) // 2**x @@ -258,6 +288,322 @@ class OperatorSuite extends FunSuite with BeforeAndAfterAll Seq(NDArray.ones(shape) * 8 * Math.log(2).toFloat)) } + test("scalar equal") { + val data = Symbol.Variable("datas") + val shape = Shape(2, 2) + val dataTmpExpected = NDArray.array(Array(0f, 1f, 0f, 0f), shape) + val test = Symbol.equal(data, 2f) + + val exec = test.simpleBind(Context.cpu(), gradReq = "write", shapeDict = Map("datas" -> shape)) + exec.argDict("datas").set(Array(1f, 2f, 3f, 4f)) + + exec.forward() + assert(reldiff(exec.outputs.head, dataTmpExpected) <= 1e-5f) + + exec.backward(NDArray.ones(shape)) + assert(exec.gradDict("datas").toArray === Array.fill[Float](shape.product)(0f)) + } + + test("symbol equal") { + val data = Symbol.Variable("datas") + val data2 = Symbol.Variable("datas2") + val shape = Shape(2, 2) + val dataTmpExpected = NDArray.array(Array(1f, 0f, 0f, 0f), shape) + val test = Symbol.equal(data, data2) + + val exec = test.simpleBind(Context.cpu(), gradReq = "write", + shapeDict = Map("datas" -> shape, "datas2" -> shape)) + exec.argDict("datas").set(Array(1f, 2f, 3f, 4f)) + exec.argDict("datas2").set(Array(1f, 3f, 2f, 6f)) + + exec.forward() + assert(reldiff(exec.outputs.head, dataTmpExpected) <= 1e-5f) + + exec.backward(NDArray.ones(shape)) + assert(exec.gradDict("datas").toArray === Array.fill[Float](shape.product)(0f)) + assert(exec.gradDict("datas2").toArray === Array.fill[Float](shape.product)(0f)) + } + + test("scalar equal 2") { + val data = Symbol.Variable("datas") + val shape = Shape(2, 2) + val dataTmpExpected = NDArray.array(Array(0f, 1f, 0f, 0f), shape) + val test = Symbol.equal(2f, data) + + val exec = test.simpleBind(Context.cpu(), gradReq = "write", shapeDict = Map("datas" -> shape)) + exec.argDict("datas").set(Array(1f, 2f, 3f, 4f)) + + exec.forward() + assert(reldiff(exec.outputs.head, dataTmpExpected) <= 1e-5f) + + exec.backward(NDArray.ones(shape)) + assert(exec.gradDict("datas").toArray === Array.fill[Float](shape.product)(0f)) + } + + test("scalar not_equal") { + val data = Symbol.Variable("datas") + val shape = Shape(2, 2) + val dataTmpExpected = NDArray.array(Array(1f, 0f, 1f, 1f), shape) + val test = Symbol.notEqual(data, 2f) + + val exec = test.simpleBind(Context.cpu(), gradReq = "write", shapeDict = Map("datas" -> shape)) + exec.argDict("datas").set(Array(1f, 2f, 3f, 4f)) + + exec.forward() + assert(reldiff(exec.outputs.head, dataTmpExpected) <= 1e-5f) + + exec.backward(NDArray.ones(shape)) + assert(exec.gradDict("datas").toArray === Array.fill[Float](shape.product)(0f)) + } + + test("symbol not_equal") { + val data = Symbol.Variable("datas") + val data2 = Symbol.Variable("datas2") + val shape = Shape(2, 2) + val dataTmpExpected = NDArray.array(Array(0f, 1f, 1f, 1f), shape) + val test = Symbol.notEqual(data, data2) + + val exec = test.simpleBind(Context.cpu(), gradReq = "write", + shapeDict = Map("datas" -> shape, "datas2" -> shape)) + exec.argDict("datas").set(Array(1f, 2f, 3f, 4f)) + exec.argDict("datas2").set(Array(1f, 3f, 2f, 6f)) + + exec.forward() + assert(reldiff(exec.outputs.head, dataTmpExpected) <= 1e-5f) + + exec.backward(NDArray.ones(shape)) + assert(exec.gradDict("datas").toArray === Array.fill[Float](shape.product)(0f)) + assert(exec.gradDict("datas2").toArray === Array.fill[Float](shape.product)(0f)) + } + + test("scalar not_equal 2") { + val data = Symbol.Variable("datas") + val shape = Shape(2, 2) + val dataTmpExpected = NDArray.array(Array(1f, 0f, 1f, 1f), shape) + val test = Symbol.notEqual(2f, data) + + val exec = test.simpleBind(Context.cpu(), gradReq = "write", shapeDict = Map("datas" -> shape)) + exec.argDict("datas").set(Array(1f, 2f, 3f, 4f)) + + exec.forward() + assert(reldiff(exec.outputs.head, dataTmpExpected) <= 1e-5f) + + exec.backward(NDArray.ones(shape)) + assert(exec.gradDict("datas").toArray === Array.fill[Float](shape.product)(0f)) + } + + test("scalar greater") { + val data = Symbol.Variable("datas") + val shape = Shape(2, 2) + val dataTmpExpected = NDArray.array(Array(0f, 0f, 1f, 1f), shape) + val test = data > 2f + + val exec = test.simpleBind(Context.cpu(), gradReq = "write", shapeDict = Map("datas" -> shape)) + exec.argDict("datas").set(Array(1f, 2f, 3f, 4f)) + + exec.forward() + assert(reldiff(exec.outputs.head, dataTmpExpected) <= 1e-5f) + + exec.backward(NDArray.ones(shape)) + assert(exec.gradDict("datas").toArray === Array.fill[Float](shape.product)(0f)) + } + + test("symbol greater") { + val data = Symbol.Variable("datas") + val data2 = Symbol.Variable("datas2") + val shape = Shape(2, 2) + val dataTmpExpected = NDArray.array(Array(0f, 0f, 1f, 0f), shape) + val test = data > data2 + + val exec = test.simpleBind(Context.cpu(), gradReq = "write", + shapeDict = Map("datas" -> shape, "datas2" -> shape)) + exec.argDict("datas").set(Array(1f, 2f, 3f, 4f)) + exec.argDict("datas2").set(Array(1f, 3f, 2f, 6f)) + + exec.forward() + assert(reldiff(exec.outputs.head, dataTmpExpected) <= 1e-5f) + + exec.backward(NDArray.ones(shape)) + assert(exec.gradDict("datas").toArray === Array.fill[Float](shape.product)(0f)) + assert(exec.gradDict("datas2").toArray === Array.fill[Float](shape.product)(0f)) + } + + test("scalar greater 2") { + val data = Symbol.Variable("datas") + val shape = Shape(2, 2) + val dataTmpExpected = NDArray.array(Array(1f, 0f, 0f, 0f), shape) + import SymbolConversions._ + val test = 2f > data + + val exec = test.simpleBind(Context.cpu(), gradReq = "write", shapeDict = Map("datas" -> shape)) + exec.argDict("datas").set(Array(1f, 2f, 3f, 4f)) + + exec.forward() + assert(reldiff(exec.outputs.head, dataTmpExpected) <= 1e-5f) + + exec.backward(NDArray.ones(shape)) + assert(exec.gradDict("datas").toArray === Array.fill[Float](shape.product)(0f)) + } + + test("scalar greater_equal") { + val data = Symbol.Variable("datas") + val shape = Shape(2, 2) + val dataTmpExpected = NDArray.array(Array(0f, 1f, 1f, 1f), shape) + val test = data >= 2f + + val exec = test.simpleBind(Context.cpu(), gradReq = "write", shapeDict = Map("datas" -> shape)) + exec.argDict("datas").set(Array(1f, 2f, 3f, 4f)) + + exec.forward() + assert(reldiff(exec.outputs.head, dataTmpExpected) <= 1e-5f) + + exec.backward(NDArray.ones(shape)) + assert(exec.gradDict("datas").toArray === Array.fill[Float](shape.product)(0f)) + } + + test("symbol greater_equal") { + val data = Symbol.Variable("datas") + val data2 = Symbol.Variable("datas2") + val shape = Shape(2, 2) + val dataTmpExpected = NDArray.array(Array(1f, 0f, 1f, 0f), shape) + val test = data >= data2 + + val exec = test.simpleBind(Context.cpu(), gradReq = "write", + shapeDict = Map("datas" -> shape, "datas2" -> shape)) + exec.argDict("datas").set(Array(1f, 2f, 3f, 4f)) + exec.argDict("datas2").set(Array(1f, 3f, 2f, 6f)) + + exec.forward() + assert(reldiff(exec.outputs.head, dataTmpExpected) <= 1e-5f) + + exec.backward(NDArray.ones(shape)) + assert(exec.gradDict("datas").toArray === Array.fill[Float](shape.product)(0f)) + assert(exec.gradDict("datas2").toArray === Array.fill[Float](shape.product)(0f)) + } + + test("scalar greater_equal 2") { + val data = Symbol.Variable("datas") + val shape = Shape(2, 2) + val dataTmpExpected = NDArray.array(Array(1f, 1f, 0f, 0f), shape) + import SymbolConversions._ + val test = 2f >= data + + val exec = test.simpleBind(Context.cpu(), gradReq = "write", shapeDict = Map("datas" -> shape)) + exec.argDict("datas").set(Array(1f, 2f, 3f, 4f)) + + exec.forward() + assert(reldiff(exec.outputs.head, dataTmpExpected) <= 1e-5f) + + exec.backward(NDArray.ones(shape)) + assert(exec.gradDict("datas").toArray === Array.fill[Float](shape.product)(0f)) + } + + test("scalar lesser") { + val data = Symbol.Variable("datas") + val shape = Shape(2, 2) + val dataTmpExpected = NDArray.array(Array(1f, 0f, 0f, 0f), shape) + val test = data < 2f + + val exec = test.simpleBind(Context.cpu(), gradReq = "write", shapeDict = Map("datas" -> shape)) + exec.argDict("datas").set(Array(1f, 2f, 3f, 4f)) + + exec.forward() + assert(reldiff(exec.outputs.head, dataTmpExpected) <= 1e-5f) + + exec.backward(NDArray.ones(shape)) + assert(exec.gradDict("datas").toArray === Array.fill[Float](shape.product)(0f)) + } + + test("symbol lesser") { + val data = Symbol.Variable("datas") + val data2 = Symbol.Variable("datas2") + val shape = Shape(2, 2) + val dataTmpExpected = NDArray.array(Array(0f, 1f, 0f, 1f), shape) + val test = data < data2 + + val exec = test.simpleBind(Context.cpu(), gradReq = "write", + shapeDict = Map("datas" -> shape, "datas2" -> shape)) + exec.argDict("datas").set(Array(1f, 2f, 3f, 4f)) + exec.argDict("datas2").set(Array(1f, 3f, 2f, 6f)) + + exec.forward() + assert(reldiff(exec.outputs.head, dataTmpExpected) <= 1e-5f) + + exec.backward(NDArray.ones(shape)) + assert(exec.gradDict("datas").toArray === Array.fill[Float](shape.product)(0f)) + assert(exec.gradDict("datas2").toArray === Array.fill[Float](shape.product)(0f)) + } + + test("scalar lesser 2") { + val data = Symbol.Variable("datas") + val shape = Shape(2, 2) + val dataTmpExpected = NDArray.array(Array(0f, 0f, 1f, 1f), shape) + import SymbolConversions._ + val test = 2f < data + + val exec = test.simpleBind(Context.cpu(), gradReq = "write", shapeDict = Map("datas" -> shape)) + exec.argDict("datas").set(Array(1f, 2f, 3f, 4f)) + + exec.forward() + assert(reldiff(exec.outputs.head, dataTmpExpected) <= 1e-5f) + + exec.backward(NDArray.ones(shape)) + assert(exec.gradDict("datas").toArray === Array.fill[Float](shape.product)(0f)) + } + + test("scalar lesser_equal") { + val data = Symbol.Variable("datas") + val shape = Shape(2, 2) + val dataTmpExpected = NDArray.array(Array(1f, 1f, 0f, 0f), shape) + val test = data <= 2f + + val exec = test.simpleBind(Context.cpu(), gradReq = "write", shapeDict = Map("datas" -> shape)) + exec.argDict("datas").set(Array(1f, 2f, 3f, 4f)) + + exec.forward() + assert(reldiff(exec.outputs.head, dataTmpExpected) <= 1e-5f) + + exec.backward(NDArray.ones(shape)) + assert(exec.gradDict("datas").toArray === Array.fill[Float](shape.product)(0f)) + } + + test("symbol lesser_equal") { + val data = Symbol.Variable("datas") + val data2 = Symbol.Variable("datas2") + val shape = Shape(2, 2) + val dataTmpExpected = NDArray.array(Array(1f, 1f, 0f, 1f), shape) + val test = data <= data2 + + val exec = test.simpleBind(Context.cpu(), gradReq = "write", + shapeDict = Map("datas" -> shape, "datas2" -> shape)) + exec.argDict("datas").set(Array(1f, 2f, 3f, 4f)) + exec.argDict("datas2").set(Array(1f, 3f, 2f, 6f)) + + exec.forward() + assert(reldiff(exec.outputs.head, dataTmpExpected) <= 1e-5f) + + exec.backward(NDArray.ones(shape)) + assert(exec.gradDict("datas").toArray === Array.fill[Float](shape.product)(0f)) + assert(exec.gradDict("datas2").toArray === Array.fill[Float](shape.product)(0f)) + } + + test("scalar lesser_equal 2") { + val data = Symbol.Variable("datas") + val shape = Shape(2, 2) + val dataTmpExpected = NDArray.array(Array(0f, 1f, 1f, 1f), shape) + import SymbolConversions._ + val test = 2f <= data + + val exec = test.simpleBind(Context.cpu(), gradReq = "write", shapeDict = Map("datas" -> shape)) + exec.argDict("datas").set(Array(1f, 2f, 3f, 4f)) + + exec.forward() + assert(reldiff(exec.outputs.head, dataTmpExpected) <= 1e-5f) + + exec.backward(NDArray.ones(shape)) + assert(exec.gradDict("datas").toArray === Array.fill[Float](shape.product)(0f)) + } + test("embedding") { val inDim = 10 val outDim = 4 @@ -564,6 +910,7 @@ class OperatorSuite extends FunSuite with BeforeAndAfterAll NDArray.zeros(Shape(numFilter, inputShape(1), kernel._1, kernel._2))) val exeConv = conv.bind(Context.cpu(), args = convArgs, argsGrad = convArgsGrad) val convOutGrad = Random.normal(0, 2, exeConv.outputs.head.shape) + exeConv.forward() exeConv.backward(convOutGrad) val deconvData = convOutGrad @@ -572,6 +919,7 @@ class OperatorSuite extends FunSuite with BeforeAndAfterAll NDArray.zeros(Shape(numFilter, inputShape(1), kernel._1, kernel._2))) val exeDeconv = deconv.bind(Context.cpu(), args = deconvArgs, argsGrad = deconvArgsGrad) val deconvOutGrad = convData + exeDeconv.forward() exeDeconv.backward(deconvOutGrad) assert(reldiff(convArgsGrad(1), deconvArgsGrad(1)) < 1e-5) } diff --git a/scala-package/examples/pom.xml b/scala-package/examples/pom.xml index 434e499a4ab4..356690cf0176 100644 --- a/scala-package/examples/pom.xml +++ b/scala-package/examples/pom.xml @@ -6,12 +6,11 @@ ml.dmlc.mxnet mxnet-parent_2.11 - 0.9.5-SNAPSHOT + ${project.version} ../pom.xml mxnet-examples_2.11 - 0.9.5-SNAPSHOT MXNet Scala Package - Examples @@ -84,7 +83,7 @@ package copy-dependencies - + ${project.build.outputDirectory}/lib runtime diff --git a/scala-package/examples/scripts/customop/run_customop.sh b/scala-package/examples/scripts/customop/run_customop.sh index 44c8ef6d50d9..b11bb89a2d91 100644 --- a/scala-package/examples/scripts/customop/run_customop.sh +++ b/scala-package/examples/scripts/customop/run_customop.sh @@ -1,5 +1,23 @@ #!/bin/bash +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + + MXNET_ROOT=$(cd "$(dirname $0)/../../../.."; pwd) CLASS_PATH=$MXNET_ROOT/scala-package/assembly/linux-x86_64-gpu/target/*:$MXNET_ROOT/scala-package/examples/target/*:$MXNET_ROOT/scala-package/examples/target/classes/lib/* @@ -13,4 +31,4 @@ DATA_PATH=$2 java -Xmx4G -cp $CLASS_PATH \ ml.dmlc.mxnetexamples.customop.ExampleCustomOp \ --data-path $DATA_PATH \ - --gpu $GPU \ + --gpu $GPU diff --git a/scala-package/examples/scripts/customop/run_customopwithrtc.sh b/scala-package/examples/scripts/customop/run_customopwithrtc.sh index 2d1391054bf4..160525e4eb54 100644 --- a/scala-package/examples/scripts/customop/run_customopwithrtc.sh +++ b/scala-package/examples/scripts/customop/run_customopwithrtc.sh @@ -1,5 +1,23 @@ #!/bin/bash +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + + MXNET_ROOT=$(cd "$(dirname $0)/../../../.."; pwd) CLASS_PATH=$MXNET_ROOT/scala-package/assembly/linux-x86_64-gpu/target/*:$MXNET_ROOT/scala-package/examples/target/*:$MXNET_ROOT/scala-package/examples/target/classes/lib/* @@ -13,4 +31,4 @@ DATA_PATH=$1 java -Xmx4G -cp $CLASS_PATH \ ml.dmlc.mxnetexamples.customop.ExampleCustomOpWithRtc \ --data-path $DATA_PATH \ - --gpu $GPU \ + --gpu $GPU diff --git a/scala-package/examples/scripts/module/mnist_mlp.sh b/scala-package/examples/scripts/module/mnist_mlp.sh index 0b450d7608a4..6bb9636e98da 100755 --- a/scala-package/examples/scripts/module/mnist_mlp.sh +++ b/scala-package/examples/scripts/module/mnist_mlp.sh @@ -1,4 +1,22 @@ #!/bin/bash + +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + ROOT_DIR=$(cd `dirname $0`/../../..; pwd) CLASSPATH=$ROOT_DIR/assembly/osx-x86_64-cpu/target/*:$ROOT_DIR/examples/target/*:$ROOT_DIR/examples/target/classes/lib/* diff --git a/scala-package/examples/scripts/module/run_sequential_module.sh b/scala-package/examples/scripts/module/run_sequential_module.sh index 15cc7dda8ba2..9d9edb719dd5 100644 --- a/scala-package/examples/scripts/module/run_sequential_module.sh +++ b/scala-package/examples/scripts/module/run_sequential_module.sh @@ -1,4 +1,22 @@ #!/bin/bash + +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + ROOT_DIR=$(cd `dirname $0`/../../..; pwd) CLASSPATH=$ROOT_DIR/assembly/linux-x86_64-cpu/target/*:$ROOT_DIR/examples/target/*:$ROOT_DIR/examples/target/classes/lib/* diff --git a/scala-package/examples/scripts/neuralstyle_end2end/run_test_end2end.sh b/scala-package/examples/scripts/neuralstyle_end2end/run_test_end2end.sh index 10bc2da4f9bf..1c683bfc9e34 100644 --- a/scala-package/examples/scripts/neuralstyle_end2end/run_test_end2end.sh +++ b/scala-package/examples/scripts/neuralstyle_end2end/run_test_end2end.sh @@ -1,9 +1,27 @@ #!/bin/bash +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + + MXNET_ROOT=$(cd "$(dirname $0)/../../../.."; pwd) CLASS_PATH=$MXNET_ROOT/scala-package/assembly/linux-x86_64-gpu/target/*:$MXNET_ROOT/scala-package/examples/target/*:$MXNET_ROOT/scala-package/examples/target/classes/lib/* -INPUT_IMG=$1 +INPUT_IMG=$1 MODEL_DIR=$2 OUTPUT_DIR=$3 GPU=0 @@ -13,4 +31,4 @@ java -Xmx1024m -cp $CLASS_PATH \ --model-path $MODEL_DIR \ --input-image $INPUT_IMG \ --output-path $OUTPUT_DIR \ - --gpu $GPU \ No newline at end of file + --gpu $GPU diff --git a/scala-package/examples/scripts/neuralstyle_end2end/run_train_end2end.sh b/scala-package/examples/scripts/neuralstyle_end2end/run_train_end2end.sh index 3ede06a78b0b..fa08ff3713c8 100644 --- a/scala-package/examples/scripts/neuralstyle_end2end/run_train_end2end.sh +++ b/scala-package/examples/scripts/neuralstyle_end2end/run_train_end2end.sh @@ -1,12 +1,30 @@ #!/bin/bash +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + + MXNET_ROOT=$(cd "$(dirname $0)/../../../.."; pwd) CLASS_PATH=$MXNET_ROOT/scala-package/assembly/linux-x86_64-gpu/target/*:$MXNET_ROOT/scala-package/examples/target/*:$MXNET_ROOT/scala-package/examples/target/classes/lib/* # more details please refer to # https://github.com/Ldpe2G/mxnet/blob/develop/example/neural-style/end_to_end/README.md -TRAIN_DATA_PATH=$1 -STYLE_IMG=$2 +TRAIN_DATA_PATH=$1 +STYLE_IMG=$2 VGG_MODEL_PATH=$3 SAVE_MODEL_DIR=$4 GPU=0 @@ -17,4 +35,4 @@ java -Xmx1024m -cp $CLASS_PATH \ --vgg--model-path $VGG_MODEL_PATH \ --save--model-path $SAVE_MODEL_DIR \ --style-image $STYLE_IMG \ - --gpu $GPU \ No newline at end of file + --gpu $GPU diff --git a/scala-package/examples/scripts/profiler/run_profiler_matmul.sh b/scala-package/examples/scripts/profiler/run_profiler_matmul.sh index b54a4226fb14..54aafafcfd61 100644 --- a/scala-package/examples/scripts/profiler/run_profiler_matmul.sh +++ b/scala-package/examples/scripts/profiler/run_profiler_matmul.sh @@ -1,5 +1,23 @@ #!/bin/bash +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + + MXNET_ROOT=$(cd "$(dirname $0)/../../../.."; pwd) CLASS_PATH=$MXNET_ROOT/scala-package/assembly/linux-x86_64-gpu/target/*:$MXNET_ROOT/scala-package/examples/target/*:$MXNET_ROOT/scala-package/examples/target/classes/lib/* diff --git a/scala-package/examples/scripts/profiler/run_profiler_ndarray.sh b/scala-package/examples/scripts/profiler/run_profiler_ndarray.sh index 4a849c57b2ee..711fe5465404 100644 --- a/scala-package/examples/scripts/profiler/run_profiler_ndarray.sh +++ b/scala-package/examples/scripts/profiler/run_profiler_ndarray.sh @@ -1,5 +1,23 @@ #!/bin/bash +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + + MXNET_ROOT=$(cd "$(dirname $0)/../../../.."; pwd) CLASS_PATH=$MXNET_ROOT/scala-package/assembly/linux-x86_64-gpu/target/*:$MXNET_ROOT/scala-package/examples/target/*:$MXNET_ROOT/scala-package/examples/target/classes/lib/* diff --git a/scala-package/examples/scripts/rnn/run_test_charrnn.sh b/scala-package/examples/scripts/rnn/run_test_charrnn.sh index 04eee3691435..a4ed91354e8c 100644 --- a/scala-package/examples/scripts/rnn/run_test_charrnn.sh +++ b/scala-package/examples/scripts/rnn/run_test_charrnn.sh @@ -1,5 +1,23 @@ #!/bin/bash +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + + MXNET_ROOT=$(cd "$(dirname $0)/../../../.."; pwd) OS=$(uname) if [ "$OS" = "Darwin" ]; then diff --git a/scala-package/examples/scripts/rnn/run_train_charrnn.sh b/scala-package/examples/scripts/rnn/run_train_charrnn.sh index 07b7dda7d6e4..2e9a3a264c88 100755 --- a/scala-package/examples/scripts/rnn/run_train_charrnn.sh +++ b/scala-package/examples/scripts/rnn/run_train_charrnn.sh @@ -1,5 +1,23 @@ #!/bin/bash +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + + MXNET_ROOT=$(cd "$(dirname $0)/../../../.."; pwd) OS=$(uname) if [ "$OS" = "Darwin" ]; then diff --git a/scala-package/examples/scripts/run_cnntextclassification.sh b/scala-package/examples/scripts/run_cnntextclassification.sh index 8ace6ff22c29..7939b0627422 100644 --- a/scala-package/examples/scripts/run_cnntextclassification.sh +++ b/scala-package/examples/scripts/run_cnntextclassification.sh @@ -1,21 +1,39 @@ #!/bin/bash +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + + MXNET_ROOT=$(cd "$(dirname $0)/../../.."; pwd) CLASS_PATH=$MXNET_ROOT/scala-package/assembly/linux-x86_64-gpu/target/*:$MXNET_ROOT/scala-package/examples/target/*:$MXNET_ROOT/scala-package/examples/target/classes/lib/* # which gpu card to use, -1 means cpu GPU=$1 # the mr dataset path, you should put the pos and neg file in the same folder -MR_DATASET_PATH=$2 +MR_DATASET_PATH=$2 # the trained word2vec file path, binary or text format -W2V_FILE_PATH=$3 +W2V_FILE_PATH=$3 # whether the format of the word2vec file is binary,1 means binary, 0 means text -W2V_FORMAT_BIN=$4 +W2V_FORMAT_BIN=$4 BATCH_SIZE=$5 SAVE_MODEL_PATH=$6 java -Xmx8G -cp $CLASS_PATH \ - ml.dmlc.mxnetexamples.cnnclassification.CNNTextClassification \ + ml.dmlc.mxnetexamples.cnntextclassification.CNNTextClassification \ --gpu $GPU \ --mr-dataset-path $MR_DATASET_PATH \ --w2v-file-path $W2V_FILE_PATH \ diff --git a/scala-package/examples/scripts/run_gan_mnist.sh b/scala-package/examples/scripts/run_gan_mnist.sh index 2d3c545cf5d3..951241fb18b7 100644 --- a/scala-package/examples/scripts/run_gan_mnist.sh +++ b/scala-package/examples/scripts/run_gan_mnist.sh @@ -1,5 +1,23 @@ #!/bin/bash +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + + MXNET_ROOT=$(cd "$(dirname $0)/../../.."; pwd) CLASS_PATH=$MXNET_ROOT/scala-package/assembly/linux-x86_64-gpu/target/*:$MXNET_ROOT/scala-package/examples/target/*:$MXNET_ROOT/scala-package/examples/target/classes/lib/* @@ -10,7 +28,7 @@ GPU=$1 # you can get the mnist data using the script core/scripts/get_mnist_data.sh MNIST_DATA_PATH=$2 -# the path to save the generated results +# the path to save the generated results OUTPUT_PATH=$3 java -Xmx4G -cp $CLASS_PATH \ diff --git a/scala-package/examples/scripts/run_multitask.sh b/scala-package/examples/scripts/run_multitask.sh index 1642cc8336f2..9e6a489e9fa7 100644 --- a/scala-package/examples/scripts/run_multitask.sh +++ b/scala-package/examples/scripts/run_multitask.sh @@ -1,5 +1,23 @@ #!/bin/bash +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + + MXNET_ROOT=$(cd "$(dirname $0)/../../.."; pwd) CLASS_PATH=$MXNET_ROOT/scala-package/assembly/linux-x86_64-gpu/target/*:$MXNET_ROOT/scala-package/examples/target/*:$MXNET_ROOT/scala-package/examples/target/classes/lib/* diff --git a/scala-package/examples/scripts/run_neuralstyle.sh b/scala-package/examples/scripts/run_neuralstyle.sh index 5fbfc3227b7c..a9c2e5c1c1ea 100644 --- a/scala-package/examples/scripts/run_neuralstyle.sh +++ b/scala-package/examples/scripts/run_neuralstyle.sh @@ -1,9 +1,27 @@ #!/bin/bash +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + + MXNET_ROOT=$(cd "$(dirname $0)/../../.."; pwd) CLASS_PATH=$MXNET_ROOT/scala-package/assembly/linux-x86_64-gpu/target/*:$MXNET_ROOT/scala-package/examples/target/*:$MXNET_ROOT/scala-package/examples/target/classes/lib/* -INPUT_IMG=$1 -STYLE_IMG=$2 +INPUT_IMG=$1 +STYLE_IMG=$2 MODEL_PATH=$MXNET_ROOT/example/neural-style/model/vgg19.params OUTPUT_DIR=$MXNET_ROOT/example/neural-style/output @@ -12,4 +30,4 @@ java -Xmx1024m -cp $CLASS_PATH \ --content-image $INPUT_IMG \ --style-image $STYLE_IMG \ --model-path $MODEL_PATH \ - --output-dir $OUTPUT_DIR + --output-dir $OUTPUT_DIR diff --git a/scala-package/examples/scripts/run_visualization.sh b/scala-package/examples/scripts/run_visualization.sh index 6f686adc06f5..a4b545e24484 100644 --- a/scala-package/examples/scripts/run_visualization.sh +++ b/scala-package/examples/scripts/run_visualization.sh @@ -1,5 +1,23 @@ #!/bin/bash +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + + MXNET_ROOT=$(cd "$(dirname $0)/../../.."; pwd) CLASS_PATH=$MXNET_ROOT/scala-package/assembly/linux-x86_64-cpu/target/*:$MXNET_ROOT/scala-package/examples/target/*:$MXNET_ROOT/scala-package/examples/target/classes/lib/* @@ -15,4 +33,4 @@ NET=$2 java -Xmx1024m -cp $CLASS_PATH \ ml.dmlc.mxnetexamples.visualization.ExampleVis \ --out-dir $OUT_DIR \ - --net $NET + --net $NET diff --git a/scala-package/init-native/linux-x86_64/pom.xml b/scala-package/init-native/linux-x86_64/pom.xml index 18a5d97fc024..9d784c471cb9 100644 --- a/scala-package/init-native/linux-x86_64/pom.xml +++ b/scala-package/init-native/linux-x86_64/pom.xml @@ -6,12 +6,11 @@ ml.dmlc.mxnet mxnet-scala-init-native-parent - 0.9.5-SNAPSHOT + ${project.version} ../pom.xml libmxnet-init-scala-linux-x86_64 - 0.9.5-SNAPSHOT MXNet Scala Package - Initializer Native Linux-x86_64 http://maven.apache.org diff --git a/scala-package/init-native/osx-x86_64/pom.xml b/scala-package/init-native/osx-x86_64/pom.xml index 16cb6bf9644c..fb3748e5698f 100644 --- a/scala-package/init-native/osx-x86_64/pom.xml +++ b/scala-package/init-native/osx-x86_64/pom.xml @@ -6,12 +6,11 @@ ml.dmlc.mxnet mxnet-scala-init-native-parent - 0.9.5-SNAPSHOT + ${project.version} ../pom.xml libmxnet-init-scala-osx-x86_64 - 0.9.5-SNAPSHOT MXNet Scala Package - Initializer Native OSX-x86_64 http://maven.apache.org diff --git a/scala-package/init-native/pom.xml b/scala-package/init-native/pom.xml index f3bc53557521..2b633169501d 100644 --- a/scala-package/init-native/pom.xml +++ b/scala-package/init-native/pom.xml @@ -6,12 +6,11 @@ ml.dmlc.mxnet mxnet-parent_2.11 - 0.9.5-SNAPSHOT + ${project.version} ../pom.xml mxnet-scala-init-native-parent - 0.9.5-SNAPSHOT MXNet Scala Package - Initializer Native Parent pom diff --git a/scala-package/init-native/src/main/native/ml_dmlc_mxnet_init_native_c_api.cc b/scala-package/init-native/src/main/native/ml_dmlc_mxnet_init_native_c_api.cc index d6daa00eec9f..114510c66afb 100644 --- a/scala-package/init-native/src/main/native/ml_dmlc_mxnet_init_native_c_api.cc +++ b/scala-package/init-native/src/main/native/ml_dmlc_mxnet_init_native_c_api.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file ml_dmlc_mxnet_native_c_api.cc * \brief JNI function implementations */ diff --git a/scala-package/init/pom.xml b/scala-package/init/pom.xml index 44913929fbdc..04413e219429 100644 --- a/scala-package/init/pom.xml +++ b/scala-package/init/pom.xml @@ -6,12 +6,11 @@ ml.dmlc.mxnet mxnet-parent_2.11 - 0.9.5-SNAPSHOT + ${project.version} ../pom.xml mxnet-init_2.11 - 0.9.5-SNAPSHOT MXNet Scala Package - Initializer diff --git a/scala-package/macros/pom.xml b/scala-package/macros/pom.xml index dcde188cf3a6..2a1498cb2639 100644 --- a/scala-package/macros/pom.xml +++ b/scala-package/macros/pom.xml @@ -6,12 +6,11 @@ ml.dmlc.mxnet mxnet-parent_2.11 - 0.9.5-SNAPSHOT + ${project.version} ../pom.xml mxnet-macros_2.11 - 0.9.5-SNAPSHOT MXNet Scala Package - Macros diff --git a/scala-package/native/linux-x86_64-cpu/pom.xml b/scala-package/native/linux-x86_64-cpu/pom.xml index 388cd874d60c..df45cd9e6c2e 100644 --- a/scala-package/native/linux-x86_64-cpu/pom.xml +++ b/scala-package/native/linux-x86_64-cpu/pom.xml @@ -6,13 +6,11 @@ ml.dmlc.mxnet mxnet-scala-native-parent - 0.9.5-SNAPSHOT + ${project.version} ../pom.xml - ml.dmlc.mxnet libmxnet-scala-linux-x86_64-cpu - 0.9.5-SNAPSHOT MXNet Scala Package - Native Linux-x86_64 CPU-only http://maven.apache.org diff --git a/scala-package/native/linux-x86_64-gpu/pom.xml b/scala-package/native/linux-x86_64-gpu/pom.xml index 7943dac5e27b..edc70e923ff7 100644 --- a/scala-package/native/linux-x86_64-gpu/pom.xml +++ b/scala-package/native/linux-x86_64-gpu/pom.xml @@ -6,13 +6,11 @@ ml.dmlc.mxnet mxnet-scala-native-parent - 0.9.5-SNAPSHOT + ${project.version} ../pom.xml - ml.dmlc.mxnet libmxnet-scala-linux-x86_64-gpu - 0.9.5-SNAPSHOT MXNet Scala Package - Native Linux-x86_64 GPU http://maven.apache.org diff --git a/scala-package/native/osx-x86_64-cpu/pom.xml b/scala-package/native/osx-x86_64-cpu/pom.xml index 10d9bce0c209..b6fb83f26a71 100644 --- a/scala-package/native/osx-x86_64-cpu/pom.xml +++ b/scala-package/native/osx-x86_64-cpu/pom.xml @@ -6,12 +6,11 @@ ml.dmlc.mxnet mxnet-scala-native-parent - 0.9.5-SNAPSHOT + ${project.version} ../pom.xml libmxnet-scala-osx-x86_64-cpu - 0.9.5-SNAPSHOT MXNet Scala Package - Native OSX-x86_64 CPU-only http://maven.apache.org diff --git a/scala-package/native/pom.xml b/scala-package/native/pom.xml index aed99a6777cd..e68ebb96666e 100644 --- a/scala-package/native/pom.xml +++ b/scala-package/native/pom.xml @@ -6,12 +6,11 @@ ml.dmlc.mxnet mxnet-parent_2.11 - 0.9.5-SNAPSHOT + ${project.version} ../pom.xml mxnet-scala-native-parent - 0.9.5-SNAPSHOT MXNet Scala Package - Native Parent pom diff --git a/scala-package/native/src/main/native/jni_helper_func.h b/scala-package/native/src/main/native/jni_helper_func.h index d1abd93d92e7..009bbec64e66 100644 --- a/scala-package/native/src/main/native/jni_helper_func.h +++ b/scala-package/native/src/main/native/jni_helper_func.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file jni_helper_func.h * \brief Helper functions for operating JVM objects */ diff --git a/scala-package/native/src/main/native/ml_dmlc_mxnet_native_c_api.cc b/scala-package/native/src/main/native/ml_dmlc_mxnet_native_c_api.cc index 3accefcbffe6..166f6b71eb9f 100644 --- a/scala-package/native/src/main/native/ml_dmlc_mxnet_native_c_api.cc +++ b/scala-package/native/src/main/native/ml_dmlc_mxnet_native_c_api.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file ml_dmlc_mxnet_native_c_api.cc * \brief JNI function implementations */ @@ -654,6 +672,30 @@ JNIEXPORT jint JNICALL Java_ml_dmlc_mxnet_LibInfo_mxKVStoreInit return ret; } +JNIEXPORT jint JNICALL Java_ml_dmlc_mxnet_LibInfo_mxKVStoreInitEx + (JNIEnv *env, jobject obj, jlong kvStorePtr, jint len, jobjectArray keys, jlongArray values) { + const char **keyArray = new const char *[len]; + for (int i = 0; i < len; i++) { + jstring jkey = reinterpret_cast(env->GetObjectArrayElement(keys, i)); + const char *key = env->GetStringUTFChars(jkey, 0); + keyArray[i] = key; + env->DeleteLocalRef(jkey); + } + jlong *valueArray = env->GetLongArrayElements(values, NULL); + int ret = MXKVStoreInitEx(reinterpret_cast(kvStorePtr), + static_cast(len), + keyArray, + reinterpret_cast(valueArray)); + env->ReleaseLongArrayElements(values, valueArray, 0); + for (int i = 0; i < len; i++) { + jstring jkey = reinterpret_cast(env->GetObjectArrayElement(keys, i)); + env->ReleaseStringUTFChars(jkey, keyArray[i]); + env->DeleteLocalRef(jkey); + } + delete[] keyArray; + return ret; +} + JNIEXPORT jint JNICALL Java_ml_dmlc_mxnet_LibInfo_mxKVStorePush (JNIEnv *env, jobject obj, jlong kvStorePtr, jint len, jintArray keys, jlongArray values, jint priority) { @@ -664,11 +706,36 @@ JNIEXPORT jint JNICALL Java_ml_dmlc_mxnet_LibInfo_mxKVStorePush static_cast(keyArray), reinterpret_cast(valueArray), priority); - env->ReleaseIntArrayElements(keys, keyArray, 0); env->ReleaseLongArrayElements(values, valueArray, 0); return ret; } +JNIEXPORT jint JNICALL Java_ml_dmlc_mxnet_LibInfo_mxKVStorePushEx + (JNIEnv *env, jobject obj, jlong kvStorePtr, jint len, jobjectArray keys, + jlongArray values, jint priority) { + const char **keyArray = new const char *[len]; + for (int i = 0; i < len; i++) { + jstring jkey = reinterpret_cast(env->GetObjectArrayElement(keys, i)); + const char *key = env->GetStringUTFChars(jkey, 0); + keyArray[i] = key; + env->DeleteLocalRef(jkey); + } + jlong *valueArray = env->GetLongArrayElements(values, NULL); + int ret = MXKVStorePushEx(reinterpret_cast(kvStorePtr), + static_cast(len), + keyArray, + reinterpret_cast(valueArray), + priority); + env->ReleaseLongArrayElements(values, valueArray, 0); + for (int i = 0; i < len; i++) { + jstring jkey = reinterpret_cast(env->GetObjectArrayElement(keys, i)); + env->ReleaseStringUTFChars(jkey, keyArray[i]); + env->DeleteLocalRef(jkey); + } + delete[] keyArray; + return ret; +} + JNIEXPORT jint JNICALL Java_ml_dmlc_mxnet_LibInfo_mxKVStorePull (JNIEnv *env, jobject obj, jlong kvStorePtr, jint len, jintArray keys, jlongArray outs, jint priority) { @@ -684,6 +751,32 @@ JNIEXPORT jint JNICALL Java_ml_dmlc_mxnet_LibInfo_mxKVStorePull return ret; } +JNIEXPORT jint JNICALL Java_ml_dmlc_mxnet_LibInfo_mxKVStorePullEx + (JNIEnv *env, jobject obj, jlong kvStorePtr, jint len, jobjectArray keys, + jlongArray outs, jint priority) { + const char **keyArray = new const char *[len]; + for (int i = 0; i < len; i++) { + jstring jkey = reinterpret_cast(env->GetObjectArrayElement(keys, i)); + const char *key = env->GetStringUTFChars(jkey, 0); + keyArray[i] = key; + env->DeleteLocalRef(jkey); + } + jlong *outArray = env->GetLongArrayElements(outs, NULL); + int ret = MXKVStorePullEx(reinterpret_cast(kvStorePtr), + static_cast(len), + keyArray, + reinterpret_cast(outArray), + priority); + env->ReleaseLongArrayElements(outs, outArray, 0); + for (int i = 0; i < len; i++) { + jstring jkey = reinterpret_cast(env->GetObjectArrayElement(keys, i)); + env->ReleaseStringUTFChars(jkey, keyArray[i]); + env->DeleteLocalRef(jkey); + } + delete[] keyArray; + return ret; +} + JNIEXPORT jint JNICALL Java_ml_dmlc_mxnet_LibInfo_mxKVStoreGetType (JNIEnv *env, jobject obj, jlong kvStorePtr, jobject kvType) { const char *type; @@ -1114,6 +1207,52 @@ JNIEXPORT jint JNICALL Java_ml_dmlc_mxnet_LibInfo_mxSymbolSetAttr return ret; } +JNIEXPORT jint JNICALL Java_ml_dmlc_mxnet_LibInfo_mxSymbolListAttrShallow + (JNIEnv *env, jobject obj, jlong symbolPtr, jobject joutSize, jobject jout) { + mx_uint outSize; + const char** out; + + int ret = MXSymbolListAttrShallow(reinterpret_cast(symbolPtr), &outSize, &out); + + jclass refIntClass = env->FindClass("ml/dmlc/mxnet/Base$RefInt"); + jfieldID valueInt = env->GetFieldID(refIntClass, "value", "I"); + env->SetIntField(joutSize, valueInt, static_cast(outSize)); + + jclass arrayClass = env->FindClass("scala/collection/mutable/ArrayBuffer"); + jmethodID arrayAppend = env->GetMethodID(arrayClass, + "$plus$eq", "(Ljava/lang/Object;)Lscala/collection/mutable/ArrayBuffer;"); + for (size_t i = 0; i < outSize * 2; ++i) { + jstring jtmp = env->NewStringUTF(out[i]); + env->CallObjectMethod(jout, arrayAppend, jtmp); + env->DeleteLocalRef(jtmp); + } + + return ret; +} + +JNIEXPORT jint JNICALL Java_ml_dmlc_mxnet_LibInfo_mxSymbolListAttr + (JNIEnv *env, jobject obj, jlong symbolPtr, jobject joutSize, jobject jout) { + mx_uint outSize; + const char** out; + + int ret = MXSymbolListAttr(reinterpret_cast(symbolPtr), &outSize, &out); + + jclass refIntClass = env->FindClass("ml/dmlc/mxnet/Base$RefInt"); + jfieldID valueInt = env->GetFieldID(refIntClass, "value", "I"); + env->SetIntField(joutSize, valueInt, static_cast(outSize)); + + jclass arrayClass = env->FindClass("scala/collection/mutable/ArrayBuffer"); + jmethodID arrayAppend = env->GetMethodID(arrayClass, + "$plus$eq", "(Ljava/lang/Object;)Lscala/collection/mutable/ArrayBuffer;"); + for (size_t i = 0; i < outSize * 2; ++i) { + jstring jtmp = env->NewStringUTF(out[i]); + env->CallObjectMethod(jout, arrayAppend, jtmp); + env->DeleteLocalRef(jtmp); + } + + return ret; +} + JNIEXPORT jint JNICALL Java_ml_dmlc_mxnet_LibInfo_mxSymbolCompose (JNIEnv *env, jobject obj, jlong symbolPtr, jstring jname, jobjectArray jkeys, jlongArray jargs) { diff --git a/scala-package/pom.xml b/scala-package/pom.xml index 13e068babae4..7bfd8774de6b 100644 --- a/scala-package/pom.xml +++ b/scala-package/pom.xml @@ -5,7 +5,7 @@ 4.0.0 ml.dmlc.mxnet mxnet-parent_2.11 - 0.9.5-SNAPSHOT + ${project.version} MXNet Scala Package - Parent https://github.com/dmlc/mxnet/tree/master/scala-package MXNet Scala Package @@ -48,6 +48,7 @@ + 0.11.0-SNAPSHOT 2.11.8 2.11 diff --git a/scala-package/spark/README.md b/scala-package/spark/README.md index 08077f7548d7..974691650ff4 100644 --- a/scala-package/spark/README.md +++ b/scala-package/spark/README.md @@ -71,7 +71,7 @@ val res = valData.mapPartitions { data => val probArrays = brModel.value.predict(points.toIterator) require(probArrays.length == 1) val prob = probArrays(0) - val py = NDArray.argmaxChannel(prob.get) + val py = NDArray.argmax_channel(prob.get) val labels = py.toArray.mkString(",") py.dispose() prob.get.dispose() diff --git a/scala-package/spark/bin/run-mnist-example.sh b/scala-package/spark/bin/run-mnist-example.sh index dc2f3adbe7ac..cae19386a8ee 100755 --- a/scala-package/spark/bin/run-mnist-example.sh +++ b/scala-package/spark/bin/run-mnist-example.sh @@ -1,4 +1,22 @@ #!/bin/bash + +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + CURR_DIR=$(cd `dirname $0`; pwd) MODULE_DIR=$(cd $CURR_DIR/../; pwd) ROOT_DIR=$(cd $CURR_DIR/../../; pwd) diff --git a/scala-package/spark/pom.xml b/scala-package/spark/pom.xml index 63cfb8c1f9db..18170b95579b 100644 --- a/scala-package/spark/pom.xml +++ b/scala-package/spark/pom.xml @@ -6,12 +6,11 @@ ml.dmlc.mxnet mxnet-parent_2.11 - 0.9.5-SNAPSHOT + ${project.version} ../pom.xml mxnet-spark_2.11 - 0.9.5-SNAPSHOT MXNet Scala Package - Spark ML diff --git a/scala-package/spark/src/main/scala/ml/dmlc/mxnet/spark/MXNet.scala b/scala-package/spark/src/main/scala/ml/dmlc/mxnet/spark/MXNet.scala index 17bed7e19b2e..27dd99f07233 100644 --- a/scala-package/spark/src/main/scala/ml/dmlc/mxnet/spark/MXNet.scala +++ b/scala-package/spark/src/main/scala/ml/dmlc/mxnet/spark/MXNet.scala @@ -20,9 +20,12 @@ package ml.dmlc.mxnet.spark import ml.dmlc.mxnet._ import ml.dmlc.mxnet.optimizer.SGD import ml.dmlc.mxnet.spark.io.LabeledPointIter + +import org.slf4j.{Logger, LoggerFactory} + import org.apache.spark.mllib.regression.LabeledPoint import org.apache.spark.rdd.RDD -import org.slf4j.{Logger, LoggerFactory} +import org.apache.spark.SparkContext /** * MXNet Training On Spark @@ -102,25 +105,10 @@ class MXNet extends Serializable { this } - def fit(data: RDD[LabeledPoint]): MXNetModel = { - val sc = data.context - // distribute native jars - params.jars.foreach(jar => sc.addFile(jar)) - - val trainData = { - if (params.numWorker > data.partitions.length) { - logger.info("repartitioning training set to {} partitions", params.numWorker) - data.repartition(params.numWorker) - } else if (params.numWorker < data.partitions.length) { - logger.info("repartitioning training set to {} partitions", params.numWorker) - data.coalesce(params.numWorker) - } else { - data - } - } - - val schedulerIP = utils.Network.ipAddress - val schedulerPort = utils.Network.availablePort + private def startParameterServers( + schedulerIP: String, + schedulerPort: Int, + sc: SparkContext): ParameterServer = { // TODO: check ip & port available logger.info("Starting scheduler on {}:{}", schedulerIP, schedulerPort) val scheduler = new ParameterServer(params.runtimeClasspath, role = "scheduler", @@ -140,14 +128,58 @@ class MXNet extends Serializable { java = params.javabin) require(server.startProcess(), "Failed to start ps server process") } + scheduler + } + + private def setFeedForwardModel( + optimizer: Optimizer, + numExamples: Int, + kv: KVStore, + inputInPartition: LabeledPointIter): FeedForward = { + logger.debug("Define model") + val model = new FeedForward(ctx = params.context, + symbol = params.getNetwork, + numEpoch = params.numEpoch, + optimizer = optimizer, + initializer = new Xavier(factorType = "in", magnitude = 2.34f), + argParams = null, + auxParams = null, + beginEpoch = 0, + epochSize = numExamples / params.batchSize / kv.numWorkers) + logger.info("Start training ...") + model.fit(trainData = inputInPartition, + evalData = null, + evalMetric = new Accuracy(), + kvStore = kv) + model + } + + private def setupKVStore(schedulerIP: String, schedulerPort: Int): KVStore = { + KVStoreServer.init(ParameterServer.buildEnv(role = "worker", + rootUri = schedulerIP, rootPort = schedulerPort, + numServer = params.numServer, + numWorker = params.numWorker)) + val kv = KVStore.create("dist_async") + kv.setBarrierBeforeExit(false) + kv + } + + private def reclaimResources(dataIter: LabeledPointIter, kv: KVStore): Unit = { + dataIter.dispose() + kv.setBarrierBeforeExit(true) + kv.dispose() + } + private def trainModel( + trainData: RDD[LabeledPoint], + schedulerIP: String, + schedulerPort: Int): MXNetModel = { val job = trainData.mapPartitions { partition => val dataIter = new LabeledPointIter( partition, params.dimension, params.batchSize, dataName = params.dataName, labelName = params.labelName) - // TODO: more nature way to get the # of examples? var numExamples = 0 while (dataIter.hasNext) { @@ -161,46 +193,40 @@ class MXNet extends Serializable { logger.info("Batch {}", params.batchSize) // give enough time for ps-lite to detect the dead nodes Thread.sleep(20000) - KVStoreServer.init(ParameterServer.buildEnv(role = "worker", - rootUri = schedulerIP, rootPort = schedulerPort, - numServer = params.numServer, - numWorker = params.numWorker)) - val kv = KVStore.create("dist_async") - kv.setBarrierBeforeExit(false) - - val optimizer: Optimizer = new SGD(learningRate = 0.01f, - momentum = 0.9f, wd = 0.00001f) - - logger.debug("Define model") - val model = new FeedForward(ctx = params.context, - symbol = params.getNetwork, - numEpoch = params.numEpoch, - optimizer = optimizer, - initializer = new Xavier(factorType = "in", magnitude = 2.34f), - argParams = null, - auxParams = null, - beginEpoch = 0, - epochSize = numExamples / params.batchSize / kv.numWorkers) - logger.info("Start training ...") - model.fit(trainData = dataIter, - evalData = null, - evalMetric = new Accuracy(), - kvStore = kv) - + val kv = setupKVStore(schedulerIP, schedulerPort) + val optimizer = new SGD(learningRate = 0.01f, momentum = 0.9f, wd = 0.00001f) + val model = setFeedForwardModel(optimizer, numExamples, kv, dataIter) logger.info("Training finished, waiting for other workers ...") - dataIter.dispose() - kv.setBarrierBeforeExit(true) - kv.dispose() + reclaimResources(dataIter, kv) Iterator(new MXNetModel( model, params.dimension, params.batchSize, dataName = params.dataName, labelName = params.labelName)) }.cache() - // force job to run job.foreachPartition(() => _) - // simply the first model - val mxModel = job.first() + job.first() + } + def fit(data: RDD[LabeledPoint]): MXNetModel = { + val sc = data.context + // distribute native jars + params.jars.foreach(jar => sc.addFile(jar)) + val trainData = { + if (params.numWorker > data.partitions.length) { + logger.info("repartitioning training set to {} partitions", params.numWorker) + data.repartition(params.numWorker) + } else if (params.numWorker < data.partitions.length) { + logger.info("repartitioning training set to {} partitions", params.numWorker) + data.coalesce(params.numWorker) + } else { + data + } + } + val schedulerIP = utils.Network.ipAddress + val schedulerPort = utils.Network.availablePort + val scheduler = startParameterServers(schedulerIP, schedulerPort, sc) + // simply the first model + val mxModel = trainModel(trainData, schedulerIP, schedulerPort) logger.info("Waiting for scheduler ...") scheduler.waitFor() mxModel diff --git a/setup-utils/install-mxnet-amz-linux.sh b/setup-utils/install-mxnet-amz-linux.sh index b8564a56ed3d..66788a984da6 100644 --- a/setup-utils/install-mxnet-amz-linux.sh +++ b/setup-utils/install-mxnet-amz-linux.sh @@ -1,4 +1,22 @@ #!/usr/bin/env bash + +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + ###################################################################### # This script installs MXNet for Python along with all required dependencies on a Amazon Linux Machine. ###################################################################### diff --git a/setup-utils/install-mxnet-fedora-python.sh b/setup-utils/install-mxnet-fedora-python.sh index 54b716b911db..86116665db88 100644 --- a/setup-utils/install-mxnet-fedora-python.sh +++ b/setup-utils/install-mxnet-fedora-python.sh @@ -1,4 +1,22 @@ #!/usr/bin/env bash + +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + ###################################################################### # This script installs MXNet for Python along with all required dependencies on a Fedora Machine. # Tested on Fedora 21.0 + distro. @@ -24,8 +42,8 @@ sudo yum install numpy echo "Installing Python setuptools..." sudo yum install -y python-setuptools python-pip -echo "Adding MXNet path to your ~/.bashrc file" -echo "export PYTHONPATH=$MXNET_HOME/python:$PYTHONPATH" >> ~/.bashrc +echo "Adding MXNet path to your ~/.bashrc file" +echo "export PYTHONPATH=$MXNET_HOME/python:$PYTHONPATH" >> ~/.bashrc source ~/.bashrc echo "Install Graphviz for plotting MXNet network graph..." diff --git a/setup-utils/install-mxnet-osx-python.sh b/setup-utils/install-mxnet-osx-python.sh old mode 100644 new mode 100755 index 62db552fe087..8bfb7dade7b1 --- a/setup-utils/install-mxnet-osx-python.sh +++ b/setup-utils/install-mxnet-osx-python.sh @@ -1,4 +1,22 @@ #!/bin/bash + +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # # This scripts installs the dependencies and compiles # MXNet source. @@ -8,32 +26,151 @@ #set -ex +export MXNET_GITPATH="https://github.com/dmlc/mxnet.git" +if [ -z ${MXNET_TAG} ]; +then + # + # TODO: Change this to latest tag + # to avoid updating this value for every release + # + export MXNET_TAG="v0.10.0" +fi + export TARIKH=`/bin/date +%Y-%m-%d-%H:%M:%S` -export MXNET_HOME="$HOME/mxnet" +if [ -z ${MXNET_HOME} ]; +then + export MXNET_HOME="$HOME/mxnet" +fi export MXNET_HOME_OLD="$HOME/mxnet_${TARIKH}" export MXNET_LOG=${MXNET_HOME}/buildMXNet_mac.log + # Insert the Homebrew directory at the top of your PATH environment variable -export PATH=/usr/local/bin:/usr/local/sbin:$PATH +export PATH="$PATH:/usr/local/bin:/usr/local/sbin" # for brew +export PATH="$PATH:/usr/bin:/opt/local/bin" # for macports + +export MACPORTS_WEB="https://guide.macports.org/chunked/installing.macports.html" + +export BREW_PKGS="pkg-config python opencv graphviz homebrew/science/openblas" +export PORT_PKGS="pkgconfig python36 opencv graphviz openblas-devel" + +# graphviz, opencv-python skipped since already installed via brew/port +export PIP_PKGS_ALL="cython numpy" +export PIP_PKGS_USER="requests jupyter" + +export SLEEP_TIME=2 LINE="########################################################################" -echo $LINE -echo " " -echo "This script installs MXNet on MacOS in ${MXNET_HOME}" -echo "If this directory is already present, it is renamed to ${MXNET_HOME_OLD}" -echo "It has been tested to work successfully on MacOS El Capitan and Sierra" -echo "and is expected to work fine on other versions as well." -echo " " -echo "Approximate run-time is around 5 minutes." -echo " " -echo $LINE -sleep 2 +print_intro_msg() { + # + # NOTE: Please test and ensure that the message does NOT scroll + # beyond the standard 80x25 format of a terminal shell. + # + echo $LINE + echo " " + echo "MXNet is a flexible, efficient and scalable library for Deep Learning." + echo " " + echo "This script installs MXNet on MacOS in \${MXNET_HOME}" + echo "If not set, the default value of \${MXNET_HOME} = ~/mxnet" + echo "The current value of \${MXNET_HOME} = ${MXNET_HOME}" + echo " " + echo "If this directory is already present, it is renamed to retain earlier contents." + echo "You may want to check and delete this directory if not required." + echo " " + echo "This script has been tested on: MacOS El Capitan (10.11) and Sierra (10.12)" + echo " " + echo "If you face any problems with this script, please let us know at:" + echo " https://stackoverflow.com/questions/tagged/mxnet" + echo " " + echo "Typical run-time for this script is around 10 minutes." + echo "If your environment has never been setup for development (e.g. gcc), " + echo "it could take up to 30 minutes or longer." + echo " " + MACOS_VERSION=`/usr/bin/uname -r` + echo "Your macOS version is: $MACOS_VERSION" + echo " " + echo $LINE + echo " " + sleep ${SLEEP_TIME} +} # print_intro_msg() -# -# Install dependencies for MXNet -# +# wrapper routine to stop the script if the command invoked returns error +chkret() { + cmd=$* + echo "$cmd" + $cmd + ret=$? + if [[ ${ret} != 0 ]]; then + echo " " + echo "ERROR: Return value non-zero for: $cmd" + echo " " + exit 1 + fi +} # chkret() + +chk_mac_vers() { + export mac_vers=`sw_vers -productVersion | cut -d '.' -f 1,2` + if [[ $mac_vers != "10.11" && $mac_vers != "10.12" ]]; + then + echo " " + echo "ERROR: macOS version $mac_vers NOT supported." + echo " " + echo "Your macOS version is:" + sw_vers + echo " " + exit 1 + fi +} # chk_mac_vers() + +install_brew() { + echo " " + while true; do + echo "This script will install/update brew and " + echo "following dependent packages required for MXNet." + echo " Dependent brew packages: ${BREW_PKGS}" + echo " Dependent pip packages: ${PIP_PKGS_ALL} ${PIP_PKGS_USER}" + read -p "Do you want to continue? (y/n): " response + echo " " + case $response in + [Yy]* ) break;; + [Nn]* ) exit;; + * ) echo "Please answer yes or no.";; + esac + done + + echo " " + echo "BEGIN: Check/Install/Update Homebrew" + BREW_PATH=`which brew` + if [[ (-z ${BREW_PATH}) || (! -f ${BREW_PATH}) ]]; + then + yes '' | /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" + ret=$? + if [[ ${ret} != 0 ]]; then + echo " " + echo "ERROR: Return value non-zero for: homebrew installation using ruby" + echo " " + exit 1 + fi + else + chkret brew update + fi + echo "END: Check/Install/Update Homebrew" + echo $LINE + echo " " + + echo "BEGIN: Install dependent brew packages for MXNet: ${BREW_PKGS}" + + chkret brew tap homebrew/science -# Install Homebrew -yes '' | /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" + # install each individually to see progress for each + for pkg in ${BREW_PKGS} + do + chkret brew_pkg_install ${pkg} + done + + echo "END: Install dependent brew packages for MXNet: ${BREW_PKGS}" + echo $LINE + echo " " +} # install_brew() brew_pkg_install () { pkg=$1 @@ -45,97 +182,316 @@ brew_pkg_install () { else echo "$pkg already installed" fi -} +} # brew_pkg_install -runme() { - cmd=$* - echo "$cmd" - $cmd - ret=$? - if [[ ${ret} != 0 ]]; then +install_port () { + echo " " + while true; do + echo "This script will install/update port and " + echo "following dependent packages required for MXNet." + echo " Dependent port packages: ${PORT_PKGS}" + echo " Dependent pip packages: ${PIP_PKGS_ALL} ${PIP_PKGS_USER}" + read -p "Do you want to continue? (y/n): " response echo " " - echo "ERROR: Return value non-zero for: $cmd" + case $response in + [Yy]* ) break;; + [Nn]* ) exit;; + * ) echo "Please answer yes or no.";; + esac + done + + echo " " + echo "BEGIN: Check/Install/Update port" + MACPORTS_PATH=`which port` + if [[ (-z ${MACPORTS_PATH}) || (! -f ${MACPORTS_PATH}) ]]; + then + echo " " + echo "ERROR: Please install port for your macOS version from:" + echo " " + echo $MACPORTS_WEB echo " " exit 1 + else + echo "NOTE: Updating port if required" + export SLEEP_TIME=2 + sudo port upgrade outdated + echo " " + echo "port version is:" + port version + echo " " + fi + echo "END: Check/Install/Update port" + echo $LINE + echo " " + + echo "BEGIN: Install dependent port packages for MXNet: ${PORT_PKGS}" + echo " " + #sudo port install python36-readline + # install each individually to see progress for each + for pkg in ${PORT_PKGS} + do + chkret sudo port install ${pkg} + done + if [[ ! -f /opt/local/include/cblas.h ]]; + then + sudo ln -s /opt/local/include/cblas_openblas.h /opt/local/include/cblas.h + fi + #if [[ ! -f /usr/local/opt/openblas/lib/libopenblas.a ]]; + #then + # sudo mkdir -p /usr/local/opt/openblas/lib + # sudo ln -s /opt/local/lib/libopenblas.a /usr/local/opt/openblas/lib/libopenblas.a + #fi + + echo " " + echo "END: Install dependent port packages for MXNet: ${PORT_PKGS}" + echo $LINE + echo " " +} # install_port + +install_mac_pkg_manager() { + BREW_PATH=`which brew` + if [[ (-z ${BREW_PATH}) || (! -f ${BREW_PATH}) ]]; + then + echo "NOTE: brew NOT installed" + export MAC_BREW=0 + else + echo "NOTE: brew installed" + export MAC_BREW=1 + export PKG_MGR="brew" + fi + + MACPORTS_PATH=`which port` + if [[ (-z ${MACPORTS_PATH}) || (! -f ${MACPORTS_PATH}) ]]; + then + echo "NOTE: port NOT installed" + export MAC_PORT=0 + else + echo "NOTE: port installed" + export MAC_PORT=1 + export PKG_MGR="port" + fi + + if [[ $MAC_PORT -eq 1 && $MAC_BREW -eq 1 ]]; + then + echo "NOTE: Both port and brew installed" + export MAC_PKG_ASK=1 + export PKG_MGR="" + elif [[ $MAC_PORT -eq 0 && $MAC_BREW -eq 0 ]]; + then + echo "NOTE: Neither port and brew installed" + export MAC_PKG_ASK=1 + export PKG_MGR="" + else + export MAC_PKG_ASK=0 + + while true; do + echo "NOTE: Using the already installed package manager: $PKG_MGR" + read -p "Do you want to continue? (y/n): " response + echo " " + case $response in + [Yy]* ) break;; + [Nn]* ) exit;; + * ) echo "Please answer yes or no.";; + esac + done fi -} + + if [[ $MAC_PKG_ASK -eq 1 ]]; + then + export MAC_BREW=0 + export MAC_PORT=0 + while true; do + echo " " + echo "NOTE: This script supports Homebrew OR Port package manager." + echo " " + read -p "Which package manager do you want to use? (b/p): " pkg_mgr + echo " " + case $pkg_mgr in + [Bb]* ) export MAC_BREW=1; break;; + [Pp]* ) export MAC_PORT=1; break;; + * ) echo "Please answer: b or p";; + esac + done + fi + + if [[ $MAC_PORT -eq 1 ]]; + then + install_port + else + install_brew + fi +} # install_mac_pkg_manager + +install_dep_pip_for_mxnet() { + echo " " + echo "BEGIN: Install dependent pip packages for MXNet: " + echo "${PIP_PKGS_ALL} ${PIP_PKGS_USER}" + echo " " + + # NOTE: sudo used here + chkret sudo easy_install pip + chkret sudo pip install --upgrade pip + for pkg in ${PIP_PKGS_ALL} + do + chkret sudo pip install ${pkg} + done + #chkret sudo pip install --upgrade numpy + + # NOTE: no sudo used here + for pkg in ${PIP_PKGS_USER} + do + chkret pip install --user ${pkg} + done + + echo "END: Install dependent pip packages for MXNet: ${PIP_PKGS_ALL} ${PIP_PKGS_USER}" + echo $LINE + echo " " +} # install_dep_pip_for_mxnet() download_mxnet() { + echo " " + echo "BEGIN: Download MXNet" if [ -d ${MXNET_HOME} ]; then - echo "Renaming directory ${MXNET_HOME} to ${MXNET_HOME_OLD}" mv ${MXNET_HOME} ${MXNET_HOME_OLD} + echo " " + echo "Renamed directory ${MXNET_HOME} to ${MXNET_HOME_OLD}" + echo "You may want to check and delete this directory if not required." + echo " " + sleep ${SLEEP_TIME} fi - echo "Downloading MXNET source repositories from github" - git clone https://github.com/dmlc/mxnet.git ${MXNET_HOME} --recursive -} - -download_mxnet -runme brew update -runme brew_pkg_install pkg-config -runme brew_pkg_install python -brew install homebrew/science/openblas -runme brew_pkg_install opencv -# Needed for /usr/local/lib/graphviz to be created -runme brew_pkg_install graphviz -runme brew_pkg_install numpy - -runme brew tap homebrew/science - -runme pip install graphviz -runme pip install jupyter -runme pip install cython -# -# Compile MXNet. It assumes you have checked out MXNet source to ~/mxnet -# + echo " " + echo "MXNET GIT Path = ${MXNET_GITPATH}" + #echo "MXNET Tag = ${MXNET_TAG}" + #echo "You can set \$MXNET_TAG to the appropriate github repo tag" + #echo "If not set, the default value used is the latest release" + echo " " + sleep ${SLEEP_TIME} -cd ${MXNET_HOME} -runme cp make/osx.mk ./config.mk -runme echo "USE_BLAS = openblas" >> ./config.mk -runme echo "ADD_CFLAGS += -I/usr/local/opt/openblas/include" >> ./config.mk -runme echo "ADD_LDFLAGS += -L/usr/local/opt/openblas/lib" >> ./config.mk -runme echo "ADD_LDFLAGS += -L/usr/local/lib/graphviz/" >> ./config.mk -echo " " -echo "Running Make" -echo " " -runme make -j$(sysctl -n hw.ncpu) + chkret git clone ${MXNET_GITPATH} ${MXNET_HOME} --recursive + sleep ${SLEEP_TIME} + cd ${MXNET_HOME} + echo " " + #echo "Checkout tag = ${MXNET_TAG}" + #chkret git checkout ${MXNET_TAG} + #echo " " + sleep ${SLEEP_TIME} + echo "END: Download MXNet" + echo $LINE + echo " " +} # download_mxnet -# -# Install MXNet package for Python -# -echo "Installing MXNet package for Python..." -runme cd ${MXNET_HOME}/python -runme sudo python setup.py install +compile_mxnet() { + # Compile MXNet: It assumes MXNet source is in ${MXNET_HOME} + echo "BEGIN: Compile MXNet" + cd ${MXNET_HOME} + chkret cp make/osx.mk ./config.mk.tmp -# -# Test MXNet -# -echo "Testing MXNet now..." -python << END > mxnet_test.log -import mxnet as mx -a = mx.nd.ones((2, 3)); -print ((a*2).asnumpy()); -END -cat << END > mxnet_test.expected -[[ 2. 2. 2.] - [ 2. 2. 2.]] -END -diff mxnet_test.log mxnet_test.expected -if [[ $? = 0 ]]; then - echo $LINE + touch ./config.mk + # rm any old setting of USE_BLAS, if present in config file + egrep -v "^USE_BLAS" ./config.mk.tmp >> ./config.mk + # add the new setting of USE_BLAS to the config file + echo "USE_BLAS = openblas" >> ./config.mk + + if [[ $MAC_PORT -eq 1 ]]; + then + echo "ADD_CFLAGS += -I/opt/local/lib" >> ./config.mk + echo "ADD_LDFLAGS += -L/opt/local/lib" >> ./config.mk + echo "ADD_LDFLAGS += -L/opt/local/lib/graphviz/" >> ./config.mk + else + echo "ADD_CFLAGS += -I/usr/local/opt/openblas/include" >> ./config.mk + echo "ADD_LDFLAGS += -L/usr/local/opt/openblas/lib" >> ./config.mk + echo "ADD_LDFLAGS += -L/usr/local/lib/graphviz/" >> ./config.mk + fi + echo " " + + echo "NOTE: The following compile-time configurations will be used." + echo " If you want to change any of them, edit the following file" + echo " in another terminal window and then press enter to continue." echo " " - echo "SUCCESS: MXNet test passed" - echo "SUCCESS: MXNet is successfully installed and works fine!" - echo ":-)" | banner -w 40 + echo " ${MXNET_HOME}/config.mk" echo " " echo $LINE - exit 0 -else + # remove commented and blank lines + egrep -v "^#" ${MXNET_HOME}/config.mk | egrep -v "^$" echo $LINE echo " " - echo "ERROR: MXNet test failed" - echo ":-(" | banner -w 40 + read -p "Press enter to continue ..." + echo " " + echo "Running Make" echo " " + chkret make -j$(sysctl -n hw.ncpu) + echo "END: Compile MXNet" + sleep ${SLEEP_TIME} echo $LINE - exit 1 -fi + echo " " +} # compile_mxnet + +install_mxnet_python() { + echo " " + echo "BEGIN: Install MXNet package for Python" + chkret cd ${MXNET_HOME}/python + chkret sudo python setup.py install + echo "END: Install MXNet package for Python" + sleep ${SLEEP_TIME} + echo $LINE + echo " " +} # install_mxnet_python + + +test_mxnet_python() { + echo "BEGIN: Test MXNet" + rm -f mxnet_test.log + python << END > mxnet_test.log +import mxnet as mx +a = mx.nd.ones((2, 3)); +print ((a*2).asnumpy()); +END + rm -f mxnet_test.expected + cat << END > mxnet_test.expected +[[ 2. 2. 2.] + [ 2. 2. 2.]] +END + diff mxnet_test.log mxnet_test.expected + if [[ $? = 0 ]]; then + echo " " + echo "SUCCESS: MXNet test passed" + echo "SUCCESS: MXNet is successfully installed and works fine!" + export MXNET_VERSION=`echo "import mxnet as mx; print(mx.__version__)" | python` + echo "SUCCESS: MXNet Version is: $MXNET_VERSION" + echo "END: Test MXNet" + echo ":-)" + echo " " + echo "FYI : You can fine-tune MXNet run-time behavior using environment variables described at:" + echo " http://mxnet.io/how_to/env_var.html" + echo " " + echo "NEXT: Try the MNIST tutorial at: http://mxnet.io/tutorials/python/mnist.html" + echo " Try other tutorials at : http://mxnet.io/tutorials" + echo " " + echo $LINE + echo " " + rm -f mxnet_test.log mxnet_test.expected + exit 0 + else + echo " " + echo "ERROR: Following files differ: mxnet_test.log mxnet_test.expected" + echo "ERROR: MXNet test failed" + echo "END: Test MXNet" + echo " " + echo ":-(" + exit 1 + fi +} # test_mxnet_python() + +main() { + print_intro_msg + chk_mac_vers + install_mac_pkg_manager + install_dep_pip_for_mxnet + download_mxnet + compile_mxnet + install_mxnet_python + test_mxnet_python +} # main + +main diff --git a/setup-utils/install-mxnet-ubuntu-python.sh b/setup-utils/install-mxnet-ubuntu-python.sh index 345b669498e8..8aa0d0256a79 100644 --- a/setup-utils/install-mxnet-ubuntu-python.sh +++ b/setup-utils/install-mxnet-ubuntu-python.sh @@ -1,4 +1,22 @@ #!/usr/bin/env bash + +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + ###################################################################### # This script installs MXNet for Python along with all required dependencies on a Ubuntu Machine. # Tested on Ubuntu 14.0 + distro. @@ -19,9 +37,12 @@ make -j$(nproc) echo "Installing Numpy..." sudo apt-get install python-numpy -echo "Installing Python setuptools..." +echo "Installing Python setuptools pip..." sudo apt-get install -y python-setuptools python-pip +echo "Updating pip..." +sudo pip install -U pip + echo "Installing Python package for MXNet..." cd python; sudo python setup.py install diff --git a/setup-utils/install-mxnet-ubuntu-r.sh b/setup-utils/install-mxnet-ubuntu-r.sh index 5f1b04daef05..ca46d7b37016 100644 --- a/setup-utils/install-mxnet-ubuntu-r.sh +++ b/setup-utils/install-mxnet-ubuntu-r.sh @@ -1,9 +1,24 @@ #!/usr/bin/env bash + +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + ###################################################################### # This script installs MXNet for R along with all required dependencies on a Ubuntu Machine. -# We recommend to install Microsoft RServer together with Intel MKL library for optimal performance -# More information can be found here: -# https://blogs.technet.microsoft.com/machinelearning/2016/09/15/building-deep-neural-networks-in-the-cloud-with-azure-gpu-vms-mxnet-and-microsoft-r-server/ # Tested on Ubuntu 14.04+ distro. ###################################################################### set -e @@ -22,6 +37,9 @@ is_rscript_installed=$(which Rscript | wc -l) if [ "$is_rscript_installed" = "0" ]; then read -p "Seems like Rscript is not installed. Install Rscript? [Y/n]" if [ x"$REPLY" = x"" -o x"$REPLY" = x"y" -o x"$REPLY" = x"Y" ]; then + sudo add-apt-repository -y "deb http://cran.rstudio.com/bin/linux/ubuntu `lsb_release -cs`/" + sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys E084DAB9 + sudo apt-get -qq update sudo apt-get install -y r-base-core fi fi @@ -32,7 +50,7 @@ sudo apt-get -y install libcurl4-openssl-dev libssl-dev # Needed for R XML sudo apt-get install libxml2-dev -# Needed for R Cairo +# Needed for R Cairo sudo apt-get install libxt-dev sudo Rscript -e "install.packages('devtools', repo = 'https://cran.rstudio.com')" diff --git a/setup-utils/install-mxnet-windows-python.bat b/setup-utils/install-mxnet-windows-python.bat index 206c66c4c008..021baaeff331 100644 --- a/setup-utils/install-mxnet-windows-python.bat +++ b/setup-utils/install-mxnet-windows-python.bat @@ -1,3 +1,20 @@ +rem Licensed to the Apache Software Foundation (ASF) under one +rem or more contributor license agreements. See the NOTICE file +rem distributed with this work for additional information +rem regarding copyright ownership. The ASF licenses this file +rem to you under the Apache License, Version 2.0 (the +rem "License"); you may not use this file except in compliance +rem with the License. You may obtain a copy of the License at +rem +rem http://www.apache.org/licenses/LICENSE-2.0 +rem +rem Unless required by applicable law or agreed to in writing, +rem software distributed under the License is distributed on an +rem "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +rem KIND, either express or implied. See the License for the +rem specific language governing permissions and limitations +rem under the License. + @echo off setlocal :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: diff --git a/snapcraft.yaml b/snapcraft.yaml index 65713447dc64..27356c332a29 100644 --- a/snapcraft.yaml +++ b/snapcraft.yaml @@ -1,5 +1,5 @@ name: mxnet -version: '0.9.5' +version: '0.11.0' summary: MXNet is a deep learning framework designed for efficiency and flexibility. description: | MXNet is a deep learning framework designed for both efficiency and diff --git a/src/c_api/c_api.cc b/src/c_api/c_api.cc index ae7af5bad129..93458d21ac5a 100644 --- a/src/c_api/c_api.cc +++ b/src/c_api/c_api.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file c_api.cc * \brief C API of mxnet */ @@ -336,12 +354,16 @@ MXNET_DLL int MXNDArrayReshape(NDArrayHandle handle, int MXNDArrayGetShape(NDArrayHandle handle, mx_uint *out_dim, const mx_uint **out_pdata) { + MXAPIThreadLocalEntry *ret = MXAPIThreadLocalStore::Get(); API_BEGIN(); NDArray *arr = static_cast(handle); if (!arr->is_none()) { const TShape &s = arr->shape(); *out_dim = s.ndim(); - *out_pdata = s.data(); + std::vector& buffer = ret->arg_shape_buffer; + buffer.resize(s.ndim()); + nnvm::ShapeTypeCast(s.begin(), s.end(), buffer.data()); + *out_pdata = buffer.data(); } else { *out_dim = 0; } @@ -394,6 +416,40 @@ int MXNDArrayGetContext(NDArrayHandle handle, API_END(); } + +int MXNDArrayGetGrad(NDArrayHandle handle, NDArrayHandle *out) { + API_BEGIN(); + NDArray *arr = static_cast(handle); + NDArray ret = arr->grad(); + if (ret.is_none()) { + *out = NULL; + } else { + *out = new NDArray(ret); + } + API_END(); +} + +int MXNDArrayDetach(NDArrayHandle handle, NDArrayHandle *out) { + API_BEGIN(); + NDArray *arr = static_cast(handle); + *out = new NDArray(arr->Detach()); + API_END(); +} + +int MXNDArraySetGradState(NDArrayHandle handle, int state) { + API_BEGIN(); + NDArray *arr = static_cast(handle); + arr->set_fresh_out_grad(static_cast(state)); + API_END(); +} + +int MXNDArrayGetGradState(NDArrayHandle handle, int *out) { + API_BEGIN(); + NDArray *arr = static_cast(handle); + *out = arr->fresh_out_grad(); + API_END(); +} + int MXListFunctions(mx_uint *out_size, FunctionHandle **out_array) { API_BEGIN(); @@ -600,6 +656,21 @@ int MXKVStoreInit(KVStoreHandle handle, API_END(); } +int MXKVStoreInitEx(KVStoreHandle handle, + mx_uint num, + const char** keys, + NDArrayHandle* vals) { + API_BEGIN(); + std::vector v_keys(num); + std::vector v_vals(num); + for (mx_uint i = 0; i < num; ++i) { + v_keys[i] = keys[i]; + v_vals[i] = *static_cast(vals[i]); + } + static_cast(handle)->Init(v_keys, v_vals); + API_END(); +} + int MXKVStorePush(KVStoreHandle handle, mx_uint num, const int* keys, @@ -616,6 +687,22 @@ int MXKVStorePush(KVStoreHandle handle, API_END(); } +int MXKVStorePushEx(KVStoreHandle handle, + mx_uint num, + const char** keys, + NDArrayHandle* vals, + int priority) { + API_BEGIN(); + std::vector v_keys(num); + std::vector v_vals(num); + for (mx_uint i = 0; i < num; ++i) { + v_keys[i] = keys[i]; + v_vals[i] = *static_cast(vals[i]); + } + static_cast(handle)->Push(v_keys, v_vals, priority); + API_END(); +} + int MXKVStorePull(KVStoreHandle handle, mx_uint num, const int* keys, @@ -632,6 +719,22 @@ int MXKVStorePull(KVStoreHandle handle, API_END(); } +int MXKVStorePullEx(KVStoreHandle handle, + mx_uint num, + const char** keys, + NDArrayHandle* vals, + int priority) { + API_BEGIN(); + std::vector v_keys(num); + std::vector v_vals(num); + for (mx_uint i = 0; i < num; ++i) { + v_keys[i] = keys[i]; + v_vals[i] = static_cast(vals[i]); + } + static_cast(handle)->Pull(v_keys, v_vals, priority); + API_END(); +} + int MXKVStoreSetUpdater(KVStoreHandle handle, MXKVStoreUpdater updater, void* updater_handle) { @@ -902,6 +1005,6 @@ int MXRtcFree(RtcHandle handle) { int MXCustomOpRegister(const char* op_type, CustomOpPropCreator creator) { API_BEGIN(); - mxnet::op::CustomOpProp::Register(op_type, creator); + mxnet::op::custom::Registry::Get()->Register(op_type, creator); API_END(); } diff --git a/src/c_api/c_api_common.h b/src/c_api/c_api_common.h index e2e739ae62a4..846b53973b07 100644 --- a/src/c_api/c_api_common.h +++ b/src/c_api/c_api_common.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file c_api_error.h * \brief Error handling for C API. */ @@ -62,16 +80,24 @@ struct MXAPIThreadLocalEntry { std::vector arg_shape_ndim, out_shape_ndim, aux_shape_ndim; /*! \brief result holder for returning shape pointer */ std::vector arg_shape_data, out_shape_data, aux_shape_data; + /*! \brief uint32_t buffer for returning shape pointer */ + std::vector arg_shape_buffer, out_shape_buffer, aux_shape_buffer; // helper function to setup return value of shape array - inline static void SetupShapeArrayReturn( + inline static void SetupShapeArrayReturnWithBuffer( const std::vector &shapes, std::vector *ndim, - std::vector *data) { + std::vector *data, + std::vector *buffer) { ndim->resize(shapes.size()); data->resize(shapes.size()); + size_t size = 0; + for (const auto& s : shapes) size += s.ndim(); + buffer->resize(size); + uint32_t *ptr = buffer->data(); for (size_t i = 0; i < shapes.size(); ++i) { ndim->at(i) = shapes[i].ndim(); - data->at(i) = shapes[i].data(); + data->at(i) = ptr; + ptr = nnvm::ShapeTypeCast(shapes[i].begin(), shapes[i].end(), ptr); } } }; diff --git a/src/c_api/c_api_error.cc b/src/c_api/c_api_error.cc index 4ee6a35363c5..4d93b908fb31 100644 --- a/src/c_api/c_api_error.cc +++ b/src/c_api/c_api_error.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file c_api_error.cc * \brief C error handling */ diff --git a/src/c_api/c_api_executor.cc b/src/c_api/c_api_executor.cc index ce765acd77bf..a4c48e426879 100644 --- a/src/c_api/c_api_executor.cc +++ b/src/c_api/c_api_executor.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2016 by Contributors * \file c_api_executor.cc * \brief C API of mxnet */ @@ -35,6 +53,13 @@ int MXExecutorForward(ExecutorHandle handle, int is_train) { int MXExecutorBackward(ExecutorHandle handle, mx_uint len, NDArrayHandle *head_grads) { + return MXExecutorBackwardEx(handle, len, head_grads, true); +} + +int MXExecutorBackwardEx(ExecutorHandle handle, + mx_uint len, + NDArrayHandle *head_grads, + int is_train) { API_BEGIN(); Executor *exec = static_cast(handle); std::vector ndarrays; @@ -42,7 +67,7 @@ int MXExecutorBackward(ExecutorHandle handle, for (mx_uint i = 0; i < len; ++i) { ndarrays.push_back(*args_ptr[i]); } - exec->Backward(ndarrays); + exec->Backward(ndarrays, is_train); API_END(); } @@ -154,6 +179,315 @@ int MXExecutorBindEX(SymbolHandle symbol_handle, API_END_HANDLE_ERROR(delete exec); } +/*! + * \brief + * \param symbol_handle symbol handle + * \param dev_type default device type + * \param dev_id default device id + * \param num_g2c_keys number of group2ctx keys + * \param g2c_keys key list of group2ctx + * \param g2c_dev_types device type list of group2ctx + * \param g2c_dev_ids id list of group2ctx + * \param provided_grad_req_list_len grad_req length provided by users in front-end + * \param provided_grad_req_names grad_req names provided by users in front-end + * \param provided_grad_req_types req types provided by users in front-end + * \param num_provided_arg_shapes number of user provided in_arg and aux_state shapes + * \param provided_arg_shape_names name list of provided shapes + * \param provided_arg_shape_data provided shape data + * \param provided_arg_shape_idx provided shape data index + * \param num_provided_arg_dtypes number of user provided in_arg and axu_state dtypes + * \param provided_arg_dtype_names argument name list of provided dtypes + * \param provided_arg_dtypes data of provided dtypes + * \param num_shared_arg_names number of parameter names passed from _bind_ith_exec + * \param shared_arg_name_list parameter name list passed from _bind_ith_exec + * \param shared_buffer_len number of shared data arrays passed from _bind_ith_exec + * \param shared_buffer_name_list shared data array names passed from _bind_ith_exec + * \param shared_buffer_handle_list shared data array handles passed from _bind_ith_exec + * \param updated_shared_buffer_name_list updated shared data array names after binding + * \param updated_shared_buffer_handle_list updated shared data arrays after binding + * \param num_in_args number of input arguments of this sym + * \param in_args list_arguments associated with the current executor + * \param arg_grads list of gradients of in_args associated with the current executor + * \param num_aux_states number of aux states of this sym + * \param aux_states list_auxiliary_states associated with the current executor + * \param shared_exec_handle shared excutor handle passed from _bind_ith_exec + * \param out the handle of the executor to be created + */ +int MXExecutorSimpleBind(SymbolHandle symbol_handle, + int dev_type, + int dev_id, + const mx_uint num_g2c_keys, + const char** g2c_keys, + const int* g2c_dev_types, + const int* g2c_dev_ids, + const mx_uint provided_grad_req_list_len, + const char** provided_grad_req_names, + const char** provided_grad_req_types, + const mx_uint num_provided_arg_shapes, + const char** provided_arg_shape_names, + const mx_uint* provided_arg_shape_data, + const mx_uint* provided_arg_shape_idx, + const mx_uint num_provided_arg_dtypes, + const char** provided_arg_dtype_names, + const int* provided_arg_dtypes, + const mx_uint num_shared_arg_names, + const char** shared_arg_name_list, + int* shared_buffer_len, + const char** shared_buffer_name_list, + NDArrayHandle* shared_buffer_handle_list, + const char*** updated_shared_buffer_name_list, + NDArrayHandle** updated_shared_buffer_handle_list, + mx_uint* num_in_args, + NDArrayHandle** in_args, + NDArrayHandle** arg_grads, + mx_uint* num_aux_states, + NDArrayHandle** aux_states, + ExecutorHandle shared_exec_handle, + ExecutorHandle* out) { + MXAPIThreadLocalEntry *ret = MXAPIThreadLocalStore::Get(); + API_BEGIN(); + nnvm::Symbol *sym = static_cast(symbol_handle); + + // get in_arg names + std::vector in_arg_names = sym->ListInputNames(nnvm::Symbol::kReadOnlyArgs); + std::vector aux_state_names = sym->ListInputNames(nnvm::Symbol::kAuxiliaryStates); + + // attr_dict for setting up type_dict and arg/aux ctx + std::unordered_map> attr_dict; + if (nullptr == provided_arg_dtypes || nullptr != g2c_keys) { + std::vector> attrs = + sym->ListAttrsRecursive(); + attr_dict.reserve(attrs.size()); + for (const auto& tp : attrs) { + attr_dict[std::get<0>(tp)][std::get<1>(tp)] = std::get<2>(tp); + } + } + + // setup arg_dtype_map + std::unordered_map arg_dtype_map; + if (nullptr == provided_arg_dtypes) { // use attr_dict + for (const auto& arg_name : in_arg_names) { + const auto it = attr_dict.find(arg_name); + if (it == attr_dict.end() || !it->second.count("__dtype__")) { + arg_dtype_map[arg_name] = mshadow::kFloat32; + } + } + } else { // use user input type_dict + // create dtype map for in_args and aux_states + arg_dtype_map.reserve(num_provided_arg_dtypes); + for (mx_uint i = 0; i < num_provided_arg_dtypes; ++i) { + arg_dtype_map[provided_arg_dtype_names[i]] = provided_arg_dtypes[i]; + } + } + + // create default ctx + Context ctx = Context::Create(static_cast(dev_type), dev_id); + // create ctx map + std::map ctx_map; + std::vector in_arg_ctx_vec(in_arg_names.size(), ctx); + std::vector aux_state_ctx_vec(aux_state_names.size(), ctx); + if (nullptr != g2c_keys) { // use user input group2ctx dict + for (mx_uint i = 0; i < num_g2c_keys; ++i) { + ctx_map[g2c_keys[i]] = Context::Create( + static_cast(g2c_dev_types[i]), g2c_dev_ids[i]); + } + + // initialize in_arg_ctx_vec using group2ctx if there are any + for (size_t i = 0; i < in_arg_ctx_vec.size(); ++i) { + const auto it1 = attr_dict.find(in_arg_names[i]); + if (it1 != attr_dict.end()) { + const auto it2 = it1->second.find("__ctx_group__"); + if (it2 != it1->second.end()) { + const auto it3 = ctx_map.find(it2->second); + if (it3 != ctx_map.end()) { + in_arg_ctx_vec[i] = it3->second; + } + } + } + } + + // initialize aux_state_ctx_vec using group2ctx if there are any + for (size_t i = 0; i < aux_state_ctx_vec.size(); ++i) { + const auto it1 = attr_dict.find(aux_state_names[i]); + if (it1 != attr_dict.end()) { + const auto it2 = it1->second.find("__ctx_group__"); + if (it2 != it1->second.end()) { + const auto it3 = ctx_map.find(it2->second); + if (it3 != ctx_map.end()) { + aux_state_ctx_vec[i] = it3->second; + } + } + } + } + } + + // create provided_grad_req_map + const std::map req_map = + {{"null", kNullOp}, {"write", kWriteTo}, {"add", kAddTo}}; + std::unordered_map provided_grad_req_map; + std::string grad_req_type; + if (0 == provided_grad_req_list_len + && nullptr == provided_grad_req_names + && nullptr != provided_grad_req_types) { // string, grad_req='write' + CHECK_EQ(req_map.count(provided_grad_req_types[0]), 1U) + << "grad_req=" << provided_grad_req_types[0] << " is not a valid input in simple_bind; " + "only \'null\', \'write\', and \'add\' are supported"; + grad_req_type = "string"; + } else if (provided_grad_req_list_len > 0 + && nullptr == provided_grad_req_names + && nullptr != provided_grad_req_types) { // list, grad_req=['null', 'write'] + grad_req_type = "list"; + CHECK_EQ(provided_grad_req_list_len, in_arg_names.size()) + << "The length of grad_req list does not match the number of input arguments in simple_bind, " + "expected " << in_arg_names.size() << ", provided " << provided_grad_req_list_len; + } else if (provided_grad_req_list_len > 0 + && nullptr != provided_grad_req_names + && nullptr != provided_grad_req_types) { // dict, grad_req=['lhs': 'null', 'rhs': 'write'] + grad_req_type = "dict"; + provided_grad_req_map.reserve(provided_grad_req_list_len); + for (mx_uint i = 0; i < provided_grad_req_list_len; ++i) { + CHECK_EQ(req_map.count(provided_grad_req_types[i]), 1U) + << "grad_req=" << provided_grad_req_types[i] << " is not a valid input in simple_bind; " + "only \'null\', \'write\', and \'add\' are supported"; + provided_grad_req_map[provided_grad_req_names[i]] = provided_grad_req_types[i]; + } + } else { // grad_req is None + grad_req_type = "none"; + } + + // initialize arg_grad_ctx_vec and grad_req_type_vec + std::vector arg_grad_ctx_vec(in_arg_names.size(), ctx); + std::vector grad_req_type_vec(in_arg_names.size(), kNullOp); + if ("none" != grad_req_type) { + for (size_t i = 0; i < in_arg_names.size(); ++i) { + OpReqType cur_req = kNullOp; + if ("string" == grad_req_type) { + cur_req = req_map.at(provided_grad_req_types[0]); + } else if ("list" == grad_req_type) { + CHECK_EQ(req_map.count(provided_grad_req_types[i]), 1U) + << "grad_req=" << provided_grad_req_types[i] << " is not a valid input in simple_bind; " + "only \'null\', \'write\', and \'add\' are supported"; + cur_req = req_map.at(provided_grad_req_types[i]); + } else if ("dict" == grad_req_type) { + const auto it = provided_grad_req_map.find(in_arg_names[i]); + if (it != provided_grad_req_map.end()) { + cur_req = req_map.at(it->second); + } + } + if (kNullOp != cur_req) { + arg_grad_ctx_vec[i] = in_arg_ctx_vec[i]; + grad_req_type_vec[i] = static_cast(cur_req); + } + } + } + + // create shape map for in_args and aux_states + std::unordered_map arg_shape_map(num_provided_arg_shapes); + for (mx_uint i = 0; i < num_provided_arg_shapes; ++i) { + auto p = arg_shape_map.emplace(provided_arg_shape_names[i], + TShape(provided_arg_shape_data+provided_arg_shape_idx[i], + provided_arg_shape_data+provided_arg_shape_idx[i+1])); + CHECK(p.second) << "Duplicate shapes are provided for argument " + << provided_arg_shape_names[i] << " in simple_bind"; + } + + // create para name set for sharing data array memory + std::unordered_set shared_arg_name_set(num_shared_arg_names); + for (mx_uint i = 0; i < num_shared_arg_names; ++i) { + shared_arg_name_set.insert(shared_arg_name_list[i]); + } + + // create shared_buffer_map + std::unordered_map shared_buffer_map; + std::vector shared_exec_in_args; + std::vector shared_exec_arg_grads; + std::vector shared_exec_aux_states; + bool use_shared_buffer = (*shared_buffer_len >= 0); + if (*shared_buffer_len > 0) { + // create shared_buffer_map + shared_buffer_map.reserve(*shared_buffer_len); + NDArray** shared_buffer_ptrs = + reinterpret_cast(shared_buffer_handle_list); + for (int i = 0; i < *shared_buffer_len; ++i) { + shared_buffer_map[shared_buffer_name_list[i]] = *(shared_buffer_ptrs[i]); + } + } + + // create temporary place holders for the initialized NDArrays + // to be passed back to front end + std::vector in_arg_vec; + std::vector arg_grad_vec; + std::vector aux_state_vec; + + *out = Executor::SimpleBind(*sym, ctx, ctx_map, in_arg_ctx_vec, arg_grad_ctx_vec, + aux_state_ctx_vec, arg_shape_map, arg_dtype_map, grad_req_type_vec, + shared_arg_name_set, &in_arg_vec, &arg_grad_vec, &aux_state_vec, + use_shared_buffer? &shared_buffer_map : nullptr, + reinterpret_cast(shared_exec_handle)); + + // copy ndarray ptrs to ret->handles so that front end + // can access them + ret->ret_handles.clear(); + ret->ret_handles.reserve(in_arg_vec.size()+arg_grad_vec.size()+aux_state_vec.size() + +shared_buffer_map.size()); + size_t nd_idx = 0; + for (const auto& nd : in_arg_vec) { + if (nd.is_none()) { + LOG(FATAL) << "Input argument NDArray cannot be un-allocated"; + } + ret->ret_handles.push_back(new NDArray(nd)); + } + if (in_arg_vec.size() > 0) { + *num_in_args = in_arg_vec.size(); + *in_args = &(ret->ret_handles[nd_idx]); + nd_idx = ret->ret_handles.size(); + } + + for (const auto& nd : arg_grad_vec) { + if (nd.is_none()) { + ret->ret_handles.push_back(nullptr); + } else { + ret->ret_handles.push_back(new NDArray(nd)); + } + } + if (arg_grad_vec.size() > 0) { + *arg_grads = &(ret->ret_handles[nd_idx]); + nd_idx = ret->ret_handles.size(); + } + + for (const auto& nd : aux_state_vec) { + if (nd.is_none()) { + LOG(FATAL) << "Auxiliary argument NDArray cannot be un-allocated"; + } + ret->ret_handles.push_back(new NDArray(nd)); + } + if (aux_state_vec.size() > 0) { + *num_aux_states = aux_state_vec.size(); + *aux_states = &(ret->ret_handles[nd_idx]); + nd_idx = ret->ret_handles.size(); + } + + if (use_shared_buffer) { + ret->ret_vec_str.clear(); + ret->ret_vec_str.reserve(shared_buffer_map.size()); + ret->ret_vec_charp.clear(); + ret->ret_vec_charp.reserve(shared_buffer_map.size()); + for (const auto& kv : shared_buffer_map) { + if (kv.second.is_none()) { + LOG(FATAL) << "Shared data NDArray cannot be un-allocated"; + } + ret->ret_handles.push_back(new NDArray(kv.second)); + ret->ret_vec_str.emplace_back(kv.first); + ret->ret_vec_charp.push_back(ret->ret_vec_str.back().c_str()); + } + *shared_buffer_len = shared_buffer_map.size(); + *updated_shared_buffer_handle_list = &(ret->ret_handles[nd_idx]); + *updated_shared_buffer_name_list = &(ret->ret_vec_charp[0]); + } + + API_END(); +} + int MXExecutorSetMonitorCallback(ExecutorHandle handle, ExecutorMonitorCallback callback, void* callback_handle) { diff --git a/src/c_api/c_api_function.cc b/src/c_api/c_api_function.cc new file mode 100644 index 000000000000..3d8b5328c1a0 --- /dev/null +++ b/src/c_api/c_api_function.cc @@ -0,0 +1,199 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +/*! + * \file custom.cc + * \brief + * \author Junyuan Xie +*/ +#include +#include +#include + +#include "./c_api_common.h" +#include "../ndarray/autograd.h" + +namespace mxnet { +namespace custom_function { + +struct CustomFunctionParam { + size_t num_args, num_outs; + std::shared_ptr info; + std::vector out_shapes; + std::vector out_dtypes; +}; + +std::vector Gradient( + const nnvm::NodePtr& n, + const std::vector& out_grads) { + const CustomFunctionParam& params = nnvm::get(n->attrs.parsed); + + nnvm::NodePtr g = nnvm::Node::Create(); + g->attrs.op = nnvm::Op::Get("_backward_CustomFunction"); + g->attrs.name = n->attrs.name + "_backward"; + g->attrs.parsed = params; + g->control_deps.emplace_back(n); + + g->inputs = out_grads; + + std::vector ret; + for (index_t i = 0; i < g->num_outputs(); ++i) { + ret.emplace_back(nnvm::NodeEntry{g, i, 0}); + } + + return ret; +} + +OpStatePtr CreateState(const nnvm::NodeAttrs& attrs, + Context ctx, + const std::vector& ishape, + const std::vector& itype) { + LOG(FATAL) << "Not reached"; + return OpStatePtr::Create(nullptr); +} + +void Forward(const OpStatePtr& state, + const OpContext& ctx, + const std::vector& inputs, + const std::vector& req, + const std::vector& outputs) { + LOG(FATAL) << "Not reached"; +} + +void Backward(const OpStatePtr& state, + const OpContext& ctx, + const std::vector& inputs, + const std::vector& req, + const std::vector& outputs) { + const CustomFunctionParam& params = state.get_state(); + + std::vector ptrs; + + for (const auto& i : inputs) { + NDArray* nd = new NDArray(i.Detach()); + ptrs.push_back(reinterpret_cast(nd)); + } + for (const auto& i : outputs) { + NDArray* nd = new NDArray(i.Detach()); + ptrs.push_back(reinterpret_cast(nd)); + } + + bool prev_recording = autograd::AutogradRuntime::Get()->SetIsRecording(false); + bool prev_training = autograd::AutogradRuntime::Get()->SetIsTraining(ctx.is_train); + + CHECK(reinterpret_cast( + params.info->callbacks[kCustomFunctionBackward])( + inputs.size(), outputs.size(), ptrs.data(), + reinterpret_cast(req.data()), ctx.is_train, + params.info->contexts[kCustomFunctionBackward])); + + autograd::AutogradRuntime::Get()->SetIsTraining(prev_training); + autograd::AutogradRuntime::Get()->SetIsRecording(prev_recording); +} + + +NNVM_REGISTER_OP(_CustomFunction) +.set_num_inputs([](const NodeAttrs& attrs) { + const CustomFunctionParam& params = nnvm::get(attrs.parsed); + return params.num_args; + }) +.set_num_outputs([](const NodeAttrs& attrs) { + const CustomFunctionParam& params = nnvm::get(attrs.parsed); + return params.num_outs; + }) +.set_attr("FInferShape", + [](const NodeAttrs& attrs, std::vector *in_shape, + std::vector *out_shape) { + const CustomFunctionParam& params = nnvm::get(attrs.parsed); + *out_shape = params.out_shapes; + return true; + }) +.set_attr("FInferType", + [](const NodeAttrs& attrs, std::vector *in_type, + std::vector *out_type) { + const CustomFunctionParam& params = nnvm::get(attrs.parsed); + *out_type = params.out_dtypes; + return true; + }) +.set_attr("FCreateOpState", CreateState) +.set_attr("FGradient", Gradient) +.set_attr("FStatefulComputeEx", Forward) +.set_attr("FStatefulComputeEx", Forward); + + +NNVM_REGISTER_OP(_backward_CustomFunction) +.set_num_inputs([](const NodeAttrs& attrs) { + const CustomFunctionParam& params = nnvm::get(attrs.parsed); + return params.num_outs; + }) +.set_num_outputs([](const NodeAttrs& attrs) { + const CustomFunctionParam& params = nnvm::get(attrs.parsed); + return params.num_args; + }) +.set_attr("TIsBackward", true) +.set_attr("TIsLayerOpBackward", true) +.set_attr("FExecType", [](const NodeAttrs& attrs) { + return ExecType::kLocal; + }) +.set_attr("FStatefulComputeEx", Backward) +.set_attr("FStatefulComputeEx", Backward); + +} // namespace custom_function +} // namespace mxnet + +int MXCustomFunctionRecord(int num_inputs, NDArrayHandle *inputs, + int num_outputs, NDArrayHandle *outputs, + MXCallbackList *callbacks) { + using namespace mxnet; + using namespace mxnet::custom_function; + using mxnet::autograd::AutogradRuntime; + API_BEGIN(); + CHECK(AutogradRuntime::Get()->IsRecording()); + std::vector ndinputs, ndoutputs; + for (int i = 0; i < num_inputs; ++i) { + ndinputs.emplace_back(*reinterpret_cast(inputs[i])); + } + for (int i = 0; i < num_outputs; ++i) { + ndoutputs.emplace_back(*reinterpret_cast(outputs[i])); + } + CustomFunctionParam params; + params.num_args = num_inputs; + params.num_outs = num_outputs; + params.info.reset(callbacks, [](MXCallbackList* ptr){ + reinterpret_cast(ptr->callbacks[kCustomFunctionDelete])( + ptr->contexts[kCustomFunctionDelete]); + }); + for (const auto& i : ndoutputs) { + params.out_shapes.emplace_back(i.shape()); + params.out_dtypes.emplace_back(i.dtype()); + } + nnvm::NodeAttrs attrs; + attrs.op = nnvm::Op::Get("_CustomFunction"); + attrs.parsed = params; + // TODO(piiswrong): remove state by using FComputeEx + auto state = OpStatePtr::Create(params); + AutogradRuntime::Get()->RecordImperativeOperator( + state, attrs.op, attrs, &ndinputs, &ndoutputs); + + for (size_t i = 0; i < ndoutputs.size(); ++i) { + *reinterpret_cast(outputs[i]) = ndoutputs[i]; + } + + API_END(); +} diff --git a/src/c_api/c_api_ndarray.cc b/src/c_api/c_api_ndarray.cc index c633e8609cd4..3202f55abea7 100644 --- a/src/c_api/c_api_ndarray.cc +++ b/src/c_api/c_api_ndarray.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2016 by Contributors * \file c_api_symbolic.cc * \brief C API of mxnet */ @@ -86,8 +104,6 @@ void SetNDInputsOutputs(const nnvm::Op* op, *num_outputs = num_visible_outputs; ndoutputs.resize(infered_num_outputs); } else { - CHECK(!AutogradRuntime::Get()->IsTraining()) - << "Cannot assign to NDArray or specify 'out' when training with autograd"; CHECK(*num_outputs == infered_num_outputs || *num_outputs == num_visible_outputs) << "Expecting " << infered_num_outputs << " (all) or " << num_visible_outputs << " (visible only) outputs, got " @@ -102,31 +118,42 @@ void SetNDInputsOutputs(const nnvm::Op* op, void SetContext(Context* p_ctx, const nnvm::NodeAttrs& attrs, - const int& num_inputs, const std::vector& ndinputs, - const int& infered_num_outputs, - const std::vector& ndoutputs) { + const std::vector& ndoutputs, + const Context& default_ctx) { Context& ctx = *p_ctx; - if (num_inputs) { + if (ndinputs.size()) { ctx = ndinputs[0].ctx(); - } else if (infered_num_outputs && !ndoutputs[0].is_none()) { + for (size_t i = 1; i < ndinputs.size(); ++i) { + CHECK_EQ(ndinputs[i].ctx().dev_mask(), ctx.dev_mask()) + << "All inputs must live on the same context. " + << "But the first argument is on " + << ctx << " while the " << i+1 << "-th argument is on " + << ndinputs[i].ctx(); + } + } else if (ndoutputs.size() && !ndoutputs[0].is_none()) { ctx = ndoutputs[0].ctx(); } else if (attrs.dict.find("ctx") != attrs.dict.end()) { ctx = Context::FromString(attrs.dict.at("ctx")); } else { - ctx = Context::CPU(); + ctx = default_ctx; } // Pinned context doesn't propagate if (ctx.dev_type == Context::kCPUPinned) { ctx = Context::CPU(); } +#if !MXNET_USE_CUDA + if (ctx.dev_mask() == gpu::kDevMask) { + LOG(INFO) << "GPU support is disabled. Compile MXNet with " + << "USE_CUDA=1 to enable GPU support."; + } +#endif // MXNET_USE_CUDA } void SetShapeType(const nnvm::Op* op, const nnvm::NodeAttrs& attrs, const Context& ctx, const std::vector& ndinputs, - const int& infered_num_outputs, std::vector* p_ndoutputs) { std::vector& ndoutputs = *p_ndoutputs; static auto& infershape = nnvm::Op::GetAttr("FInferShape"); @@ -147,7 +174,7 @@ void SetShapeType(const nnvm::Op* op, CHECK(infershape.count(op)) << "Operator " << op->name << " is missing FInferShape attribute"; CHECK(infershape[op](attrs, &in_shapes, &out_shapes)); - CHECK_EQ(out_shapes.size(), static_cast(infered_num_outputs)); + CHECK_EQ(out_shapes.size(), ndoutputs.size()); // infer type std::vector& in_types = ret->arg_types; @@ -164,9 +191,9 @@ void SetShapeType(const nnvm::Op* op, CHECK(infertype.count(op)) << "Operator " << op->name << " is missing FInferType attribute"; CHECK(infertype[op](attrs, &in_types, &out_types)); - CHECK_EQ(out_types.size(), static_cast(infered_num_outputs)); + CHECK_EQ(out_types.size(), ndoutputs.size()); - for (int i = 0; i < infered_num_outputs; ++i) { + for (size_t i = 0; i < ndoutputs.size(); ++i) { if (ndoutputs[i].is_none()) { ndoutputs[i] = NDArray(out_shapes[i], ctx, true, out_types[i]); } else { @@ -266,98 +293,94 @@ void PushFCompute(const FCompute& fn, 0, PROFILER_MESSAGE(op->name.c_str())); } -void PushOperator(std::shared_ptr opr, +void PushOperator(const OpStatePtr& state, const nnvm::Op* op, const nnvm::NodeAttrs& attrs, const Context& ctx, const std::vector& read_vars, const std::vector& write_vars, const std::vector& requested, - const std::vector& auxidx, const std::vector& ndinputs, const std::vector& ndoutputs) { - struct Capture { - engine::CallbackOnComplete on_complete; - std::shared_ptr opr; - }; + static auto& fexec_type = nnvm::Op::GetAttr("FExecType"); bool is_train = AutogradRuntime::Get()->IsTraining(); - Engine::Get()->PushAsync( - [ctx, opr, auxidx, ndinputs, ndoutputs, requested, is_train]( - RunContext rctx, - engine::CallbackOnComplete on_complete) { - std::vector input_blobs, aux_blobs, output_blobs; - auto atop = auxidx.begin(); - for (size_t i = 0; i < ndinputs.size(); ++i) { - if (atop != auxidx.end() && i == *atop) { - aux_blobs.push_back(ndinputs[i].data()); - ++atop; - } else { - input_blobs.push_back(ndinputs[i].data()); + ExecType exec_type = ExecType::kSync; + if (fexec_type.count(op)) { + exec_type = fexec_type[op](attrs); + } + + auto fcompute = common::GetFCompute(op, "FStatefulCompute", ctx); + if (fcompute != nullptr) { + CHECK(exec_type == ExecType::kSync || exec_type == ExecType::kAsync); + Engine::Get()->PushAsync( + [state, fcompute, ndinputs, ndoutputs, requested, is_train, exec_type]( + RunContext rctx, + engine::CallbackOnComplete on_complete) { + OpContext opctx{is_train, rctx, on_complete, requested}; + std::vector input_blobs, output_blobs; + for (const auto& i : ndinputs) input_blobs.push_back(i.data()); + for (const auto& i : ndoutputs) output_blobs.push_back(i.data()); + std::vector req(output_blobs.size(), kWriteTo); + fcompute(state, opctx, input_blobs, req, output_blobs); + if (exec_type == ExecType::kSync) { + if (rctx.get_ctx().dev_mask() == gpu::kDevMask) { + rctx.get_stream()->Wait(); + } + on_complete(); } - } - for (auto& i : ndoutputs) { - output_blobs.push_back(i.data()); - } - Capture* capture = new Capture({on_complete, opr}); - OpContext opctx{is_train, rctx, - Engine::Get()->CreateCallback( - [](Engine* engine, void *cpt_handle) { - Capture* cpt = static_cast(cpt_handle); - cpt->on_complete(); - delete cpt; - }, static_cast(capture)), - requested}; - std::vector req(output_blobs.size(), kWriteTo); - opr->Forward(opctx, input_blobs, req, output_blobs, aux_blobs); - if (opr->exec_type() != Operator::kAsync) { - if (ctx.dev_mask() == gpu::kDevMask) { - rctx.get_stream()->Wait(); + }, ctx, read_vars, write_vars, FnProperty::kNormal, + 0, PROFILER_MESSAGE(op->name.c_str())); + } else { + auto fcompute_ex = common::GetFCompute( + op, "FStatefulComputeEx", ctx); + CHECK(fcompute_ex != nullptr) + << "One of FStatefulCompute and FStatefulComputeEx must be registered " + << "for stateful operator " << op->name; + const auto& run = [state, fcompute_ex, ndinputs, ndoutputs, requested, is_train, exec_type]( + RunContext rctx, + engine::CallbackOnComplete on_complete) { + OpContext opctx{is_train, rctx, on_complete, requested}; + std::vector req(ndoutputs.size(), kWriteTo); + fcompute_ex(state, opctx, ndinputs, req, ndoutputs); + if (exec_type == ExecType::kSync) { + if (rctx.get_ctx().dev_mask() == gpu::kDevMask) { + rctx.get_stream()->Wait(); + } + on_complete(); } - delete capture; - on_complete(); - } - }, ctx, read_vars, write_vars, FnProperty::kNormal, - 0, PROFILER_MESSAGE(op->name.c_str())); + }; + if (exec_type == ExecType::kLocal) { + run(RunContext{ctx, nullptr}, engine::CallbackOnComplete()); + } else { + Engine::Get()->PushAsync(run, ctx, read_vars, write_vars, FnProperty::kNormal, + 0, PROFILER_MESSAGE(op->name.c_str())); + } + } } -int MXImperativeInvoke(AtomicSymbolCreator creator, - int num_inputs, - NDArrayHandle *inputs, - int *num_outputs, - NDArrayHandle **outputs, - int num_params, - const char **param_keys, - const char **param_vals) { +void ImperativeInvokeImpl(const Context& default_ctx, + const nnvm::NodeAttrs& attrs, + std::vector* p_ndinputs, + std::vector* p_ndoutputs) { static auto& fcpu = nnvm::Op::GetAttr("FCompute"); static auto& fgpu = nnvm::Op::GetAttr("FCompute"); static auto& ndfunc = nnvm::Op::GetAttr("FNDArrayFunction"); - static auto& createop = nnvm::Op::GetAttr("FCreateLayerOp"); - const nnvm::Op* op = static_cast(creator); - NDArray** outarray = *reinterpret_cast(outputs); + static auto& createop = nnvm::Op::GetAttr("FCreateOpState"); MXAPIThreadLocalEntry *ret = MXAPIThreadLocalStore::Get(); - API_BEGIN(); - nnvm::NodeAttrs attrs; - SetOpAttrs(op, &attrs, - num_inputs, num_params, param_keys, param_vals); - - int infered_num_outputs; - int num_visible_outputs; - SetNumOutputs(op, attrs, num_inputs, - &infered_num_outputs, &num_visible_outputs); + const nnvm::Op *op = attrs.op; + std::vector& ndinputs = *p_ndinputs; + std::vector& ndoutputs = *p_ndoutputs; - std::vector ndinputs, ndoutputs; - SetNDInputsOutputs(op, &ndinputs, &ndoutputs, num_inputs, inputs, - num_outputs, infered_num_outputs, num_visible_outputs, outarray); if (ndfunc.count(op)) { ndfunc[op](attrs, ndinputs, &ndoutputs); } else { // TODO(piiswrong): infer ctx Context ctx; - SetContext(&ctx, attrs, num_inputs, ndinputs, infered_num_outputs, ndoutputs); - SetShapeType(op, attrs, ctx, ndinputs, infered_num_outputs, &ndoutputs); + SetContext(&ctx, attrs, ndinputs, ndoutputs, default_ctx); + SetShapeType(op, attrs, ctx, ndinputs, &ndoutputs); std::vector read_vars, write_vars; std::vector requested; @@ -373,29 +396,55 @@ int MXImperativeInvoke(AtomicSymbolCreator creator, } if (fn) { - if (AutogradRuntime::Get()->IsTraining()) { + if (AutogradRuntime::Get()->IsRecording()) { AutogradRuntime::Get()->RecordImperativeFCompute(op, attrs, &ndinputs, &ndoutputs); } PushFCompute(fn, op, attrs, ctx, read_vars, write_vars, requested, ndinputs, ndoutputs); } else if (createop.count(op)) { - std::shared_ptr opr( - createop[op](attrs, ctx, ret->arg_shapes, ret->arg_types)); - if (AutogradRuntime::Get()->IsTraining()) { - AutogradRuntime::Get()->RecordImperativeOperator(opr, op, + auto state = + createop[op](attrs, ctx, ret->arg_shapes, ret->arg_types); + if (AutogradRuntime::Get()->IsRecording()) { + AutogradRuntime::Get()->RecordImperativeOperator(state, op, attrs, &ndinputs, &ndoutputs); } - PushOperator(opr, op, attrs, ctx, read_vars, write_vars, - requested, auxidx, ndinputs, ndoutputs); + write_vars.push_back(state.get_var()); + PushOperator(state, op, attrs, ctx, read_vars, write_vars, + requested, ndinputs, ndoutputs); } else { LOG(FATAL) - << "Operator " << op->name - << " cannot be run; requires at least one of" - << " FCompute, NDArrayFunction, FCreateOperator be registered"; + << "Operator " << op->name << " is not implemented for " + << (ctx.dev_mask() == gpu::kDevMask ? "GPU." : "CPU."); } } +} + +int MXImperativeInvoke(AtomicSymbolCreator creator, + int num_inputs, + NDArrayHandle *inputs, + int *num_outputs, + NDArrayHandle **outputs, + int num_params, + const char **param_keys, + const char **param_vals) { + const nnvm::Op* op = static_cast(creator); + MXAPIThreadLocalEntry *ret = MXAPIThreadLocalStore::Get(); + NDArray** outarray = *reinterpret_cast(outputs); + + API_BEGIN(); + nnvm::NodeAttrs attrs; + SetOpAttrs(op, &attrs, num_inputs, num_params, param_keys, param_vals); + int infered_num_outputs; + int num_visible_outputs; + SetNumOutputs(op, attrs, num_inputs, &infered_num_outputs, &num_visible_outputs); + + std::vector ndinputs, ndoutputs; + SetNDInputsOutputs(op, &ndinputs, &ndoutputs, num_inputs, inputs, + num_outputs, infered_num_outputs, num_visible_outputs, outarray); + + ImperativeInvokeImpl(Context::CPU(), attrs, &ndinputs, &ndoutputs); if (outarray == nullptr) { ret->ret_handles.clear(); @@ -412,12 +461,109 @@ int MXImperativeInvoke(AtomicSymbolCreator creator, API_END(); } +int MXCreateCachedOp(SymbolHandle handle, + CachedOpHandle *out) { + nnvm::Symbol* sym = static_cast(handle); + + API_BEGIN(); + nnvm::Graph *g = new nnvm::Graph; + g->outputs = sym->outputs; + auto vars = sym->ListInputs(nnvm::Symbol::kAll); + CHECK_GE(vars.size(), 1) << "CachedOp must have at least 1 input."; + g->attrs["vars"] = std::make_shared(std::move(vars)); + *out = g; + API_END(); +} + +int MXFreeCachedOp(CachedOpHandle handle) { + nnvm::Graph *g = static_cast(handle); + API_BEGIN(); + delete g; + API_END(); +} + +int MXInvokeCachedOp(CachedOpHandle handle, + int num_inputs, + NDArrayHandle *inputs, + int *num_outputs, + NDArrayHandle **outputs) { + nnvm::Graph *g = static_cast(handle); + MXAPIThreadLocalEntry *ret = MXAPIThreadLocalStore::Get(); + NDArray** outarray = *reinterpret_cast(outputs); + + API_BEGIN(); + const std::vector& vars = + g->GetAttr >("vars"); + const nnvm::IndexedGraph& idx = g->indexed_graph(); + CHECK_EQ(static_cast(num_inputs), vars.size()) + << "Actually number of inputs differs from expected number of inputs"; + Context default_ctx = static_cast(inputs[0])->ctx(); + + std::vector buff(idx.num_node_entries()); + for (size_t i = 0; i < vars.size(); ++i) { + buff[idx.entry_id(idx.node_id(vars[i].get()), 0)] = + *static_cast(inputs[i]); + } + + for (size_t i = 0; i < idx.num_nodes(); ++i) { + const nnvm::IndexedGraph::Node& node = idx[i]; + if (node.source->attrs.op == nullptr) continue; + std::vector in; + in.reserve(node.inputs.size()); + for (const auto& j : node.inputs) { + in.emplace_back(buff[idx.entry_id(j)]); + } + std::vector out(node.source->num_outputs()); + ImperativeInvokeImpl(default_ctx, node.source->attrs, &in, &out); + + for (size_t j = 0; j < node.source->num_outputs(); ++j) { + buff[idx.entry_id(i, j)] = std::move(out[j]); + } + } + + if (outarray == nullptr) { + ret->ret_handles.clear(); + for (const auto& i : idx.outputs()) { + ret->ret_handles.push_back( + reinterpret_cast( + new NDArray(buff[idx.entry_id(i)]))); + } + *num_outputs = idx.outputs().size(); + *outputs = dmlc::BeginPtr(ret->ret_handles); + } else { + CHECK_EQ(static_cast(*num_outputs), idx.outputs().size()) + << "Specifed number of output differs from expected number of outputs"; + for (size_t i = 0; i < idx.outputs().size(); ++i) { + *outarray[i] = buff[idx.entry_id(idx.outputs()[i])]; + } + } + API_END(); +} + +int MXAutogradIsTraining(bool* curr) { + API_BEGIN(); + *curr = AutogradRuntime::Get()->IsTraining(); + API_END(); +} + int MXAutogradSetIsTraining(int is_training, int* prev) { API_BEGIN(); *prev = AutogradRuntime::Get()->SetIsTraining(static_cast(is_training)); API_END(); } +int MXAutogradIsRecording(bool* curr) { + API_BEGIN(); + *curr = AutogradRuntime::Get()->IsRecording(); + API_END(); +} + +int MXAutogradSetIsRecording(int is_recording, int* prev) { + API_BEGIN(); + *prev = AutogradRuntime::Get()->SetIsRecording(static_cast(is_recording)); + API_END(); +} + int MXAutogradMarkVariables(mx_uint num_var, NDArrayHandle *var_handles, mx_uint *reqs_array, @@ -439,16 +585,47 @@ int MXAutogradMarkVariables(mx_uint num_var, int MXAutogradComputeGradient(mx_uint num_output, NDArrayHandle *output_handles) { + return MXAutogradBackward(num_output, output_handles, nullptr, 0); +} + +int MXAutogradBackward(mx_uint num_output, + NDArrayHandle *output_handles, + NDArrayHandle *ograd_handles, + int retain_graph) { + return MXAutogradBackwardEx(num_output, output_handles, ograd_handles, retain_graph, true); +} + +int MXAutogradBackwardEx(mx_uint num_output, + NDArrayHandle *output_handles, + NDArrayHandle *ograd_handles, + int retain_graph, + int is_train) { API_BEGIN(); MXAPIThreadLocalEntry *ret = MXAPIThreadLocalStore::Get(); - std::vector outputs; + std::vector outputs, ograds; outputs.reserve(num_output); for (mx_uint i = 0; i < num_output; ++i) { outputs.emplace_back(*static_cast(output_handles[i])); } - AutogradRuntime::Get()->ComputeGradient(outputs); + ograds.reserve(num_output); + for (mx_uint i = 0; i < num_output; ++i) { + if (ograd_handles != nullptr && ograd_handles[i] != nullptr) { + ograds.emplace_back(*static_cast(ograd_handles[i])); + } else { + ograds.emplace_back(); + } + } + AutogradRuntime::Get()->ComputeGradient(outputs, ograds, retain_graph, is_train); + API_END(); +} + +int MXAutogradGetSymbol(NDArrayHandle handle, SymbolHandle *out) { + API_BEGIN(); + NDArray *head = reinterpret_cast(handle); + auto sym = new nnvm::Symbol(head->get_autograd_symbol()); + *out = reinterpret_cast(sym); API_END(); } diff --git a/src/c_api/c_api_symbolic.cc b/src/c_api/c_api_symbolic.cc index f7281c999e6a..e2c29b888ada 100644 --- a/src/c_api/c_api_symbolic.cc +++ b/src/c_api/c_api_symbolic.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2016 by Contributors * \file c_api_symbolic.cc * \brief C API of mxnet */ @@ -363,7 +381,6 @@ int MXSymbolSaveToJSON(SymbolHandle symbol, const char **out_json) { API_END(); } - namespace mxnet { template @@ -429,14 +446,14 @@ int MXSymbolInferShape(SymbolHandle sym, std::vector read_only_args = mxnet::ReadOnlyArgIndices(g.indexed_graph()); CHECK_LE(num_args, read_only_args.size()); for (mx_uint i = 0; i < num_args; ++i) { - arg_shapes[read_only_args[i]] = TShape(arg_shape_data + arg_ind_ptr[i], - arg_shape_data + arg_ind_ptr[i+1]); + arg_shapes[read_only_args[i]] = nnvm::ShapeTypeCast( + arg_shape_data + arg_ind_ptr[i], arg_shape_data + arg_ind_ptr[i+1]); } } else { std::unordered_map kwargs; for (mx_uint i = 0; i < num_args; ++i) { - kwargs[keys[i]] = TShape(arg_shape_data + arg_ind_ptr[i], - arg_shape_data + arg_ind_ptr[i+1]); + kwargs[keys[i]] = nnvm::ShapeTypeCast( + arg_shape_data + arg_ind_ptr[i], arg_shape_data + arg_ind_ptr[i+1]); } mxnet::MatchArguments(g.indexed_graph(), kwargs, &arg_shapes, "InferShape"); } @@ -452,12 +469,12 @@ int MXSymbolInferShape(SymbolHandle sym, &(ret->arg_shapes), &(ret->out_shapes), &(ret->aux_shapes)); // copy data back - MXAPIThreadLocalEntry::SetupShapeArrayReturn( - ret->arg_shapes, &(ret->arg_shape_ndim), &(ret->arg_shape_data)); - MXAPIThreadLocalEntry::SetupShapeArrayReturn( - ret->out_shapes, &(ret->out_shape_ndim), &(ret->out_shape_data)); - MXAPIThreadLocalEntry::SetupShapeArrayReturn( - ret->aux_shapes, &(ret->aux_shape_ndim), &(ret->aux_shape_data)); + MXAPIThreadLocalEntry::SetupShapeArrayReturnWithBuffer(ret->arg_shapes, + &(ret->arg_shape_ndim), &(ret->arg_shape_data), &(ret->arg_shape_buffer)); + MXAPIThreadLocalEntry::SetupShapeArrayReturnWithBuffer(ret->out_shapes, + &(ret->out_shape_ndim), &(ret->out_shape_data), &(ret->out_shape_buffer)); + MXAPIThreadLocalEntry::SetupShapeArrayReturnWithBuffer(ret->aux_shapes, + &(ret->aux_shape_ndim), &(ret->aux_shape_data), &(ret->aux_shape_buffer)); *in_shape_size = static_cast(ret->arg_shapes.size()); *in_shape_ndim = dmlc::BeginPtr(ret->arg_shape_ndim); *in_shape_data = dmlc::BeginPtr(ret->arg_shape_data); diff --git a/src/c_api/c_predict_api.cc b/src/c_api/c_predict_api.cc index 26bc44b701e5..5ca01492800e 100644 --- a/src/c_api/c_predict_api.cc +++ b/src/c_api/c_predict_api.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file c_predict_api.cc * \brief C predict API of mxnet */ @@ -25,6 +43,8 @@ struct MXAPIPredictor { std::vector arg_arrays; // output shapes std::vector out_shapes; + // uint32_t buffer for output shapes + std::vector out_shapes_buffer; // key to arguments std::unordered_map key2arg; // executor @@ -34,6 +54,7 @@ struct MXAPIPredictor { struct MXAPINDList { std::vector keys; std::vector shapes; + std::vector shapes_buffer; std::vector indptr; std::vector data; }; @@ -228,7 +249,11 @@ int MXPredGetOutputShape(PredictorHandle handle, API_BEGIN(); CHECK_LT(out_index, p->out_arrays.size()) << "Index exceed number of outputs"; - *shape_data = p->out_shapes[out_index].data(); + + const TShape& s = p->out_shapes[out_index]; + p->out_shapes_buffer.resize(s.ndim()); + nnvm::ShapeTypeCast(s.begin(), s.end(), p->out_shapes_buffer.data()); + *shape_data = p->out_shapes_buffer.data(); *shape_ndim = p->out_shapes[out_index].ndim(); API_END(); } @@ -322,7 +347,10 @@ int MXNDListGet(NDListHandle handle, << "Index out of range"; *out_key = p->keys[index].c_str(); *out_data = dmlc::BeginPtr(p->data) + p->indptr[index]; - *out_shape = p->shapes[index].data(); + const TShape& s = p->shapes[index]; + p->shapes_buffer.resize(s.ndim()); + nnvm::ShapeTypeCast(s.begin(), s.end(), p->shapes_buffer.data()); + *out_shape = p->shapes_buffer.data(); *out_ndim = p->shapes[index].ndim(); API_END(); } diff --git a/src/common/cuda_utils.h b/src/common/cuda_utils.h index bb0afb819cf2..483390fc9bea 100644 --- a/src/common/cuda_utils.h +++ b/src/common/cuda_utils.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file cuda_utils.h * \brief CUDA debugging utilities. */ @@ -7,10 +25,10 @@ #define MXNET_COMMON_CUDA_UTILS_H_ #include +#include +#include #include -#if MXNET_USE_CUDA - /*! \brief Macros/inlines to assist CLion to parse Cuda files (*.cu, *.cuh) */ #ifdef __JETBRAINS_IDE__ #define __CUDACC__ 1 @@ -22,12 +40,14 @@ inline void __syncthreads() {} inline void __threadfence_block() {} template inline T __clz(const T val) { return val; } -struct __cuda_fake_struct { int x; int y; }; +struct __cuda_fake_struct { int x; int y; int z; }; extern __cuda_fake_struct blockDim; extern __cuda_fake_struct threadIdx; extern __cuda_fake_struct blockIdx; #endif +#if MXNET_USE_CUDA + #include #include #include @@ -67,6 +87,35 @@ inline const char* CublasGetErrorString(cublasStatus_t error) { return "Unknown cuBLAS status"; } +/*! + * \brief Get string representation of cuSOLVER errors. + * \param error The error. + * \return String representation. + */ +inline const char* CusolverGetErrorString(cusolverStatus_t error) { + switch (error) { + case CUSOLVER_STATUS_SUCCESS: + return "CUSOLVER_STATUS_SUCCESS"; + case CUSOLVER_STATUS_NOT_INITIALIZED: + return "CUSOLVER_STATUS_NOT_INITIALIZED"; + case CUSOLVER_STATUS_ALLOC_FAILED: + return "CUSOLVER_STATUS_ALLOC_FAILED"; + case CUSOLVER_STATUS_INVALID_VALUE: + return "CUSOLVER_STATUS_INVALID_VALUE"; + case CUSOLVER_STATUS_ARCH_MISMATCH: + return "CUSOLVER_STATUS_ARCH_MISMATCH"; + case CUSOLVER_STATUS_EXECUTION_FAILED: + return "CUSOLVER_STATUS_EXECUTION_FAILED"; + case CUSOLVER_STATUS_INTERNAL_ERROR: + return "CUSOLVER_STATUS_INTERNAL_ERROR"; + case CUSOLVER_STATUS_MATRIX_TYPE_NOT_SUPPORTED: + return "CUSOLVER_STATUS_MATRIX_TYPE_NOT_SUPPORTED"; + default: + break; + } + return "Unknown cuSOLVER status"; +} + /*! * \brief Get string representation of cuRAND errors. * \param status The status. @@ -104,6 +153,16 @@ inline const char* CurandGetErrorString(curandStatus_t status) { return "Unknown cuRAND status"; } +template +inline DType __device__ CudaMax(DType a, DType b) { + return a > b ? a : b; +} + +template +inline DType __device__ CudaMin(DType a, DType b) { + return a < b ? a : b; +} + } // namespace cuda } // namespace common } // namespace mxnet @@ -144,6 +203,19 @@ inline const char* CurandGetErrorString(curandStatus_t status) { << "cuBLAS: " << common::cuda::CublasGetErrorString(e); \ } +/*! + * \brief Protected cuSolver call. + * \param func Expression to call. + * + * It checks for cuSolver errors after invocation of the expression. + */ +#define CUSOLVER_CALL(func) \ + { \ + cusolverStatus_t e = (func); \ + CHECK_EQ(e, CUSOLVER_STATUS_SUCCESS) \ + << "cuSolver: " << common::cuda::CusolverGetErrorString(e); \ + } + /*! * \brief Protected cuRAND call. * \param func Expression to call. @@ -157,6 +229,86 @@ inline const char* CurandGetErrorString(curandStatus_t status) { << "cuRAND: " << common::cuda::CurandGetErrorString(e); \ } +#if !defined(_MSC_VER) +#define CUDA_UNROLL _Pragma("unroll") +#define CUDA_NOUNROLL _Pragma("nounroll") +#else +#define CUDA_UNROLL +#define CUDA_NOUNROLL +#endif + +/*! + * \brief Determine major version number of the gpu's cuda compute architecture. + * \param device_id The device index of the cuda-capable gpu of interest. + * \return the major version number of the gpu's cuda compute architecture. + */ +inline int ComputeCapabilityMajor(int device_id) { + int major = 0; + CUDA_CALL(cudaDeviceGetAttribute(&major, + cudaDevAttrComputeCapabilityMajor, device_id)); + return major; +} + +/*! + * \brief Determine minor version number of the gpu's cuda compute architecture. + * \param device_id The device index of the cuda-capable gpu of interest. + * \return the minor version number of the gpu's cuda compute architecture. + */ +inline int ComputeCapabilityMinor(int device_id) { + int minor = 0; + CUDA_CALL(cudaDeviceGetAttribute(&minor, + cudaDevAttrComputeCapabilityMinor, device_id)); + return minor; +} + +/*! + * \brief Return the integer SM architecture (e.g. Volta = 70). + * \param device_id The device index of the cuda-capable gpu of interest. + * \return the gpu's cuda compute architecture as an int. + */ +inline int SMArch(int device_id) { + auto major = ComputeCapabilityMajor(device_id); + auto minor = ComputeCapabilityMinor(device_id); + return 10 * major + minor; +} + +/*! + * \brief Determine whether a cuda-capable gpu's architecture supports float16 math. + * \param device_id The device index of the cuda-capable gpu of interest. + * \return whether the gpu's architecture supports float16 math. + */ +inline bool SupportsFloat16Compute(int device_id) { + // Kepler and most Maxwell GPUs do not support fp16 compute + int computeCapabilityMajor = ComputeCapabilityMajor(device_id); + int computeCapabilityMinor = ComputeCapabilityMinor(device_id); + return (computeCapabilityMajor > 5) || + (computeCapabilityMajor == 5 && computeCapabilityMinor >= 3); +} + +/*! + * \brief Determine whether a cuda-capable gpu's architecture supports Tensor Core math. + * \param device_id The device index of the cuda-capable gpu of interest. + * \return whether the gpu's architecture supports Tensor Core math. + */ +inline bool SupportsTensorCore(int device_id) { + // Volta (sm_70) supports TensorCore algos + int computeCapabilityMajor = ComputeCapabilityMajor(device_id); + return (computeCapabilityMajor >= 7); +} + +// The policy if the user hasn't set the environment variable MXNET_CUDA_ALLOW_TENSOR_CORE +#define MXNET_CUDA_ALLOW_TENSOR_CORE_DEFAULT true + +/*! + * \brief Returns global policy for TensorCore algo use. + * \return whether to allow TensorCore algo (if not specified by the Operator locally). + */ +inline bool GetEnvAllowTensorCore() { + // Use of optional here permits: "0", "1", "true" and "false" to all be legal. + bool default_value = MXNET_CUDA_ALLOW_TENSOR_CORE_DEFAULT; + return dmlc::GetEnv("MXNET_CUDA_ALLOW_TENSOR_CORE", + dmlc::optional(default_value)).value(); +} #endif // MXNET_USE_CUDA #if MXNET_USE_CUDNN @@ -169,6 +321,57 @@ inline const char* CurandGetErrorString(curandStatus_t status) { CHECK_EQ(e, CUDNN_STATUS_SUCCESS) << "cuDNN: " << cudnnGetErrorString(e); \ } +/*! + * \brief Return max number of perf structs cudnnFindConvolutionForwardAlgorithm() + * may want to populate. + * \param cudnn_handle cudnn handle needed to perform the inquiry. + * \return max number of perf structs cudnnFindConvolutionForwardAlgorithm() may + * want to populate. + */ +inline int MaxForwardAlgos(cudnnHandle_t cudnn_handle) { +#if CUDNN_MAJOR >= 7 + int max_algos = 0; + CUDNN_CALL(cudnnGetConvolutionForwardAlgorithmMaxCount(cudnn_handle, &max_algos)); + return max_algos; +#else + return 10; +#endif +} + +/*! + * \brief Return max number of perf structs cudnnFindConvolutionBackwardFilterAlgorithm() + * may want to populate. + * \param cudnn_handle cudnn handle needed to perform the inquiry. + * \return max number of perf structs cudnnFindConvolutionBackwardFilterAlgorithm() may + * want to populate. + */ +inline int MaxBackwardFilterAlgos(cudnnHandle_t cudnn_handle) { +#if CUDNN_MAJOR >= 7 + int max_algos = 0; + CUDNN_CALL(cudnnGetConvolutionBackwardFilterAlgorithmMaxCount(cudnn_handle, &max_algos)); + return max_algos; +#else + return 10; +#endif +} + +/*! + * \brief Return max number of perf structs cudnnFindConvolutionBackwardDataAlgorithm() + * may want to populate. + * \param cudnn_handle cudnn handle needed to perform the inquiry. + * \return max number of perf structs cudnnFindConvolutionBackwardDataAlgorithm() may + * want to populate. + */ +inline int MaxBackwardDataAlgos(cudnnHandle_t cudnn_handle) { +#if CUDNN_MAJOR >= 7 + int max_algos = 0; + CUDNN_CALL(cudnnGetConvolutionBackwardDataAlgorithmMaxCount(cudnn_handle, &max_algos)); + return max_algos; +#else + return 10; +#endif +} + #endif // MXNET_USE_CUDNN // Overload atomicAdd to work for floats on all architectures @@ -215,6 +418,15 @@ static inline __device__ void atomicAdd(mshadow::half::half_t *address, old = atomicCAS(address_as_ui, assumed, old); } while (assumed != old); } + +template +__device__ inline DType ldg(const DType* address) { +#if __CUDA_ARCH__ >= 350 + return __ldg(address); +#else + return *address; +#endif +} #endif #endif // MXNET_COMMON_CUDA_UTILS_H_ diff --git a/src/common/lazy_alloc_array.h b/src/common/lazy_alloc_array.h index d3722e557b04..aa2cd4a139ee 100644 --- a/src/common/lazy_alloc_array.h +++ b/src/common/lazy_alloc_array.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file lazy_alloc_array.h * \brief An array that lazily allocate elements as * First time the cell get visited. @@ -12,6 +30,7 @@ #include #include #include +#include namespace mxnet { namespace common { @@ -19,6 +38,7 @@ namespace common { template class LazyAllocArray { public: + LazyAllocArray(); /*! * \brief Get element of corresponding index, * if it is not created create by creator @@ -26,7 +46,7 @@ class LazyAllocArray { * \param creator a lambda function to create new element when needed. */ template - inline TElem* Get(int index, FCreate creator); + inline std::shared_ptr Get(int index, FCreate creator); /*! * \brief for each not null element of the array, call fvisit * \param fvisit a function of (size_t, TElem*) @@ -36,53 +56,104 @@ class LazyAllocArray { /*! \brief clear all the allocated elements in array */ inline void Clear(); + void SignalForKill(); + private: + template + class unique_unlock { + public: + explicit unique_unlock(std::unique_lock *lock) + : lock_(lock) { + if (lock_) { + lock_->unlock(); + } + } + ~unique_unlock() { + if (lock_) { + lock_->lock(); + } + } + private: + std::unique_lock *lock_; + }; + /*! \brief the initial size of the array */ static constexpr std::size_t kInitSize = 16; /*! \brief mutex used during creation */ std::mutex create_mutex_; /*! \brief internal data fir initial size */ - std::array, kInitSize> head_; + std::array, kInitSize> head_; /*! \brief overflow array of more elements */ - std::vector > more_; + std::vector > more_; + /*! \brief Signal shutdown of array */ + std::atomic exit_now_; }; +template +inline LazyAllocArray::LazyAllocArray() + : exit_now_(false) { +} + // implementations template template -inline TElem* LazyAllocArray::Get(int index, FCreate creator) { +inline std::shared_ptr LazyAllocArray::Get(int index, FCreate creator) { CHECK_GE(index, 0); size_t idx = static_cast(index); if (idx < kInitSize) { - TElem *ptr = head_[idx].get(); - if (ptr != nullptr) { + std::shared_ptr ptr = head_[idx]; + if (ptr) { return ptr; } else { std::lock_guard lock(create_mutex_); - TElem *ptr = head_[idx].get(); - if (ptr != nullptr) return ptr; - head_[idx].reset(ptr = creator()); - return ptr; + if (!exit_now_.load()) { + std::shared_ptr ptr = head_[idx]; + if (ptr) { + return ptr; + } + ptr = head_[idx] = std::shared_ptr(creator()); + return ptr; + } } } else { std::lock_guard lock(create_mutex_); - idx -= kInitSize; - if (more_.size() <= idx) more_.resize(idx + 1); - TElem *ptr = more_[idx].get(); - if (ptr != nullptr) return ptr; - more_[idx].reset(ptr = creator()); - return ptr; + if (!exit_now_.load()) { + idx -= kInitSize; + if (more_.size() <= idx) { + more_.reserve(idx + 1); + while (more_.size() <= idx) { + more_.push_back(std::shared_ptr(nullptr)); + } + } + std::shared_ptr ptr = more_[idx]; + if (ptr) { + return ptr; + } + ptr = more_[idx] = std::shared_ptr(creator()); + return ptr; + } } + return nullptr; } template inline void LazyAllocArray::Clear() { - std::lock_guard lock(create_mutex_); + std::unique_lock lock(create_mutex_); + exit_now_.store(true); + // Currently, head_ and more_ never get smaller, so it's safe to + // iterate them outside of the lock. The loops should catch + // any growth which might happen when create_mutex_ is unlocked for (size_t i = 0; i < head_.size(); ++i) { - head_[i].reset(nullptr); + std::shared_ptr p = head_[i]; + head_[i] = std::shared_ptr(nullptr); + unique_unlock unlocker(&lock); + p = std::shared_ptr(nullptr); } for (size_t i = 0; i < more_.size(); ++i) { - more_[i].reset(nullptr); + std::shared_ptr p = more_[i]; + more_[i] = std::shared_ptr(nullptr); + unique_unlock unlocker(&lock); + p = std::shared_ptr(nullptr); } } @@ -101,6 +172,13 @@ inline void LazyAllocArray::ForEach(FVisit fvisit) { } } } + +template +inline void LazyAllocArray::SignalForKill() { + std::lock_guard lock(create_mutex_); + exit_now_.store(true); +} + } // namespace common } // namespace mxnet #endif // MXNET_COMMON_LAZY_ALLOC_ARRAY_H_ diff --git a/src/common/mxrtc.cc b/src/common/mxrtc.cc index e808e11215bf..e72ac0bacdde 100644 --- a/src/common/mxrtc.cc +++ b/src/common/mxrtc.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file mxrtc.cc * \brief Wrapper for NVRTC * \author Junyuan Xie diff --git a/src/common/object_pool.h b/src/common/object_pool.h index 5e22d49a9e9b..6e11ce5ca785 100644 --- a/src/common/object_pool.h +++ b/src/common/object_pool.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors */ #ifndef MXNET_COMMON_OBJECT_POOL_H_ #define MXNET_COMMON_OBJECT_POOL_H_ diff --git a/src/common/utils.h b/src/common/utils.h index 789b4d14b9f2..85e30970f1a0 100644 --- a/src/common/utils.h +++ b/src/common/utils.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file utils.h * \brief Basic utilility functions. */ @@ -12,6 +30,7 @@ #include #include #include +#include #include #include #endif // DMLC_USE_CXX11 @@ -124,6 +143,22 @@ typename helper::UniqueIf::UnknownBound MakeUnique(size_t n) { template typename helper::UniqueIf::KnownBound MakeUnique(Args&&... args) = delete; +template +FCompType GetFCompute(const nnvm::Op* op, const std::string& name, + const Context& ctx) { + static auto& fcompute_cpu = nnvm::Op::GetAttr(name + ""); + static auto& fcompute_gpu = nnvm::Op::GetAttr(name + ""); + + if (ctx.dev_mask() == cpu::kDevMask) { + return fcompute_cpu.get(op, nullptr); + } else if (ctx.dev_mask() == gpu::kDevMask) { + return fcompute_gpu.get(op, nullptr); + } else { + LOG(FATAL) << "Unknown device mask"; + return nullptr; + } +} + #endif // DMLC_USE_CXX11 } // namespace common diff --git a/src/engine/engine.cc b/src/engine/engine.cc index ae72861260e1..d6196085bee9 100644 --- a/src/engine/engine.cc +++ b/src/engine/engine.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file engine.cc * \brief Implementation of engine. */ diff --git a/src/engine/engine_impl.h b/src/engine/engine_impl.h index 9d3fc4cd09f7..cf727366f6d9 100644 --- a/src/engine/engine_impl.h +++ b/src/engine/engine_impl.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file engine_impl.h * \brief Internal implementation header of engine components. */ diff --git a/src/engine/naive_engine.cc b/src/engine/naive_engine.cc index b6eee3e7d615..9814e19d047b 100644 --- a/src/engine/naive_engine.cc +++ b/src/engine/naive_engine.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file naive_engine.cc * \brief Implementation of NaiveEngine */ @@ -149,14 +167,12 @@ class NaiveEngine final : public Engine { if (streams_[dev_id] == nullptr) { streams_[dev_id] = mshadow::NewStream(true, MXNET_USE_CUDNN != 0); } - ctx_.stream = streams_[dev_id]; - exec_fun(ctx_, callback); + exec_fun(RunContext{exec_ctx, streams_[dev_id]}, callback); #else LOG(FATAL) << "GPU is not enabled"; #endif } else { - ctx_.stream = &cpu_stream_; - exec_fun(ctx_, callback); + exec_fun(RunContext{exec_ctx, &cpu_stream_}, callback); } CHECK(this->req_completed_) << "NaiveEngine only support synchronize Push so far"; @@ -187,8 +203,6 @@ class NaiveEngine final : public Engine { static void OnComplete(Engine *engine, void *param) { static_cast(engine)->req_completed_ = true; } - // runtime contetxt - RunContext ctx_; // whether action is completed bool req_completed_; // counter diff --git a/src/engine/profiler.cc b/src/engine/profiler.cc index 9c27f906bc9b..d79ed8870230 100644 --- a/src/engine/profiler.cc +++ b/src/engine/profiler.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file profiler.cc * \brief implements profiler */ diff --git a/src/engine/profiler.h b/src/engine/profiler.h index 8883332fccdf..57f42760452d 100644 --- a/src/engine/profiler.h +++ b/src/engine/profiler.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file profiler.h * \brief implements profiler */ diff --git a/src/engine/stream_manager.h b/src/engine/stream_manager.h index 313db6d2010b..1a66277bb4ec 100644 --- a/src/engine/stream_manager.h +++ b/src/engine/stream_manager.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors */ #ifndef MXNET_ENGINE_STREAM_MANAGER_H_ #define MXNET_ENGINE_STREAM_MANAGER_H_ @@ -46,9 +64,10 @@ template RunContext StreamManager::GetRunContext( Context const& ctx) { RunContext ret; - ret.stream = nullptr; switch (ctx.dev_mask()) { - case cpu::kDevMask: break; + case cpu::kDevMask: + ret = RunContext{ctx, nullptr}; + break; case gpu::kDevMask: { #if MXNET_USE_CUDA std::size_t use_counter; @@ -65,7 +84,7 @@ RunContext StreamManager::GetRunContext( use_counter = counter; counter = (counter + 1) % kStreams; } - ret.stream = gpu_streams_.at(ctx.dev_id).at(use_counter); + ret = RunContext{ctx, gpu_streams_.at(ctx.dev_id).at(use_counter)}; break; #else LOG(FATAL) << MXNET_GPU_NOT_ENABLED_ERROR; @@ -79,9 +98,10 @@ template RunContext StreamManager::GetIORunContext( Context const& ctx) { RunContext ret; - ret.stream = nullptr; switch (ctx.dev_mask()) { - case cpu::kDevMask: break; + case cpu::kDevMask: + ret = RunContext{ctx, nullptr}; + break; case gpu::kDevMask: { #if MXNET_USE_CUDA CUDA_CALL(cudaSetDevice(ctx.dev_id)); @@ -91,7 +111,7 @@ RunContext StreamManager::GetIORunContext( gpu_io_streams_.at(ctx.dev_id) = mshadow::NewStream(false, false); } } - ret.stream = gpu_io_streams_.at(ctx.dev_id); + ret = RunContext{ctx, gpu_io_streams_.at(ctx.dev_id)}; break; #else LOG(FATAL) << MXNET_GPU_NOT_ENABLED_ERROR; diff --git a/src/engine/thread_pool.h b/src/engine/thread_pool.h index b88cddaa29c5..b6fe3c2d5d6a 100644 --- a/src/engine/thread_pool.h +++ b/src/engine/thread_pool.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors */ #ifndef MXNET_ENGINE_THREAD_POOL_H_ #define MXNET_ENGINE_THREAD_POOL_H_ @@ -7,6 +25,7 @@ #include #include #include +#include #include #include #include "mxnet/base.h" @@ -19,6 +38,42 @@ namespace engine { */ class ThreadPool { public: + /*! \brief Simple manually-signalled event gate which remains open */ + class SimpleEvent { + public: + SimpleEvent() + : signaled_(false) {} + void wait() { + std::unique_lock lock(mutex_); + if (!signaled_) { + condition_variable_.wait(lock); + } + } + void signal() { + signaled_ = true; + std::unique_lock lk(mutex_); + condition_variable_.notify_all(); + } + + /*! \brief Signal event upon destruction, even for exceptions (RAII) */ + struct SetReadyOnDestroy { + explicit inline SetReadyOnDestroy(std::shared_ptr event) + : event_(event) { + } + inline ~SetReadyOnDestroy() { + if (event_) { + event_->signal(); + } + } + std::shared_ptr event_; + }; + + private: + std::mutex mutex_; + std::condition_variable condition_variable_; + std::atomic signaled_; + }; + /*! * \brief Constructor takes function to run. * \param size size of the thread pool. @@ -30,6 +85,19 @@ class ThreadPool { i = std::thread(func); } } + explicit ThreadPool(size_t size, + std::function ready)> func, + const bool wait) + : worker_threads_(size) { + for (auto& i : worker_threads_) { + std::shared_ptr ptr = std::make_shared(); + ready_events_.emplace_back(ptr); + i = std::thread(func, ptr); + } + if (wait) { + WaitForReady(); + } + } ~ThreadPool() noexcept(false) { for (auto&& i : worker_threads_) { i.join(); @@ -37,10 +105,23 @@ class ThreadPool { } private: + /*! + * \brief Wait for all started threads to signal that they're ready + */ + void WaitForReady() { + for (std::shared_ptr ptr : ready_events_) { + ptr->wait(); + } + } + /*! * \brief Worker threads. */ std::vector worker_threads_; + /*! + * \brief Startup synchronization objects + */ + std::list> ready_events_; /*! * \brief Disallow default construction. */ diff --git a/src/engine/threaded_engine.cc b/src/engine/threaded_engine.cc index 894827573ec8..290c2166694e 100644 --- a/src/engine/threaded_engine.cc +++ b/src/engine/threaded_engine.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file threaded_engine.cc * \brief implements base threaded engine. * \author Yutian Li diff --git a/src/engine/threaded_engine.h b/src/engine/threaded_engine.h index d330900b2daf..350f70401951 100644 --- a/src/engine/threaded_engine.h +++ b/src/engine/threaded_engine.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file threaded_engine.h * \brief Implements base class of threaded engine * that tracks the dependency and pushes actions to execute. diff --git a/src/engine/threaded_engine_perdevice.cc b/src/engine/threaded_engine_perdevice.cc index a0731683cef7..66cfc9de1468 100644 --- a/src/engine/threaded_engine_perdevice.cc +++ b/src/engine/threaded_engine_perdevice.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file threaded_engine_perdevice.cc * \brief ThreadedEngine that uses fix amount of thread for each device. */ @@ -33,18 +51,18 @@ class ThreadedEnginePerDevice : public ThreadedEngine { ThreadedEnginePerDevice() noexcept(false) { gpu_worker_nthreads_ = common::GetNumThreadPerGPU(); - gpu_copy_nthreads_ = dmlc::GetEnv("MXNET_GPU_COPY_NTHREADS", 1); cpu_worker_nthreads_ = dmlc::GetEnv("MXNET_CPU_WORKER_NTHREADS", 1); // create CPU task int cpu_priority_nthreads = dmlc::GetEnv("MXNET_CPU_PRIORITY_NTHREADS", 4); cpu_priority_worker_.reset(new ThreadWorkerBlock()); cpu_priority_worker_->pool.reset(new ThreadPool( - cpu_priority_nthreads, [this] { - this->CPUWorker(cpu_priority_worker_.get()); + cpu_priority_nthreads, [this]() { + this->CPUWorker(Context(), cpu_priority_worker_.get()); })); // GPU tasks will be created lazily } ~ThreadedEnginePerDevice() noexcept(false) { + SignalQueuesForKill(); gpu_normal_workers_.Clear(); gpu_copy_workers_.Clear(); cpu_normal_workers_.Clear(); @@ -60,9 +78,7 @@ class ThreadedEnginePerDevice : public ThreadedEngine { MSHADOW_CATCH_ERROR(mshadow::SetDevice(ctx.dev_id)); #endif } - RunContext run_ctx; - run_ctx.stream = nullptr; - this->ExecuteOprBlock(run_ctx, opr_block); + this->ExecuteOprBlock(RunContext{ctx, nullptr}, opr_block); } else { if (ctx.dev_mask() == cpu::kDevMask) { if (opr_block->opr->prop == FnProperty::kCPUPrioritized) { @@ -70,13 +86,17 @@ class ThreadedEnginePerDevice : public ThreadedEngine { } else { int dev_id = ctx.dev_id; int nthread = cpu_worker_nthreads_; - cpu_normal_workers_.Get(dev_id, [this, dev_id, nthread]() { + auto ptr = + cpu_normal_workers_.Get(dev_id, [this, ctx, nthread]() { auto blk = new ThreadWorkerBlock(); - blk->pool.reset(new ThreadPool(nthread, [this, blk] () { - this->CPUWorker(blk); + blk->pool.reset(new ThreadPool(nthread, [this, ctx, blk] () { + this->CPUWorker(ctx, blk); })); return blk; - })->task_queue.Push(opr_block, opr_block->priority); + }); + if (ptr) { + ptr->task_queue.Push(opr_block, opr_block->priority); + } } } else { CHECK_EQ(ctx.dev_mask(), gpu::kDevMask); @@ -85,23 +105,35 @@ class ThreadedEnginePerDevice : public ThreadedEngine { bool is_copy = (prop == FnProperty::kCopyFromGPU || prop == FnProperty::kCopyToGPU); int nthread = gpu_worker_nthreads_; - int dev_id = ctx.dev_id; if (is_copy) { - gpu_copy_workers_.Get(dev_id, [this, dev_id, is_copy, nthread]() { + auto ptr = + gpu_copy_workers_.Get(ctx.dev_id, [this, ctx, is_copy, nthread]() { auto blk = new ThreadWorkerBlock(); - blk->pool.reset(new ThreadPool(nthread, [this, dev_id, is_copy, blk] () { - this->GPUWorker(dev_id, is_copy, blk); - })); + blk->pool.reset(new ThreadPool( + nthread, + [this, ctx, is_copy, blk] + (std::shared_ptr ready_event) { + this->GPUWorker(ctx, is_copy, blk, ready_event); + }, true)); return blk; - })->task_queue.Push(opr_block, opr_block->priority); + }); + if (ptr) { + ptr->task_queue.Push(opr_block, opr_block->priority); + } } else { - gpu_normal_workers_.Get(dev_id, [this, dev_id, is_copy, nthread]() { + auto ptr = gpu_normal_workers_.Get(ctx.dev_id, [this, ctx, is_copy, nthread]() { auto blk = new ThreadWorkerBlock(); - blk->pool.reset(new ThreadPool(nthread, [this, dev_id, is_copy, blk] () { - this->GPUWorker(dev_id, is_copy, blk); - })); + blk->pool.reset(new ThreadPool( + nthread, + [this, ctx, is_copy, blk] + (std::shared_ptr ready_event) { + this->GPUWorker(ctx, is_copy, blk, ready_event); + }, true)); return blk; - })->task_queue.Push(opr_block, opr_block->priority); + }); + if (ptr) { + ptr->task_queue.Push(opr_block, opr_block->priority); + } } } } @@ -115,17 +147,16 @@ class ThreadedEnginePerDevice : public ThreadedEngine { dmlc::ConcurrentBlockingQueue task_queue; // thread pool that works on this task std::unique_ptr pool; + // constructor + ThreadWorkerBlock() = default; // destructor - ~ThreadWorkerBlock() noexcept(false) { - task_queue.SignalForKill(); - } + ~ThreadWorkerBlock() noexcept(false) {} }; + /*! \brief number of concurrent thread cpu worker uses */ int cpu_worker_nthreads_; /*! \brief number of concurrent thread each gpu worker uses */ int gpu_worker_nthreads_; - /*! \brief number of concurrent thread each gpu copy worker uses */ - int gpu_copy_nthreads_; // cpu worker common::LazyAllocArray > cpu_normal_workers_; // cpu priority worker @@ -141,45 +172,68 @@ class ThreadedEnginePerDevice : public ThreadedEngine { * \param block The task block of the worker. */ template - inline void GPUWorker(int dev_id, + inline void GPUWorker(Context ctx, bool is_copy_worker, - ThreadWorkerBlock *block) { - #if MXNET_USE_CUDA - // allocate stream - mshadow::SetDevice(dev_id); - RunContext run_ctx; + ThreadWorkerBlock *block, + std::shared_ptr ready_event) { +#if MXNET_USE_CUDA mshadow::Stream *stream; - if (is_copy_worker) { - stream = mshadow::NewStream(false, false); - } else { - stream = mshadow::NewStream(true, MXNET_USE_CUDNN != 0); - } - run_ctx.stream = stream; + do { + ThreadPool::SimpleEvent::SetReadyOnDestroy setReady(ready_event); + // allocate stream + mshadow::SetDevice(ctx.dev_id); + if (is_copy_worker) { + stream = mshadow::NewStream(false, false); + } else { + stream = mshadow::NewStream(true, MXNET_USE_CUDNN != 0); + } + } while (false); // execute task OprBlock* opr_block; + RunContext run_ctx{ctx, stream}; auto* task_queue = &(block->task_queue); while (task_queue->Pop(&opr_block)) { this->ExecuteOprBlock(run_ctx, opr_block); } // Catch exception for CUDA driver shutdown MSHADOW_CATCH_ERROR(mshadow::DeleteStream(stream)); - #endif +#else + ready_event->signal(); +#endif } /*! * \brief CPU worker that performs operations on CPU. * \param block The task block of the worker. */ template - inline void CPUWorker(ThreadWorkerBlock *block) { + inline void CPUWorker(Context ctx, + ThreadWorkerBlock *block) { auto* task_queue = &(block->task_queue); - RunContext run_ctx; - run_ctx.stream = nullptr; + RunContext run_ctx{ctx, nullptr}; // execute task OprBlock* opr_block; while (task_queue->Pop(&opr_block)) { this->ExecuteOprBlock(run_ctx, opr_block); } } + +/*! \brief Signal a single queue for shutdown */ + template + static inline void SignalQueueForKill(common::LazyAllocArray *array) { + array->ForEach([](size_t i, Object *block) { + block->task_queue.SignalForKill(); + }); + } + + /*! Signal all queues for shutdown */ + void SignalQueuesForKill() { + SignalQueueForKill(&gpu_normal_workers_); + SignalQueueForKill(&gpu_copy_workers_); + SignalQueueForKill(&cpu_normal_workers_); + if (cpu_priority_worker_) { + cpu_priority_worker_->task_queue.SignalForKill(); + } + } }; Engine *CreateThreadedEnginePerDevice() { diff --git a/src/engine/threaded_engine_pooled.cc b/src/engine/threaded_engine_pooled.cc index d806c382390c..6db7c4bb7a92 100644 --- a/src/engine/threaded_engine_pooled.cc +++ b/src/engine/threaded_engine_pooled.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file threaded_engine_pooled.cc * \brief Pooled threaded engine * \author Yutian Li diff --git a/src/executor/attach_op_execs_pass.cc b/src/executor/attach_op_execs_pass.cc index 16b55adc15e8..47b74758d702 100644 --- a/src/executor/attach_op_execs_pass.cc +++ b/src/executor/attach_op_execs_pass.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2016 by Contributors * \file attach_op_execs_pass.cc * \brief Operator executor to execute each operator. */ @@ -7,6 +25,7 @@ #include #include #include +#include "../common/utils.h" #include "./exec_pass.h" #if MXNET_USE_MKL2017 == 1 #include @@ -22,120 +41,100 @@ const OperatorProperty* OpPropGetOpProperty(const NodeAttrs& attrs); namespace exec { // forward executor -class ForwardOpExecutor : public OpExecutor { +class StatefulComputeExecutor : public OpExecutor { public: void Run(RunContext rctx) override { + if (!init_) { + in_data_.clear(); + for (size_t i = 0; i < in_array.size(); ++i) { + in_data_.push_back(in_array[i].data()); + } + out_data_.clear(); + for (size_t i = 0; i < out_array.size(); ++i) { + out_data_.push_back(out_array[i].data()); + } + init_ = true; + } op_ctx.run_ctx = rctx; - op_->Forward(op_ctx, in_data_, req, out_data_, aux_data_); + fcompute_(state_, op_ctx, in_data_, req, out_data_); #if MKL_EXPERIMENTAL == 1 mkl_tblobs_prv_to_cpu(in_data_); mkl_tblobs_prv_to_cpu(out_data_); - mkl_tblobs_prv_to_cpu(aux_data_); #endif } void Setup() override { - in_data_.clear(); aux_data_.clear(); - for (size_t i = 0; i < in_array.size(); ++i) { - if (!std::binary_search(aux_index_.begin(), aux_index_.end(), i)) { - in_data_.push_back(in_array[i].data()); - } else { - aux_data_.push_back(in_array[i].data()); - } - } - out_data_.resize(out_array.size()); - std::transform(out_array.begin(), out_array.end(), out_data_.begin(), [](const NDArray& nd) { - return nd.data(); - }); + init_ = false; } - Operator::ExecType exec_type() const override { - return op_->exec_type(); + + ExecType exec_type() const override { + return exec_type_; } - explicit ForwardOpExecutor(std::shared_ptr op, - std::vector aux_index) - : op_(op), aux_index_(aux_index) { - std::sort(aux_index_.begin(), aux_index_.end()); + + engine::VarHandle var() const override { + return state_.get_var(); } + explicit StatefulComputeExecutor(const OpStatePtr& state, + const FStatefulCompute& fcompute, + ExecType exec_type) + : state_(state), fcompute_(fcompute), exec_type_(exec_type) {} + private: friend Graph AttachOpExecs(Graph g); - std::shared_ptr op_; - std::vector aux_index_; - std::vector in_data_, out_data_, aux_data_; + OpStatePtr state_; + FStatefulCompute fcompute_; + ExecType exec_type_; + bool init_; + std::vector in_data_, out_data_; }; -// backward executor -class BackwardOpExecutor : public OpExecutor { + +// forward executor +class StatefulComputeExExecutor : public OpExecutor { public: void Run(RunContext rctx) override { op_ctx.run_ctx = rctx; - op_->Backward(op_ctx, out_grad_, in_data_, out_data_, - req, in_grad_, aux_data_); -#if MKL_EXPERIMENTAL == 1 - mkl_tblobs_prv_to_cpu(out_grad_); - mkl_tblobs_prv_to_cpu(in_data_); - mkl_tblobs_prv_to_cpu(out_data_); - mkl_tblobs_prv_to_cpu(in_grad_); - mkl_tblobs_prv_to_cpu(aux_data_); -#endif - } - void Setup() override { - size_t arg_top = 0, aux_top = 0; - aux_data_.resize(aux_index_.size()); - for (size_t i = 0; i < in_array.size(); ++i) { - if (!std::binary_search(aux_index_.begin(), aux_index_.end(), i)) { - CHECK_GT(arg_data_ptr_.size(), arg_top); - *arg_data_ptr_[arg_top++] = in_array[i].data(); - } else { - aux_data_.at(aux_top++) = in_array[i].data(); - } - } - CHECK_EQ(out_array.size(), in_grad_.size()); - std::transform(out_array.begin(), out_array.end(), - in_grad_.begin(), [](const NDArray& nd) { - return nd.data(); - }); + fcompute_(state_, op_ctx, in_array, req, out_array); } - Operator::ExecType exec_type() const override { - return op_->exec_type(); + + void Setup() override {} + + ExecType exec_type() const override { + return exec_type_; } - explicit BackwardOpExecutor(std::shared_ptr op, - const OperatorProperty* prop, - std::vector aux_index) - : op_(op), aux_index_(aux_index) { - std::sort(aux_index_.begin(), aux_index_.end()); - out_grad_.resize(prop->NumVisibleOutputs()); - in_data_.resize(prop->ListArguments().size()); - in_grad_.resize(in_data_.size()); - out_data_.resize(prop->NumOutputs()); - - std::vector out_grad_ptr(out_grad_.size()); - for (size_t i = 0; i < out_grad_.size(); ++i) { - out_grad_ptr[i] = &out_grad_[i]; - } - std::vector in_data_ptr(in_data_.size()); - for (size_t i = 0; i < in_data_.size(); ++i) { - in_data_ptr[i] = &in_data_[i]; - } - std::vector out_data_ptr(out_data_.size()); - for (size_t i = 0; i < out_data_.size(); ++i) { - out_data_ptr[i] = &out_data_[i]; - } - arg_data_ptr_ = prop->BackwardInputs( - out_grad_ptr, in_data_ptr, out_data_ptr); + + engine::VarHandle var() const override { + return state_.get_var(); } + explicit StatefulComputeExExecutor(const OpStatePtr& state, + const FStatefulComputeEx& fcompute, + ExecType exec_type) + : state_(state), fcompute_(fcompute), exec_type_(exec_type) {} + private: - std::shared_ptr op_; - std::vector aux_index_; - std::vector out_grad_, in_grad_, in_data_, out_data_, aux_data_; - std::vector arg_data_ptr_; + friend Graph AttachOpExecs(Graph g); + OpStatePtr state_; + FStatefulComputeEx fcompute_; + ExecType exec_type_; }; + // fcompute executor executor class FComputeExecutor : public OpExecutor { public: void Run(RunContext rctx) override { + if (!init_) { + in_data_.resize(in_array.size()); + out_data_.resize(out_array.size()); + auto get_blob = [](const NDArray& nd) { + return nd.data(); + }; + std::transform(in_array.begin(), in_array.end(), in_data_.begin(), get_blob); + std::transform(out_array.begin(), out_array.end(), out_data_.begin(), get_blob); + init_ = true; + } op_ctx.run_ctx = rctx; fcompute_(attrs_, op_ctx, in_data_, req, out_data_); #if MKL_EXPERIMENTAL == 1 @@ -143,38 +142,25 @@ class FComputeExecutor : public OpExecutor { mkl_tblobs_prv_to_cpu(out_data_); #endif } + void Setup() override { - in_data_.resize(in_array.size()); - out_data_.resize(out_array.size()); - auto get_blob = [](const NDArray& nd) { - return nd.data(); - }; - std::transform(in_array.begin(), in_array.end(), in_data_.begin(), get_blob); - std::transform(out_array.begin(), out_array.end(), out_data_.begin(), get_blob); - } - Operator::ExecType exec_type() const override { - return Operator::kSync; + init_ = false; } - explicit FComputeExecutor(FCompute fcompute, const NodeAttrs& attrs) - : fcompute_(fcompute), attrs_(attrs) { + + ExecType exec_type() const override { + return exec_type_; } - static FCompute GetFCompute(const Op* op, Context ctx) { - static auto& fcompute_cpu = nnvm::Op::GetAttr("FCompute"); - static auto& fcompute_gpu = nnvm::Op::GetAttr("FCompute"); - if (ctx.dev_mask() == cpu::kDevMask) { - return fcompute_cpu.get(op, nullptr); - } else if (ctx.dev_mask() == gpu::kDevMask) { - return fcompute_gpu.get(op, nullptr); - } else { - LOG(FATAL) << "Unknown device mask"; - return nullptr; - } + explicit FComputeExecutor(const NodeAttrs& attrs, FCompute fcompute, + ExecType exec_type) + : attrs_(attrs), fcompute_(fcompute), exec_type_(exec_type) { } private: - FCompute fcompute_; NodeAttrs attrs_; + FCompute fcompute_; + ExecType exec_type_; + bool init_; std::vector in_data_, out_data_; }; @@ -184,15 +170,16 @@ Graph AttachOpExecs(Graph g) { using nnvm::ShapeVector; using nnvm::FMutateInputs; - auto& fcreate_layer_op = nnvm::Op::GetAttr("FCreateLayerOp"); + auto& fcreate_op_state = nnvm::Op::GetAttr("FCreateOpState"); auto& fmutate_inputs = nnvm::Op::GetAttr("FMutateInputs"); + auto& fexec_type = nnvm::Op::GetAttr("FExecType"); auto& is_layer_backward = nnvm::Op::GetAttr("TIsLayerOpBackward"); const auto& vdtype = g.GetAttr("dtype"); const auto& vshape = g.GetAttr("shape"); const auto& vctx = g.GetAttr("context"); - const auto& saved_opr = g.GetAttr< - std::unordered_map>>("saved_opr"); + const auto& saved_states = g.GetAttr< + std::unordered_map >("saved_states"); // get the graph const auto& idx = g.indexed_graph(); @@ -202,39 +189,72 @@ Graph AttachOpExecs(Graph g) { for (size_t i = 0; i < idx.num_nodes(); ++i) { const auto& inode = idx[i]; if (inode.source->is_variable()) continue; + const nnvm::Op *op = inode.source->op(); + ExecType exec_type = ExecType::kSync; std::vector mutate_index; - if (fmutate_inputs.count(inode.source->op())) { - mutate_index = fmutate_inputs[inode.source->op()](inode.source->attrs); + if (fmutate_inputs.count(op)) { + mutate_index = fmutate_inputs[op](inode.source->attrs); } - FCompute fcompute = FComputeExecutor::GetFCompute(inode.source->op(), vctx[i]); - if (fcreate_layer_op.count(inode.source->op())) { + if (fexec_type.count(op)) { + exec_type = fexec_type[op](inode.source->attrs); + } + + if (fcreate_op_state.count(op)) { std::vector ishape; std::vector itype; for (const auto& e : inode.inputs) { ishape.emplace_back(vshape[idx.entry_id(e)]); itype.emplace_back(vdtype[idx.entry_id(e)]); } - std::shared_ptr opr; - if (saved_opr.count(inode.source)) { - opr = saved_opr.at(inode.source); + + OpStatePtr state; + if (saved_states.count(inode.source)) { + state = saved_states.at(inode.source); } else { - opr.reset(fcreate_layer_op[inode.source->op()]( - inode.source->attrs, vctx[i], ishape, itype)); + state = fcreate_op_state[op]( + inode.source->attrs, vctx[i], ishape, itype); } - ret[i] = std::make_shared(opr, mutate_index); - } else if (is_layer_backward.get(inode.source->op(), false)) { + FStatefulCompute fcompute = common::GetFCompute( + op, "FStatefulCompute", vctx[i]); + if (fcompute != nullptr) { + ret[i] = std::make_shared(state, fcompute, exec_type); + } else { + FStatefulComputeEx fcompute_ex = common::GetFCompute( + op, "FStatefulComputeEx", vctx[i]); + CHECK(fcompute_ex != nullptr) + << "One of FStatefulCompute and FStatefulComputeEx must be registered " + << "for stateful operator " << op->name; + ret[i] = std::make_shared(state, fcompute_ex, exec_type); + } + } else if (is_layer_backward.get(op, false)) { CHECK_GE(inode.control_deps.size(), 1); uint32_t fwd_id = inode.control_deps[0]; CHECK(vctx[fwd_id] == vctx[i]); CHECK(ret[fwd_id] != nullptr); - ret[i] = std::make_shared( - dynamic_cast(ret[fwd_id].get())->op_, - mxnet::op::OpPropGetOpProperty(inode.source->attrs), - mutate_index); - } else if (fcompute != nullptr) { - ret[i] = std::make_shared(fcompute, inode.source->attrs); + FStatefulCompute fcompute = common::GetFCompute( + op, "FStatefulCompute", vctx[i]); + if (fcompute != nullptr) { + ret[i] = std::make_shared( + dynamic_cast(ret[fwd_id].get())->state_, + fcompute, exec_type); + } else { + FStatefulComputeEx fcompute_ex = common::GetFCompute( + op, "FStatefulComputeEx", vctx[i]); + CHECK(fcompute_ex != nullptr) + << "One of FStatefulCompute and FStatefulComputeEx must be registered " + << "for stateful operator " << op->name; + ret[i] = std::make_shared( + dynamic_cast(ret[fwd_id].get())->state_, + fcompute_ex, exec_type); + } } else { - LOG(INFO) << "FCompute not registered " << inode.source->op()->name; + FCompute fcompute = common::GetFCompute(op, "FCompute", vctx[i]); + if (fcompute != nullptr) { + ret[i] = std::make_shared( + inode.source->attrs, fcompute, exec_type); + } else { + LOG(FATAL) << "FCompute not registered " << op->name; + } } } g.attrs["op_execs"] = std::make_shared(ret); diff --git a/src/executor/attach_op_resource_pass.cc b/src/executor/attach_op_resource_pass.cc index 73c8f4807b77..ef26a3575c25 100644 --- a/src/executor/attach_op_resource_pass.cc +++ b/src/executor/attach_op_resource_pass.cc @@ -1,6 +1,24 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2016 by Contributors * \file attach_op_resource_pass.cc * \brief Pass to attach resource to OpExecVector of the graph. */ diff --git a/src/executor/exec_pass.h b/src/executor/exec_pass.h index 8df6a3c5d3bb..0eda71d98214 100644 --- a/src/executor/exec_pass.h +++ b/src/executor/exec_pass.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2016 by Contributors * \file exec_pass.h * \brief All the execution related pass and data structures. */ @@ -49,7 +67,11 @@ class OpExecutor { */ virtual void Run(RunContext rctx) = 0; /*! \return the execution type */ - virtual Operator::ExecType exec_type() const = 0; + virtual ExecType exec_type() const = 0; + /*! \return return engine variable for operator states */ + virtual engine::VarHandle var() const { + return nullptr; + } }; /*! diff --git a/src/executor/graph_executor.cc b/src/executor/graph_executor.cc index 1f6187d2d11e..a03179aa19cb 100644 --- a/src/executor/graph_executor.cc +++ b/src/executor/graph_executor.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file graph_executor.cc * \brief graph executor */ @@ -42,7 +60,7 @@ void GraphExecutor::PartialForward(bool is_train, int step, int *step_left) { *step_left = static_cast(num_forward_nodes_ - sstep - 1); } -void GraphExecutor::Backward(const std::vector& head_grads) { +void GraphExecutor::Backward(const std::vector& head_grads, bool is_train) { const auto& idx = graph_.indexed_graph(); if (num_forward_inputs_ != idx.input_nodes().size()) { for (size_t i = 0; i < head_grad_array_.size(); ++i) { @@ -57,7 +75,7 @@ void GraphExecutor::Backward(const std::vector& head_grads) { } } } - RunOps(true, num_forward_nodes_, idx.num_nodes()); + RunOps(is_train, num_forward_nodes_, idx.num_nodes()); } void GraphExecutor::Print(std::ostream &os) const { // NOLINT(*) @@ -78,6 +96,18 @@ const std::vector& GraphExecutor::outputs() const { return output_arrays_; } +const std::unordered_map& GraphExecutor::in_arg_map() const { + return in_arg_map_; +} + +const std::unordered_map& GraphExecutor::arg_grad_map() const { + return arg_grad_map_; +} + +const std::unordered_map& GraphExecutor::aux_state_map() const { + return aux_state_map_; +} + nnvm::NodeEntry AttrHint(nnvm::NodeEntry src, nnvm::NodeEntry like) { static const Op* id_like = Op::Get("_identity_with_attr_like_rhs"); nnvm::NodePtr n = nnvm::Node::Create(); @@ -178,10 +208,12 @@ inline ValueType get_node_attr( } } -nnvm::Graph GraphExecutor::InitFullGraph( - nnvm::Symbol symbol, - const std::vector& grad_req_type, - const std::vector& arg_grad_store) { +/*! + * \brief Create the graph for backward pass. + * This is triggered by both simple_bind and bind flows. + */ +nnvm::Graph GraphExecutor::InitFullGraph(nnvm::Symbol symbol, + const std::vector& grad_req_types) { using nnvm::NodePtr; using nnvm::NodeEntry; // initial information @@ -191,7 +223,7 @@ nnvm::Graph GraphExecutor::InitFullGraph( nnvm::Graph g; g.outputs = symbol.outputs; bool need_grad = false; - for (OpReqType req : grad_req_type) { + for (OpReqType req : grad_req_types) { if (req != kNullOp) need_grad = true; } if (!need_grad) return g; @@ -202,10 +234,8 @@ nnvm::Graph GraphExecutor::InitFullGraph( } std::vector args = symbol.ListInputs(nnvm::Symbol::kReadOnlyArgs); std::vector xs; - for (size_t i = 0; i < grad_req_type.size(); ++i) { - if (grad_req_type[i] != kNullOp) { - grad_store_.emplace_back( - std::make_pair(grad_req_type[i], arg_grad_store[i])); + for (size_t i = 0; i < grad_req_types.size(); ++i) { + if (grad_req_types[i] != kNullOp) { xs.emplace_back(NodeEntry{args[i], 0, 0}); } } @@ -221,6 +251,7 @@ nnvm::Graph GraphExecutor::InitFullGraph( if (type == "FullyConnected") return false; if (type == "Concat") return false; if (type == "SoftmaxOutput") return false; + if (type == "BatchNorm") return false; if (type == "CuDNNBatchNorm") return false; if (type == "ROIPOoling") return false; if (type == "Proposal") return false; @@ -235,7 +266,7 @@ nnvm::Graph GraphExecutor::InitFullGraph( nnvm::Graph g_grad = nnvm::pass::Gradient( g, symbol.outputs, xs, head_grad_entry_, AggregateGradient, need_mirror, nullptr, - zero_ops); + zero_ops, "_copy"); CHECK_EQ(g_grad.outputs.size(), xs.size()); for (const auto &e : g_grad.outputs) { g.outputs.push_back(e); @@ -243,13 +274,16 @@ nnvm::Graph GraphExecutor::InitFullGraph( return g; } -// pass to assign context to the graph +/*! + * \brief Assign context to the graph. + * This is triggered by both simple_bind and bind flows. + */ Graph AssignContext(Graph g, const Context& default_ctx, const std::map& ctx_map, - const std::vector& in_args, - const std::vector >& grad_store, - const std::vector& aux_states, + const std::vector& in_arg_ctxes, + const std::vector& arg_grad_ctxes, + const std::vector& aux_state_ctxes, size_t num_forward_inputs, size_t num_forward_outputs) { const auto& idx = g.indexed_graph(); @@ -258,56 +292,65 @@ Graph AssignContext(Graph g, if (ctx_map.size() == 0) { g.attrs["context"] = std::make_shared( ContextVector(idx.num_nodes(), default_ctx)); - for (const auto& x : in_args) { - CHECK(x.ctx() == default_ctx) - << "Input array is in " << x.ctx() << " while binding with ctx=" << default_ctx + for (const auto& x : in_arg_ctxes) { + CHECK(x == default_ctx) + << "Input array is in " << x << " while binding with ctx=" << default_ctx << ". All arguments must be in global context (" << default_ctx << ") unless group2ctx is specified for cross-device graph."; } - for (const auto& x : grad_store) { - CHECK(x.second.ctx() == default_ctx) - << "Gradient array is in " << x.second.ctx() << " while binding with ctx=" + for (const auto& x : arg_grad_ctxes) { + CHECK(x == default_ctx) + << "Gradient array is in " << x << " while binding with ctx=" << default_ctx << ". All gradients must be in global context (" << default_ctx << ") unless group2ctx is specified for cross-device graph."; } return g; } + // otherwise, use context assignment. - std::map ctx2id; - std::vector ctx_list; - nnvm::DeviceVector device(idx.num_nodes(), -1); - nnvm::DeviceAssignMap device_map; + std::map ctx2id; // map ctx to device id + std::vector ctx_list; // index is device id + nnvm::DeviceVector device(idx.num_nodes(), -1); // index is node id + nnvm::DeviceAssignMap device_map; // map arg name to device id + // loop through the user input ctx_map and + // populate maps and lists for (auto &kv : ctx_map) { - if (ctx2id.count(kv.second) == 0) { - ctx2id[kv.second] = static_cast(ctx_list.size()); - ctx_list.push_back(kv.second); + if (ctx2id.count(kv.second) == 0) { // if context has no device id, create one + ctx2id[kv.second] = static_cast(ctx_list.size()); // assign device id to ctx + ctx_list.push_back(kv.second); // save ctx to the list } + // assign device id to to the arg name with the corresponding ctx device_map[kv.first] = ctx2id.at(kv.second); } + // loop through all the rest of input nodes not specified + // in the ctx_map and populate maps and lists size_t arg_top = 0, aux_top = 0; for (size_t i = 0; i < num_forward_inputs; ++i) { const uint32_t nid = idx.input_nodes().at(i); Context ctx; - if (mutable_nodes.count(nid)) { - CHECK_LT(aux_top, aux_states.size()); - ctx = aux_states[aux_top].ctx(); + if (mutable_nodes.count(nid)) { // aux node is mutable + CHECK_LT(aux_top, aux_state_ctxes.size()); + ctx = aux_state_ctxes[aux_top]; ++aux_top; - } else { - CHECK_LT(arg_top, in_args.size()); - ctx = in_args[arg_top].ctx(); + } else { // regular input node is immutable + CHECK_LT(arg_top, in_arg_ctxes.size()); + ctx = in_arg_ctxes[arg_top]; ++arg_top; } - if (ctx2id.count(ctx) == 0) { - ctx2id[ctx] = static_cast(ctx_list.size()); - ctx_list.push_back(ctx); + if (ctx2id.count(ctx) == 0) { // if the current ctx is not in the map of ctx and device id + ctx2id[ctx] = static_cast(ctx_list.size()); // assign the current ctx with device id + ctx_list.push_back(ctx); // save the current ctx in the list } - device[nid] = ctx2id.at(ctx); + device[nid] = ctx2id.at(ctx); // assign device id to the current node } + + // loop through backward input nodes and populate maps and lists + // the backward input nodes is the gradient of the loss wrt the output for (size_t i = num_forward_outputs; i < g.outputs.size(); ++i) { const uint32_t nid = idx.outputs()[i].node_id; - Context ctx = grad_store[i - num_forward_outputs].second.ctx(); + Context ctx = arg_grad_ctxes[i - num_forward_outputs]; if (ctx2id.count(ctx) == 0) { ctx2id[ctx] = static_cast(ctx_list.size()); ctx_list.push_back(ctx); @@ -319,6 +362,7 @@ Graph AssignContext(Graph g, device[nid] = devid; } } + g.attrs["device"] = std::make_shared(std::move(device)); g = nnvm::pass::PlaceDevice(g, "__ctx_group__", device_map, "_CrossDeviceCopy"); const auto& assigned_device = g.GetAttr("device"); @@ -331,31 +375,384 @@ Graph AssignContext(Graph g, vcontext.push_back(ctx_list[assigned_device[i]]); } } + + // after device planning, we should check again + // if the assigned device of gradient node + // corresponds to storage of grads + auto &new_idx = g.indexed_graph(); + for (size_t i = num_forward_outputs; i < g.outputs.size(); ++i) { + const uint32_t nid = new_idx.outputs()[i].node_id; + Context ctx = arg_grad_ctxes[i - num_forward_outputs]; + CHECK(ctx == vcontext[nid]) + << "Trying to save gradient to " << ctx + << " while its source node \"" << new_idx[nid].source->attrs.name + << "\" computes it on " << vcontext[nid] + << ". Check your ctx in NDArray allocation."; + } + g.attrs["context"] = std::make_shared(std::move(vcontext)); return g; } +void HandleInferShapeError(const size_t num_forward_inputs, + const nnvm::IndexedGraph& idx, + const nnvm::ShapeVector& inferred_shapes) { + int cnt = 10; + std::ostringstream oss; + for (size_t i = 0; i < num_forward_inputs; ++i) { + const uint32_t nid = idx.input_nodes().at(i); + const uint32_t eid = idx.entry_id(nid, 0); + const TShape& inferred_shape = inferred_shapes[eid]; + if (inferred_shape.ndim() == 0 || inferred_shape.Size() == 0U) { + const std::string& arg_name = idx[nid].source->attrs.name; + oss << arg_name << ": " << inferred_shape << ", "; + if (--cnt == 0) { + oss << "..."; + break; + } + } + } + LOG(FATAL) << "InferShape pass cannot decide shapes for the following arguments " + "(0s means unknown dimensions). Please consider providing them as inputs:\n" + << oss.str(); +} + +void HandleInferTypeError(const size_t num_forward_inputs, + const nnvm::IndexedGraph& idx, + const nnvm::DTypeVector& inferred_dtypes) { + int cnt = 10; + std::ostringstream oss; + for (size_t i = 0; i < num_forward_inputs; ++i) { + const uint32_t nid = idx.input_nodes().at(i); + const uint32_t eid = idx.entry_id(nid, 0); + const int inferred_dtype = inferred_dtypes[eid]; + if (inferred_dtype == -1) { + const std::string& arg_name = idx[nid].source->attrs.name; + oss << arg_name << ": " << inferred_dtype << ", "; + if (--cnt == 0) { + oss << "..."; + break; + } + } + } + LOG(FATAL) << "InferType pass cannot decide dtypes for the following arguments " + "(-1 means unknown dtype). Please consider providing them as inputs:\n" + << oss.str(); +} + +/*! + * \brief GraphExecutor initializer for regular bind flow in which + * input arguments and gradients are provided by users. This initializer + * uses the user provided NDArrays to populate data entries of the graph. + */ void GraphExecutor::Init(nnvm::Symbol symbol, const Context& default_ctx, const std::map& ctx_map, const std::vector& in_args, const std::vector& arg_grad_store, - const std::vector& grad_req_type, + const std::vector& grad_req_types, const std::vector& aux_states, Executor* shared_exec, const nnvm::NodeEntryMap& feed_dict) { - nnvm::Graph g = InitGraph(symbol, default_ctx, - ctx_map, in_args, arg_grad_store, - grad_req_type, aux_states, feed_dict); - g.attrs["saved_opr"] = std::make_shared(std::move(saved_opr_)); + // create in_arg_ctxes, arg_grad_ctxes, aux_state_ctxes + auto get_ctx1 = [](const NDArray& nd) { return nd.ctx(); }; + auto get_ctx2 = [default_ctx](const NDArray& nd) -> Context { + if (nd.is_none()) return default_ctx; + return nd.ctx(); + }; + std::vector in_arg_ctxes(in_args.size()); + std::transform(in_args.begin(), in_args.end(), in_arg_ctxes.begin(), get_ctx1); + std::vector arg_grad_ctxes(arg_grad_store.size()); + std::transform(arg_grad_store.begin(), arg_grad_store.end(), arg_grad_ctxes.begin(), get_ctx2); + std::vector aux_state_ctxes(aux_states.size()); + std::transform(aux_states.begin(), aux_states.end(), aux_state_ctxes.begin(), get_ctx1); + + nnvm::Graph g = InitGraph(symbol, default_ctx, ctx_map, in_arg_ctxes, + arg_grad_ctxes, aux_state_ctxes, grad_req_types); + + // create arg_shapes and arg_dtypes for shape and type inferences + const auto& idx = g.indexed_graph(); + const auto& mutable_nodes = idx.mutable_input_nodes(); + size_t arg_top = 0, aux_top = 0; + data_entry_.resize(idx.num_node_entries()); + nnvm::ShapeVector arg_shapes; + nnvm::DTypeVector arg_dtypes; + for (size_t i = 0; i < num_forward_inputs_; ++i) { + const uint32_t nid = idx.input_nodes().at(i); + const std::string& arg_name = idx[nid].source->attrs.name; + if (mutable_nodes.count(nid)) { + CHECK_LT(aux_top, aux_states.size()); + data_entry_[idx.entry_id(nid, 0)] = aux_states[aux_top]; + arg_shapes.push_back(aux_states[aux_top].shape()); + arg_dtypes.push_back(aux_states[aux_top].dtype()); + aux_state_map_.emplace(arg_name, aux_states[aux_top]); + ++aux_top; + } else { + CHECK_LT(arg_top, in_args.size()); + data_entry_[idx.entry_id(nid, 0)] = in_args[arg_top]; + arg_shapes.push_back(in_args[arg_top].shape()); + arg_dtypes.push_back(in_args[arg_top].dtype()); + in_arg_map_.emplace(arg_name, in_args[arg_top]); + if (kNullOp != grad_req_types[arg_top]) { + grad_store_.emplace_back(grad_req_types[arg_top], arg_grad_store[arg_top]); + arg_grad_map_.emplace(arg_name, arg_grad_store[arg_top]); + } + ++arg_top; + } + } + + // expand arg_shapes and arg_dtypes to contain backward inputs + arg_shapes.resize(idx.input_nodes().size(), TShape()); + g = nnvm::pass::InferShape(g, arg_shapes, "__shape__"); + if (g.GetAttr("shape_num_unknown_nodes") != 0U) { + HandleInferShapeError(num_forward_inputs_, g.indexed_graph(), + g.GetAttr("shape")); + } + + arg_dtypes.resize(idx.input_nodes().size(), -1); + g = nnvm::pass::InferType(g, arg_dtypes, "__dtype__"); + if (g.GetAttr("dtype_num_unknown_nodes") != 0U) { + HandleInferTypeError(num_forward_inputs_, g.indexed_graph(), + g.GetAttr("dtype")); + } + + // Initialize the rest attributes of the graph. + // This function can be called by regular bind + // operation flow as well. + FinishInitGraph(symbol, g, shared_exec, feed_dict); +} + +/*! + * \brief Initialize in_args, arg_grads, and aux_states + * and their data_entry_ of the executor. This function + * is called for regular simple_bind flow, i.e. no + * shared data arrays are provided. + */ +void GraphExecutor::InitArguments(const nnvm::IndexedGraph& idx, + const nnvm::ShapeVector& inferred_shapes, + const nnvm::DTypeVector& inferred_dtypes, + const std::vector& in_arg_ctxes, + const std::vector& arg_grad_ctxes, + const std::vector& aux_state_ctxes, + const std::vector& grad_req_types, + std::vector* in_arg_vec, + std::vector* arg_grad_vec, + std::vector* aux_state_vec) { + // initialize in_args, arg_grads, and aux_states + // populate grad_store_ + data_entry_.resize(idx.num_node_entries()); + size_t arg_top = 0, aux_top = 0; + const auto& mutable_nodes = idx.mutable_input_nodes(); + for (size_t i = 0; i < num_forward_inputs_; ++i) { + const uint32_t nid = idx.input_nodes().at(i); + const uint32_t eid = idx.entry_id(nid, 0); + const TShape& inferred_shape = inferred_shapes[eid]; + const int inferred_dtype = inferred_dtypes[eid]; + const std::string& arg_name = idx[nid].source->attrs.name; + if (mutable_nodes.count(nid)) { // aux_states + aux_state_vec->emplace_back(inferred_shape, aux_state_ctxes[aux_top], false, inferred_dtype); + aux_state_vec->back() = 0; + data_entry_[eid] = aux_state_vec->back(); + aux_state_map_.emplace(arg_name, aux_state_vec->back()); + ++aux_top; + } else { // in_args + in_arg_vec->emplace_back(inferred_shape, in_arg_ctxes[arg_top], false, inferred_dtype); + in_arg_vec->back() = 0; + data_entry_[eid] = in_arg_vec->back(); + if (kNullOp == grad_req_types[arg_top]) { + arg_grad_vec->emplace_back(); + } else { + arg_grad_vec->emplace_back(inferred_shape, arg_grad_ctxes[arg_top], false, inferred_dtype); + arg_grad_vec->back() = 0; + grad_store_.emplace_back(grad_req_types[arg_top], arg_grad_vec->back()); + arg_grad_map_.emplace(arg_name, arg_grad_vec->back()); + } + in_arg_map_.emplace(arg_name, in_arg_vec->back()); + ++arg_top; + } + } +} + +/*! + * \brief If the requested ndarray's shape size is less than + * the corresponding shared_data_array's shape size, reuse + * the memory allocation; otherwise, create a zero ndarray. + */ +NDArray ReshapeOrCreate(const std::string& name, + const TShape& dest_arg_shape, + const int dest_arg_dtype, + const Context& ctx, + std::unordered_map* shared_buffer) { + auto it = shared_buffer->find(name); + if (it != shared_buffer->end()) { + if (it->second.shape().Size() >= dest_arg_shape.Size()) { // memory can be reused + CHECK_EQ(it->second.dtype(), dest_arg_dtype) + << "Requested arg array's dtype does not match the reusable ndarray"; + return it->second.Reshape(dest_arg_shape); + } else { + LOG(WARNING) << "Bucketing: data " << name << " has a shape " << dest_arg_shape + << ", which is larger than already allocated shape " << it->second.shape() + << ". Need to re-allocate. Consider putting default bucket key to be " + << "the bucket taking the largest input for better memory sharing."; + it->second = NDArray(dest_arg_shape, ctx, false, dest_arg_dtype); + it->second = 0; + return it->second; + } // arg_array.shape().Size() >= arg_shape.Size() + } else { + auto p = shared_buffer->emplace(name, NDArray(dest_arg_shape, ctx, false, dest_arg_dtype)); + p.first->second = 0; + return p.first->second; + } // if (it != shared_buffer->end()) +} + +/*! + * \brief Initialize in_args, arg_grads, and aux_states + * and their data_entry_ of the executor using + * shared_buffer from DataParallelExecutorGroup + * and shared_exec if available. + */ +void GraphExecutor::InitArguments(const nnvm::IndexedGraph& idx, + const nnvm::ShapeVector& inferred_shapes, + const nnvm::DTypeVector& inferred_dtypes, + const std::vector& in_arg_ctxes, + const std::vector& arg_grad_ctxes, + const std::vector& aux_state_ctxes, + const std::vector& grad_req_types, + const std::unordered_set& shared_arg_names, + const Executor* shared_exec, + std::unordered_map* shared_buffer, + std::vector* in_arg_vec, + std::vector* arg_grad_vec, + std::vector* aux_state_vec) { + // initialize in_args, arg_grads, and aux_states and populate grad_store_ + data_entry_.resize(idx.num_node_entries()); + size_t arg_top = 0, aux_top = 0; + const auto& mutable_nodes = idx.mutable_input_nodes(); + for (size_t i = 0; i < num_forward_inputs_; ++i) { + const uint32_t nid = idx.input_nodes().at(i); + const uint32_t eid = idx.entry_id(nid, 0); + const TShape& inferred_shape = inferred_shapes[eid]; + const int inferred_dtype = inferred_dtypes[eid]; + const std::string& arg_name = idx[nid].source->attrs.name; + if (mutable_nodes.count(nid)) { // aux_states + if (nullptr != shared_exec) { + const NDArray& aux_nd = shared_exec->aux_state_map().at(arg_name); + CHECK_EQ(inferred_shape, aux_nd.shape()) + << "Inferred shape does not match shared_exec.aux_array's shape." + " Therefore, the allocated memory for shared_exec.aux_array cannot" + " be resued for creating auxilliary NDArray of the argument" + << arg_name << " for the current executor"; + CHECK_EQ(inferred_dtype, aux_nd.dtype()) + << "Inferred dtype does not match shared_exec.aux_array's dtype." + " Therefore, the allocated memory for shared_exec.aux_array cannot" + " be resued for creating auxilliary NDArray of the argument" + << arg_name << " for the current executor"; + aux_state_vec->emplace_back(aux_nd); + } else { + aux_state_vec->emplace_back(inferred_shape, aux_state_ctxes[aux_top], + false, inferred_dtype); + aux_state_vec->back() = 0; + } // if (has_shared_exec) + data_entry_[eid] = aux_state_vec->back(); + aux_state_map_.emplace(arg_name, aux_state_vec->back()); + ++aux_top; + } else { // in_args + if (shared_arg_names.count(arg_name)) { // model parameter + if (nullptr != shared_exec) { + const NDArray& in_arg_nd = shared_exec->in_arg_map().at(arg_name); + CHECK_EQ(inferred_shape, in_arg_nd.shape()) + << "Inferred shape does not match shared_exec.arg_array's shape" + " Therefore, the allocated memory for shared_exec.arg_array cannot" + " be resued for creating NDArray of the argument" + << arg_name << " for the current executor"; + CHECK_EQ(inferred_dtype, in_arg_nd.dtype()) + << "Inferred dtype does not match shared_exec.arg_array's dtype" + " Therefore, the allocated memory for shared_exec.arg_array cannot" + " be resued for creating NDArray of the argument" + << arg_name << " for the current executor"; + in_arg_vec->emplace_back(in_arg_nd); + if (kNullOp == grad_req_types[arg_top]) { + arg_grad_vec->emplace_back(); + } else { + arg_grad_vec->emplace_back(shared_exec->arg_grad_map().at(arg_name)); + grad_store_.emplace_back(grad_req_types[arg_top], arg_grad_vec->back()); + } // if (kNullOp == grad_req_types[arg_top]) + } else { // !has shared_exec + in_arg_vec->emplace_back(inferred_shape, in_arg_ctxes[arg_top], false, inferred_dtype); + in_arg_vec->back() = 0; + if (kNullOp == grad_req_types[arg_top]) { + arg_grad_vec->emplace_back(); + } else { + arg_grad_vec->emplace_back(inferred_shape, arg_grad_ctxes[arg_top], + false, inferred_dtype); + arg_grad_vec->back() = 0; + grad_store_.emplace_back(grad_req_types[arg_top], arg_grad_vec->back()); + } // if (kNullOp == grad_req_types[arg_top]) + } // if (has_shared_exec) + } else { // !shared_arg_names.count(arg_name) + in_arg_vec->emplace_back(ReshapeOrCreate(arg_name, inferred_shape, inferred_dtype, + in_arg_ctxes[arg_top], shared_buffer)); + if (kNullOp == grad_req_types[arg_top]) { + arg_grad_vec->emplace_back(); + } else { + arg_grad_vec->emplace_back(ReshapeOrCreate("grad of " + arg_name, inferred_shape, + inferred_dtype, arg_grad_ctxes[arg_top], + shared_buffer)); + grad_store_.emplace_back(grad_req_types[arg_top], arg_grad_vec->back()); + } // if (kNullOp == grad_req_types[arg_top]) + } // if (shared_arg_names.count(arg_name)) + in_arg_map_.emplace(arg_name, in_arg_vec->back()); + if (!arg_grad_vec->back().is_none()) { + arg_grad_map_.emplace(arg_name, arg_grad_vec->back()); + } + data_entry_[eid] = in_arg_vec->back(); + ++arg_top; + } + } +} + +/*! + * \brief Finish graph initialization after shape and dtype inferences. + * This function is used by both simple_bind and bind flows. + */ +void GraphExecutor::FinishInitGraph(nnvm::Symbol symbol, + nnvm::Graph g, + Executor* shared_exec, + const nnvm::NodeEntryMap& feed_dict) { + const auto& idx = g.indexed_graph(); + for (size_t j = num_forward_outputs_; j < idx.outputs().size(); ++j) { + data_entry_[idx.entry_id(idx.outputs()[j])] = grad_store_[j - num_forward_outputs_].second; + } + + { + // memory allocator + const int kBadStorageID = -1; + const int kExternalStorageID = -2; + nnvm::StorageVector arg_storage_id(idx.num_node_entries(), kBadStorageID); + for (size_t j = num_forward_outputs_; j < idx.outputs().size(); ++j) { + arg_storage_id[idx.entry_id(idx.outputs()[j])] = kExternalStorageID; + } + for (const auto& kv : feed_dict) { + uint32_t eid = idx.entry_id(kv.first); + data_entry_[eid] = kv.second; + arg_storage_id[eid] = kExternalStorageID; + } + g.attrs["storage"] = std::make_shared(std::move(arg_storage_id)); + g = nnvm::ApplyPass(g, "PlanMemory"); + } + g = DetectInplaceAddTo(g); + + g.attrs["saved_states"] = std::make_shared(std::move(saved_states_)); g = AttachOpExecs(g); g = AttachOpResources(g); graph_ = std::move(g); + if (shared_exec != nullptr) { this->InitDataEntryMemory(&(dynamic_cast(shared_exec)->data_pool_)); } else { this->InitDataEntryMemory(nullptr); } + { // initialize output arrays auto& idx = graph_.indexed_graph(); @@ -375,22 +772,120 @@ void GraphExecutor::Init(nnvm::Symbol symbol, this->InitOpSegs(); } +/*! + * \brief GraphExecutor initializer for simple bind flow in + * which only certain input shapes and dtypes are provided by users. + * The initializer uses these shapes and dtypes to perform + * shape and dtype inferences, and then create NDArrays + * to populate data entries of the graph. The created NDArrays + * for in_args, arg_grads and aux_states are passed to the + * front end to attach the created executor. + * In front end, if the simple_bind flow is trigger by + * _bind_ith_exec, the shared data arrays of DataParallelExecutorGroup + * and shared executor will be taken into account in creating + * NDArrays for in_args, arg_grads, and aux_states for resuing + * already allocated memory. + */ +void GraphExecutor::Init(nnvm::Symbol symbol, + const Context& default_ctx, + const std::map& ctx_map, + const std::vector& in_arg_ctxes, + const std::vector& arg_grad_ctxes, + const std::vector& aux_state_ctxes, + const std::unordered_map& arg_shape_map, + const std::unordered_map& arg_dtype_map, + const std::vector& grad_req_types, + const std::unordered_set& shared_arg_names, + std::vector* in_arg_vec, + std::vector* arg_grad_vec, + std::vector* aux_state_vec, + std::unordered_map* shared_buffer, + Executor* shared_exec, + const nnvm::NodeEntryMap& feed_dict) { + nnvm::Graph g = InitGraph(symbol, default_ctx, ctx_map, in_arg_ctxes, arg_grad_ctxes, + aux_state_ctxes, grad_req_types); + // The following code of shape and dtype inferences and argument + // initialization is for simple_bind only. Regular bind operation + // should do this differently. + + // Initialize arg_shapes and arg_dtypes for shape and type inferences. + // It contains all in_args and aux_states' shapes and types in a certain order. + const nnvm::IndexedGraph& idx = g.indexed_graph(); + nnvm::ShapeVector arg_shapes(idx.input_nodes().size(), TShape()); + nnvm::DTypeVector arg_dtypes(idx.input_nodes().size(), -1); + for (size_t i = 0; i < num_forward_inputs_; ++i) { + const uint32_t nid = idx.input_nodes().at(i); + const std::string& name = idx[nid].source->attrs.name; + auto it1 = arg_shape_map.find(name); + if (arg_shape_map.end() != it1) { + arg_shapes[i] = it1->second; + } + auto it2 = arg_dtype_map.find(name); + if (arg_dtype_map.end() != it2) { + arg_dtypes[i] = it2->second; + } + } + g = nnvm::pass::InferShape(g, arg_shapes, "__shape__"); + if (g.GetAttr("shape_num_unknown_nodes") != 0U) { + HandleInferShapeError(num_forward_inputs_, g.indexed_graph(), + g.GetAttr("shape")); + } + + g = nnvm::pass::InferType(g, arg_dtypes, "__dtype__"); + if (g.GetAttr("dtype_num_unknown_nodes") != 0U) { + HandleInferTypeError(num_forward_inputs_, g.indexed_graph(), + g.GetAttr("dtype")); + } + + // Create in_args, arg_grads, and aux_states using + // the inferred shapes and dtypes. + if (nullptr == shared_buffer) { // regular simple bind + InitArguments(idx, g.GetAttr("shape"), + g.GetAttr("dtype"), + in_arg_ctxes, arg_grad_ctxes, aux_state_ctxes, + grad_req_types, in_arg_vec, arg_grad_vec, aux_state_vec); + } else { // simple bind using shared data arrays and shared_exec + InitArguments(idx, g.GetAttr("shape"), + g.GetAttr("dtype"), + in_arg_ctxes, arg_grad_ctxes, aux_state_ctxes, + grad_req_types, shared_arg_names, shared_exec, + shared_buffer, in_arg_vec, arg_grad_vec, aux_state_vec); + } + // The above code of shape and dtype inferences and argument + // initialization is for simple_bind only. Regular bind operation + // should do this differently. + + // Initialize the rest attributes of the graph. + // This function can be called by regular bind + // operation flow as well. + FinishInitGraph(symbol, g, shared_exec, feed_dict); +} + +/*! + * \brief This function is triggered by both simple_bind + * and bind flows. + * Setup backward graph, create device and context + * attributes in the graph, and calculate the number + * of forward nodes. + */ Graph GraphExecutor::InitGraph(nnvm::Symbol symbol, const Context& default_ctx, const std::map& ctx_map, - const std::vector& in_args, - const std::vector& arg_grad_store, - const std::vector& grad_req_type, - const std::vector& aux_states, - const nnvm::NodeEntryMap& feed_dict) { + const std::vector& in_arg_ctxes, + const std::vector& arg_grad_ctxes, + const std::vector& aux_state_ctxes, + const std::vector& grad_req_types) { // setup gradient - nnvm::Graph g = InitFullGraph(symbol, grad_req_type, arg_grad_store); + nnvm::Graph g = InitFullGraph(symbol, grad_req_types); + + // create "device" and "context" attrs for the graph g = AssignContext(g, default_ctx, ctx_map, - in_args, - grad_store_, - aux_states, + in_arg_ctxes, + arg_grad_ctxes, + aux_state_ctxes, num_forward_inputs_, num_forward_outputs_); + const auto& idx = g.indexed_graph(); // get number of nodes used in forward pass num_forward_nodes_ = 0; @@ -398,55 +893,6 @@ Graph GraphExecutor::InitGraph(nnvm::Symbol symbol, num_forward_nodes_ = std::max( num_forward_nodes_, static_cast(idx.outputs()[i].node_id + 1)); } - // Setup data entry, shape and type. - data_entry_.resize(idx.num_node_entries()); - auto mutable_nodes = idx.mutable_input_nodes(); - nnvm::ShapeVector arg_shapes; - nnvm::DTypeVector arg_types; - size_t arg_top = 0, aux_top = 0; - for (size_t i = 0; i < num_forward_inputs_; ++i) { - const uint32_t nid = idx.input_nodes().at(i); - if (mutable_nodes.count(nid)) { - CHECK_LT(aux_top, aux_states.size()); - data_entry_[idx.entry_id(nid, 0)] = aux_states[aux_top]; - arg_shapes.push_back(aux_states[aux_top].shape()); - arg_types.push_back(aux_states[aux_top].dtype()); - ++aux_top; - } else { - CHECK_LT(arg_top, in_args.size()); - data_entry_[idx.entry_id(nid, 0)] = in_args[arg_top]; - arg_shapes.push_back(in_args[arg_top].shape()); - arg_types.push_back(in_args[arg_top].dtype()); - ++arg_top; - } - } - for (size_t j = num_forward_outputs_; j < idx.outputs().size(); ++j) { - data_entry_[idx.entry_id(idx.outputs()[j])] - = grad_store_[j - num_forward_outputs_].second; - } - arg_shapes.resize(idx.input_nodes().size(), TShape()); - arg_types.resize(idx.input_nodes().size(), -1); - // other initializations - g = nnvm::pass::InferShape(g, arg_shapes, "__shape__"); - g = nnvm::pass::InferType(g, arg_types, "__dtype__"); - - { - // memory allocator - const int kBadStorageID = -1; - const int kExternalStorageID = -2; - nnvm::StorageVector arg_storage_id(idx.num_node_entries(), kBadStorageID); - for (size_t j = num_forward_outputs_; j < idx.outputs().size(); ++j) { - arg_storage_id[idx.entry_id(idx.outputs()[j])] = kExternalStorageID; - } - for (const auto& kv : feed_dict) { - uint32_t eid = idx.entry_id(kv.first); - data_entry_[eid] = kv.second; - arg_storage_id[eid] = kExternalStorageID; - } - g.attrs["storage"] = std::make_shared(std::move(arg_storage_id)); - g = nnvm::ApplyPass(g, "PlanMemory"); - } - g = DetectInplaceAddTo(g); return g; } @@ -540,9 +986,9 @@ void GraphExecutor::InitDataEntryMemory(std::vector* shared_pool) { } if (!allocated) { size_t nword = (bytes + 3) / 4; - CHECK_LE(nword, std::numeric_limits::max()); + CHECK_LE(nword, std::numeric_limits::max()); // allocate float arrays - TShape shape{index_t(nword)}; + TShape shape{static_cast(nword)}; NDArray nd(shape, ctx); data_pool_[i] = nd; // put the new allocated arrays to shared pool @@ -628,7 +1074,7 @@ void GraphExecutor::InitCachedOps() { if (inode.source->is_variable()) continue; if (op_nodes_[nid].skip_exec_node) continue; auto& exec = op_nodes_[nid].exec; - bool is_async = op_nodes_[nid].exec->exec_type() == Operator::kAsync; + bool is_async = op_nodes_[nid].exec->exec_type() == ExecType::kAsync; bool is_gpu = op_nodes_[nid].ctx.dev_mask() == gpu::kDevMask; // the variables @@ -643,6 +1089,9 @@ void GraphExecutor::InitCachedOps() { for (auto& nd : exec->out_array) { mutate_vars.push_back(nd.var()); } + if (exec->var() != nullptr) { + mutate_vars.push_back(exec->var()); + } // dedup vars Engine::Get()->DeduplicateVarHandle(&use_vars, &mutate_vars); // all vars include both mutate vars and use vars @@ -692,16 +1141,15 @@ void GraphExecutor::InitOpSegs() { // Generate segments based on the graph structure bool prefer_bulk_exec_inference = dmlc::GetEnv("MXNET_EXEC_BULK_EXEC_INFERENCE", true); - if (prefer_bulk_exec_inference && num_forward_nodes_ == total_num_nodes) { - // bulk the whole graph for inference - cached_seg_opr_[0] = this->CreateCachedSegOpr(0, num_forward_nodes_); - return; - } - // Whether to perform bulk exec for training bool prefer_bulk_exec = dmlc::GetEnv("MXNET_EXEC_BULK_EXEC_TRAIN", 1); // The maximum number of node in a segment executed in bulk size_t num_nodes_threshold = dmlc::GetEnv("MXNET_EXEC_BULK_EXEC_MAX_NODE_TRAIN", 15); + if (prefer_bulk_exec_inference && num_forward_nodes_ == total_num_nodes) { + // bulk the whole graph for inference + num_nodes_threshold = std::numeric_limits::max(); + } + // create forward segments for training if (prefer_bulk_exec > 0) { size_t topo_start = 0; @@ -711,7 +1159,7 @@ void GraphExecutor::InitOpSegs() { // check if the segment relies on external input, or exceeds maxinum number of node, // or requires async ops if (node->is_variable() || nid - topo_start > num_nodes_threshold || - op_node.exec->exec_type() != Operator::kSync) { + op_node.exec->exec_type() != ExecType::kSync) { // create a new segment for the previous nodes if the current one cannot be bulked cached_seg_opr_[topo_start] = this->CreateCachedSegOpr(topo_start, nid); topo_start = nid + 1; @@ -738,7 +1186,7 @@ void GraphExecutor::InitOpSegs() { continue; } if (idx[nid].source->is_variable() || nid - topo_start > num_nodes_threshold || - op_node.exec->exec_type() != Operator::kSync) { + op_node.exec->exec_type() != ExecType::kSync) { cached_seg_opr_[topo_start] = this->CreateCachedSegOpr(topo_start, nid); topo_start = nid + 1; } else { @@ -816,11 +1264,13 @@ void GraphExecutor::RunOps(bool is_train, size_t topo_start, size_t topo_end) { OpNode& opnode = op_nodes_[nid]; if (op_nodes_[nid].skip_exec_node) continue; opnode.exec->op_ctx.is_train = is_train; - if (opnode.exec->exec_type() == Operator::kCrossDeviceCopy) { + if (opnode.exec->exec_type() == ExecType::kCrossDeviceCopy) { CHECK_EQ(inode.inputs.size(), 1U); CHECK_EQ(opnode.exec->in_array.size(), 1U); CHECK_EQ(opnode.exec->out_array.size(), 1U); CopyFromTo(opnode.exec->in_array[0], &(opnode.exec->out_array[0])); + } else if (opnode.exec->exec_type() == ExecType::kLocal) { + opnode.exec->Run(RunContext{opnode.ctx, nullptr}); } else if (opnode.cached_opr != nullptr) { #if MXNET_USE_PROFILER bool profiling = engine::Profiler::Get()->GetState() == engine::Profiler::kRunning; @@ -865,7 +1315,7 @@ GraphExecutor::CachedSegOpr GraphExecutor::CreateCachedSegOpr(size_t topo_start, OpNode& op_node = op_nodes_[nid]; if (op_node.skip_exec_node) continue; if (inode.source->is_variable()) continue; - if (op_node.exec->exec_type() != Operator::kSync) { + if (op_node.exec->exec_type() != ExecType::kSync) { return ret; } if (pctx == nullptr) pctx = &(op_node.ctx); @@ -877,7 +1327,7 @@ GraphExecutor::CachedSegOpr GraphExecutor::CreateCachedSegOpr(size_t topo_start, std::inserter(mutate_vars, mutate_vars.end())); std::copy(op_node.use_vars.begin(), op_node.use_vars.end(), std::inserter(use_vars, use_vars.end())); - ret.exec_list.push_back(exec.get()); + ret.exec_list.push_back(exec); #if MXNET_USE_PROFILER opr_names += inode.source->op()->name + ","; attr_names += inode.source->attrs.name + ","; @@ -925,6 +1375,31 @@ GraphExecutor::CachedSegOpr GraphExecutor::CreateCachedSegOpr(size_t topo_start, } } // namespace exec +Executor *Executor::SimpleBind(nnvm::Symbol symbol, + const Context& default_ctx, + const std::map& group2ctx, + const std::vector& in_arg_ctxes, + const std::vector& arg_grad_ctxes, + const std::vector& aux_state_ctxes, + const std::unordered_map& arg_shape_map, + const std::unordered_map& arg_dtype_map, + const std::vector& grad_req_types, + const std::unordered_set& shared_arg_names, + std::vector* in_args, + std::vector* arg_grads, + std::vector* aux_states, + std::unordered_map* shared_buffer, + Executor* shared_exec) { + auto exec = new exec::GraphExecutor(); + exec->Init(symbol, default_ctx, group2ctx, + in_arg_ctxes, arg_grad_ctxes, aux_state_ctxes, + arg_shape_map, arg_dtype_map, + grad_req_types, shared_arg_names, + in_args, arg_grads, aux_states, + shared_buffer, shared_exec); + return exec; +} + Executor *Executor::Bind(nnvm::Symbol symbol, const Context& default_ctx, const std::map& group2ctx, diff --git a/src/executor/graph_executor.h b/src/executor/graph_executor.h index 793df14983b9..ba42e5780ec7 100644 --- a/src/executor/graph_executor.h +++ b/src/executor/graph_executor.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2016 by Contributors * \file graph_executor.h * \brief Executor to execute the computation graph. */ @@ -21,9 +39,6 @@ namespace mxnet { -using NodeOperatorMap = std::unordered_map>; - // forward declaration namespace exec { class GraphExecutor; @@ -47,21 +62,49 @@ class GraphExecutor : public Executor { virtual ~GraphExecutor(); void Forward(bool is_train) override; void PartialForward(bool is_train, int step, int *step_left) override; - void Backward(const std::vector &head_grads) override; + void Backward(const std::vector &head_grads, bool is_train = true) override; const std::vector& outputs() const override; + const std::unordered_map& in_arg_map() const override; + const std::unordered_map& arg_grad_map() const override; + const std::unordered_map& aux_state_map() const override; void Print(std::ostream &os) const override; // NOLINT(*) void SetMonitorCallback(const MonitorCallback& callback) override; - // initialized the executor + // Initialize the rest of attributes + // after setting up arguments. + void FinishInitGraph(nnvm::Symbol symbol, nnvm::Graph g, + Executor* shared_exec = nullptr, + const nnvm::NodeEntryMap& feed_dict + = nnvm::NodeEntryMap()); + + // initialize executor for bind void Init(nnvm::Symbol symbol, const Context& default_ctx, const std::map& ctx_map, const std::vector& in_args, const std::vector& arg_grad_store, - const std::vector& grad_req_type, + const std::vector& grad_req_types, const std::vector& aux_states, Executor* shared_exec = nullptr, const nnvm::NodeEntryMap& feed_dict = nnvm::NodeEntryMap()); + // initialize executor for simple bind + void Init(nnvm::Symbol symbol, + const Context& default_ctx, + const std::map& ctx_map, + const std::vector& in_arg_ctxes, + const std::vector& arg_grad_ctxes, + const std::vector& aux_state_ctxes, + const std::unordered_map& arg_shape_map, + const std::unordered_map& arg_dtype_map, + const std::vector& grad_req_types, + const std::unordered_set& shared_arg_names, + std::vector* in_arg_vec, + std::vector* arg_grad_vec, + std::vector* aux_state_vec, + std::unordered_map* shared_buffer = nullptr, + Executor* shared_exec = nullptr, + const nnvm::NodeEntryMap& feed_dict + = nnvm::NodeEntryMap()); protected: // Information about operational node @@ -94,23 +137,45 @@ class GraphExecutor : public Executor { // the cached operator Engine::OprHandle opr = nullptr; // list of op executors - std::vector exec_list; + std::vector > exec_list; }; - - // internal initialization of the graph. + // Initialize in_args, arg_grads, and aux_states + void InitArguments(const nnvm::IndexedGraph& idx, + const nnvm::ShapeVector& inferred_shapes, + const nnvm::DTypeVector& inferred_dtypes, + const std::vector& in_arg_ctxes, + const std::vector& arg_grad_ctxes, + const std::vector& aux_state_ctxes, + const std::vector& grad_req_types, + std::vector* in_arg_vec, + std::vector* arg_grad_vec, + std::vector* aux_state_vec); + // Initialize in_args, arg_grads and aux_states with + // shared_buffer and shared_exec + void InitArguments(const nnvm::IndexedGraph& idx, + const nnvm::ShapeVector& inferred_shapes, + const nnvm::DTypeVector& inferred_dtypes, + const std::vector& in_arg_ctxes, + const std::vector& arg_grad_ctxes, + const std::vector& aux_state_ctxes, + const std::vector& grad_req_types, + const std::unordered_set& shared_arg_names, + const Executor* shared_exec, + std::unordered_map* shared_buffer, + std::vector* in_arg_vec, + std::vector* arg_grad_vec, + std::vector* aux_state_vec); + // internal initialization of the graph for simple bind Graph InitGraph(nnvm::Symbol symbol, const Context& default_ctx, const std::map& ctx_map, - const std::vector& in_args, - const std::vector& arg_grad_store, - const std::vector& grad_req_type, - const std::vector& aux_states, - const nnvm::NodeEntryMap& feed_dict - = nnvm::NodeEntryMap()); - // initialize the full graph, including gradient. + const std::vector& in_arg_ctxes, + const std::vector& arg_grad_ctxes, + const std::vector& aux_state_ctxes, + const std::vector& grad_req_types); + // intialize the full graph for simple bind, including gradient Graph InitFullGraph(nnvm::Symbol symbol, - const std::vector& grad_req_type, - const std::vector& arg_grad_store); + const std::vector& grad_req_types); // initialize the cached operator void InitCachedOps(); // initialize the opr segments for bulk exec @@ -142,6 +207,12 @@ class GraphExecutor : public Executor { std::vector data_pool_; // output arrays std::vector output_arrays_; + // input argument map, key is arg name, value is arg's NDArray + std::unordered_map in_arg_map_; + // arg grad map, key is arg name, value is arg grad NDArray + std::unordered_map arg_grad_map_; + // aux state map, key is aux state name, value is aux state NDArray + std::unordered_map aux_state_map_; // gradient store std::vector > grad_store_; // array to hold head gradient. @@ -157,7 +228,7 @@ class GraphExecutor : public Executor { // number of forward nodes size_t num_forward_nodes_{0}; // saved operator for autograd - NodeOperatorMap saved_opr_; + std::unordered_map saved_states_; // monitor call back std::function monitor_callback_{nullptr}; // whether to enable bulk execution diff --git a/src/executor/inplace_addto_detect_pass.cc b/src/executor/inplace_addto_detect_pass.cc index 75a2608313aa..26a91e3f1b5e 100644 --- a/src/executor/inplace_addto_detect_pass.cc +++ b/src/executor/inplace_addto_detect_pass.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2016 by Contributors * \file inplace_addto_detect_pass.cc * \brief Detect whether inplace addto operation is possible for certain op. */ diff --git a/src/initialize.cc b/src/initialize.cc index d57fec84f72b..092dacfb26f2 100644 --- a/src/initialize.cc +++ b/src/initialize.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2016 by Contributors * \file initialize.cc * \brief initialize mxnet library */ @@ -28,7 +46,7 @@ void segfault_logger(int sig) { } #endif // DMLC_LOG_STACK_TRACE - exit(1); + exit(-1); } class LibraryInitializer { diff --git a/src/io/image_aug_default.cc b/src/io/image_aug_default.cc index 2a070d89b3cc..6db14bd583c8 100644 --- a/src/io/image_aug_default.cc +++ b/src/io/image_aug_default.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file image_aug_default.cc * \brief Default augmenter. */ @@ -84,10 +102,10 @@ struct DefaultImageAugmentParam : public dmlc::Parameter min_crop_scales; /*! \brief max crop scales */ Tuple max_crop_scales; - /*! \brief min crop aspecct ratios */ + /*! \brief min crop aspect ratios */ Tuple min_crop_aspect_ratios; /*! \brief max crop aspect ratios */ Tuple max_crop_aspect_ratios; @@ -103,7 +121,7 @@ struct DefaultImageDetAugmentParam : public dmlc::Parameter({1.0f})) .describe("Augmentation Param: Max crop scales."); DMLC_DECLARE_FIELD(min_crop_aspect_ratios).set_default(Tuple({1.0f})) - .describe("Augmentation Param: Min crop aspecct ratios."); + .describe("Augmentation Param: Min crop aspect ratios."); DMLC_DECLARE_FIELD(max_crop_aspect_ratios).set_default(Tuple({1.0f})) .describe("Augmentation Param: Max crop aspect ratios."); DMLC_DECLARE_FIELD(min_crop_overlaps).set_default(Tuple({0.0f})) @@ -255,9 +273,9 @@ class ImageDetLabel { obj.right = *(it++); obj.bottom = *(it++); obj.extra.assign(it, it - 5 + object_width_); - objects_.push_back(obj); - CHECK_GT(obj.right, obj.left); - CHECK_GT(obj.bottom, obj.top); + if (obj.right > obj.left && obj.bottom > obj.top) { + objects_.push_back(obj); + } } } diff --git a/src/io/image_io.cc b/src/io/image_io.cc index 9c65edd1aa87..e6b5a624448e 100644 --- a/src/io/image_io.cc +++ b/src/io/image_io.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2016 by Contributors * \file optimizer_op-inl.h * \brief Optimizer operators * \author Junyuan Xie @@ -13,6 +31,9 @@ #include #include #include +#include + +#include #include "../operator/elemwise_op_common.h" @@ -26,7 +47,7 @@ namespace io { // http://www.64lines.com/jpeg-width-height // Gets the JPEG size from the array of data passed to the function, // file reference: http://www.obrador.com/essentialjpeg/headerinfo.htm -bool get_jpeg_size(const uint8_t* data, uint32_t data_size, uint32_t *width, uint32_t *height) { +bool get_jpeg_size(const uint8_t* data, uint32_t data_size, int64_t *width, int64_t *height) { // Check for valid JPEG image uint32_t i = 0; // Keeps track of the position within the file if (data[i] == 0xFF && data[i+1] == 0xD8 && data[i+2] == 0xFF && data[i+3] == 0xE0) { @@ -63,7 +84,7 @@ bool get_jpeg_size(const uint8_t* data, uint32_t data_size, uint32_t *width, uin } } -bool get_png_size(const uint8_t* data, uint32_t data_size, uint32_t *width, uint32_t *height) { +bool get_png_size(const uint8_t* data, uint32_t data_size, int64_t *width, int64_t *height) { if (data[0] == 0x89 && data[1] == 0x50 && data[2] ==0x4E && data[3] == 0x47) { uint8_t const* p = data + 16; *width = ((p[0]*256 + p[1])*256 + p[2])*256 + p[3]; @@ -89,8 +110,66 @@ struct ImdecodeParam : public dmlc::Parameter { "(instead of opencv's default BGR)."); } }; + DMLC_REGISTER_PARAMETER(ImdecodeParam); +struct ImreadParam : public dmlc::Parameter { + std::string filename; + int flag; + bool to_rgb; + DMLC_DECLARE_PARAMETER(ImreadParam) { + DMLC_DECLARE_FIELD(filename) + .describe("Name of the image file to be loaded."); + DMLC_DECLARE_FIELD(flag) + .set_lower_bound(0) + .set_default(1) + .describe("Convert decoded image to grayscale (0) or color (1)."); + DMLC_DECLARE_FIELD(to_rgb) + .set_default(true) + .describe("Whether to convert decoded image to mxnet's default RGB format " + "(instead of opencv's default BGR)."); + } +}; + +DMLC_REGISTER_PARAMETER(ImreadParam); + + +#if MXNET_USE_OPENCV +void ImdecodeImpl(int flag, bool to_rgb, void* data, size_t size, + NDArray* out) { + cv::Mat buf(1, size, CV_8U, data); + cv::Mat dst; + if (out->is_none()) { + cv::Mat res = cv::imdecode(buf, flag); + if (res.empty()) { + LOG(INFO) << "Invalid image file. Only supports png and jpg."; + *out = NDArray(); + return; + } + *out = NDArray(mshadow::Shape3(res.rows, res.cols, flag == 0 ? 1 : 3), + Context::CPU(), false, mshadow::kUint8); + dst = cv::Mat(out->shape()[0], out->shape()[1], flag == 0 ? CV_8U : CV_8UC3, + out->data().dptr_); + res.copyTo(dst); + } else { + dst = cv::Mat(out->shape()[0], out->shape()[1], flag == 0 ? CV_8U : CV_8UC3, + out->data().dptr_); +#if (CV_MAJOR_VERSION > 2 || (CV_MAJOR_VERSION == 2 && CV_MINOR_VERSION >=4)) + cv::imdecode(buf, flag, &dst); +#else + cv::Mat tmp = cv::imdecode(buf, flag); + CHECK(!tmp.empty()); + tmp.copyTo(dst); +#endif + } + CHECK(!dst.empty()); + CHECK_EQ(static_cast(dst.ptr()), out->data().dptr_); + if (to_rgb && flag != 0) { + cv::cvtColor(dst, dst, CV_BGR2RGB); + } +} +#endif // MXNET_USE_OPENCV + void Imdecode(const nnvm::NodeAttrs& attrs, const std::vector& inputs, std::vector* outputs) { @@ -99,63 +178,71 @@ void Imdecode(const nnvm::NodeAttrs& attrs, CHECK_EQ(inputs[0].ctx().dev_mask(), cpu::kDevMask) << "Only supports cpu input"; CHECK_EQ(inputs[0].dtype(), mshadow::kUint8) << "Input needs to be uint8 buffer"; - const uint8_t* str_img = reinterpret_cast(inputs[0].data().dptr_); - uint32_t len = inputs[0].shape().Size(); + inputs[0].WaitToRead(); - NDArray ndin = inputs[0]; - ndin.WaitToRead(); + uint8_t* str_img = inputs[0].data().dptr(); + size_t len = inputs[0].shape().Size(); TShape oshape(3); oshape[2] = param.flag == 0 ? 1 : 3; if (get_jpeg_size(str_img, len, &oshape[1], &oshape[0])) { } else if (get_png_size(str_img, len, &oshape[1], &oshape[0])) { } else { - cv::Mat buf(1, ndin.shape().Size(), CV_8U, ndin.data().dptr_); - cv::Mat res = cv::imdecode(buf, param.flag); - if (res.empty()) { - LOG(INFO) << "Invalid image file. Only supports png and jpg."; - (*outputs)[0] = NDArray(); - return; - } - oshape[0] = res.rows; - oshape[1] = res.cols; - NDArray ndout(oshape, Context::CPU(), false, mshadow::kUint8); - cv::Mat dst(ndout.shape()[0], ndout.shape()[1], - param.flag == 0 ? CV_8U : CV_8UC3, - ndout.data().dptr_); - res.copyTo(dst); - if (param.to_rgb && param.flag != 0) { - cv::cvtColor(dst, dst, CV_BGR2RGB); - } - (*outputs)[0] = ndout; + (*outputs)[0] = NDArray(); + ImdecodeImpl(param.flag, param.to_rgb, str_img, len, &((*outputs)[0])); return; } - NDArray ndout(oshape, Context::CPU(), true, mshadow::kUint8); - Engine::Get()->PushSync([ndin, ndout, param](RunContext ctx){ - cv::Mat buf(1, ndin.shape().Size(), CV_8U, ndin.data().dptr_); - cv::Mat dst(ndout.shape()[0], ndout.shape()[1], - param.flag == 0 ? CV_8U : CV_8UC3, - ndout.data().dptr_); -#if (CV_MAJOR_VERSION > 2 || (CV_MAJOR_VERSION == 2 && CV_MINOR_VERSION >=4)) - cv::imdecode(buf, param.flag, &dst); -#else - cv::Mat tmp = cv::imdecode(buf, param.flag); - CHECK(!tmp.empty()); - tmp.copyTo(dst); -#endif - CHECK(!dst.empty()); - CHECK_EQ(static_cast(dst.ptr()), ndout.data().dptr_); - if (param.to_rgb && param.flag != 0) { - cv::cvtColor(dst, dst, CV_BGR2RGB); - } + const NDArray& ndin = inputs[0]; + NDArray& ndout = (*outputs)[0]; + ndout = NDArray(oshape, Context::CPU(), true, mshadow::kUint8); + Engine::Get()->PushSync([ndin, ndout, str_img, len, param](RunContext ctx){ + ImdecodeImpl(param.flag, param.to_rgb, str_img, len, + const_cast(&ndout)); }, ndout.ctx(), {ndin.var()}, {ndout.var()}, FnProperty::kNormal, 0, PROFILER_MESSAGE("Imdecode")); - (*outputs)[0] = ndout; #else LOG(FATAL) << "Build with USE_OPENCV=1 for image io."; #endif // MXNET_USE_OPENCV } +void Imread(const nnvm::NodeAttrs& attrs, + const std::vector& inputs, + std::vector* outputs) { +#if MXNET_USE_OPENCV + const auto& param = nnvm::get(attrs.parsed); + + std::ifstream file(param.filename, std::ios::binary | std::ios::ate); + size_t fsize = file.tellg(); + file.seekg(0, std::ios::beg); + auto buff = new uint8_t[fsize]; + file.read(reinterpret_cast(buff), fsize); + CHECK(file.good()) << "Failed reading image file " << param.filename; + + TShape oshape(3); + oshape[2] = param.flag == 0 ? 1 : 3; + if (get_jpeg_size(buff, fsize, &oshape[1], &oshape[0])) { + } else if (get_png_size(buff, fsize, &oshape[1], &oshape[0])) { + } else { + (*outputs)[0] = NDArray(); + ImdecodeImpl(param.flag, param.to_rgb, buff, fsize, &((*outputs)[0])); + delete buff; + return; + } + + NDArray& ndout = (*outputs)[0]; + ndout = NDArray(oshape, Context::CPU(), true, mshadow::kUint8); + Engine::Get()->PushSync([ndout, buff, fsize, param](RunContext ctx){ + ImdecodeImpl(param.flag, param.to_rgb, buff, fsize, + const_cast(&ndout)); + delete buff; + }, ndout.ctx(), {}, {ndout.var()}, + FnProperty::kNormal, 0, PROFILER_MESSAGE("Imread")); +#else + LOG(FATAL) << "Build with USE_OPENCV=1 for image io."; +#endif // MXNET_USE_OPENCV +} + + struct ResizeParam : public dmlc::Parameter { int w; int h; @@ -210,6 +297,7 @@ struct MakeBorderParam : public dmlc::Parameter { int top, bot, left, right; int type; double value; + nnvm::Tuple values; DMLC_DECLARE_PARAMETER(MakeBorderParam) { DMLC_DECLARE_FIELD(top) .describe("Top margin."); @@ -224,7 +312,10 @@ struct MakeBorderParam : public dmlc::Parameter { .describe("Filling type (default=cv2.BORDER_CONSTANT)."); DMLC_DECLARE_FIELD(value) .set_default(0.0) - .describe("Fill with value."); + .describe("(Deprecated! Use ``values`` instead.) Fill with single value."); + DMLC_DECLARE_FIELD(values) + .set_default({}) + .describe("Fill with value(RGB[A] or gray), up to 4 channels."); } }; DMLC_REGISTER_PARAMETER(MakeBorderParam); @@ -255,9 +346,11 @@ inline void copyMakeBorder(const nnvm::NodeAttrs& attrs, const auto& param = nnvm::get(attrs.parsed); cv::Mat buf(inputs[0].shape_[0], inputs[0].shape_[1], cv_type, inputs[0].dptr_); cv::Mat dst(outputs[0].shape_[0], outputs[0].shape_[1], cv_type, outputs[0].dptr_); - cv::copyMakeBorder(buf, dst, - param.top, param.bot, param.left, param.right, - param.type, cv::Scalar(param.value)); + cv::Scalar color(param.value, param.value, param.value); + if (param.values.ndim() > 0) { + color = cv::Scalar(cv::Vec(param.values.begin())); + } + cv::copyMakeBorder(buf, dst, param.top, param.bot, param.left, param.right, param.type, color); CHECK(!dst.empty()); CHECK_EQ(static_cast(dst.ptr()), outputs[0].dptr_); #else @@ -276,6 +369,16 @@ NNVM_REGISTER_OP(_cvimdecode) .add_argument("buf", "NDArray", "Buffer containing binary encoded image") .add_arguments(ImdecodeParam::__FIELDS__()); +NNVM_REGISTER_OP(_cvimread) +.describe("Read and decode image with OpenCV. \n" + "Note: return image in RGB by default, " + "instead of OpenCV's default BGR.") +.set_num_inputs(0) +.set_num_outputs(1) +.set_attr_parser(op::ParamParser) +.set_attr("FNDArrayFunction", Imread) +.add_arguments(ImreadParam::__FIELDS__()); + NNVM_REGISTER_OP(_cvimresize) .describe("Resize image with OpenCV. \n") .set_num_inputs(1) @@ -300,5 +403,3 @@ NNVM_REGISTER_OP(_cvcopyMakeBorder) } // namespace io } // namespace mxnet - - diff --git a/src/io/image_iter_common.h b/src/io/image_iter_common.h index 59916c9b9997..f2f72dc928eb 100644 --- a/src/io/image_iter_common.h +++ b/src/io/image_iter_common.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2017 by Contributors * \file image_iter_common.h * \brief common types used by image data iterators */ diff --git a/src/io/image_recordio.h b/src/io/image_recordio.h index 10674ec20355..a931539aa296 100644 --- a/src/io/image_recordio.h +++ b/src/io/image_recordio.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file image_recordio.h * \brief image recordio struct */ @@ -24,7 +42,7 @@ struct ImageRecordIO { /*! * \brief label field that returns label of images * when image list was not presented, - * + * * NOTE: user do not need to repack recordio just to * change label field, just supply a list file that * maps image id to new labels @@ -58,9 +76,9 @@ struct ImageRecordIO { return header.image_id[0]; } /*! - * \brief load header from a record content + * \brief load header from a record content * \param buf the head of record - * \param size the size of the entire record + * \param size the size of the entire record */ inline void Load(void *buf, size_t size) { CHECK(size >= sizeof(header)); diff --git a/src/io/inst_vector.h b/src/io/inst_vector.h index 83ede8ef1342..6dc7bdfd730a 100644 --- a/src/io/inst_vector.h +++ b/src/io/inst_vector.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file inst_vector.h * \brief holder of a sequence of DataInst in CPU * that are not necessarily of same shape @@ -12,6 +30,7 @@ #include #include #include +#include #include #include @@ -20,7 +39,7 @@ namespace io { /*! * \brief a vector of tensor with various shape * - * data are stored in memory continously + * data are stored in memory continuously */ template class TensorVector { diff --git a/src/io/io.cc b/src/io/io.cc index 822f66f47453..e7c92843b44e 100644 --- a/src/io/io.cc +++ b/src/io/io.cc @@ -1,4 +1,22 @@ -// Copyright (c) 2015 by Contributors +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 diff --git a/src/io/iter_batchloader.h b/src/io/iter_batchloader.h index 2b53393679c6..c5ec10618080 100644 --- a/src/io/iter_batchloader.h +++ b/src/io/iter_batchloader.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file iter_batchloader.h * \brief define a batch adapter to create tblob batch */ @@ -145,7 +163,7 @@ class BatchLoader : public IIterator { shape_[i] = dst_shape; data_[i].resize(mshadow::Shape1(dst_shape.Size()), src_type_flag); unit_size_[i] = src_shape.Size(); - out_.data.push_back(TBlob(data_[i].dptr_, dst_shape, cpu::kDevMask, src_type_flag)); + out_.data.push_back(TBlob(data_[i].dptr_, dst_shape, cpu::kDevMask, src_type_flag, 0)); } } }; // class BatchLoader diff --git a/src/io/iter_csv.cc b/src/io/iter_csv.cc index 9cbb46e18ca6..a28b8d4d9d13 100644 --- a/src/io/iter_csv.cc +++ b/src/io/iter_csv.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file iter_csv.cc * \brief define a CSV Reader to read in arrays */ @@ -107,7 +125,7 @@ class CSVIter: public IIterator { << "The data size in CSV do not match size of shape: " << "specified shape=" << shape << ", the csv row-length=" << row.length; const real_t* ptr = row.value; - return TBlob((real_t*)ptr, shape, cpu::kDevMask); // NOLINT(*) + return TBlob((real_t*)ptr, shape, cpu::kDevMask, 0); // NOLINT(*) } CSVIterParam param_; @@ -164,7 +182,7 @@ Examples:: [[ 3. 4. 5.] [ 4. 5. 6.]] - // Creates a `CSVIter` with `round_batch` set to False. + // Creates a `CSVIter` with default `round_batch` set to True. CSVIter = mx.io.CSVIter(data_csv = 'data/data.csv', data_shape = (3,), batch_size = 3) @@ -174,8 +192,8 @@ Examples:: [3. 4. 5.]] [[4. 5. 6.] - [2. 3. 4.] - [3. 4. 5.]] + [1. 2. 3.] + [2. 3. 4.]] // Now, `reset` method is called. CSVIter.reset() @@ -187,10 +205,10 @@ Examples:: // Creates a `CSVIter` with `round_batch`=False. CSVIter = mx.io.CSVIter(data_csv = 'data/data.csv', data_shape = (3,), - batch_size = 3, round_batch=True) + batch_size = 3, round_batch=False) - // Contents of two batches read from the above iterator in both passes after calling - // `reset` method before second pass is as follows: + // Contents of two batches read from the above iterator in both passes, after calling + // `reset` method before second pass, is as follows: [[1. 2. 3.] [2. 3. 4.] [3. 4. 5.]] diff --git a/src/io/iter_image_det_recordio.cc b/src/io/iter_image_det_recordio.cc index 25e920d77c13..4e80d5d53172 100644 --- a/src/io/iter_image_det_recordio.cc +++ b/src/io/iter_image_det_recordio.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file iter_image_recordio-inl.hpp * \brief recordio data iterator */ diff --git a/src/io/iter_image_recordio.cc b/src/io/iter_image_recordio.cc index 49694d07f1bb..64f31a65fa51 100644 --- a/src/io/iter_image_recordio.cc +++ b/src/io/iter_image_recordio.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file iter_image_recordio-inl.hpp * \brief recordio data iterator */ diff --git a/src/io/iter_image_recordio_2.cc b/src/io/iter_image_recordio_2.cc index 94019fe293df..c4d1e8624bcc 100644 --- a/src/io/iter_image_recordio_2.cc +++ b/src/io/iter_image_recordio_2.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2017 by Contributors * \file iter_image_recordio_2.cc * \brief new version of recordio data iterator */ @@ -266,7 +284,7 @@ inline bool ImageRecordIOParser2::ParseNext(DataBatch *out) { auto dtype = prefetch_param_.dtype ? prefetch_param_.dtype.value() : first_batch.data[i].type_flag_; - out->data.at(i) = NDArray(dst_shape, Context::CPU(), false , src_type_flag); + out->data.at(i) = NDArray(dst_shape, Context::CPUPinned(0), false, src_type_flag); unit_size_[i] = src_shape.Size(); } } @@ -360,10 +378,10 @@ inline void ImageRecordIOParser2::ParseChunk(dmlc::InputSplit::Blob * chu (rand_uniform(*(prnds_[tid])) * normalize_param_.max_random_illumination * 2 - normalize_param_.max_random_illumination) * normalize_param_.scale; } + DType RGBA[4] = {}; for (int i = 0; i < res.rows; ++i) { uchar* im_data = res.ptr(i); for (int j = 0; j < res.cols; ++j) { - DType RGBA[4]; for (int k = 0; k < n_channels; ++k) { RGBA[k] = im_data[swap_indices[k]]; } diff --git a/src/io/iter_mnist.cc b/src/io/iter_mnist.cc index 09799f2b1991..055af52aaebd 100644 --- a/src/io/iter_mnist.cc +++ b/src/io/iter_mnist.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file iter_mnist.cc * \brief register mnist iterator */ diff --git a/src/io/iter_normalize.h b/src/io/iter_normalize.h index 2cebaaa3a48e..409231b59bc8 100644 --- a/src/io/iter_normalize.h +++ b/src/io/iter_normalize.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file iter_normalize.h * \brief Iterator that subtracts mean and do a few augmentations. */ diff --git a/src/io/iter_prefetcher.h b/src/io/iter_prefetcher.h index 9050ef2d1b38..89960c71a12f 100644 --- a/src/io/iter_prefetcher.h +++ b/src/io/iter_prefetcher.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file iter_prefetcher.h * \brief define a prefetcher using threaditer to keep k batch fetched */ diff --git a/src/kvstore/comm.h b/src/kvstore/comm.h index 1197d4ef3edb..ade9c95feda7 100644 --- a/src/kvstore/comm.h +++ b/src/kvstore/comm.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /** - * Copyright (c) 2015 by Contributors */ #ifndef MXNET_KVSTORE_COMM_H_ #define MXNET_KVSTORE_COMM_H_ @@ -18,14 +36,7 @@ namespace kvstore { class Comm { public: Comm() { -#if MXNET_USE_CUDA - int gpu_num; - int ret = cudaGetDeviceCount(&gpu_num); - pinned_ctx_ = (ret == 0 && gpu_num > 0) ? - Context::CPUPinned(0) : Context::CPU(); -#else - pinned_ctx_ = Context::CPU(); -#endif + pinned_ctx_ = Context::CPUPinned(0); } virtual ~Comm() { } /** diff --git a/src/kvstore/kvstore.cc b/src/kvstore/kvstore.cc index be5662e8a6db..a288676102cb 100644 --- a/src/kvstore/kvstore.cc +++ b/src/kvstore/kvstore.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file kvstore.cc * \brief implement kv_store */ @@ -7,7 +25,6 @@ #include #include #include "./kvstore_local.h" -// #include "./kvstore_device.h" #if MXNET_USE_DIST_KVSTORE #include "./kvstore_dist.h" #endif // MXNET_USE_DIST_KVSTORE diff --git a/src/kvstore/kvstore_dist.h b/src/kvstore/kvstore_dist.h index 5f5a0cc67a64..b64d7c6369bc 100644 --- a/src/kvstore/kvstore_dist.h +++ b/src/kvstore/kvstore_dist.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /** - * Copyright (c) 2015 by Contributors * @file kvstore_dist.h * @brief distributed implementation based on ps-lite */ @@ -100,23 +118,22 @@ class KVStoreDist : public KVStoreLocal { if (recv_buf.is_none()) { // it may happen for the first time a no-rank-0 worker pull the weight. recv_buf = NDArray( - grouped_vals[i][0]->shape(), pinned_ctx_, false, grouped_vals[i][0]->dtype()); + grouped_vals[i][0]->shape(), pinned_ctx_, true, grouped_vals[i][0]->dtype()); } -#if MKL_EXPERIMENTAL == 1 - mkl_set_tblob_eager_mode(recv_buf.data()); -#endif - real_t* data = static_cast(recv_buf.data().dptr_); - size_t size = recv_buf.shape().Size(); - - auto pull_from_servers = [this, key, data, size]( + auto pull_from_servers = [this, key, recv_buf]( RunContext rctx, Engine::CallbackOnComplete cb) { // convert to ps keys + size_t size = recv_buf.shape().Size(); PSKV& pskv = EncodeKey(key, size); - - // issue pull, false means no delete +#if MKL_EXPERIMENTAL == 1 + mkl_set_tblob_eager_mode(recv_buf.data()); +#endif + real_t* data = static_cast(recv_buf.data().dptr_); + // false means not to delete data when SArray is deleted auto vals = new ps::SArray(data, size, false); + // issue pull CHECK_NOTNULL(ps_worker_)->ZPull( - pskv.keys, vals, &pskv.lens, 0, [vals, cb](){ delete vals; cb(); }); + pskv.keys, vals, &pskv.lens, 0, [vals, cb](){ delete vals; cb(); }); }; CHECK_NOTNULL(Engine::Get())->PushAsync( @@ -208,27 +225,26 @@ class KVStoreDist : public KVStoreLocal { send_buf = merged; // avoid memory copy } else { if (send_buf.is_none()) { - send_buf = NDArray(merged.shape(), pinned_ctx_, false, merged.dtype()); + send_buf = NDArray(merged.shape(), pinned_ctx_, true, merged.dtype()); } CopyFromTo(merged, &send_buf); } // push to servers - send_buf.WaitToRead(); - size_t size = send_buf.shape().Size(); -#if MKL_EXPERIMENTAL == 1 - mkl_set_tblob_eager_mode(send_buf.data()); -#endif - real_t* data = static_cast(send_buf.data().dptr_); auto push_to_servers = - [this, key, data, size](RunContext rctx, Engine::CallbackOnComplete cb) { - // convert to ps keys + [this, key, send_buf](RunContext rctx, Engine::CallbackOnComplete cb) { + // convert to ps keys + size_t size = send_buf.shape().Size(); PSKV& pskv = EncodeKey(key, size); +#if MKL_EXPERIMENTAL == 1 + mkl_set_tblob_eager_mode(send_buf.data()); +#endif + real_t* data = static_cast(send_buf.data().dptr_); // do push. false means no delete ps::SArray vals(data, size, false); CHECK_NOTNULL(ps_worker_)->ZPush( - pskv.keys, vals, pskv.lens, 0, [cb]() { cb(); }); + pskv.keys, vals, pskv.lens, 0, [cb]() { cb(); }); }; Engine::Get()->PushAsync( push_to_servers, diff --git a/src/kvstore/kvstore_dist_server.h b/src/kvstore/kvstore_dist_server.h index 02d4a38c2b10..4e9f887173c5 100644 --- a/src/kvstore/kvstore_dist_server.h +++ b/src/kvstore/kvstore_dist_server.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file mxnet_node.h * \brief implement mxnet nodes */ diff --git a/src/kvstore/kvstore_local.h b/src/kvstore/kvstore_local.h index caa57a20d46e..536a89b46e13 100644 --- a/src/kvstore/kvstore_local.h +++ b/src/kvstore/kvstore_local.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /** - * Copyright (c) 2015 by Contributors * @file kvstore_local.h * @brief local implementation */ @@ -10,6 +28,7 @@ #include #include #include +#include #include #include #include "./comm.h" @@ -47,6 +66,20 @@ class KVStoreLocal : public KVStore { } } + void Init(const std::vector& str_keys, + const std::vector& values) override { + std::vector keys(str_keys.size()); + for (size_t i = 0; i < str_keys.size(); ++i) { + auto &str_key = str_keys[i]; + CHECK(str_key_dict_.find(str_key) == str_key_dict_.end()) + << "duplicate init of key " << str_key; + auto key = next_str_key_++; + str_key_dict_[str_key] = key; + keys[i] = key; + } + Init(keys, values); + } + void Push(const std::vector& keys, const std::vector& values, int priority) override { @@ -87,6 +120,22 @@ class KVStoreLocal : public KVStore { } } + void Push(const std::vector& str_keys, + const std::vector& values, + int priority) override { + std::vector keys(str_keys.size()); + LookupKeys(str_keys, &keys); + Push(keys, values, priority); + } + + void Pull(const std::vector& str_keys, + const std::vector& values, + int priority) override { + std::vector keys(str_keys.size()); + LookupKeys(str_keys, &keys); + Pull(keys, values, priority); + } + protected: /** * \brief group values on keys @@ -118,12 +167,27 @@ class KVStoreLocal : public KVStore { } } } + + void LookupKeys(const std::vector& str_keys, + std::vector *keys) { + for (size_t i = 0; i < str_keys.size(); ++i) { + auto &str_key = str_keys[i]; + CHECK(str_key_dict_.find(str_key) != str_key_dict_.end()) + << "key " << str_key << " doesn't exist. Did you init?"; + keys->at(i) = str_key_dict_[str_key]; + } + } + /// reducer and broadcaster Comm* comm_; /// pinned context Context pinned_ctx_; /// \brief buffer for storing local values std::unordered_map local_; + /// key mapping for string -> integer + std::unordered_map str_key_dict_; + /// the next available integer for string->int key mapping + int next_str_key_ = 0; }; } // namespace kvstore } // namespace mxnet diff --git a/src/ndarray/autograd.cc b/src/ndarray/autograd.cc index e7b57956a39b..5ecea5decf03 100644 --- a/src/ndarray/autograd.cc +++ b/src/ndarray/autograd.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2017 by Contributors * \file autograd.cc * \brief Implementation of AutogradRuntime module. */ @@ -23,9 +41,11 @@ using nnvm::NodeEntryMap; using exec::GraphExecutor; #if DMLC_CXX11_THREAD_LOCAL -thread_local bool AutogradRuntime::is_train_; +thread_local bool AutogradRuntime::is_train_ = false; +thread_local bool AutogradRuntime::is_recording_ = false; #else -MX_THREAD_LOCAL bool AutogradRuntime::is_train_; +MX_THREAD_LOCAL bool AutogradRuntime::is_train_ = false; +MX_THREAD_LOCAL bool AutogradRuntime::is_recording_ = false; #endif template @@ -49,6 +69,10 @@ nnvm::NodeEntry AGNodeEntry::nn_entry() const { return nnvm::NodeEntry{ag_node->nn_node, index, version}; } +bool AGNodeEntry::is_none() const { + return ag_node == nullptr || ag_node->outputs.empty(); +} + AutogradRuntime::AutogradRuntime() {} void AutogradRuntime::MarkVariables( @@ -56,13 +80,23 @@ void AutogradRuntime::MarkVariables( const std::vector& grad_reqs, const std::vector& gradients) { for (uint32_t i = 0; i < variables.size(); ++i) { - AGNodeEntry e{AGNode::Create(Node::Create()), 0, 0}; + std::string str_c(std::to_string(variable_count_++)); + + AGNodeEntry e{ + AGNode::Create( + nnvm::Symbol::CreateVariable("var" + str_c).outputs[0].node), 0, 0}; variables[i]->entry_.clear(); - e.ag_node->outputs.push_back(*variables[i]); + e.ag_node->outputs.emplace_back(*variables[i]); + + AGNodeEntry ge{ + AGNode::Create( + nnvm::Symbol::CreateVariable("grad" + str_c).outputs[0].node), 0, 0}; gradients[i]->entry_.clear(); - e.ag_node->out_grads.push_back(*gradients[i]); + ge.ag_node->outputs.emplace_back(*gradients[i]); + gradients[i]->entry_ = std::move(ge); + e.ag_node->out_grads.emplace_back(*gradients[i]); + e.ag_node->grad_req = static_cast(grad_reqs[i]); - e.ag_node->nn_node->attrs.name = "agvar" + std::to_string(variable_count_++); variables[i]->entry_ = std::move(e); // assign last to prevent cyclic reference } } @@ -71,15 +105,15 @@ void AutogradRuntime::RecordImperativeFCompute(const nnvm::Op* op, const nnvm::NodeAttrs& attrs, std::vector *p_inputs, std::vector *p_outputs) { - RecordOp(op, attrs, p_inputs, p_outputs, nullptr); + RecordOp(op, attrs, p_inputs, p_outputs, OpStatePtr()); } -void AutogradRuntime::RecordImperativeOperator(const std::shared_ptr& opr, +void AutogradRuntime::RecordImperativeOperator(const OpStatePtr& state, const nnvm::Op* op, const nnvm::NodeAttrs& attrs, std::vector *p_inputs, std::vector *p_outputs) { - RecordOp(op, attrs, p_inputs, p_outputs, opr); + RecordOp(op, attrs, p_inputs, p_outputs, state); } std::shared_ptr AutogradRuntime::_GetSharedRef() { @@ -92,58 +126,119 @@ AutogradRuntime* AutogradRuntime::Get() { return ptr; } -AGNodePtr AutogradRuntime::RecordOp(const nnvm::Op* op, +void AutogradRuntime::RecordOp(const nnvm::Op* op, const nnvm::NodeAttrs& attrs, std::vector *p_inputs, std::vector *p_outputs, - const std::shared_ptr& opr) { + const OpStatePtr& state) { + static auto& fgradient = nnvm::Op::GetAttr("FGradient"); std::vector& inputs = *p_inputs; std::vector& outputs = *p_outputs; + for (uint32_t i = 0; i < outputs.size(); ++i) { + CHECK(outputs[i].entry_.is_none()) + << "Inplace operations (+=, -=, x[:]=, etc) are not supported when " + << "recording with autograd. " + << "Assigning to NDArrays that are already in a computational graph " + << "will cause undefined behavior when evaluating gradients. " + << "Please call backward first to clear the graph or do this out side of " + << "a record section. "; + } + if (!fgradient.count(attrs.op)) return; + bool need_grad = false; + for (const auto& i : inputs) { + if (!i.entry_.is_none()) { + need_grad = true; + break; + } + } + if (!need_grad) return; + NodePtr nn_node = Node::Create(); nn_node->attrs = attrs; - nn_node->attrs.name = "agnode_" + std::to_string(node_count_++); - - AGNodePtr ag_node = AGNode::Create(nn_node); - ag_node->opr = opr; + nn_node->attrs.name = "node_" + std::to_string(node_count_++); + // Get backward dependency + std::vector save_inputs(inputs.size()), save_outputs(outputs.size()); + for (uint32_t i = 0; i < inputs.size(); ++i) { + nn_node->inputs.emplace_back(NodeEntry{nullptr, i, 0}); + } + std::vector ograd_entries; for (uint32_t i = 0; i < outputs.size(); ++i) { - if (outputs[i].entry_.ag_node == nullptr || - !outputs[i].entry_.ag_node->out_grads.size()) { - outputs[i].entry_.clear(); - ag_node->outputs.push_back(outputs[i]); - outputs[i].entry_ = AGNodeEntry{ag_node, i, 0}; - } else { - NDArray copy = outputs[i]; - copy.entry_.clear(); - ag_node->outputs.push_back(copy); + ograd_entries.emplace_back(NodeEntry{nullptr, i, 1}); + } + auto igrad_entries = fgradient[nn_node->op()](nn_node, ograd_entries); + for (const auto& i : igrad_entries) { + if (i.node == nullptr && i.version == 0) { + save_inputs[i.index] = true; + } else if (i.node == nn_node) { + save_outputs[i.index] = true; } } + DFSVisit(igrad_entries, [&](const NodePtr& node) { + if (!node || node == nn_node) return; + for (const auto& i : node->inputs) { + if (i.node == nullptr && i.version == 0) { + save_inputs[i.index] = true; + } else if (i.node == nn_node) { + save_outputs[i.index] = true; + } + } + }); + + AGNodePtr ag_node = AGNode::Create(nn_node); + ag_node->state = state; for (size_t i = 0; i < inputs.size(); ++i) { - if (inputs[i].entry_.ag_node.get() == nullptr) { - AGNodeEntry e{AGNode::Create(Node::Create()), 0, 0}; - e.ag_node->outputs.emplace_back(inputs[i]); + if (inputs[i].entry_.is_none()) { + AGNodeEntry e{ + AGNode::Create( + nnvm::Symbol::CreateVariable( + "null" + std::to_string(variable_count_++)).outputs[0].node), 0, 0}; + if (save_inputs[i]) { + e.ag_node->outputs.emplace_back(inputs[i]); + } else { + // Put a dummy array here since it will not be used. + e.ag_node->outputs.emplace_back( + TBlob(nullptr, inputs[i].shape(), inputs[i].ctx().dev_mask(), + inputs[i].dtype()), inputs[i].ctx().dev_id); + } e.ag_node->out_grads.emplace_back(); - e.ag_node->nn_node->attrs.name = "agvar_" + std::to_string(variable_count_++); inputs[i].entry_ = std::move(e); // assign last to prevent cyclic reference } - nn_node->inputs.push_back(inputs[i].entry_.nn_entry()); + nn_node->inputs[i] = inputs[i].entry_.nn_entry(); ag_node->inputs.push_back(inputs[i].entry_); + if (save_inputs[i]) { + inputs[i].entry_.ag_node->outputs[inputs[i].entry_.index] = inputs[i].Detach(); + } } - return ag_node; + for (uint32_t i = 0; i < outputs.size(); ++i) { + if (save_outputs[i]) { + ag_node->outputs.emplace_back(outputs[i].Detach()); + } else { + // Put a dummy array here since it will not be used. + ag_node->outputs.emplace_back( + TBlob(nullptr, outputs[i].shape(), outputs[i].ctx().dev_mask(), + outputs[i].dtype()), outputs[i].ctx().dev_id); + } + outputs[i].entry_ = AGNodeEntry{ag_node, i, 0}; + } } -void AutogradRuntime::ComputeGradient(const std::vector& outputs) { +void AutogradRuntime::ComputeGradient(const std::vector& outputs, + const std::vector& ograds, + bool retain_graph, bool is_train) { static auto& fmutate_inputs = nnvm::Op::GetAttr("FMutateInputs"); std::vector heads; Symbol sym; NodeEntryMap feed_dict; for (const auto& i : outputs) { - CHECK(i.entry_.ag_node.get() != nullptr) - << "Cannot differentiate node because it doesn't have " - << "computation history. Did you forget to set is_training?"; + CHECK(!i.entry_.is_none()) + << "Cannot differentiate node because it is not in a computational graph. " + << "You need to set is_recording to true or use autograd.record() to save " + << "computational graphs for backward. If you want to differentiate the same " + << "graph twice, you need to pass retain_graph=True to backward."; heads.emplace_back(i.entry_); sym.outputs.emplace_back(i.entry_.nn_entry()); } @@ -153,13 +248,19 @@ void AutogradRuntime::ComputeGradient(const std::vector& outputs) { std::vector args, args_grad; std::vector aux_states; std::vector grad_reqs; - std::unordered_map> saved_opr; + std::unordered_map saved_states; AGDFSVisit(heads, [&](const AGNodePtr& n) { + CHECK(n->nn_node != nullptr) + << "Node is differentiated twice without retaining graph the first time. " + << "This usually happens when you want to differentiate a graph twice but " + << "forgot to set retain_graph=True the first time. If you are training " + << "recurrent model (like LSTMs) maybe you forgot to detach the hidden " + << "state from the previous iteration before feeding it to the next iteration."; if (n->nn_node->is_variable()) { vlist.push_back(n); } else { - if (n->opr != nullptr) { - saved_opr.insert({n->nn_node.get(), n->opr}); + if (n->state) { + saved_states.insert({n->nn_node.get(), n->state}); } if (fmutate_inputs.count(n->nn_node->op())) { for (uint32_t i : fmutate_inputs[n->nn_node->op()](n->nn_node->attrs)) { @@ -172,13 +273,18 @@ void AutogradRuntime::ComputeGradient(const std::vector& outputs) { } }); + bool has_writeto = false; for (const auto& n : vlist) { if (mutable_set.count(n.get())) { aux_states.push_back(n->outputs[0]); } else { + if (n->grad_req != kNullOp) { + n->fresh_out_grad = true; + } args.push_back(n->outputs[0]); args_grad.push_back(n->out_grads[0]); grad_reqs.push_back(n->grad_req); + has_writeto = has_writeto || n->grad_req == kWriteTo; } } @@ -186,26 +292,45 @@ void AutogradRuntime::ComputeGradient(const std::vector& outputs) { std::map ctx_map; auto exec = new exec::GraphExecutor(); // (TODO) too hack here - exec->saved_opr_ = saved_opr; + exec->saved_states_ = saved_states; exec->Init(sym, args[0].ctx(), ctx_map, args, args_grad, grad_reqs, aux_states, nullptr, feed_dict); std::vector head_grads; head_grads.reserve(exec->head_grad_array_.size()); + CHECK_EQ(ograds.size(), exec->output_arrays_.size()); - for (size_t i = 0; i < exec->output_arrays_.size(); ++i) { - NDArray grad(exec->output_arrays_[i].shape(), exec->output_arrays_[i].ctx()); - grad = static_cast(1.0); - head_grads.push_back(grad); + for (size_t i = 0; i < ograds.size(); ++i) { + if (ograds[i].is_none()) { + head_grads.emplace_back( + exec->output_arrays_[i].shape(), exec->output_arrays_[i].ctx(), + false, exec->output_arrays_[i].dtype()); + head_grads.back() = static_cast(1.0); + } else { + head_grads.emplace_back(ograds[i]); + } } - exec->Backward(head_grads); + // std::stringstream os; + // exec->Print(os); + // LOG(INFO) << os.str(); + + exec->Backward(head_grads, is_train); delete exec; } - for (auto& i : heads) { - i.ag_node->clear_history(); + if (!retain_graph) { + for (auto& i : heads) { + i.ag_node->clear_history(); + } + } else if (has_writeto) { + LOG(INFO) + << "Warning: when calling backward with retain_graph=True, grad_req for " + << "Parameters should be set to 'add'. Otherwise the second backward " + << "will over-write gradients from the first backward. Also remember " + << "to manually set gradients to zero with zero_grad before starting the " + << "next iteration."; } } diff --git a/src/ndarray/autograd.h b/src/ndarray/autograd.h index 3603b0a111d0..199af350bf93 100644 --- a/src/ndarray/autograd.h +++ b/src/ndarray/autograd.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2017 by Contributors * \file autograd.h * \brief AutogradRuntime can automatically compute gradients */ @@ -20,17 +38,19 @@ namespace mxnet { namespace autograd { + class AGNode { public: OpReqType grad_req; nnvm::NodePtr nn_node; - std::shared_ptr opr; + OpStatePtr state; std::vector inputs; std::vector outputs; std::vector out_grads; + bool fresh_out_grad; explicit AGNode(const nnvm::NodePtr& nn_node_) : - grad_req(kNullOp), nn_node(nn_node_) {} + grad_req(kNullOp), nn_node(nn_node_), fresh_out_grad(false) {} static AGNodePtr Create(const nnvm::NodePtr& nn_node_) { return std::make_shared(nn_node_); @@ -38,7 +58,7 @@ class AGNode { void clear_history() { if (out_grads.size()) return; - opr.reset(); + state.reset(); outputs.clear(); nn_node.reset(); for (auto& i : inputs) i.ag_node->clear_history(); @@ -61,6 +81,16 @@ class AutogradRuntime { bool IsTraining() const { return is_train_; } + /*! \brief turn on or turn off operator recording for autograd. */ + bool SetIsRecording(bool is_recording) { + bool old = is_recording_; + is_recording_ = is_recording; + return old; + } + /*! \brief whether operator recording is on. */ + bool IsRecording() const { + return is_recording_; + } /*! \brief mark variables for computing gradients. */ void MarkVariables(const std::vector& variables, const std::vector& grad_reqs, @@ -71,13 +101,15 @@ class AutogradRuntime { std::vector* p_inputs, std::vector* p_outputs); /*! \brief record imperative operator which is executed by operator. */ - void RecordImperativeOperator(const std::shared_ptr& opr, + void RecordImperativeOperator(const OpStatePtr& state, const nnvm::Op* op, const nnvm::NodeAttrs& attrs, std::vector* p_inputs, std::vector* p_outputs); /*! \brief compute the gradient of outputs w.r.t variables. */ - void ComputeGradient(const std::vector& outputs); + void ComputeGradient(const std::vector& outputs, + const std::vector& ograds, + bool retain_graph, bool is_train); /*! \return AutogradRuntime singleton */ static AutogradRuntime* Get(); /*! \brief Get shared pointer reference to AutogradRuntime singleton. @@ -95,18 +127,20 @@ class AutogradRuntime { private: /*! \brief to record operator, return corresponding node. */ - AGNodePtr RecordOp(const nnvm::Op* op, + void RecordOp(const nnvm::Op* op, const nnvm::NodeAttrs& attrs, std::vector* p_inputs, std::vector* p_outputs, - const std::shared_ptr& opr); + const OpStatePtr& state); /*! \brief AutogradRuntime singleton. */ static AutogradRuntime* instance_; /*! \brief indicate whether is training. */ #if DMLC_CXX11_THREAD_LOCAL static thread_local bool is_train_; + static thread_local bool is_recording_; #else static MX_THREAD_LOCAL bool is_train_; + static MX_THREAD_LOCAL bool is_recording_; #endif /*! \brief node count used for naming */ std::atomic node_count_{0}; diff --git a/src/ndarray/ndarray.cc b/src/ndarray/ndarray.cc index c19a82b164c4..8e71df729b73 100644 --- a/src/ndarray/ndarray.cc +++ b/src/ndarray/ndarray.cc @@ -1,9 +1,28 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file ndarray.cc * \brief ndarry module of mxnet */ #include +#include #include #include #include @@ -23,13 +42,22 @@ DMLC_REGISTRY_ENABLE(::mxnet::NDArrayFunctionReg); namespace mxnet { +NDArray NDArray::grad() const { + if (this->entry_.ag_node && this->entry_.ag_node->out_grads.size()) { + CHECK_EQ(this->entry_.ag_node->out_grads.size(), 1); + return this->entry_.ag_node->out_grads[0]; + } + return NDArray(); +} + NDArray NDArray::Reshape(const TShape &shape) const { using namespace autograd; - CHECK_GE(shape_.Size(), shape.Size()) - << "NDArray.Reshape: target shape size is different from current shape"; - NDArray ret = *this; - ret.shape_ = shape; if (AutogradRuntime::Get()->IsTraining()) { + CHECK_GE(shape_.Size(), shape.Size()) + << "NDArray.Reshape: target shape must have must have the same size as " + << "current shape when recording with autograd."; + NDArray ret = *this; + ret.shape_ = shape; // fake a Reshape op ret.entry_.clear(); const nnvm::Op* op = nnvm::Op::Get("Reshape"); @@ -46,6 +74,10 @@ NDArray NDArray::Reshape(const TShape &shape) const { op, attrs, &inputs, &outputs); return outputs[0]; } else { + CHECK_GE(shape_.Size(), shape.Size()) + << "NDArray.Reshape: target shape size is larger current shape"; + NDArray ret = *this; + ret.shape_ = shape; return ret; } } @@ -55,9 +87,12 @@ NDArray NDArray::Slice(index_t begin, index_t end) const { using namespace autograd; NDArray ret = *this; CHECK(!is_none()) << "NDArray is not initialized"; + CHECK_LT(begin, end) << "Invalid slicing range [" << begin << ", " << end << ")"; CHECK_GE(shape_[0], end) << "Slice end index out of range"; size_t length = shape_.ProdShape(1, shape_.ndim()); - ret.offset_ += begin * length; + MSHADOW_TYPE_SWITCH(ret.dtype(), DType, { + ret.byte_offset_ += begin * length * sizeof(DType); + }); ret.shape_[0] = end - begin; if (AutogradRuntime::Get()->IsTraining()) { // fake a slice_axis op @@ -90,6 +125,20 @@ NDArray NDArray::At(index_t idx) const { } } + +bool NDArray::fresh_out_grad() const { + if (entry_.ag_node != nullptr) return entry_.ag_node->fresh_out_grad; + return false; +} + + +void NDArray::set_fresh_out_grad(bool state) const { + CHECK(entry_.ag_node != nullptr) + << "NDArray has not been marked as a variable and does not have gradient state"; + entry_.ag_node->fresh_out_grad = state; +} + + /*! * \brief run a ternary operation * \param lhs left operand @@ -613,8 +662,11 @@ NDArray &NDArray::operator/=(const real_t &src) { return ScalarOpApply(this, src); } +/* magic number for ndarray version 1, with int64_t TShape */ +static const uint32_t NDARRAY_V1_MAGIC = 0xF993fac8; + void NDArray::Save(dmlc::Stream *strm) const { - // save shape + strm->Write(NDARRAY_V1_MAGIC); shape_.Save(strm); if (is_none()) return; // save context @@ -638,10 +690,28 @@ void NDArray::Save(dmlc::Stream *strm) const { strm->Write(save_data.dptr_, type_size * shape_.Size()); } +bool LegacyTShapeLoad(dmlc::Stream *strm, TShape *shape) { + uint32_t magic; + if (strm->Read(&magic, sizeof(uint32_t)) != sizeof(uint32_t)) return false; + switch (magic) { + case NDARRAY_V1_MAGIC: + return shape->Load(strm); + default: + // meet legacy TShape, magic is ndim here + uint32_t ndim = magic; + *shape = TShape(ndim); + std::vector buffer(ndim); + size_t nread = ndim * sizeof(uint32_t); + if (strm->Read(buffer.data(), nread) != nread) return false; + nnvm::ShapeTypeCast(buffer.begin(), buffer.end(), shape->begin()); + return true; + } +} + bool NDArray::Load(dmlc::Stream *strm) { // load shape TShape shape; - if (!shape.Load(strm)) return false; + if (!LegacyTShapeLoad(strm, &shape)) return false; if (shape.ndim() == 0) { *this = NDArray(); return true; } @@ -710,12 +780,11 @@ void NDArray::SyncCopyFromCPU(const void *data, size_t size) const { TShape dshape = this->shape(); CHECK_EQ(dshape.Size(), size) << "Memory size do not match"; - TBlob src((void*)data, dshape, cpu::kDevMask, this->dtype_); // NOLINT(*) + TBlob src((void*)data, dshape, cpu::kDevMask, this->dtype_, 0); // NOLINT(*) if (this->ctx().dev_mask() == cpu::kDevMask) { this->WaitToWrite(); - RunContext rctx; - rctx.stream = nullptr; + RunContext rctx{this->ctx(), nullptr}; TBlob dst = this->data(); ndarray::Copy(src, &dst, Context::CPU(), Context::CPU(), rctx); } else { @@ -739,12 +808,11 @@ void NDArray::SyncCopyToCPU(void *data, size_t size) const { TShape dshape = this->shape(); CHECK_EQ(dshape.Size(), size) << "Memory size do not match"; - TBlob dst(data, dshape, cpu::kDevMask, this->dtype_); // NOLINT(*) + TBlob dst(data, dshape, cpu::kDevMask, this->dtype_, 0); // NOLINT(*) if (this->ctx().dev_mask() == cpu::kDevMask) { this->WaitToRead(); - RunContext rctx; - rctx.stream = nullptr; + RunContext rctx{this->ctx(), nullptr}; ndarray::Copy(this->data(), &dst, Context::CPU(), Context::CPU(), rctx); } else { diff --git a/src/ndarray/ndarray_function-inl.h b/src/ndarray/ndarray_function-inl.h index 28524b73d0dd..2be55f50f934 100644 --- a/src/ndarray/ndarray_function-inl.h +++ b/src/ndarray/ndarray_function-inl.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file ndarray_function-inl.h * \brief The real implementation of NDArray functions. */ diff --git a/src/ndarray/ndarray_function.cc b/src/ndarray/ndarray_function.cc index a5ba2660fd34..e4af86d2c824 100644 --- a/src/ndarray/ndarray_function.cc +++ b/src/ndarray/ndarray_function.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file ndarray_function_cpu.cc * \brief CPU Implementation of ndarray function. */ diff --git a/src/ndarray/ndarray_function.cu b/src/ndarray/ndarray_function.cu index ff6702f2f41b..30d532673cff 100644 --- a/src/ndarray/ndarray_function.cu +++ b/src/ndarray/ndarray_function.cu @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file ndarray_function_cpu.cc * \brief GPU Implementation of ndarray function. */ @@ -20,7 +38,7 @@ void Copy(const TBlob &from, TBlob *to, MSHADOW_TYPE_SWITCH(to->type_flag_, DType, { mshadow::Copy(to->FlatTo1D(), from.FlatTo1D(), - static_cast*>(ctx.stream)); + ctx.get_stream()); }); } @@ -33,7 +51,7 @@ void Copy(const TBlob &from, TBlob *to, MSHADOW_TYPE_SWITCH(to->type_flag_, DType, { mshadow::Copy(to->FlatTo1D(), from.FlatTo1D(), - static_cast*>(ctx.stream)); + ctx.get_stream()); }); } @@ -42,7 +60,7 @@ void Copy(const TBlob &from, TBlob *to, Context from_ctx, Context to_ctx, RunContext ctx) { if (from_ctx.dev_id == to_ctx.dev_id) { - mshadow::Stream* s = static_cast*>(ctx.stream); + mshadow::Stream* s = ctx.get_stream(); MSHADOW_TYPE_SWITCH(to->type_flag_, DType, { if (to->type_flag_ == from.type_flag_) { mshadow::Copy(to->FlatTo1D(s), @@ -60,7 +78,7 @@ void Copy(const TBlob &from, TBlob *to, << "copy across only support continugous memory"; CHECK_EQ(to->type_flag_, from.type_flag_) << "Source and target must have the same data type when copying across devices."; - mshadow::Stream *s = static_cast*>(ctx.stream); + mshadow::Stream *s = ctx.get_stream(); CHECK(s != NULL) << "need stream in GPU context"; cudaMemcpyPeerAsync(to->dptr_, to_ctx.dev_id, diff --git a/src/ndarray/ndarray_function.h b/src/ndarray/ndarray_function.h index 00dd3d0e959a..b1ed58db3e74 100644 --- a/src/ndarray/ndarray_function.h +++ b/src/ndarray/ndarray_function.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file ndarray_op.h * \brief the real execution functions of ndarray operations */ @@ -41,6 +59,10 @@ struct Div : public BinaryBase { typedef mshadow::op::div mshadow_op; }; +struct Mod : public BinaryBase { + typedef op::mshadow_op::mod mshadow_op; +}; + struct ClipMin : public BinaryBase { struct mshadow_op { template diff --git a/src/nnvm/legacy_op_util.cc b/src/nnvm/legacy_op_util.cc index 9b39794b4782..2bba5f1c3655 100644 --- a/src/nnvm/legacy_op_util.cc +++ b/src/nnvm/legacy_op_util.cc @@ -54,6 +54,97 @@ class ParsedOpProp { } }; +class OperatorState { + public: + OperatorState(Operator *opr, const OperatorProperty *prop) { + opr_ = opr; + fwd_init_ = bwd_init_ = false; + + in_data_.resize(prop->ListArguments().size()); + out_data_.resize(prop->NumOutputs()); + aux_data_.resize(prop->ListAuxiliaryStates().size()); + in_grad_.resize(in_data_.size()); + out_grad_.resize(prop->NumVisibleOutputs()); + + std::vector out_grad_ptr(out_grad_.size()); + for (size_t i = 0; i < out_grad_.size(); ++i) { + out_grad_ptr[i] = &out_grad_[i]; + } + std::vector in_data_ptr(in_data_.size()); + for (size_t i = 0; i < in_data_.size(); ++i) { + in_data_ptr[i] = &in_data_[i]; + } + std::vector out_data_ptr(out_data_.size()); + for (size_t i = 0; i < out_data_.size(); ++i) { + out_data_ptr[i] = &out_data_[i]; + } + arg_data_ptr_ = prop->BackwardInputs( + out_grad_ptr, in_data_ptr, out_data_ptr); + } + + ~OperatorState() { delete opr_; } + + void Forward(const OpContext &ctx, + const std::vector& inputs, + const std::vector& req, + const std::vector& outputs) { + if (!fwd_init_) { + CHECK_EQ(inputs.size(), in_data_.size() + aux_data_.size()); + CHECK_EQ(outputs.size(), out_data_.size()); + for (size_t i = 0; i < in_data_.size(); ++i) in_data_[i] = inputs[i]; + for (size_t i = 0; i < aux_data_.size(); ++i) { + aux_data_[i] = inputs[i + in_data_.size()]; + } + for (size_t i = 0; i < out_data_.size(); ++i) out_data_[i] = outputs[i]; + fwd_init_ = true; + } + opr_->Forward(ctx, in_data_, req, out_data_, aux_data_); + } + + void Backward(const OpContext &ctx, + const std::vector& inputs, + const std::vector& req, + const std::vector& outputs) { + if (!bwd_init_) { + CHECK(fwd_init_); + CHECK_EQ(arg_data_ptr_.size() + aux_data_.size(), inputs.size()); + for (size_t i = 0; i < arg_data_ptr_.size(); ++i) { + *arg_data_ptr_[i] = inputs[i]; + } + for (size_t i = 0; i < aux_data_.size(); ++i) { + aux_data_[i] = inputs[inputs.size() - aux_data_.size() + i]; + } + CHECK_EQ(outputs.size(), in_grad_.size()); + for (size_t i = 0; i < outputs.size(); ++i) in_grad_[i] = outputs[i]; + bwd_init_ = true; + } + opr_->Backward(ctx, out_grad_, in_data_, out_data_, req, in_grad_, aux_data_); + } + + private: + Operator *opr_; + bool fwd_init_, bwd_init_; + std::vector in_data_, aux_data_, out_data_, in_grad_, out_grad_; + std::vector arg_data_ptr_; +}; + +void LegacyOpForward(const OpStatePtr& state, + const OpContext& ctx, + const std::vector& inputs, + const std::vector& req, + const std::vector& outputs) { + auto& op = state.get_state(); + op.Forward(ctx, inputs, req, outputs); +} + +void LegacyOpBackward(const OpStatePtr& state, + const OpContext& ctx, + const std::vector& inputs, + const std::vector& req, + const std::vector& outputs) { + auto& op = state.get_state(); + op.Backward(ctx, inputs, req, outputs); +} // function to use operator property to infer attr // get op property from the attribute @@ -182,14 +273,15 @@ std::vector OpBackResourceRequest(const NodeAttrs& attrs) { return prop.ptr->BackwardResource(ishape); } -Operator* OpPropCreateLayerOp(const NodeAttrs& attrs, - Context ctx, - const std::vector& ishape, - const std::vector& itype) { +OpStatePtr OpPropCreateLayerOp(const NodeAttrs& attrs, + Context ctx, + const std::vector& ishape, + const std::vector& itype) { auto& prop = nnvm::get(attrs.parsed); std::vector is(ishape.begin(), ishape.begin() + prop.arguments.size()); std::vector it(itype.begin(), itype.begin() + prop.arguments.size()); - return prop.ptr->CreateOperatorEx(ctx, &is, &it); + return OpStatePtr::Create(prop.ptr->CreateOperatorEx(ctx, &is, &it), + prop.ptr.get()); } inline std::vector OpPropGradient( @@ -300,6 +392,11 @@ std::vector > OpBackInplaceOption(const NodeAttrs& attrs) { return remap; } +inline ExecType OpExecType(const NodeAttrs& attrs) { + auto& prop = nnvm::get(attrs.parsed); + return prop.ptr->exec_type(); +} + // register the legacy operator properties under NNVM registry. void RegisterLegacyOpProp() { for (auto reg : dmlc::Registry::List()) { @@ -328,10 +425,14 @@ void RegisterLegacyOpProp() { op.set_attr("FMutateInputs", OpPropMutateInputs); op.set_attr("FInplaceOption", OpPropInplaceOption); op.set_attr("FResourceRequest", OpPropResourceRequest); - op.set_attr("FCreateLayerOp", OpPropCreateLayerOp); + op.set_attr("FExecType", OpExecType); + op.set_attr("FCreateOpState", OpPropCreateLayerOp); + op.set_attr("FStatefulCompute", LegacyOpForward); + op.set_attr("FStatefulCompute", LegacyOpForward); if (reg->key_var_num_args.length() != 0) { op.set_attr("key_var_num_args", reg->key_var_num_args); } + // register BackwardOps std::string back_op_name = "_backward_" + reg->name; Op& back_op = ::dmlc::Registry<::nnvm::Op>::Get()->__REGISTER__(back_op_name); @@ -348,6 +449,9 @@ void RegisterLegacyOpProp() { "FResourceRequest", OpBackResourceRequest); back_op.set_attr("TIsLayerOpBackward", true); back_op.set_attr("TIsBackward", true); + back_op.set_attr("FExecType", OpExecType); + back_op.set_attr("FStatefulCompute", LegacyOpBackward); + back_op.set_attr("FStatefulCompute", LegacyOpBackward); } } diff --git a/src/operator/activation-inl.h b/src/operator/activation-inl.h index 26ed07ce9c70..6e6222bb64dd 100644 --- a/src/operator/activation-inl.h +++ b/src/operator/activation-inl.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file activation-inl.h * \brief Activation operator * \author Bing Xu @@ -84,7 +102,7 @@ class ActivationOp : public Operator { // Decalre Factory function, used for dispatch specialization template -Operator* CreateOp(ActivationParam type, int dtype); +Operator* CreateOp(ActivationParam type, int dtype, const TShape& dshape); #if DMLC_USE_CXX11 class ActivationProp : public OperatorProperty { diff --git a/src/operator/activation.cc b/src/operator/activation.cc index 0b1562925398..a33c11ce546d 100644 --- a/src/operator/activation.cc +++ b/src/operator/activation.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file activation.cc * \brief activation op * \author Bing Xu @@ -15,10 +33,10 @@ namespace mxnet { namespace op { template<> -Operator *CreateOp(ActivationParam param, int dtype) { +Operator *CreateOp(ActivationParam param, int dtype, const TShape& dshape) { Operator *op = NULL; #if MXNET_USE_MKL2017 == 1 - if (param.act_type == activation::kReLU) { + if (param.act_type == activation::kReLU && dshape.ndim() <= 4) { switch (dtype) { case mshadow::kFloat32: return new MKLReluOp(); @@ -54,12 +72,8 @@ Operator *CreateOp(ActivationParam param, int dtype) { // DO_BIND_DISPATCH comes from operator_common.h Operator *ActivationProp::CreateOperatorEx(Context ctx, std::vector *in_shape, - std::vector *in_type) const { - std::vector out_shape, aux_shape; - std::vector out_type, aux_type; - CHECK(InferType(in_type, &out_type, &aux_type)); - CHECK(InferShape(in_shape, &out_shape, &aux_shape)); - DO_BIND_DISPATCH(CreateOp, param_, (*in_type)[0]); + std::vector *in_type) const { + DO_BIND_DISPATCH(CreateOp, param_, (*in_type)[0], (*in_shape)[0]); } DMLC_REGISTER_PARAMETER(ActivationParam); diff --git a/src/operator/activation.cu b/src/operator/activation.cu index 66e734965784..0ac51ad03109 100644 --- a/src/operator/activation.cu +++ b/src/operator/activation.cu @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file activation.cu * \brief * \author Bing Xu @@ -13,7 +31,7 @@ namespace mxnet { namespace op { template<> -Operator *CreateOp(ActivationParam param, int dtype) { +Operator *CreateOp(ActivationParam param, int dtype, const TShape& dshape) { Operator *op = NULL; // SoftReLU not supported by CUDNN yet if (param.act_type == activation::kSoftReLU) { @@ -48,4 +66,3 @@ Operator *CreateOp(ActivationParam param, int dtype) { } } // namespace op } // namespace mxnet - diff --git a/src/operator/batch_norm-inl.h b/src/operator/batch_norm-inl.h old mode 100755 new mode 100644 index e8b91f907d2e..a5313e292b6d --- a/src/operator/batch_norm-inl.h +++ b/src/operator/batch_norm-inl.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2017 by Contributors * \file batch_norm-inl.h * \brief * \author Bing Xu, Chris Olivier @@ -10,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -30,21 +49,25 @@ namespace batchnorm { enum BatchNormOpInputs {kData, kGamma, kBeta}; // kGamma: weights, kBeta: biases enum BatchNormOpOutputs {kOut, kMean, kVar}; // req, out_data enum BatchNormOpAuxiliary {kMovingMean, kMovingVar}; // aux_states + +/*! \brief Default channel axis if none specified int he params */ +constexpr int DEFAULT_AXIS = 1; } // namespace batchnorm /*! \brief Parameters for BatchNoram operator */ struct BatchNormParam : public dmlc::Parameter { - float eps; + double eps; float momentum; bool fix_gamma; bool fix_linear_trans; bool use_global_stats; bool output_mean_var; + int axis; bool cudnn_off; DMLC_DECLARE_PARAMETER(BatchNormParam) { DMLC_DECLARE_FIELD(eps).set_default(1e-3f) .describe("Epsilon to prevent div 0. " - "Must be bigger than CUDNN_BN_MIN_EPSILON " + "Must be no less than CUDNN_BN_MIN_EPSILON " "defined in cudnn.h when using cudnn (usually 1e-5)"); DMLC_DECLARE_FIELD(momentum).set_default(0.9f) .describe("Momentum for moving average"); @@ -57,6 +80,8 @@ struct BatchNormParam : public dmlc::Parameter { "This will force change batch-norm into a scale shift operator."); DMLC_DECLARE_FIELD(output_mean_var).set_default(false) .describe("Output All,normal mean and var"); + DMLC_DECLARE_FIELD(axis).set_default(mxnet::op::batchnorm::DEFAULT_AXIS) + .describe("Specify which shape axis the channel is specified"); DMLC_DECLARE_FIELD(cudnn_off).set_default(false) .describe("Do not select CUDNN operator, if available"); } @@ -190,7 +215,7 @@ class BatchNormOp : public Operator { }; // class BatchNormOp template -Operator *CreateOp(const BatchNormParam& param, const int dtype, const TShape& shape); +Operator *CreateOp(BatchNormParam param, const int dtype, const TShape& shape); #if DMLC_USE_CXX11 class BatchNormProp : public OperatorProperty { @@ -210,21 +235,28 @@ class BatchNormProp : public OperatorProperty { CHECK_EQ(in_shape->size(), 3U) << "Input:[data, gamma, beta]"; const TShape &dshape = in_shape->at(0); + const size_t channelAxis = static_cast(param_.axis < 0 + ? static_cast(dshape.ndim()) + param_.axis + : param_.axis); + CHECK_LT(channelAxis, dshape.ndim()) << "Channel axis out of range: " << param_.axis; + + const int channelCount = dshape[channelAxis]; + if (dshape.ndim() == 0) { return false; } - in_shape->at(1) = TShape(Shape1(dshape[1])); - in_shape->at(2) = TShape(Shape1(dshape[1])); + in_shape->at(1) = TShape(Shape1(channelCount)); + in_shape->at(2) = TShape(Shape1(channelCount)); out_shape->clear(); - out_shape->push_back(dshape); // kOut - out_shape->push_back(Shape1(dshape[1])); // kMean - out_shape->push_back(Shape1(dshape[1])); // kVar + out_shape->push_back(dshape); // kOut + out_shape->push_back(Shape1(channelCount)); // kMean + out_shape->push_back(Shape1(channelCount)); // kVar aux_shape->clear(); - aux_shape->push_back(Shape1(dshape[1])); // kMovingMean - aux_shape->push_back(Shape1(dshape[1])); // kMovingVar + aux_shape->push_back(Shape1(channelCount)); // kMovingMean + aux_shape->push_back(Shape1(channelCount)); // kMovingVar return true; } @@ -332,6 +364,129 @@ class BatchNormProp : public OperatorProperty { BatchNormParam param_; }; // class BatchNormProp +namespace batchnorm { + +template +class BNTensor3 { + enum { OUTER, CHANNEL, INNER, COUNT }; + + public: + inline BNTensor3(const TBlob& blob, const int indexOfChannel) + : dptr_(blob.dptr()) + , indexOfChannel_(static_cast(indexOfChannel < 0 + ? (static_cast(blob.shape_.ndim()) + indexOfChannel) + : indexOfChannel)) { + shape_[OUTER] = 1; + for (size_t i = 0; i < indexOfChannel_; ++i) { + shape_[OUTER] *= blob.shape_[i]; + } + shape_[CHANNEL] = blob.shape_[indexOfChannel_]; + shape_[INNER] = 1; + for (size_t i = indexOfChannel_ + 1, n = blob.shape_.ndim(); i < n; ++i) { + shape_[INNER] *= blob.shape_[i]; + } + } + + inline BNTensor3(DType *p, const TShape& shape, const int indexOfChannel) + : dptr_(p) + , indexOfChannel_(static_cast(indexOfChannel < 0 + ? (static_cast(shape.ndim()) + indexOfChannel) + : indexOfChannel)) { + shape_[OUTER] = 1; + for (size_t i = 0; i < indexOfChannel_; ++i) { + shape_[OUTER] *= shape[i]; + } + shape_[CHANNEL] = shape[indexOfChannel_]; + shape_[INNER] = 1; + for (size_t i = indexOfChannel_ + 1, n = shape.ndim(); i < n; ++i) { + shape_[INNER] *= shape[i]; + } + } + + MSHADOW_FORCE_INLINE bool IsEmpty() const { + return dptr_ == nullptr; + } + + MSHADOW_XINLINE size_t Size() const { + size_t n = 1; + for (int i = 0; i < COUNT; ++i) { + n *= shape_[i]; + } + return n; + } + + MSHADOW_XINLINE size_t ChannelCount() const { + return shape_[CHANNEL]; + } + + MSHADOW_XINLINE size_t OuterSize() const { + return shape_[OUTER]; + } + + MSHADOW_XINLINE size_t InnerSize() const { + return shape_[INNER]; + } + + /*! \brief start of a given channel's spatial data */ + MSHADOW_XINLINE size_t StartOffset(const size_t channel) const { + return channel * InnerSize(); + } + + /*! \brief This is the amount to skip to next same-channel data + * This is the number of bytes to skip from one past the end of the current spatial data + * to the next start of the same channel's "spatial data" + * It is assume that the pointer being calculated points just beyond the + * end of the last blobk of spatial data + * i.e. RGBRGB <-- 2 + * RRGGBB <-- 4 + **/ + MSHADOW_XINLINE size_t SkipLengthToNextSameChannelData() const { + return (ChannelCount() - 1) * InnerSize(); + } + + MSHADOW_XINLINE size_t offset(const size_t outer, + const size_t channel, + const size_t i) const { + const size_t spatial_size = InnerSize(); + const size_t skip_length = SkipLengthToNextSameChannelData(); + size_t off = StartOffset(channel); + off += outer * shape_[CHANNEL] * shape_[INNER]; + const size_t skips = i / spatial_size; + off += (1 + skip_length) * skips; + off += i % spatial_size; + return off; + } + + MSHADOW_XINLINE DType& get_ref(const size_t batch, + const size_t channel, + const size_t i) { + const size_t off = offset(batch, channel, i); + return dptr_[off]; + } + + MSHADOW_XINLINE const DType& get_ref(const size_t batch, + const size_t channel, + const size_t i) const { + const size_t off = offset(batch, channel, i); + return dptr_[off]; + } + + DType *dptr_; + size_t indexOfChannel_; + size_t shape_[COUNT]; +}; + +inline int GetRealAxis(const TShape& shape, int axis) { + if (axis < 0) { + axis += shape.ndim(); + } + return axis; +} + +extern volatile bool disable_mkl; + +} // namespace batchnorm + #endif // DMLC_USE_CXX11 } // namespace op } // namespace mxnet diff --git a/src/operator/batch_norm.cc b/src/operator/batch_norm.cc index 169cb608aa6e..86f47dd6163f 100644 --- a/src/operator/batch_norm.cc +++ b/src/operator/batch_norm.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file batch_norm.cc * \brief * \author Bing Xu, Chris Olivier @@ -21,209 +39,48 @@ namespace mxnet { namespace op { namespace batchnorm { -template -class DeviceTensor3 { - DeviceTensor3(const DeviceTensor3&) = delete; - - public: - inline DeviceTensor3(const TBlob& blob, const size_t indexOfChannel) - : dptr_(blob.dptr()) - , indexOfChannel_(indexOfChannel) - , shape_(3) { - if (indexOfChannel) { - shape_[0] = 1; - for (size_t i = 0; i < indexOfChannel_; ++i) { - shape_[0] *= blob.shape_[i]; - } - } else { - shape_[0] = 0; - } - shape_[1] = blob.shape_[indexOfChannel_]; - shape_[2] = 1; - for (size_t i = indexOfChannel_ + 1, n = blob.shape_.ndim(); i < n; ++i) { - shape_[2] *= blob.shape_[i]; - } - } - - inline size_t Size() const { - size_t n = 1; - for (int i = 0; i < 3; ++i) { - n *= shape_[i]; - } - return n; - } - - inline size_t ChannelCount() const { - return shape_[1]; - } - - inline size_t BatchSize() const { - return shape_[0]; - } - - inline size_t SpatialSize() const { - return shape_[2]; - } - - DType *dptr_; - size_t indexOfChannel_; - TShape shape_; -}; - -/*! \brief offset, given indices such as bn, channel, depth, row, column */ -static inline index_t offset(const TShape& shape, - const size_t *indices, - const size_t indicesSize) { - const size_t dim = shape.ndim(); - size_t offset = 0; - for (size_t i = 0; i < dim; ++i) { - offset *= shape[i]; - if (indicesSize > i) { - offset += indices[i]; - } - } - return offset; -} +/*! \brief Global disable of batchnorm mkl operator for unit testing */ +volatile bool disable_mkl = false; /*! \brief Fast-foreach when you don't care about the position other than channel */ template -static inline void ForEachFast(const DeviceTensor3 &tensor, +static inline void ForEachFast(const BNTensor3 &tensor, const size_t channel, OnData onData) { - const size_t num = tensor.BatchSize(); - const size_t matrixSize = tensor.SpatialSize(); - - size_t indices[2] = {0, channel}; + const size_t num = tensor.OuterSize(); + const size_t matrixSize = tensor.InnerSize(); + const size_t skipLength = tensor.SkipLengthToNextSameChannelData(); + const size_t startOffset = tensor.StartOffset(channel); + DType *data = tensor.dptr_ + startOffset; - for (size_t batchItem = 0; batchItem < num; ++batchItem) { - indices[0] = batchItem; - DType *data = tensor.dptr_ + offset(tensor.shape_, &indices[0], - sizeof(indices)/sizeof(indices[0])); + for (size_t outer = 0; outer < num; ++outer) { for (size_t i = 0; i < matrixSize; ++i) { onData(data++); } + data += skipLength; } } /*! \brief Fast-foreach when you don't care about the position other than channel */ template -static inline void ForEachFast(const DeviceTensor3 &in_data, - const DeviceTensor3 &out_data, +static inline void ForEachFast(const BNTensor3 &in_data, + const BNTensor3 &out_data, const size_t channel, OnData onData) { - const size_t num = in_data.BatchSize(); - const size_t matrixSize = in_data.SpatialSize(); + const size_t num = in_data.OuterSize(); + const size_t matrixSize = in_data.InnerSize(); + const size_t skipLength = in_data.SkipLengthToNextSameChannelData(); + const size_t startOffset = in_data.StartOffset(channel); - size_t indices[2] = {0, channel}; + DType1 *data = in_data.dptr_ + startOffset; + DType2 *odata = out_data.dptr_ + startOffset; - for (size_t batchItem = 0; batchItem < num; ++batchItem) { - indices[0] = batchItem; - const size_t off = offset(in_data.shape_, &indices[0], sizeof(indices)/sizeof(indices[0])); - const DType1 *data = in_data.dptr_ + off; - DType2 *odata = out_data.dptr_ + off; + for (size_t outer = 0; outer < num; ++outer) { for (size_t i = 0; i < matrixSize; ++i) { onData(data++, odata++); } - } -} - -/*! \brief Fast-foreach when you don't care about the position other than channel */ -template -static inline void ForEachFast(const DeviceTensor3& tensor, - OnData onData) { - const size_t num = tensor.BatchSize(); - const size_t channels = tensor.ChannelCount(); - const size_t matrixSize = tensor.SpatialSize(); - - for (size_t batchItem = 0; batchItem < num; ++batchItem) { -#pragma openmp for - for (size_t channel = 0; channel < channels; ++channel) { - size_t indices[2] = { batchItem, channel }; - const size_t off = offset(tensor.shape_, &indices[0], sizeof(indices)/sizeof(indices[0])); - const DType *inData = tensor.dptr_ + off; - for (size_t i = 0; i < matrixSize; ++i) { - onData(channel, inData++); - } - } - } -} - -/*! \brief Fast-foreach when you don't care about the position other than channel */ -template -static inline void ForEachFast(const DeviceTensor3& in_data, - const DeviceTensor3& out_data, - OnData onData) { - const size_t num = in_data.BatchSize(); - const size_t channels = in_data.ChannelCount(); - const size_t matrixSize = in_data.SpatialSize(); - - for (size_t batchItem = 0; batchItem < num; ++batchItem) { -#pragma omp parallel for - for (int channel = 0; channel < channels; ++channel) { - size_t indices[2] = { batchItem, static_cast(channel) }; - const size_t off = offset(in_data.shape_, &indices[0], sizeof(indices)/sizeof(indices[0])); - const DType *inData = in_data.dptr_ + off; - DType *outData = out_data.dptr_ + off; - for (size_t i = 0; i < matrixSize; ++i) { - onData(channel, inData++, outData++); - } - } - } -} - -/*! \brief Compute the mean of each input channel */ -template -static inline void ComputeMean(const DeviceTensor3 &tensor, - AccReal *save_mean) { - const size_t channelCount = tensor.ChannelCount(); - - for (size_t i = 0; i < channelCount; ++i) { - save_mean[i] = 0; - } - - ForEachFast(tensor, - [&save_mean](const size_t channel, const DType *in_data){ - save_mean[channel] += *in_data; - }); - - const size_t itemCount = tensor.Size() / channelCount; - for (size_t i = 0, n = channelCount; i < n; ++i) { - save_mean[i] /= itemCount; - } -} - -/*! \brief Compute the variance of each input channel, as well as update moving mean/variants */ -template -static inline void ComputeVariance(const DeviceTensor3 &tensor, - const AccReal *mean_data, - const DType eps, - const TShape &oshape, - AccReal *save_std) { - const size_t channels = tensor.ChannelCount(); - for (size_t i = 0; i < channels; ++i) { - save_std[i] = 0; - } - ForEachFast(tensor, - [&save_std, &mean_data](const index_t channel, const DType *current_in_data) { - const AccReal mean = mean_data[channel]; - const AccReal current = *current_in_data; - save_std[channel] += (current - mean) * (current - mean); - }); - - const size_t itemCount = tensor.Size() / channels; -#pragma omp parallel for - for (int channel = 0; channel < channels; ++channel) { - const AccReal sum = save_std[channel]; - - AccReal invstd; - if (sum == 0 && eps == 0.0) { - // Nobody likes to divide by zero - invstd = 0; - } else { - const AccReal variance = sum/itemCount; - invstd = VARIANCE_TO_INVSTD(variance, eps); - } - save_std[channel] = invstd; + data += skipLength; + odata += skipLength; } } @@ -238,7 +95,7 @@ void BatchNormOp::DoForward(mshadow::Stream *, const std::vector &out_data, const std::vector &aux_states) { // Input - batchnorm::DeviceTensor3 inputData(in_data[batchnorm::kData], 1); + batchnorm::BNTensor3 inputData(in_data[batchnorm::kData], param_.axis); const TBlob &weights = in_data[batchnorm::kGamma]; const TBlob &bias = in_data[batchnorm::kBeta]; @@ -247,7 +104,7 @@ void BatchNormOp::DoForward(mshadow::Stream *, const TBlob &runningVariance = aux_states[batchnorm::kMovingVar]; // Output - batchnorm::DeviceTensor3 outputData(out_data[batchnorm::kOut], 1); + batchnorm::BNTensor3 outputData(out_data[batchnorm::kOut], param_.axis); const TBlob &meanVector = out_data[batchnorm::kMean]; const TBlob &varianceVector = out_data[batchnorm::kVar]; @@ -255,54 +112,79 @@ void BatchNormOp::DoForward(mshadow::Stream *, AccReal *var = varianceVector.dptr(); const bool is_train_and_not_global_stats = ctx.is_train && !param_.use_global_stats; + const size_t channelCount = inputData.ChannelCount(); + const size_t itemCountPerChannel = inputData.Size() / channelCount; - if (is_train_and_not_global_stats) { - // compute mean per input - ComputeMean(inputData, meanVector.dptr()); - - // compute variance per input - ComputeVariance(inputData, - meanVector.dptr(), - static_cast(param_.eps), - varianceVector.shape_, - var); // var is actually returned as invstd - } else { - const AccReal *rm = runningMean.dptr(); - const AccReal *rv = runningVariance.dptr(); - - for (size_t i = 0, n = inputData.shape_[1]; i < n; ++i) { - mean[i] = rm[i]; - var[i] = VARIANCE_TO_INVSTD(rv[i], param_.eps); + #pragma omp parallel for + for (int channel = 0; channel < static_cast(channelCount); ++channel) { + if (is_train_and_not_global_stats) { + // compute mean per input + mean[channel] = 0; + ForEachFast(inputData, channel, [mean, channel](const DType *in_data) { + mean[channel] += *in_data; }); + mean[channel] /= itemCountPerChannel; + + // compute variance per input + const AccReal thisMean = mean[channel]; + var[channel] = 0; + ForEachFast(inputData, channel, + [var, thisMean, channel](const DType *current_in_data) { + const AccReal current = *current_in_data; + var[channel] += (current - thisMean) * (current - thisMean); + }); + + const AccReal sum = var[channel]; + + AccReal invstd; + if (sum == 0 && param_.eps == 0.0) { + // Nobody likes to divide by zero + invstd = 0; + } else { + const AccReal variance = sum / itemCountPerChannel; + invstd = VARIANCE_TO_INVSTD(variance, param_.eps); + } + var[channel] = invstd; + } else { + const AccReal *rm = runningMean.dptr(); + const AccReal *rv = runningVariance.dptr(); + + mean[channel] = rm[channel]; + var[channel] = VARIANCE_TO_INVSTD(rv[channel], param_.eps); } - } - // compute output - AccReal *w = weights.dptr(); - const AccReal *b = bias.dptr(); + // compute output + AccReal *w = weights.dptr(); + const AccReal *b = bias.dptr(); + + const AccReal thisMean = mean[channel]; + const AccReal thisInvstd = var[channel]; + const AccReal thisWeight = w[channel]; + const AccReal thisBias = b[channel]; // note that var is still invstd if (!param_.fix_gamma) { if (IsWriting(req[batchnorm::kData])) { - ForEachFast(inputData, outputData, - [w, b, mean, var](const size_t channel, const DType *in_data, DType *out_data) { + ForEachFast(inputData, outputData, channel, + [thisWeight, thisBias, thisMean, thisInvstd](const DType *in_data, + DType *out_data) { *out_data = static_cast( - ((*in_data - mean[channel]) * var[channel]) * w[channel] + b[channel]); + ((*in_data - thisMean) * thisInvstd) * thisWeight + thisBias); }); } } else { if (IsWriting(req[batchnorm::kGamma])) { - for (size_t i =0, n = weights.Size(); i < n; ++i) { - w[i] = AccReal(1); - } + w[channel] = AccReal(1); } if (IsWriting(req[batchnorm::kData])) { - ForEachFast(inputData, outputData, - [w, b, mean, var](const size_t channel, const DType *in_data, DType *out_data) { + ForEachFast(inputData, outputData, channel, + [thisWeight, thisBias, thisMean, thisInvstd](const DType *in_data, + DType *out_data) { *out_data = static_cast( - ((*in_data - mean[channel]) * var[channel]) + b[channel]); + ((*in_data - thisMean) * thisInvstd) + thisBias); }); } } + } } template @@ -315,11 +197,11 @@ void BatchNormOp::DoBackward(mshadow::Stream *, const std::vector &in_grad, const std::vector &aux_states) { // Input Data - batchnorm::DeviceTensor3 inputData(in_data[batchnorm::kData], 1); + batchnorm::BNTensor3 inputData(in_data[batchnorm::kData], param_.axis); const TBlob &weights = in_data[batchnorm::kGamma]; // Input Grad - batchnorm::DeviceTensor3 gradIn(in_grad[batchnorm::kData], 1); + batchnorm::BNTensor3 gradIn(in_grad[batchnorm::kData], param_.axis); const TBlob &gradWeight = in_grad[batchnorm::kGamma]; const TBlob &gradBias = in_grad[batchnorm::kBeta]; @@ -328,18 +210,18 @@ void BatchNormOp::DoBackward(mshadow::Stream *, const TBlob &runningVariance = aux_states[batchnorm::kMovingVar]; // Output - batchnorm::DeviceTensor3 gradOut(out_grad[batchnorm::kOut], 1); + batchnorm::BNTensor3 gradOut(out_grad[batchnorm::kOut], param_.axis); const TBlob &saveMean = out_data[batchnorm::kMean]; const TBlob &saveStd = out_data[batchnorm::kVar]; - const size_t channelCount = inputData.shape_[1]; + const size_t channelCount = inputData.ChannelCount(); const size_t itemCount = inputData.Size() / channelCount; // Avoid multiple dptr() call within the channel loop AccReal *runningMeanDataPtr = runningMean.dptr(); AccReal *runningVarDataPtr = runningVariance.dptr(); - AccReal *saveMeanDataPtr = saveMean.dptr(); - AccReal *saveInvStdDataPtr = saveStd.dptr(); + const AccReal *saveMeanDataPtr = saveMean.dptr(); + const AccReal *saveInvStdDataPtr = saveStd.dptr(); AccReal *gradWeightData = gradWeight.dptr(); AccReal *gradBiasData = gradBias.dptr(); @@ -347,7 +229,7 @@ void BatchNormOp::DoBackward(mshadow::Stream *, #pragma omp parallel for for (int channel = 0; channel < static_cast(channelCount); ++channel) { - AccReal *weight = weights.dptr(); + const AccReal *weight = weights.dptr(); const AccReal w = weight ? weight[channel] : AccReal(1); AccReal mean, invstd; if (is_train_and_not_global_stats) { @@ -381,7 +263,7 @@ void BatchNormOp::DoBackward(mshadow::Stream *, dotp += (*thisInputData - mean) * (*gradOut_data); }); - if (gradIn.shape_.ndim() && IsWriting(req[batchnorm::kData])) { // if there's a grad input + if (!gradIn.IsEmpty() && IsWriting(req[batchnorm::kData])) { // if there's a grad input if (is_train_and_not_global_stats) { // when in training mode // Q(X) = X - E[x] ; i.e. input centered to zero mean @@ -431,12 +313,14 @@ void BatchNormOp::DoBackward(mshadow::Stream *, } } - template<> -Operator *CreateOp(const BatchNormParam& param, const int dtype, const TShape& shape) { +Operator *CreateOp(BatchNormParam param, const int dtype, const TShape& shape) { + param.axis = mxnet::op::batchnorm::GetRealAxis(shape, param.axis); Operator *op = nullptr; #if MXNET_USE_MKL2017 == 1 - if (shape.ndim() == 4) { + if (shape.ndim() == 4 + && param.axis == mxnet::op::batchnorm::DEFAULT_AXIS + && !mxnet::op::batchnorm::disable_mkl) { switch (dtype) { case mshadow::kFloat32: op = new MKLBatchNormOp(param); @@ -449,18 +333,11 @@ Operator *CreateOp(const BatchNormParam& param, const int dtype, const TSha break; } } -#define BATCHNORM_LOG_MKL_INFO() do { \ - LOG(INFO) << MKLBatchNormOp::getName() \ - << " Skipping MKL optimization (unsupported dimension or type)"; \ - } while (0) -#else -#define BATCHNORM_LOG_MKL_INFO() ((void)0) #endif if (!op) { MSHADOW_REAL_TYPE_SWITCH_EX(dtype, DType, AccReal, { - BATCHNORM_LOG_MKL_INFO(); op = new BatchNormOp(param); }); } return op; @@ -469,11 +346,6 @@ Operator *CreateOp(const BatchNormParam& param, const int dtype, const TSha // DO_BIND_DISPATCH comes from operator_common.h Operator *BatchNormProp::CreateOperatorEx(Context ctx, std::vector *in_shape, std::vector *in_type) const { - std::vector out_shape, aux_shape; - std::vector out_type, aux_type; - CHECK(InferType(in_type, &out_type, &aux_type)); - CHECK(InferShape(in_shape, &out_shape, &aux_shape)); - CHECK_GE(in_shape->size(), 1U); DO_BIND_DISPATCH(CreateOp, param_, (*in_type)[0], (*in_shape)[0]); } @@ -517,6 +389,10 @@ If ``use_global_stats`` is set to be true, then ``moving_mean`` and ``moving_var`` are used instead of ``data_mean`` and ``data_var`` to compute the output. It is often used during inference. +The parameter ``axis`` specifies which axis of the input shape denotes +the 'channel' (separately normalized groups). The default is 1. Specifying -1 sets the channel +axis to be the last item in the input shape. + Both ``gamma`` and ``beta`` are learnable parameters. But if ``fix_gamma`` is true, then set ``gamma`` to 1 and its gradient to 0. @@ -542,4 +418,3 @@ NNVM_REGISTER_OP(BatchNorm) } // namespace op } // namespace mxnet - diff --git a/src/operator/batch_norm.cu b/src/operator/batch_norm.cu old mode 100755 new mode 100644 index f081383b8276..64f7d9373823 --- a/src/operator/batch_norm.cu +++ b/src/operator/batch_norm.cu @@ -1,14 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2017 by Contributors * \file batch_norm.cu * \brief CUDA Batch Normalization code * \author Chris Olivier, Bing Xu * Adapted from Torch */ #include -#include #include -#include #include "batch_norm-inl.h" #define WRITE_DATA_FLAG 1 @@ -22,9 +38,10 @@ #include "./cudnn_batch_norm-inl.h" #endif -#include #include "../common/cuda_utils.h" +using namespace mxnet; + /*! \brief inverse standard deviation <-> variance */ #define VARIANCE_TO_INVSTD(__var$, __eps$) (1.0/sqrt((__var$) + DType(__eps$))) #define INVSTD_TO_VARIANCE(__invstd$, __eps$) ((1.0 / ((__invstd$) * (__invstd$))) - (__eps$)) @@ -45,14 +62,15 @@ struct ScalarConvert { }; // Number of threads in a block given an input size up to MAX_BLOCK_SIZE -static unsigned getNumThreads(int nElem) { +static unsigned getNumThreads(int nElem, const bool smaller) { unsigned threadSizes[5] = {32, 64, 128, 256, MAX_BLOCK_SIZE}; - for (int i = 0; i != 5; ++i) { + const int maxi = smaller ? 4 : 5; + for (int i = 0; i != maxi; ++i) { if (static_cast(nElem) <= threadSizes[i]) { return threadSizes[i]; } } - return MAX_BLOCK_SIZE; + return smaller ? (MAX_BLOCK_SIZE >> 1) : MAX_BLOCK_SIZE; } // Returns the index of the most significant 1 bit in `val`. @@ -80,60 +98,65 @@ struct Float2 { } }; -template +template struct SumOp { - __device__ SumOp(const DeviceTensor3 t) : tensor(t) {} + __device__ SumOp(const DeviceTensor t) : tensor(t) {} __device__ __forceinline__ AccReal operator()(int batch, int plane, int n) { - return ScalarConvert::to(tensor(batch, plane, n)); + return ScalarConvert::to(tensor.get_ref(batch, plane, n)); } - const DeviceTensor3 tensor; + const DeviceTensor tensor; }; -template +template struct VarOp { - __device__ VarOp(AccReal m, const DeviceTensor3 t) + __device__ VarOp(AccReal m, const DeviceTensor t) : mean(m) , tensor(t) { } __device__ __forceinline__ AccReal operator()(int batch, int plane, int n) { - DType val = tensor(batch, plane, n); + DType val = tensor.get_ref(batch, plane, n); return (val - mean) * (val - mean); } const AccReal mean; - const DeviceTensor3 tensor; + const DeviceTensor tensor; }; -template +template struct GradOp { - __device__ GradOp(AccReal m, const DeviceTensor3 i, const DeviceTensor3 g) + __device__ GradOp(AccReal m, const DeviceTensor i, const DeviceTensor g) : mean(m), input(i), gradOutput(g) {} __device__ __forceinline__ Float2 operator()(int batch, int plane, int n) { - const DType g = gradOutput(batch, plane, n); - const DType c = ScalarConvert::to(input(batch, plane, n) - mean); + const DType g = gradOutput.get_ref(batch, plane, n); + const DType c = ScalarConvert::to(input.get_ref(batch, plane, n) - mean); return Float2(g, g * c); } const AccReal mean; - const DeviceTensor3 input; - const DeviceTensor3 gradOutput; + const DeviceTensor input; + const DeviceTensor gradOutput; }; +#if CUDA_VERSION >= 9000 +#define FULLMASK 0xFFFFFFFF +#define __shfl_xor(...) __shfl_xor_sync(FULLMASK, __VA_ARGS__) +#endif + // Sum across all threads within a warp template static __device__ __forceinline__ T warpSum(T val) { #if __CUDA_ARCH__ >= 300 - for (int i = 0; i < getMSB(WARP_SIZE); ++i) { +for (int i = 0; i < getMSB(WARP_SIZE); ++i) { val += __shfl_xor(val, 1 << i, WARP_SIZE); } #else - __shared__ T values[MAX_BLOCK_SIZE]; - values[threadIdx.x] = val; - __threadfence_block(); - const int base = (threadIdx.x / WARP_SIZE) * WARP_SIZE; - for (int i = 1; i < WARP_SIZE; i++) { - val += values[base + ((i + threadIdx.x) % WARP_SIZE)]; - } +__shared__ T values[MAX_BLOCK_SIZE]; +values[threadIdx.x] = val; +__threadfence_block(); +const int base = (threadIdx.x / WARP_SIZE) * WARP_SIZE; +for (int i = 1; i < WARP_SIZE; i++) { +val += values[base + ((i + threadIdx.x) % WARP_SIZE)]; +} #endif - return val; +return val; } template @@ -144,11 +167,11 @@ static __device__ __forceinline__ Float2 warpSum(Float2 -static __device__ T reduce(Op op, DeviceTensor3 tensor, int plane) { +template +static __device__ T reduce(Op op, DeviceTensor tensor, int plane) { T sum = (T) 0; - for (int batch = 0; batch < tensor.getSize(0); ++batch) { - for (int x = threadIdx.x; x < tensor.getSize(2); x += blockDim.x) { + for (int batch = 0; batch < tensor.OuterSize(); ++batch) { + for (int x = threadIdx.x; x < tensor.InnerSize(); x += blockDim.x) { sum += op(batch, plane, x); } } @@ -179,10 +202,10 @@ static __device__ T reduce(Op op, DeviceTensor3 tensor, int plane) { return shared[0]; } -template +template __global__ void BatchNormalizationUpdateOutputInferenceKernel( - DeviceTensor3 input, - DeviceTensor3 output, + DeviceTensor input, + DeviceTensor output, DeviceTensor1 runningMean, DeviceTensor1 runningVar, DeviceTensor1 saveMean, @@ -209,19 +232,19 @@ __global__ void BatchNormalizationUpdateOutputInferenceKernel( } } // Write normalized and update the output - for (int batch = 0, nbatch = input.getSize(0); batch < nbatch; ++batch) { - for (int x = threadIdx.x, nx = input.getSize(2); x < nx; x += blockDim.x) { - const DType inp = input(batch, plane, x); - output(batch, plane, x) = + for (int batch = 0, nbatch = input.OuterSize(); batch < nbatch; ++batch) { + for (int x = threadIdx.x, nx = input.InnerSize(); x < nx; x += blockDim.x) { + const DType inp = input.get_ref(batch, plane, x); + output.get_ref(batch, plane, x) = ScalarConvert::to(gamma * (inp - mean) * invstd + beta); } } } -template +template __global__ void BatchNormalizationUpdateOutputKernel( - DeviceTensor3 input, - DeviceTensor3 output, + DeviceTensor input, + DeviceTensor output, DeviceTensor1 weight, DeviceTensor1 bias, const AccReal epsilon, @@ -232,15 +255,15 @@ __global__ void BatchNormalizationUpdateOutputKernel( DeviceTensor1 saveInvStd, const uint32_t flags) { const int plane = blockIdx.x; - const int N = input.getSize(0) * input.getSize(2); + const int N = input.OuterSize() * input.InnerSize(); const AccReal norm = AccReal(1) / N; // Compute the mean and variance across (batch, x/y/z) const AccReal mean = reduce( - SumOp(input), input, plane) * norm; + SumOp(input), input, plane) * norm; __syncthreads(); - const AccReal varN = reduce(VarOp(mean, input), + const AccReal varN = reduce(VarOp(mean, input), input, plane); AccReal invStd = 0; if (varN != AccReal(0) || epsilon != AccReal(0)) { @@ -265,55 +288,60 @@ __global__ void BatchNormalizationUpdateOutputKernel( : ScalarConvert::to(1); const AccReal beta = bias.numElements() > 0 ? ScalarConvert::to(bias[plane]) : ScalarConvert::to(0); - for (int batch = 0, nbatch = input.getSize(0); batch < nbatch; ++batch) { - for (int x = threadIdx.x, nx = input.getSize(2); x < nx; x += blockDim.x) { - const DType inp = input(batch, plane, x); - output(batch, plane, x) = + for (int batch = 0, nbatch = input.OuterSize(); batch < nbatch; ++batch) { + for (int x = threadIdx.x, nx = input.InnerSize(); x < nx; x += blockDim.x) { + const DType inp = input.get_ref(batch, plane, x); + output.get_ref(batch, plane, x) = ScalarConvert::to(gamma * (inp - mean) * invStd + beta); } } } -template +template +struct CUDATensors { + DeviceTensor1 gradWeight; + DeviceTensor1 gradBias; + DeviceTensor1 weight; + DeviceTensor1 runningMean; + DeviceTensor1 runningVar; + DeviceTensor1 saveMean; + DeviceTensor1 saveInvStd; +}; + +template static __global__ void BatchNormalizationBackwardKernel( - const DeviceTensor3 input, - const DeviceTensor3 gradOutput, - DeviceTensor3 gradInput, - DeviceTensor1 gradWeight, - DeviceTensor1 gradBias, - const DeviceTensor1 weight, - const DeviceTensor1 runningMean, - const DeviceTensor1 runningVar, - const DeviceTensor1 saveMean, - const DeviceTensor1 saveInvstd, + const DeviceTensor input, + const DeviceTensor gradOutput, + DeviceTensor gradInput, + CUDATensors tensors, const uint32_t flags, const AccReal momentum, const double eps) { int plane = blockIdx.x; - int N = gradOutput.getSize(0) * gradOutput.getSize(2); + int N = gradOutput.OuterSize() * gradOutput.InnerSize(); const bool is_train_and_not_global_stats = (flags & IS_TRAINING_FLAG) != 0 && (flags & USE_GLOBAL_STATS_FLAG) == 0; AccReal mean, invstd; if (is_train_and_not_global_stats) { - mean = ScalarConvert::to(saveMean[plane]); - invstd = saveInvstd[plane]; + mean = ScalarConvert::to(tensors.saveMean[plane]); + invstd = tensors.saveInvStd[plane]; } else { - mean = ScalarConvert::to(runningMean[plane]); - invstd = VARIANCE_TO_INVSTD(runningVar[plane], eps); + mean = ScalarConvert::to(tensors.runningMean[plane]); + invstd = VARIANCE_TO_INVSTD(tensors.runningVar[plane], eps); } - const AccReal weightVal = weight.numElements() > 0 ? - ScalarConvert::to(weight[plane]) : AccReal(1); + const AccReal weightVal = tensors.weight.numElements() > 0 ? + ScalarConvert::to(tensors.weight[plane]) : AccReal(1); const AccReal norm = AccReal(1) / N; // Compute two values across (batch, x/y/z) in one pass: // 1. Sum(gradOutput) // 2. DotProduct(input - mean, gradOutput) - GradOp g(mean, input, gradOutput); + GradOp g(mean, input, gradOutput); Float2< DType, AccReal > res = reduce < Float2 < DType, AccReal >, - GradOp< DType, AccReal, DeviceTensor3 >, DeviceTensor3 > (g, gradOutput, plane); + GradOp< DType, AccReal, DeviceTensor >, DeviceTensor > (g, gradOutput, plane); const AccReal gradOutputSum = res.v1; const AccReal dotP = res.v2; @@ -322,46 +350,50 @@ static __global__ void BatchNormalizationBackwardKernel( const AccReal gradScale = invstd * weightVal; if (threadIdx.x == 0 && is_train_and_not_global_stats) { - const AccReal localVariance = INVSTD_TO_VARIANCE(saveInvstd[plane], eps); - const AccReal localMean = saveMean[plane]; + const AccReal localVariance = INVSTD_TO_VARIANCE(tensors.saveInvStd[plane], eps); + const AccReal localMean = tensors.saveMean[plane]; // update running averages - runningMean[plane] = runningMean[plane] * momentum + localMean * (AccReal(1) - momentum); - runningVar[plane] = runningVar[plane] * momentum + localVariance * (AccReal(1) - momentum); + tensors.runningMean[plane] = tensors.runningMean[plane] + * momentum + localMean * (AccReal(1) - momentum); + tensors.runningVar[plane] = tensors.runningVar[plane] + * momentum + localVariance * (AccReal(1) - momentum); } - if (gradInput.numElements() > 0 && (flags & WRITE_DATA_FLAG) != 0) { - for (int batch = 0, nbatch = gradOutput.getSize(0); batch < nbatch; ++batch) { - for (int x = threadIdx.x, nx = gradOutput.getSize(2); x < nx; x += blockDim.x) { - const DType gradOut = gradOutput(batch, plane, x); + if (gradInput.Size() > 0 && (flags & WRITE_DATA_FLAG) != 0) { + for (int batch = 0, nbatch = gradOutput.OuterSize(); batch < nbatch; ++batch) { + for (int x = threadIdx.x, nx = gradOutput.InnerSize(); x < nx; x += blockDim.x) { + const DType gradOut = gradOutput.get_ref(batch, plane, x); if (is_train_and_not_global_stats) { - const DType inp = input(batch, plane, x); + const DType inp = input.get_ref(batch, plane, x); const AccReal proj = (inp - mean) * projScale; - gradInput(batch, plane, x) = + gradInput.get_ref(batch, plane, x) = ScalarConvert::to((gradOut - proj - gradMean) * gradScale); } else { - gradInput(batch, plane, x) = ScalarConvert::to(gradOut * gradScale); + gradInput.get_ref(batch, plane, x) = ScalarConvert::to( + gradOut * gradScale); } } } } - if (gradWeight.numElements() > 0 && threadIdx.x == 0 && (flags & WRITE_GAMMA_FLAG) != 0) { + if (tensors.gradWeight.numElements() > 0 && threadIdx.x == 0 && (flags & WRITE_GAMMA_FLAG) != 0) { if ((flags & FIX_GAMMA_FLAG) == 0) { - gradWeight[plane] = ScalarConvert::to(dotP * invstd); + tensors.gradWeight[plane] = ScalarConvert::to(dotP * invstd); } else { - gradWeight[plane] = DType(0); + tensors.gradWeight[plane] = DType(0); } } - if (gradBias.numElements() > 0 && threadIdx.x == 0 && (flags & WRITE_BETA_FLAG) != 0) { - gradBias[plane] = ScalarConvert::to(gradOutputSum); + if (tensors.gradBias.numElements() > 0 && threadIdx.x == 0 && (flags & WRITE_BETA_FLAG) != 0) { + tensors.gradBias[plane] = ScalarConvert::to(gradOutputSum); } } template struct DeviceTensor { public: + inline DeviceTensor() {} inline DeviceTensor(DType *p, const int *size) : dptr_(p) { for (int i = 0; i < Dim; ++i) { @@ -369,13 +401,11 @@ struct DeviceTensor { } } - __host__ __device__ - __forceinline__ unsigned getSize(const int i) const { + MSHADOW_XINLINE unsigned getSize(const int i) const { return size_[i]; } - __host__ __device__ - __forceinline__ int numElements() const { + MSHADOW_XINLINE int numElements() const { int n = 1; for (int i = 0; i < Dim; ++i) { n *= size_[i]; @@ -383,8 +413,7 @@ struct DeviceTensor { return n; } - __host__ __device__ - __forceinline__ DType &operator()(const size_t batch, + MSHADOW_XINLINE DType &operator()(const size_t batch, const size_t plane, const size_t x) const { int offset = 0; @@ -401,12 +430,11 @@ struct DeviceTensor { return *(const_cast(dptr_ + offset)); } - __host__ __device__ - __forceinline__ DType &operator[](const size_t x) const { + MSHADOW_XINLINE DType &operator[](const size_t x) const { return *(dptr_ + x); } - __forceinline__ size_t SpatialSize() const { + MSHADOW_XINLINE size_t InnerSize() const { size_t sz = 1; for (size_t i = 2; i < Dim; ++i) { sz *= size_[i]; @@ -414,7 +442,7 @@ struct DeviceTensor { return sz; } - __forceinline__ size_t ChannelCount() const { + MSHADOW_XINLINE size_t ChannelCount() const { return size_[1]; } @@ -450,19 +478,23 @@ static DeviceTensor devicetensor(const TBlob &blob) { #define DeviceTensor1 DeviceTensor -#define DeviceTensor3 DeviceTensor + +using namespace mxnet::op; template static void BatchNormalizationUpdateOutput(mshadow::Stream *s, const OpContext &ctx, + const BatchNormParam& param, const std::vector &in_data, const std::vector &out_data, const std::vector &aux_states, const uint32_t flags, double momentum, double eps) { - DeviceTensor3 input = devicetensor(in_data[batchnorm::kData]); - DeviceTensor3 output = devicetensor(out_data[batchnorm::kOut]); + batchnorm::BNTensor3 input = batchnorm::BNTensor3( + in_data[batchnorm::kData], param.axis); + batchnorm::BNTensor3 output = batchnorm::BNTensor3( + out_data[batchnorm::kOut], param.axis); DeviceTensor1 weight = devicetensor(in_data[batchnorm::kGamma]); DeviceTensor1 bias = devicetensor(in_data[batchnorm::kBeta]); DeviceTensor1 runningMean = devicetensor(aux_states[batchnorm::kMovingMean]); @@ -474,15 +506,17 @@ static void BatchNormalizationUpdateOutput(mshadow::Stream *s, if ((flags & IS_TRAINING_FLAG) == 0 || (flags & USE_GLOBAL_STATS_FLAG) != 0) { dim3 blocks(input.ChannelCount()); - dim3 threads(getNumThreads(input.SpatialSize())); - BatchNormalizationUpdateOutputInferenceKernel + dim3 threads(batchnorm::cuda::getNumThreads(input.InnerSize(), false)); + BatchNormalizationUpdateOutputInferenceKernel> <<< blocks, threads, 0, mshadow::Stream::GetStream(s) >>> ( input, output, runningMean, runningVar, saveMean, saveInvStd, weight, bias, eps, flags); } else { dim3 blocks(input.ChannelCount()); - dim3 threads(getNumThreads(input.SpatialSize())); - BatchNormalizationUpdateOutputKernel + dim3 threads(batchnorm::cuda::getNumThreads(input.InnerSize(), false)); + BatchNormalizationUpdateOutputKernel> << < blocks, threads, 0, mshadow::Stream::GetStream(s) >> > ( input, output, weight, bias, eps, momentum, runningMean, runningVar, saveMean, saveInvStd, flags); @@ -493,6 +527,7 @@ static void BatchNormalizationUpdateOutput(mshadow::Stream *s, template static void BatchNormalizationBackward(mshadow::Stream *s, const OpContext &ctx, + const BatchNormParam& param, const std::vector &out_grad, const std::vector &in_data, const std::vector &out_data, @@ -501,25 +536,34 @@ static void BatchNormalizationBackward(mshadow::Stream *s, const uint32_t flags, double momentum, double eps) { - DeviceTensor3 input = devicetensor(in_data[batchnorm::kData]); - DeviceTensor3 gradOutput = devicetensor(out_grad[batchnorm::kOut]); - DeviceTensor3 gradInput = devicetensor(in_grad[batchnorm::kData]); - DeviceTensor1 gradWeight = devicetensor(in_grad[batchnorm::kGamma]); - DeviceTensor1 gradBias = devicetensor(in_grad[batchnorm::kBeta]); - DeviceTensor1 weight = devicetensor(in_data[batchnorm::kGamma]); - DeviceTensor1 runningMean = devicetensor(aux_states[batchnorm::kMovingMean]); - DeviceTensor1 runningVar = devicetensor(aux_states[batchnorm::kMovingVar]); - DeviceTensor1 saveMean = devicetensor(out_data[batchnorm::kMean]); - DeviceTensor1 saveInvStd = devicetensor(out_data[batchnorm::kVar]); - - DCHECK_GT(weight.numElements(), 0); - + batchnorm::BNTensor3 input = batchnorm::BNTensor3( + in_data[batchnorm::kData], param.axis); + batchnorm::BNTensor3gradOutput = batchnorm::BNTensor3( + out_grad[batchnorm::kOut], param.axis); + batchnorm::BNTensor3gradInput = batchnorm::BNTensor3( + in_grad[batchnorm::kData], param.axis); + + CUDATensors tensors; + + tensors.gradWeight = devicetensor(in_grad[batchnorm::kGamma]); + tensors.gradBias = devicetensor(in_grad[batchnorm::kBeta]); + tensors.weight = devicetensor(in_data[batchnorm::kGamma]); + tensors.runningMean = devicetensor(aux_states[batchnorm::kMovingMean]); + tensors.runningVar = devicetensor(aux_states[batchnorm::kMovingVar]); + tensors.saveMean = devicetensor(out_data[batchnorm::kMean]); + tensors.saveInvStd = devicetensor(out_data[batchnorm::kVar]); + + DCHECK_GT(tensors.weight.numElements(), 0); +#ifdef NDEBUG + constexpr bool SMALLER_THREADS = false; +#else + constexpr bool SMALLER_THREADS = true; +#endif dim3 blocks(gradOutput.ChannelCount()); - dim3 threads(getNumThreads(gradOutput.SpatialSize())); - BatchNormalizationBackwardKernel + dim3 threads(batchnorm::cuda::getNumThreads(gradOutput.InnerSize(), SMALLER_THREADS)); + BatchNormalizationBackwardKernel> <<< blocks, threads, 0, mshadow::Stream::GetStream(s) >>> ( - input, gradOutput, gradInput, gradWeight, gradBias, weight, runningMean, runningVar, - saveMean, saveInvStd, flags, momentum, eps); + input, gradOutput, gradInput, tensors, flags, momentum, eps); MSHADOW_CUDA_POST_KERNEL_CHECK(BatchNormalizationBackward); } @@ -557,6 +601,7 @@ void BatchNormOp::DoForward(mshadow::Stream *stream, batchnorm::cuda::BatchNormalizationUpdateOutput( stream, ctx, + param_, in_data, out_data, aux_states, @@ -579,6 +624,7 @@ void BatchNormOp::DoBackward(mshadow::Stream *stream, batchnorm::cuda::BatchNormalizationBackward( stream, ctx, + param_, out_grad, in_data, out_data, @@ -592,10 +638,12 @@ void BatchNormOp::DoBackward(mshadow::Stream *stream, /*! \brief Create GPU operator for batch normalization */ template<> -Operator *CreateOp(const BatchNormParam& param, const int dtype, const TShape& shape) { +Operator *CreateOp(BatchNormParam param, const int dtype, const TShape& shape) { + param.axis = mxnet::op::batchnorm::GetRealAxis(shape, param.axis); Operator *op = NULL; #if MXNET_USE_CUDNN == 1 && CUDNN_MAJOR >= 5 - if (!param.use_global_stats && !param.cudnn_off && shape.ndim() <= 4) { + if (!param.use_global_stats && !param.cudnn_off && shape.ndim() <= 4 + && param.axis == mxnet::op::batchnorm::DEFAULT_AXIS) { MSHADOW_REAL_TYPE_SWITCH(dtype, DType, { op = new CuDNNBatchNormOp(param); }) diff --git a/src/operator/batch_norm_v1-inl.h b/src/operator/batch_norm_v1-inl.h old mode 100755 new mode 100644 index 19215c5400d5..092c4824f9e6 --- a/src/operator/batch_norm_v1-inl.h +++ b/src/operator/batch_norm_v1-inl.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file batch_norm-inl_v1.h * \brief * \author Bing Xu diff --git a/src/operator/batch_norm_v1.cc b/src/operator/batch_norm_v1.cc index 32c5034b0832..1abced8763c5 100644 --- a/src/operator/batch_norm_v1.cc +++ b/src/operator/batch_norm_v1.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file batch_norm_v1.cc * \brief * \author Bing Xu diff --git a/src/operator/batch_norm_v1.cu b/src/operator/batch_norm_v1.cu old mode 100755 new mode 100644 index 302dc47dbca4..8ed22a4dc6f1 --- a/src/operator/batch_norm_v1.cu +++ b/src/operator/batch_norm_v1.cu @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file batch_norm_v1.cu * \brief * \author Bing Xu diff --git a/src/operator/bilinear_sampler-inl.h b/src/operator/bilinear_sampler-inl.h index b4c9d991865f..2d68d7855b6d 100644 --- a/src/operator/bilinear_sampler-inl.h +++ b/src/operator/bilinear_sampler-inl.h @@ -1,219 +1,237 @@ -/*! - * Copyright (c) 2017 by Contributors - * \file bilinear_Sampler-inl.h - * \brief - * \author Xu Dong -*/ -#ifndef MXNET_OPERATOR_BILINEAR_SAMPLER_INL_H_ -#define MXNET_OPERATOR_BILINEAR_SAMPLER_INL_H_ - -#include -#include -#include -#include -#include -#include -#include -#include "./operator_common.h" - -namespace mxnet { -namespace op { - -namespace bs { -enum BilinearSamplerOpInputs {kData, kGrid}; -enum BilinearSamplerOpOutputs {kOut, kTmp}; -} - -struct BilinearSamplerParam : public dmlc::Parameter { - DMLC_DECLARE_PARAMETER(BilinearSamplerParam) { - } -}; - -template -class BilinearSamplerOp : public Operator { - public: - explicit BilinearSamplerOp(BilinearSamplerParam p) { - this->param_ = p; - } - - virtual void Forward(const OpContext &ctx, - const std::vector &in_data, - const std::vector &req, - const std::vector &out_data, - const std::vector &aux_args) { - using namespace mshadow; - using namespace mshadow::expr; - CHECK_EQ(req[bs::kOut], kWriteTo); - CHECK_EQ(in_data.size(), 2U); - Stream *s = ctx.get_stream(); - - Tensor data = in_data[bs::kData].get(s); - Tensor grid = in_data[bs::kGrid].get(s); - Tensor out = out_data[bs::kOut].get(s); - - BilinearSamplerForward(out, data, grid); - } - - virtual void Backward(const OpContext &ctx, - const std::vector &out_grad, - const std::vector &in_data, - const std::vector &out_data, - const std::vector &req, - const std::vector &in_grad, - const std::vector &aux_args) { - using namespace mshadow; - using namespace mshadow::expr; - CHECK_EQ(in_data.size(), 2U); - CHECK_NE(req[bs::kData], kWriteInplace); - CHECK_NE(req[bs::kGrid], kWriteInplace); - Stream *s = ctx.get_stream(); - - Tensor data = in_data[bs::kData].get(s); - Tensor grid = in_data[bs::kGrid].get(s); - Tensor gdata = in_grad[bs::kData].get(s); - Tensor ggrid = in_grad[bs::kGrid].get(s); - Tensor grad = out_grad[bs::kOut].get(s); - if (req[bs::kData] != kNullOp && req[bs::kGrid] != kNullOp) { - if (req[bs::kData] == kWriteTo) { - gdata = scalar(0.0f); - } - if (req[bs::kGrid] == kWriteTo) { - ggrid = scalar(0.0f); - } - BilinearSamplerBackward(gdata, ggrid, grad, data, grid); - } else if (req[bs::kData] == kNullOp && req[bs::kGrid] == kNullOp) { - return; - } else { - LOG(FATAL) << "Have not implemented the data req combinations! gdata_req=" - << req[bs::kData] << " ggrid_req=" << req[bs::kGrid]; - } - } - - private: - BilinearSamplerParam param_; -}; // class BilinearSamplerOp - -template -Operator* CreateOp(BilinearSamplerParam param, int dtype); - -#if DMLC_USE_CXX11 -class BilinearSamplerProp : public OperatorProperty { - public: - int NumVisibleOutputs() const override { - return 1; - } - - int NumOutputs() const override { - return 2; - } - - std::vector ListArguments() const override { - return {"data", "grid"}; - } - - std::vector ListOutputs() const override { - return {"output", "tmp"}; - } - - void Init(const std::vector >& kwargs) override { - param_.Init(kwargs); - } - - std::map GetParams() const override { - return param_.__DICT__(); - } - - bool InferShape(std::vector *in_shape, - std::vector *out_shape, - std::vector *aux_shape) const override { - using namespace mshadow; - CHECK_EQ(in_shape->size(), 2U) << "Input:[data, grid]"; - const TShape &dshape = (*in_shape)[bs::kData]; - const TShape &lshape = (*in_shape)[bs::kGrid]; - if (dshape.ndim() == 0) return false; - CHECK_EQ(dshape.ndim(), 4U) \ - << "input data should be 4D in batch-num_filter-y-x"; - if (lshape.ndim() == 0) return false; - CHECK_EQ(lshape.ndim(), 4U) \ - << "Sampler grid should be 4D in batch-2-y-x"; - CHECK_EQ(dshape[0], lshape[0]); - CHECK_EQ(lshape[1], 2U) << "incorrect grid shape[1], should be 2"; - // target height - CHECK_GT(lshape[2], 0U) \ - << "incorrect grid_shape: " << lshape[2]; - // target width - CHECK_GT(lshape[3], 0U) \ - << "incorrect grid_shape: " << lshape[3]; - out_shape->clear(); - // output_shape : (data.shape[0], data.shape[1], grid.shape[2], grid.shape[3]) - out_shape->push_back(dshape); - (*out_shape)[bs::kOut][2] = lshape[2]; - (*out_shape)[bs::kOut][3] = lshape[3]; - out_shape->push_back(Shape4(lshape[0], lshape[2], lshape[3], 2)); - return true; - } - - bool InferType(std::vector *in_type, - std::vector *out_type, - std::vector *aux_type) const override { - int dtype = -1; - for (size_t i = 0; i < in_type->size(); ++i) { - if (dtype == -1) { - dtype = in_type->at(i); - } else { - CHECK(in_type->at(i) == dtype || - in_type->at(i) == -1) << - "Non-uniform data type in BilinearSampler"; - } - } - if (dtype == -1) { - LOG(FATAL) << "Not enough information to infer type in BilinearSampler."; - return false; - } - size_t nin = this->ListArguments().size(); - in_type->clear(); - for (size_t i = 0; i < nin; ++i) in_type->push_back(dtype); - size_t naux = this->ListAuxiliaryStates().size(); - aux_type->clear(); - for (size_t i = 0; i < naux; ++i) aux_type->push_back(dtype); - size_t nout = this->ListOutputs().size(); - out_type->clear(); - for (size_t i = 0; i < nout; ++i) out_type->push_back(dtype); - return true; - } - - OperatorProperty* Copy() const override { - auto ptr = new BilinearSamplerProp(); - ptr->param_ = param_; - return ptr; - } - - std::string TypeString() const override { - return "BilinearSampler"; - } - - std::vector DeclareBackwardDependency( - const std::vector &out_grad, - const std::vector &in_data, - const std::vector &out_data) const override { - return {out_grad[bs::kOut], - in_data[bs::kData], - out_data[bs::kTmp], - in_data[bs::kGrid]}; - } - - Operator* CreateOperator(Context ctx) const override { - LOG(FATAL) << "Not Implemented."; - return NULL; - } - - Operator* CreateOperatorEx(Context ctx, std::vector *in_shape, - std::vector *in_type) const override; - - private: - BilinearSamplerParam param_; -}; // class BilinearSamplerProp -#endif // DMLC_USE_CXX11 -} // namespace op -} // namespace mxnet -#endif // MXNET_OPERATOR_BILINEAR_SAMPLER_INL_H_ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +/*! + * \file bilinear_Sampler-inl.h + * \brief + * \author Xu Dong +*/ +#ifndef MXNET_OPERATOR_BILINEAR_SAMPLER_INL_H_ +#define MXNET_OPERATOR_BILINEAR_SAMPLER_INL_H_ + +#include +#include +#include +#include +#include +#include +#include +#include "./operator_common.h" + +namespace mxnet { +namespace op { + +namespace bs { +enum BilinearSamplerOpInputs {kData, kGrid}; +enum BilinearSamplerOpOutputs {kOut, kTmp}; +} + +struct BilinearSamplerParam : public dmlc::Parameter { + DMLC_DECLARE_PARAMETER(BilinearSamplerParam) { + } +}; + +template +class BilinearSamplerOp : public Operator { + public: + explicit BilinearSamplerOp(BilinearSamplerParam p) { + this->param_ = p; + } + + virtual void Forward(const OpContext &ctx, + const std::vector &in_data, + const std::vector &req, + const std::vector &out_data, + const std::vector &aux_args) { + using namespace mshadow; + using namespace mshadow::expr; + CHECK_EQ(req[bs::kOut], kWriteTo); + CHECK_EQ(in_data.size(), 2U); + Stream *s = ctx.get_stream(); + + Tensor data = in_data[bs::kData].get(s); + Tensor grid = in_data[bs::kGrid].get(s); + Tensor out = out_data[bs::kOut].get(s); + + BilinearSamplerForward(out, data, grid); + } + + virtual void Backward(const OpContext &ctx, + const std::vector &out_grad, + const std::vector &in_data, + const std::vector &out_data, + const std::vector &req, + const std::vector &in_grad, + const std::vector &aux_args) { + using namespace mshadow; + using namespace mshadow::expr; + CHECK_EQ(in_data.size(), 2U); + CHECK_NE(req[bs::kData], kWriteInplace); + CHECK_NE(req[bs::kGrid], kWriteInplace); + Stream *s = ctx.get_stream(); + + Tensor data = in_data[bs::kData].get(s); + Tensor grid = in_data[bs::kGrid].get(s); + Tensor gdata = in_grad[bs::kData].get(s); + Tensor ggrid = in_grad[bs::kGrid].get(s); + Tensor grad = out_grad[bs::kOut].get(s); + if (req[bs::kData] != kNullOp && req[bs::kGrid] != kNullOp) { + if (req[bs::kData] == kWriteTo) { + gdata = scalar(0.0f); + } + if (req[bs::kGrid] == kWriteTo) { + ggrid = scalar(0.0f); + } + BilinearSamplerBackward(gdata, ggrid, grad, data, grid); + } else if (req[bs::kData] == kNullOp && req[bs::kGrid] == kNullOp) { + return; + } else { + LOG(FATAL) << "Have not implemented the data req combinations! gdata_req=" + << req[bs::kData] << " ggrid_req=" << req[bs::kGrid]; + } + } + + private: + BilinearSamplerParam param_; +}; // class BilinearSamplerOp + +template +Operator* CreateOp(BilinearSamplerParam param, int dtype); + +#if DMLC_USE_CXX11 +class BilinearSamplerProp : public OperatorProperty { + public: + int NumVisibleOutputs() const override { + return 1; + } + + int NumOutputs() const override { + return 2; + } + + std::vector ListArguments() const override { + return {"data", "grid"}; + } + + std::vector ListOutputs() const override { + return {"output", "tmp"}; + } + + void Init(const std::vector >& kwargs) override { + param_.Init(kwargs); + } + + std::map GetParams() const override { + return param_.__DICT__(); + } + + bool InferShape(std::vector *in_shape, + std::vector *out_shape, + std::vector *aux_shape) const override { + using namespace mshadow; + CHECK_EQ(in_shape->size(), 2U) << "Input:[data, grid]"; + const TShape &dshape = (*in_shape)[bs::kData]; + const TShape &lshape = (*in_shape)[bs::kGrid]; + if (dshape.ndim() == 0) return false; + CHECK_EQ(dshape.ndim(), 4U) \ + << "input data should be 4D in batch-num_filter-y-x"; + if (lshape.ndim() == 0) return false; + CHECK_EQ(lshape.ndim(), 4U) \ + << "Sampler grid should be 4D in batch-2-y-x"; + CHECK_EQ(dshape[0], lshape[0]); + CHECK_EQ(lshape[1], 2U) << "incorrect grid shape[1], should be 2"; + // target height + CHECK_GT(lshape[2], 0U) \ + << "incorrect grid_shape: " << lshape[2]; + // target width + CHECK_GT(lshape[3], 0U) \ + << "incorrect grid_shape: " << lshape[3]; + out_shape->clear(); + // output_shape : (data.shape[0], data.shape[1], grid.shape[2], grid.shape[3]) + out_shape->push_back(dshape); + (*out_shape)[bs::kOut][2] = lshape[2]; + (*out_shape)[bs::kOut][3] = lshape[3]; + out_shape->push_back(Shape4(lshape[0], lshape[2], lshape[3], 2)); + return true; + } + + bool InferType(std::vector *in_type, + std::vector *out_type, + std::vector *aux_type) const override { + int dtype = -1; + for (size_t i = 0; i < in_type->size(); ++i) { + if (dtype == -1) { + dtype = in_type->at(i); + } else { + CHECK(in_type->at(i) == dtype || + in_type->at(i) == -1) << + "Non-uniform data type in BilinearSampler"; + } + } + if (dtype == -1) { + LOG(FATAL) << "Not enough information to infer type in BilinearSampler."; + return false; + } + size_t nin = this->ListArguments().size(); + in_type->clear(); + for (size_t i = 0; i < nin; ++i) in_type->push_back(dtype); + size_t naux = this->ListAuxiliaryStates().size(); + aux_type->clear(); + for (size_t i = 0; i < naux; ++i) aux_type->push_back(dtype); + size_t nout = this->ListOutputs().size(); + out_type->clear(); + for (size_t i = 0; i < nout; ++i) out_type->push_back(dtype); + return true; + } + + OperatorProperty* Copy() const override { + auto ptr = new BilinearSamplerProp(); + ptr->param_ = param_; + return ptr; + } + + std::string TypeString() const override { + return "BilinearSampler"; + } + + std::vector DeclareBackwardDependency( + const std::vector &out_grad, + const std::vector &in_data, + const std::vector &out_data) const override { + return {out_grad[bs::kOut], + in_data[bs::kData], + out_data[bs::kTmp], + in_data[bs::kGrid]}; + } + + Operator* CreateOperator(Context ctx) const override { + LOG(FATAL) << "Not Implemented."; + return NULL; + } + + Operator* CreateOperatorEx(Context ctx, std::vector *in_shape, + std::vector *in_type) const override; + + private: + BilinearSamplerParam param_; +}; // class BilinearSamplerProp +#endif // DMLC_USE_CXX11 +} // namespace op +} // namespace mxnet +#endif // MXNET_OPERATOR_BILINEAR_SAMPLER_INL_H_ diff --git a/src/operator/bilinear_sampler.cc b/src/operator/bilinear_sampler.cc index 7cc94c50982f..d03f6798fde5 100644 --- a/src/operator/bilinear_sampler.cc +++ b/src/operator/bilinear_sampler.cc @@ -1,172 +1,246 @@ -/*! - * Copyright (c) 2017 by Contributors - * \file bilinear_sampler.cc - * \brief - * \author Xu Dong -*/ - -#include "./bilinear_sampler-inl.h" - -namespace mshadow { -template -bool between(DType value, int lowerBound, int upperBound) { - return (value >= lowerBound && value <= upperBound); -} -template -inline void BilinearSamplerForward(const Tensor &output, - const Tensor &input, - const Tensor &grid_src) { - DType *out = output.dptr_; - const DType *data = input.dptr_; - const DType *grid = grid_src.dptr_; - int o_n = output.size(0), o_c = output.size(1), o_h = output.size(2), o_w = output.size(3); - int i_c = input.size(1), i_h = input.size(2), i_w = input.size(3); - for (index_t n = 0; n < o_n; ++n) { - for (index_t c = 0; c < o_c; ++c) { - for (index_t h = 0; h < o_h; ++h) { - for (index_t w = 0; w < o_w; ++w) { - index_t out_index = n * o_c * o_h * o_w + c * o_h * o_w + h * o_w + w; - index_t grid_index = n * o_h * o_w * 2 + h * o_w + w; - DType y_real = (*(grid + grid_index + o_h * o_w) + 1) * (i_h - 1) / 2; - DType x_real = (*(grid + grid_index) + 1) * (i_w - 1) / 2; - int top_left_y = static_cast(floor(y_real)); - int top_left_x = static_cast(floor(x_real)); - DType top_left_y_w = 1.0 - (y_real - top_left_y); - DType top_left_x_w = 1.0 - (x_real - top_left_x); - int data_index = n * i_c * i_h * i_w + c * i_h * i_w + - top_left_y * i_w + top_left_x; - DType top_left_v = 0; - DType top_right_v = 0; - DType bottom_left_v = 0; - DType bottom_right_v = 0; - if (between(top_left_x, 0, i_w-1) && between(top_left_y, 0, i_h-1)) - top_left_v = *(data + data_index); - if (between(top_left_x + 1, 0, i_w-1) && between(top_left_y, 0, i_h-1)) - top_right_v = *(data + data_index + 1); - if (between(top_left_x, 0, i_w-1) && between(top_left_y + 1, 0, i_h-1)) - bottom_left_v = *(data + data_index + i_w); - if (between(top_left_x+1, 0, i_w-1) && between(top_left_y + 1, 0, i_h-1)) - bottom_right_v = *(data + data_index + i_w + 1); - *(out+out_index) = top_left_v * top_left_y_w * top_left_x_w + - top_right_v * top_left_y_w * (1.0 - top_left_x_w) + - bottom_left_v * (1.0 - top_left_y_w) * top_left_x_w + - bottom_right_v * (1.0 - top_left_y_w) * (1.0 - top_left_x_w); - } - } - } - } -} - -template -inline void BilinearSamplerBackward(const Tensor &gdata, - const Tensor &ggrid, - const Tensor &output_grad, - const Tensor &input_data, - const Tensor &grid) { - DType *g_input = gdata.dptr_; - DType *grad_grid = ggrid.dptr_; - const DType *grid_src = grid.dptr_; - const DType *grad = output_grad.dptr_; - const DType *data = input_data.dptr_; - int o_n = output_grad.size(0), o_c = output_grad.size(1), - o_h = output_grad.size(2), o_w = output_grad.size(3); - int i_c = input_data.size(1), i_h = input_data.size(2), i_w = input_data.size(3); - for (index_t n = 0; n < o_n; ++n) { - for (index_t h = 0; h < o_h; ++h) { - for (index_t w = 0; w < o_w; ++w) { - DType top_left_y_gw = 0.0; - DType top_left_x_gw = 0.0; - index_t grid_src_index = n * o_h * o_w * 2 + h * o_w + w; - DType y_real = (*(grid_src + grid_src_index + o_h * o_w) + 1) * (i_h - 1) / 2; - DType x_real = (*(grid_src + grid_src_index) + 1) * (i_w - 1) / 2; - int top_left_y = static_cast(floor(y_real)); - int top_left_x = static_cast(floor(x_real)); - DType top_left_y_w = 1.0 - (y_real - top_left_y); - DType top_left_x_w = 1.0 - (x_real - top_left_x); - for (index_t c = 0; c < o_c; ++c) { - index_t grad_index = n * o_c * o_h * o_w + c * o_h * o_w + h * o_w + w; - int data_index = n * i_c * i_h * i_w + c * i_h * i_w + top_left_y * i_w - + top_left_x; - // calc 4 vertex value in input data - DType top_left_v = 0; - DType top_right_v = 0; - DType bottom_left_v = 0; - DType bottom_right_v = 0; - // calc input grad - if (between(top_left_x, 0, i_w-1) && between(top_left_y, 0, i_h-1)) { - *(g_input + data_index) += *(grad + grad_index) * top_left_y_w * top_left_x_w; - top_left_v = *(data + data_index); - } - if (between(top_left_x+1, 0, i_w-1) && between(top_left_y, 0, i_h-1)) { - *(g_input + data_index + 1) += *(grad + grad_index) * top_left_y_w - * (1.0 - top_left_x_w); - top_right_v = *(data + data_index + 1); - } - if (between(top_left_x, 0, i_w-1) && between(top_left_y+1, 0, i_h-1)) { - *(g_input + data_index+ i_w) += *(grad + grad_index) * (1.0 - top_left_y_w) - * top_left_x_w; - bottom_left_v = *(data + data_index + i_w); - } - if (between(top_left_x+1, 0, i_w-1) && between(top_left_y+1, 0, i_h-1)) { - *(g_input + data_index+ i_w + 1) += *(grad + grad_index) * (1.0 - top_left_y_w) - * (1.0 - top_left_x_w); - bottom_right_v = *(data + data_index + i_w + 1); - } - // calc weight grad of top_left_w, then multiple -1 is the grad of grid_src - top_left_y_gw -= *(grad + grad_index) * (top_right_v - bottom_right_v + - (top_left_v - top_right_v - bottom_left_v + bottom_right_v) - * top_left_x_w); - top_left_x_gw -= *(grad + grad_index) * (bottom_left_v - bottom_right_v + - (top_left_v - top_right_v - bottom_left_v + bottom_right_v) - * top_left_y_w); - } - // calc grad of grid - *(grad_grid + grid_src_index + o_h * o_w) += top_left_y_gw * (i_h - 1) / 2; - *(grad_grid + grid_src_index) += top_left_x_gw * (i_w - 1) / 2; - } - } - } - } -} // namespace mshadow - -namespace mxnet { -namespace op { -template<> -Operator* CreateOp(BilinearSamplerParam param, int dtype) { - Operator *op = NULL; - MSHADOW_REAL_TYPE_SWITCH(dtype, DType, { - op = new BilinearSamplerOp(param); - }) - return op; -} - -Operator *BilinearSamplerProp::CreateOperatorEx(Context ctx, std::vector *in_shape, - std::vector *in_type) const { - std::vector out_shape, aux_shape; - std::vector out_type, aux_type; - CHECK(InferType(in_type, &out_type, &aux_type)); - CHECK(InferShape(in_shape, &out_shape, &aux_shape)); - DO_BIND_DISPATCH(CreateOp, param_, (*in_type)[0]); -} - -DMLC_REGISTER_PARAMETER(BilinearSamplerParam); - -MXNET_REGISTER_OP_PROPERTY(BilinearSampler, BilinearSamplerProp) -.add_argument("data", "NDArray-or-Symbol", "Input data to the BilinearsamplerOp.") -.add_argument("grid", "NDArray-or-Symbol", "Input grid to the BilinearsamplerOp." - "grid has two channels: x_src, y_src") -.add_arguments(BilinearSamplerParam::__FIELDS__()) -.describe("Applies bilinear sampling to input feature map," -" which is the key of \"[NIPS2015] Spatial Transformer Networks\"\n " -"output[batch, channel, y_dst, x_dst] = G(data[batch, channel, y_src, x_src)\n " -"x_dst, y_dst enumerate all spatial locations in output\n " -"x_src = grid[batch, 0, y_dst, x_dst]\n " -"y_src = grid[batch, 1, y_dst, x_dst]\n " -"G() denotes the bilinear interpolation kernel\n" -"The out-boundary points will be padded as zeros. (The boundary is defined to be [-1, 1])\n" -"The shape of output will be (data.shape[0], data.shape[1], grid.shape[2], grid.shape[3])\n" -"The operator assumes that grid has been nomalized. " -"If you want to design a CustomOp to manipulate grid, " -"please refer to GridGeneratorOp."); -} // namespace op -} // namespace mxnet +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +/*! + * \file bilinear_sampler.cc + * \brief + * \author Xu Dong +*/ + +#include "./bilinear_sampler-inl.h" + +namespace mshadow { +template +bool between(DType value, int lowerBound, int upperBound) { + return (value >= lowerBound && value <= upperBound); +} +template +inline void BilinearSamplerForward(const Tensor &output, + const Tensor &input, + const Tensor &grid_src) { + DType *out = output.dptr_; + const DType *data = input.dptr_; + const DType *grid = grid_src.dptr_; + int o_n = output.size(0), o_c = output.size(1), o_h = output.size(2), o_w = output.size(3); + int i_c = input.size(1), i_h = input.size(2), i_w = input.size(3); + for (index_t n = 0; n < static_cast(o_n); ++n) { + for (index_t c = 0; c < static_cast(o_c); ++c) { + for (index_t h = 0; h < static_cast(o_h); ++h) { + for (index_t w = 0; w < static_cast(o_w); ++w) { + index_t out_index = n * o_c * o_h * o_w + c * o_h * o_w + h * o_w + w; + index_t grid_index = n * o_h * o_w * 2 + h * o_w + w; + DType y_real = (*(grid + grid_index + o_h * o_w) + 1) * (i_h - 1) / 2; + DType x_real = (*(grid + grid_index) + 1) * (i_w - 1) / 2; + int top_left_y = static_cast(floor(y_real)); + int top_left_x = static_cast(floor(x_real)); + DType top_left_y_w = 1.0 - (y_real - top_left_y); + DType top_left_x_w = 1.0 - (x_real - top_left_x); + int data_index = n * i_c * i_h * i_w + c * i_h * i_w + + top_left_y * i_w + top_left_x; + DType top_left_v = 0; + DType top_right_v = 0; + DType bottom_left_v = 0; + DType bottom_right_v = 0; + if (between(top_left_x, 0, i_w-1) && between(top_left_y, 0, i_h-1)) + top_left_v = *(data + data_index); + if (between(top_left_x + 1, 0, i_w-1) && between(top_left_y, 0, i_h-1)) + top_right_v = *(data + data_index + 1); + if (between(top_left_x, 0, i_w-1) && between(top_left_y + 1, 0, i_h-1)) + bottom_left_v = *(data + data_index + i_w); + if (between(top_left_x+1, 0, i_w-1) && between(top_left_y + 1, 0, i_h-1)) + bottom_right_v = *(data + data_index + i_w + 1); + *(out+out_index) = top_left_v * top_left_y_w * top_left_x_w + + top_right_v * top_left_y_w * (1.0 - top_left_x_w) + + bottom_left_v * (1.0 - top_left_y_w) * top_left_x_w + + bottom_right_v * (1.0 - top_left_y_w) * (1.0 - top_left_x_w); + } + } + } + } +} + +template +inline void BilinearSamplerBackward(const Tensor &gdata, + const Tensor &ggrid, + const Tensor &output_grad, + const Tensor &input_data, + const Tensor &grid) { + DType *g_input = gdata.dptr_; + DType *grad_grid = ggrid.dptr_; + const DType *grid_src = grid.dptr_; + const DType *grad = output_grad.dptr_; + const DType *data = input_data.dptr_; + int o_n = output_grad.size(0), o_c = output_grad.size(1), + o_h = output_grad.size(2), o_w = output_grad.size(3); + int i_c = input_data.size(1), i_h = input_data.size(2), i_w = input_data.size(3); + for (index_t n = 0; n < static_cast(o_n); ++n) { + for (index_t h = 0; h < static_cast(o_h); ++h) { + for (index_t w = 0; w < static_cast(o_w); ++w) { + DType top_left_y_gw = 0.0; + DType top_left_x_gw = 0.0; + index_t grid_src_index = n * o_h * o_w * 2 + h * o_w + w; + DType y_real = (*(grid_src + grid_src_index + o_h * o_w) + 1) * (i_h - 1) / 2; + DType x_real = (*(grid_src + grid_src_index) + 1) * (i_w - 1) / 2; + int top_left_y = static_cast(floor(y_real)); + int top_left_x = static_cast(floor(x_real)); + DType top_left_y_w = 1.0 - (y_real - top_left_y); + DType top_left_x_w = 1.0 - (x_real - top_left_x); + for (index_t c = 0; c < static_cast(o_c); ++c) { + index_t grad_index = n * o_c * o_h * o_w + c * o_h * o_w + h * o_w + w; + int data_index = n * i_c * i_h * i_w + c * i_h * i_w + top_left_y * i_w + + top_left_x; + // calc 4 vertex value in input data + DType top_left_v = 0; + DType top_right_v = 0; + DType bottom_left_v = 0; + DType bottom_right_v = 0; + // calc input grad + if (between(top_left_x, 0, i_w-1) && between(top_left_y, 0, i_h-1)) { + *(g_input + data_index) += *(grad + grad_index) * top_left_y_w * top_left_x_w; + top_left_v = *(data + data_index); + } + if (between(top_left_x+1, 0, i_w-1) && between(top_left_y, 0, i_h-1)) { + *(g_input + data_index + 1) += *(grad + grad_index) * top_left_y_w + * (1.0 - top_left_x_w); + top_right_v = *(data + data_index + 1); + } + if (between(top_left_x, 0, i_w-1) && between(top_left_y+1, 0, i_h-1)) { + *(g_input + data_index+ i_w) += *(grad + grad_index) * (1.0 - top_left_y_w) + * top_left_x_w; + bottom_left_v = *(data + data_index + i_w); + } + if (between(top_left_x+1, 0, i_w-1) && between(top_left_y+1, 0, i_h-1)) { + *(g_input + data_index+ i_w + 1) += *(grad + grad_index) * (1.0 - top_left_y_w) + * (1.0 - top_left_x_w); + bottom_right_v = *(data + data_index + i_w + 1); + } + // calc weight grad of top_left_w, then multiple -1 is the grad of grid_src + top_left_y_gw -= *(grad + grad_index) * (top_right_v - bottom_right_v + + (top_left_v - top_right_v - bottom_left_v + bottom_right_v) + * top_left_x_w); + top_left_x_gw -= *(grad + grad_index) * (bottom_left_v - bottom_right_v + + (top_left_v - top_right_v - bottom_left_v + bottom_right_v) + * top_left_y_w); + } + // calc grad of grid + *(grad_grid + grid_src_index + o_h * o_w) += top_left_y_gw * (i_h - 1) / 2; + *(grad_grid + grid_src_index) += top_left_x_gw * (i_w - 1) / 2; + } + } + } + } +} // namespace mshadow + +namespace mxnet { +namespace op { +template<> +Operator* CreateOp(BilinearSamplerParam param, int dtype) { + Operator *op = NULL; + MSHADOW_REAL_TYPE_SWITCH(dtype, DType, { + op = new BilinearSamplerOp(param); + }) + return op; +} + +Operator *BilinearSamplerProp::CreateOperatorEx(Context ctx, std::vector *in_shape, + std::vector *in_type) const { + DO_BIND_DISPATCH(CreateOp, param_, (*in_type)[0]); +} + +DMLC_REGISTER_PARAMETER(BilinearSamplerParam); + +MXNET_REGISTER_OP_PROPERTY(BilinearSampler, BilinearSamplerProp) +.add_argument("data", "NDArray-or-Symbol", "Input data to the BilinearsamplerOp.") +.add_argument("grid", "NDArray-or-Symbol", "Input grid to the BilinearsamplerOp." + "grid has two channels: x_src, y_src") +.add_arguments(BilinearSamplerParam::__FIELDS__()) +.describe(R"code(Applies bilinear sampling to input feature map. + +Bilinear Sampling is the key of [NIPS2015] \"Spatial Transformer Networks\". The usage of the operator is very similar to remap function in OpenCV, +except that the operator has the backward pass. + +Given :math:`data` and :math:`grid`, then the output is computed by + +.. math:: + x_{src} = grid[batch, 0, y_{dst}, x_{dst}] \\ + y_{src} = grid[batch, 1, y_{dst}, x_{dst}] \\ + output[batch, channel, y_{dst}, x_{dst}] = G(data[batch, channel, y_{src}, x_{src}) + +:math:`x_{dst}`, :math:`y_{dst}` enumerate all spatial locations in :math:`output`, and :math:`G()` denotes the bilinear interpolation kernel. +The out-boundary points will be padded with zeros.The shape of the output will be (data.shape[0], data.shape[1], grid.shape[2], grid.shape[3]). + +The operator assumes that :math:`data` has 'NCHW' layout and :math:`grid` has been normalized to [-1, 1]. + +BilinearSampler often cooperates with GridGenerator which generates sampling grids for BilinearSampler. +GridGenerator supports two kinds of transformation: ``affine`` and ``warp``. +If users want to design a CustomOp to manipulate :math:`grid`, please firstly refer to the code of GridGenerator. + +Example 1:: + + ## Zoom out data two times + data = array([[[[1, 4, 3, 6], + [1, 8, 8, 9], + [0, 4, 1, 5], + [1, 0, 1, 3]]]]) + + affine_matrix = array([[2, 0, 0], + [0, 2, 0]]) + + affine_matrix = reshape(affine_matrix, shape=(1, 6)) + + grid = GridGenerator(data=affine_matrix, transform_type='affine', target_shape=(4, 4)) + + out = BilinearSampler(data, grid) + + out + [[[[ 0, 0, 0, 0], + [ 0, 3.5, 6.5, 0], + [ 0, 1.25, 2.5, 0], + [ 0, 0, 0, 0]]] + + +Example 2:: + + ## shift data horizontally by -1 pixel + + data = array([[[[1, 4, 3, 6], + [1, 8, 8, 9], + [0, 4, 1, 5], + [1, 0, 1, 3]]]]) + + warp_maxtrix = array([[[[1, 1, 1, 1], + [1, 1, 1, 1], + [1, 1, 1, 1], + [1, 1, 1, 1]], + [[0, 0, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 0]]]]) + + grid = GridGenerator(data=warp_matrix, transform_type='warp') + out = BilinearSampler(data, grid) + + out + [[[[ 4, 3, 6, 0], + [ 8, 8, 9, 0], + [ 4, 1, 5, 0], + [ 0, 1, 3, 0]]] +)code" ADD_FILELINE); +} // namespace op +} // namespace mxnet diff --git a/src/operator/bilinear_sampler.cu b/src/operator/bilinear_sampler.cu index dc394f130b0a..14b5cd20a3d7 100644 --- a/src/operator/bilinear_sampler.cu +++ b/src/operator/bilinear_sampler.cu @@ -1,207 +1,225 @@ -/*! - * Copyright (c) 2017 by Contributors - * \file bilinear_sampler.cu - * \brief - * \author Xu Dong -*/ - -#include "./bilinear_sampler-inl.h" -#include -#include "../common/cuda_utils.h" -#if MXNET_USE_CUDNN == 1 && CUDNN_MAJOR >= 5 -#include "./cudnn_bilinear_sampler-inl.h" -#endif // MXNET_USE_CUDNN && CUDNN_MAJOR - -namespace mshadow { -namespace cuda { -template -__device__ bool between(DType value, int lowerBound, int upperBound) { - return (value >= lowerBound && value <= upperBound); -} -template -__global__ void BilinearSamplerForwardKernel(const int i_c, const int i_h, - const int i_w, const DType* data, - const DType* grid, const int o_n, - const int o_c, const int o_h, - const int o_w, DType* out) { - for (int index = (blockIdx.x + blockIdx.y * gridDim.x) * blockDim.x + threadIdx.x; - index < o_n * o_c * o_h * o_w; - index += blockDim.x * gridDim.x * gridDim.y) { - // (n, c, h, w) is the element in out - int w = index % o_w; - int h = (index / o_w) % o_h; - int c = (index / o_w / o_h) % o_c; - int n = index / o_w / o_h / o_c; - index_t out_index = n * o_c * o_h * o_w + c * o_h * o_w + h * o_w + w; - index_t grid_index = n * o_h * o_w * 2 + h * o_w + w; - DType y_real = (*(grid + grid_index + o_h * o_w) + 1) * (i_h - 1) / 2; - DType x_real = (*(grid + grid_index) + 1) * (i_w - 1) / 2; - int top_left_y = static_cast(floor(y_real)); - int top_left_x = static_cast(floor(x_real)); - DType top_left_y_w = 1.0 - (y_real - top_left_y); - DType top_left_x_w = 1.0 - (x_real - top_left_x); - int data_index = n * i_c * i_h * i_w + c * i_h * i_w + top_left_y * i_w + top_left_x; - DType top_left_v = 0; - DType top_right_v = 0; - DType bottom_left_v = 0; - DType bottom_right_v = 0; - if (between(top_left_x, 0, i_w-1) && between(top_left_y, 0, i_h-1)) - top_left_v = *(data + data_index); - if (between(top_left_x + 1, 0, i_w-1) && between(top_left_y, 0, i_h-1)) - top_right_v = *(data + data_index + 1); - if (between(top_left_x, 0, i_w-1) && between(top_left_y + 1, 0, i_h-1)) - bottom_left_v = *(data + data_index + i_w); - if (between(top_left_x+1, 0, i_w-1) && between(top_left_y + 1, 0, i_h-1)) - bottom_right_v = *(data + data_index + i_w + 1); - *(out+out_index) = top_left_v * top_left_y_w * top_left_x_w + - top_right_v * top_left_y_w * (1.0 - top_left_x_w) + - bottom_left_v * (1.0 - top_left_y_w) * top_left_x_w + - bottom_right_v * (1.0 - top_left_y_w) * (1.0 - top_left_x_w); - } -} - -template -__global__ void BilinearSamplerBackwardKernel(const int i_c, const int i_h, - const int i_w, const DType* grad, - const DType* data, const int o_n, - const int o_c, const int o_h, - const int o_w, DType* g_input, - const DType* grid_src, - DType* grad_grid) { - for (int index = (blockIdx.x + blockIdx.y * gridDim.x) * blockDim.x + threadIdx.x; - index < o_n * o_h * o_w; - index += blockDim.x * gridDim.x * gridDim.y) { - // (n, c, h, w) is the element in grad - int w = index % o_w; - int h = (index / o_w) % o_h; - int n = index / o_w / o_h; - DType top_left_y_gw = 0.0; - DType top_left_x_gw = 0.0; - index_t grid_src_index = n * o_h * o_w * 2 + h * o_w + w; - DType y_real = (*(grid_src + grid_src_index + o_h * o_w) + 1) * (i_h - 1) / 2; - DType x_real = (*(grid_src + grid_src_index) + 1) * (i_w - 1) / 2; - - int top_left_y = static_cast(floor(y_real)); - int top_left_x = static_cast(floor(x_real)); - DType top_left_y_w = 1.0 - (y_real - top_left_y); - DType top_left_x_w = 1.0 - (x_real - top_left_x); - for (index_t c = 0; c < o_c; ++c) { - index_t grad_index = n * o_c * o_h * o_w + c * o_h * o_w + h * o_w + w; - int data_index = n * i_c * i_h * i_w + c * i_h * i_w + top_left_y * i_w + top_left_x; - // calc 4 vertex value in input data - DType top_left_v = 0; - DType top_right_v = 0; - DType bottom_left_v = 0; - DType bottom_right_v = 0; - // calc input grad - if (between(top_left_x, 0, i_w-1) && between(top_left_y, 0, i_h-1)) { - atomicAdd(&g_input[data_index], *(grad + grad_index) * top_left_y_w * top_left_x_w); - top_left_v = *(data + data_index); - } - if (between(top_left_x+1, 0, i_w-1) && between(top_left_y, 0, i_h-1)) { - atomicAdd(&g_input[data_index + 1], *(grad + grad_index) * top_left_y_w - * (1.0 - top_left_x_w)); - top_right_v = *(data + data_index + 1); - } - if (between(top_left_x, 0, i_w-1) && between(top_left_y+1, 0, i_h-1)) { - atomicAdd(&g_input[data_index+ i_w], *(grad + grad_index) * (1.0 - top_left_y_w) - * top_left_x_w); - bottom_left_v = *(data + data_index + i_w); - } - if (between(top_left_x+1, 0, i_w-1) && between(top_left_y+1, 0, i_h-1)) { - atomicAdd(&g_input[data_index+ i_w + 1], *(grad + grad_index) * (1.0 - top_left_y_w) - * (1.0 - top_left_x_w)); - bottom_right_v = *(data + data_index + i_w + 1); - } - // calc weight grad of top_left_w, then multiple -1 is the grad of grid_src - top_left_y_gw -= *(grad + grad_index) * (top_right_v - bottom_right_v + - (top_left_v - top_right_v - bottom_left_v + bottom_right_v) - * top_left_x_w); - top_left_x_gw -= *(grad + grad_index) * (bottom_left_v - bottom_right_v + - (top_left_v - top_right_v - bottom_left_v + bottom_right_v) - * top_left_y_w); - } - // calc grad of grid - *(grad_grid + grid_src_index + o_h * o_w) += top_left_y_gw * (i_h - 1) / 2; - *(grad_grid + grid_src_index) += top_left_x_gw * (i_w - 1) / 2; - } -} -} // namespace cuda - -template -inline void BilinearSamplerForward(const Tensor &output, - const Tensor &input, - const Tensor &grid_src) { - DType *out = output.dptr_; - const DType *data = input.dptr_; - const DType *grid = grid_src.dptr_; - int o_n = output.size(0), o_c = output.size(1), o_h = output.size(2), o_w = output.size(3); - int i_c = input.size(1), i_h = input.size(2), i_w = input.size(3); - using namespace cuda; - const int max_block = (output.shape_.Size() + kMaxThreadsPerBlock - 1) / kMaxThreadsPerBlock; - const int grid_dim_x = (max_block > kMaxGridDim) ? kMaxGridDim : max_block; - const int grid_dim_y = - (max_block > kMaxGridDim) ? (max_block + kMaxGridDim - 1) / kMaxGridDim : 1; - dim3 num_blocks(grid_dim_x, grid_dim_y); - dim3 threads_per_block(kMaxThreadsPerBlock); - CheckLaunchParam(num_blocks, threads_per_block, "bilinear sampler forward"); - cudaStream_t stream = Stream::GetStream(output.stream_); - cuda::BilinearSamplerForwardKernel << > >( - i_c, i_h, i_w, data, grid, o_n, o_c, o_h, o_w, out); - // post kernel check - cudaError err = cudaPeekAtLastError(); - CHECK_EQ(err, cudaSuccess) << cudaGetErrorString(err); -} - -template -inline void BilinearSamplerBackward(const Tensor &input_grad, - const Tensor &ggrid, - const Tensor &output_grad, - const Tensor &input_data, - const Tensor &grid) { - DType *g_input = input_grad.dptr_; - DType *grad_grid = ggrid.dptr_; - const DType *grid_src = grid.dptr_; - const DType *grad = output_grad.dptr_; - const DType *data = input_data.dptr_; - int o_n = output_grad.size(0), o_c = output_grad.size(1), - o_h = output_grad.size(2), o_w = output_grad.size(3); - int i_c = input_data.size(1), i_h = input_data.size(2), i_w = input_data.size(3); - using namespace cuda; - const int max_block = (output_grad.shape_.Size() / o_c + kMaxThreadsPerBlock - 1) - / kMaxThreadsPerBlock; - const int grid_dim_x = (max_block > kMaxGridDim) ? kMaxGridDim : max_block; - const int grid_dim_y = - (max_block > kMaxGridDim) ? (max_block + kMaxGridDim - 1) / kMaxGridDim : 1; - dim3 num_blocks(grid_dim_x, grid_dim_y); - dim3 threads_per_block(kMaxThreadsPerBlock); - CheckLaunchParam(num_blocks, threads_per_block, "bilinear sampler backward"); - cudaStream_t stream = Stream::GetStream(input_grad.stream_); - cuda::BilinearSamplerBackwardKernel << > >( - i_c, i_h, i_w, grad, data, o_n, o_c, o_h, o_w, g_input, grid_src, grad_grid); - // post kernel check - cudaError err = cudaPeekAtLastError(); - CHECK_EQ(err, cudaSuccess) << cudaGetErrorString(err); -} - -} // namespace mshadow - -namespace mxnet { -namespace op { -template<> -Operator* CreateOp(BilinearSamplerParam param, int dtype) { - Operator *op = NULL; -#if MXNET_USE_CUDNN == 1 && CUDNN_MAJOR >= 5 - MSHADOW_REAL_TYPE_SWITCH(dtype, DType, { - op = new CuDNNBilinearSamplerOp(param); - }) -#else - MSHADOW_REAL_TYPE_SWITCH(dtype, DType, { - op = new BilinearSamplerOp(param); - }) -#endif // MXNET_USE_CUDNN && CUDNN_MAJOR - return op; -} - -} // namespace op -} // namespace mxnet +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +/*! + * \file bilinear_sampler.cu + * \brief + * \author Xu Dong +*/ + +#include "./bilinear_sampler-inl.h" +#include +#include "../common/cuda_utils.h" +#if MXNET_USE_CUDNN == 1 && CUDNN_MAJOR >= 5 +#include "./cudnn_bilinear_sampler-inl.h" +#endif // MXNET_USE_CUDNN && CUDNN_MAJOR + +namespace mshadow { +namespace cuda { +template +__device__ bool between(DType value, int lowerBound, int upperBound) { + return (value >= lowerBound && value <= upperBound); +} +template +__global__ void BilinearSamplerForwardKernel(const int i_c, const int i_h, + const int i_w, const DType* data, + const DType* grid, const int o_n, + const int o_c, const int o_h, + const int o_w, DType* out) { + for (int index = (blockIdx.x + blockIdx.y * gridDim.x) * blockDim.x + threadIdx.x; + index < o_n * o_c * o_h * o_w; + index += blockDim.x * gridDim.x * gridDim.y) { + // (n, c, h, w) is the element in out + int w = index % o_w; + int h = (index / o_w) % o_h; + int c = (index / o_w / o_h) % o_c; + int n = index / o_w / o_h / o_c; + index_t out_index = n * o_c * o_h * o_w + c * o_h * o_w + h * o_w + w; + index_t grid_index = n * o_h * o_w * 2 + h * o_w + w; + DType y_real = (*(grid + grid_index + o_h * o_w) + 1) * (i_h - 1) / 2; + DType x_real = (*(grid + grid_index) + 1) * (i_w - 1) / 2; + int top_left_y = static_cast(floor(y_real)); + int top_left_x = static_cast(floor(x_real)); + DType top_left_y_w = 1.0 - (y_real - top_left_y); + DType top_left_x_w = 1.0 - (x_real - top_left_x); + int data_index = n * i_c * i_h * i_w + c * i_h * i_w + top_left_y * i_w + top_left_x; + DType top_left_v = 0; + DType top_right_v = 0; + DType bottom_left_v = 0; + DType bottom_right_v = 0; + if (between(top_left_x, 0, i_w-1) && between(top_left_y, 0, i_h-1)) + top_left_v = *(data + data_index); + if (between(top_left_x + 1, 0, i_w-1) && between(top_left_y, 0, i_h-1)) + top_right_v = *(data + data_index + 1); + if (between(top_left_x, 0, i_w-1) && between(top_left_y + 1, 0, i_h-1)) + bottom_left_v = *(data + data_index + i_w); + if (between(top_left_x+1, 0, i_w-1) && between(top_left_y + 1, 0, i_h-1)) + bottom_right_v = *(data + data_index + i_w + 1); + *(out+out_index) = top_left_v * top_left_y_w * top_left_x_w + + top_right_v * top_left_y_w * (1.0 - top_left_x_w) + + bottom_left_v * (1.0 - top_left_y_w) * top_left_x_w + + bottom_right_v * (1.0 - top_left_y_w) * (1.0 - top_left_x_w); + } +} + +template +__global__ void BilinearSamplerBackwardKernel(const int i_c, const int i_h, + const int i_w, const DType* grad, + const DType* data, const int o_n, + const int o_c, const int o_h, + const int o_w, DType* g_input, + const DType* grid_src, + DType* grad_grid) { + for (int index = (blockIdx.x + blockIdx.y * gridDim.x) * blockDim.x + threadIdx.x; + index < o_n * o_h * o_w; + index += blockDim.x * gridDim.x * gridDim.y) { + // (n, c, h, w) is the element in grad + int w = index % o_w; + int h = (index / o_w) % o_h; + int n = index / o_w / o_h; + DType top_left_y_gw = 0.0; + DType top_left_x_gw = 0.0; + index_t grid_src_index = n * o_h * o_w * 2 + h * o_w + w; + DType y_real = (*(grid_src + grid_src_index + o_h * o_w) + 1) * (i_h - 1) / 2; + DType x_real = (*(grid_src + grid_src_index) + 1) * (i_w - 1) / 2; + + int top_left_y = static_cast(floor(y_real)); + int top_left_x = static_cast(floor(x_real)); + DType top_left_y_w = 1.0 - (y_real - top_left_y); + DType top_left_x_w = 1.0 - (x_real - top_left_x); + for (index_t c = 0; c < o_c; ++c) { + index_t grad_index = n * o_c * o_h * o_w + c * o_h * o_w + h * o_w + w; + int data_index = n * i_c * i_h * i_w + c * i_h * i_w + top_left_y * i_w + top_left_x; + // calc 4 vertex value in input data + DType top_left_v = 0; + DType top_right_v = 0; + DType bottom_left_v = 0; + DType bottom_right_v = 0; + // calc input grad + if (between(top_left_x, 0, i_w-1) && between(top_left_y, 0, i_h-1)) { + atomicAdd(&g_input[data_index], *(grad + grad_index) * top_left_y_w * top_left_x_w); + top_left_v = *(data + data_index); + } + if (between(top_left_x+1, 0, i_w-1) && between(top_left_y, 0, i_h-1)) { + atomicAdd(&g_input[data_index + 1], *(grad + grad_index) * top_left_y_w + * (1.0 - top_left_x_w)); + top_right_v = *(data + data_index + 1); + } + if (between(top_left_x, 0, i_w-1) && between(top_left_y+1, 0, i_h-1)) { + atomicAdd(&g_input[data_index+ i_w], *(grad + grad_index) * (1.0 - top_left_y_w) + * top_left_x_w); + bottom_left_v = *(data + data_index + i_w); + } + if (between(top_left_x+1, 0, i_w-1) && between(top_left_y+1, 0, i_h-1)) { + atomicAdd(&g_input[data_index+ i_w + 1], *(grad + grad_index) * (1.0 - top_left_y_w) + * (1.0 - top_left_x_w)); + bottom_right_v = *(data + data_index + i_w + 1); + } + // calc weight grad of top_left_w, then multiple -1 is the grad of grid_src + top_left_y_gw -= *(grad + grad_index) * (top_right_v - bottom_right_v + + (top_left_v - top_right_v - bottom_left_v + bottom_right_v) + * top_left_x_w); + top_left_x_gw -= *(grad + grad_index) * (bottom_left_v - bottom_right_v + + (top_left_v - top_right_v - bottom_left_v + bottom_right_v) + * top_left_y_w); + } + // calc grad of grid + *(grad_grid + grid_src_index + o_h * o_w) += top_left_y_gw * (i_h - 1) / 2; + *(grad_grid + grid_src_index) += top_left_x_gw * (i_w - 1) / 2; + } +} +} // namespace cuda + +template +inline void BilinearSamplerForward(const Tensor &output, + const Tensor &input, + const Tensor &grid_src) { + DType *out = output.dptr_; + const DType *data = input.dptr_; + const DType *grid = grid_src.dptr_; + int o_n = output.size(0), o_c = output.size(1), o_h = output.size(2), o_w = output.size(3); + int i_c = input.size(1), i_h = input.size(2), i_w = input.size(3); + using namespace cuda; + const int max_block = (output.shape_.Size() + kMaxThreadsPerBlock - 1) / kMaxThreadsPerBlock; + const int grid_dim_x = (max_block > kMaxGridDim) ? kMaxGridDim : max_block; + const int grid_dim_y = + (max_block > kMaxGridDim) ? (max_block + kMaxGridDim - 1) / kMaxGridDim : 1; + dim3 num_blocks(grid_dim_x, grid_dim_y); + dim3 threads_per_block(kMaxThreadsPerBlock); + CheckLaunchParam(num_blocks, threads_per_block, "bilinear sampler forward"); + cudaStream_t stream = Stream::GetStream(output.stream_); + cuda::BilinearSamplerForwardKernel << > >( + i_c, i_h, i_w, data, grid, o_n, o_c, o_h, o_w, out); + // post kernel check + cudaError err = cudaPeekAtLastError(); + CHECK_EQ(err, cudaSuccess) << cudaGetErrorString(err); +} + +template +inline void BilinearSamplerBackward(const Tensor &input_grad, + const Tensor &ggrid, + const Tensor &output_grad, + const Tensor &input_data, + const Tensor &grid) { + DType *g_input = input_grad.dptr_; + DType *grad_grid = ggrid.dptr_; + const DType *grid_src = grid.dptr_; + const DType *grad = output_grad.dptr_; + const DType *data = input_data.dptr_; + int o_n = output_grad.size(0), o_c = output_grad.size(1), + o_h = output_grad.size(2), o_w = output_grad.size(3); + int i_c = input_data.size(1), i_h = input_data.size(2), i_w = input_data.size(3); + using namespace cuda; + const int max_block = (output_grad.shape_.Size() / o_c + kMaxThreadsPerBlock - 1) + / kMaxThreadsPerBlock; + const int grid_dim_x = (max_block > kMaxGridDim) ? kMaxGridDim : max_block; + const int grid_dim_y = + (max_block > kMaxGridDim) ? (max_block + kMaxGridDim - 1) / kMaxGridDim : 1; + dim3 num_blocks(grid_dim_x, grid_dim_y); + dim3 threads_per_block(kMaxThreadsPerBlock); + CheckLaunchParam(num_blocks, threads_per_block, "bilinear sampler backward"); + cudaStream_t stream = Stream::GetStream(input_grad.stream_); + cuda::BilinearSamplerBackwardKernel << > >( + i_c, i_h, i_w, grad, data, o_n, o_c, o_h, o_w, g_input, grid_src, grad_grid); + // post kernel check + cudaError err = cudaPeekAtLastError(); + CHECK_EQ(err, cudaSuccess) << cudaGetErrorString(err); +} + +} // namespace mshadow + +namespace mxnet { +namespace op { +template<> +Operator* CreateOp(BilinearSamplerParam param, int dtype) { + Operator *op = NULL; +#if MXNET_USE_CUDNN == 1 && CUDNN_MAJOR >= 5 + MSHADOW_REAL_TYPE_SWITCH(dtype, DType, { + op = new CuDNNBilinearSamplerOp(param); + }) +#else + MSHADOW_REAL_TYPE_SWITCH(dtype, DType, { + op = new BilinearSamplerOp(param); + }) +#endif // MXNET_USE_CUDNN && CUDNN_MAJOR + return op; +} + +} // namespace op +} // namespace mxnet diff --git a/src/operator/c_lapack_api.h b/src/operator/c_lapack_api.h new file mode 100644 index 000000000000..96a9b3a23709 --- /dev/null +++ b/src/operator/c_lapack_api.h @@ -0,0 +1,227 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +/*! + * \file c_lapack_api.h + * \brief Unified interface for CPU-based LAPACK calls. + * Purpose is to hide the platform specific differences. + */ +#ifndef MXNET_OPERATOR_C_LAPACK_API_H_ +#define MXNET_OPERATOR_C_LAPACK_API_H_ + +// Manually maintained list of LAPACK interfaces that can be used +// within MXNET. Conventions: +// - We should only import LAPACK-functions that are useful and +// ensure that we support them most efficiently on CPU/GPU. As an +// example take "potrs": It can be emulated by two calls to +// "trsm" (from BLAS3) so not really needed from functionality point +// of view. In addition, trsm on GPU supports batch-mode processing +// which is much more efficient for a bunch of smaller matrices while +// there is no such batch support for potrs. As a result, we may +// not support "potrs" internally and if we want to expose it to the user as +// a convenience operator at some time, then we may implement it internally +// as a sequence of trsm. +// - Interfaces must be compliant with lapacke.h in terms of signature and +// naming conventions so wrapping a function "foo" which has the +// signature +// lapack_int LAPACKE_foo(int, char, lapack_int, float* , lapack_int) +// within lapacke.h should result in a wrapper with the following signature +// int MXNET_LAPACK_foo(int, char, int, float* , int) +// Note that function signatures in lapacke.h will always have as first +// argument the storage order (row/col-major). All wrappers have to support +// that argument. The underlying fortran functions will always assume a +// column-major layout. +// - In the (usual) case that a wrapper is called specifying row-major storage +// order of input/output data, there are two ways to handle this: +// 1) The wrapper may support this without allocating any additional memory +// for example by exploiting the fact that a matrix is symmetric and switching +// certain flags (upper/lower triangular) when calling the fortran code. +// 2) The wrapper may cause a runtime error. In that case it should be clearly +// documented that these functions do only support col-major layout. +// Rationale: This is a low level interface that is not expected to be called +// directly from many upstream functions. Usually all calls should go through +// the tensor-based interfaces in linalg.h which simplify calls to lapack further +// and are better suited to handle additional transpositions that may be necessary. +// Also we want to push allocation of temporary storage higher up in order to +// allow more efficient re-use of temporal storage. And don't want to plaster +// these interfaces here with additional requirements of providing buffers. +// - It is desired to add some basic checking in the C++-wrappers in order +// to catch simple mistakes when calling these wrappers. +// - Must support compilation without lapack-package but issue runtime error in this case. + +#include +#include "mshadow/tensor.h" + +using namespace mshadow; + +extern "C" { + + // Fortran signatures + #define MXNET_LAPACK_FSIGNATURE1(func, dtype) \ + void func##_(char *uplo, int *n, dtype *a, int *lda, int *info); + + MXNET_LAPACK_FSIGNATURE1(spotrf, float) + MXNET_LAPACK_FSIGNATURE1(dpotrf, double) + MXNET_LAPACK_FSIGNATURE1(spotri, float) + MXNET_LAPACK_FSIGNATURE1(dpotri, double) + + void dposv_(char *uplo, int *n, int *nrhs, + double *a, int *lda, double *b, int *ldb, int *info); + + void sposv_(char *uplo, int *n, int *nrhs, + float *a, int *lda, float *b, int *ldb, int *info); +} + +#define MXNET_LAPACK_ROW_MAJOR 101 +#define MXNET_LAPACK_COL_MAJOR 102 + +#define CHECK_LAPACK_UPLO(a) \ + CHECK(a == 'U' || a == 'L') << "neither L nor U specified as triangle in lapack call"; + +inline char loup(char uplo, bool invert) { return invert ? (uplo == 'U' ? 'L' : 'U') : uplo; } + + +/*! + * \brief Transpose matrix data in memory + * + * Equivalently we can see it as flipping the layout of the matrix + * between row-major and column-major. + * + * \param m number of rows of input matrix a + * \param n number of columns of input matrix a + * \param b output matrix + * \param ldb leading dimension of b + * \param a input matrix + * \param lda leading dimension of a + */ +template +inline void flip(int m, int n, DType *b, int ldb, DType *a, int lda); + +template <> +inline void flip(int m, int n, + float *b, int ldb, float *a, int lda) { + for (int i = 0; i < m; ++i) + for (int j = 0; j < n; ++j) + b[j * ldb + i] = a[i * lda + j]; +} + +template <> +inline void flip(int m, int n, + double *b, int ldb, double *a, int lda) { + for (int i = 0; i < m; ++i) + for (int j = 0; j < n; ++j) + b[j * ldb + i] = a[i * lda + j]; +} + + +#if MXNET_USE_LAPACK + + // These functions can be called with either row- or col-major format. + #define MXNET_LAPACK_CWRAPPER1(func, dtype) \ + inline int MXNET_LAPACK_##func(int matrix_layout, char uplo, int n, dtype *a, int lda) { \ + CHECK_LAPACK_UPLO(uplo); \ + char o(loup(uplo, (matrix_layout == MXNET_LAPACK_ROW_MAJOR))); \ + int ret(0); \ + func##_(&o, &n, a, &lda, &ret); \ + return ret; \ + } + MXNET_LAPACK_CWRAPPER1(spotrf, float) + MXNET_LAPACK_CWRAPPER1(dpotrf, double) + MXNET_LAPACK_CWRAPPER1(spotri, float) + MXNET_LAPACK_CWRAPPER1(dpotri, double) + + inline int mxnet_lapack_sposv(int matrix_layout, char uplo, int n, int nrhs, + float *a, int lda, float *b, int ldb) { + int info; + if (matrix_layout == MXNET_LAPACK_ROW_MAJOR) { + // Transpose b to b_t of shape (nrhs, n) + float *b_t = new float[nrhs * n]; + flip(n, nrhs, b_t, n, b, ldb); + sposv_(&uplo, &n, &nrhs, a, &lda, b_t, &n, &info); + flip(nrhs, n, b, ldb, b_t, n); + delete [] b_t; + return info; + } + sposv_(&uplo, &n, &nrhs, a, &lda, b, &ldb, &info); + return info; + } + + inline int mxnet_lapack_dposv(int matrix_layout, char uplo, int n, int nrhs, + double *a, int lda, double *b, int ldb) { + int info; + if (matrix_layout == MXNET_LAPACK_ROW_MAJOR) { + // Transpose b to b_t of shape (nrhs, n) + double *b_t = new double[nrhs * n]; + flip(n, nrhs, b_t, n, b, ldb); + dposv_(&uplo, &n, &nrhs, a, &lda, b_t, &n, &info); + flip(nrhs, n, b, ldb, b_t, n); + delete [] b_t; + return info; + } + dposv_(&uplo, &n, &nrhs, a, &lda, b, &ldb, &info); + return info; + } + +#else + + // use pragma message instead of warning + #pragma message("Warning: lapack usage not enabled, linalg-operators will not be available." \ + " Ensure that lapack library is installed and build with USE_LAPACK=1 to get lapack" \ + " functionalities.") + + // Define compilable stubs. + #define MXNET_LAPACK_CWRAPPER1(func, dtype) \ + inline int MXNET_LAPACK_##func(int matrix_layout, char uplo, int n, dtype* a, int lda) { \ + LOG(FATAL) << "MXNet build without lapack. Function " << #func << " is not available."; \ + return 1; \ + } + + #define MXNET_LAPACK_UNAVAILABLE(func) \ + inline int mxnet_lapack_##func(...) { \ + LOG(FATAL) << "MXNet build without lapack. Function " << #func << " is not available."; \ + return 1; \ + } + + MXNET_LAPACK_CWRAPPER1(spotrf, float) + MXNET_LAPACK_CWRAPPER1(dpotrf, double) + MXNET_LAPACK_CWRAPPER1(spotri, float) + MXNET_LAPACK_CWRAPPER1(dpotri, double) + + MXNET_LAPACK_UNAVAILABLE(sposv) + MXNET_LAPACK_UNAVAILABLE(dposv) + +#endif + +template +inline int MXNET_LAPACK_posv(int matrix_layout, char uplo, int n, int nrhs, + DType *a, int lda, DType *b, int ldb); + +template <> +inline int MXNET_LAPACK_posv(int matrix_layout, char uplo, int n, + int nrhs, float *a, int lda, float *b, int ldb) { + return mxnet_lapack_sposv(matrix_layout, uplo, n, nrhs, a, lda, b, ldb); +} + +template <> +inline int MXNET_LAPACK_posv(int matrix_layout, char uplo, int n, + int nrhs, double *a, int lda, double *b, int ldb) { + return mxnet_lapack_dposv(matrix_layout, uplo, n, nrhs, a, lda, b, ldb); +} + +#endif // MXNET_OPERATOR_C_LAPACK_API_H_ diff --git a/src/operator/channel_op_common.h b/src/operator/channel_op_common.h index 9ae6a6602c2e..113da9b35825 100644 --- a/src/operator/channel_op_common.h +++ b/src/operator/channel_op_common.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file channel_op_common.h * \brief common function used for concat and split channel * \author Bing Xu diff --git a/src/operator/concat-inl.h b/src/operator/concat-inl.h index 09b0c4b21e89..ed553c8f99e7 100644 --- a/src/operator/concat-inl.h +++ b/src/operator/concat-inl.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file concat-inl.h * \brief * \author Bing Xu diff --git a/src/operator/concat.cc b/src/operator/concat.cc index 85a8c7e7b2ee..1bee4b45cd21 100644 --- a/src/operator/concat.cc +++ b/src/operator/concat.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file concat.cc * \brief * \author Bing Xu @@ -51,7 +69,7 @@ MXNET_REGISTER_OP_PROPERTY(Concat, ConcatProp) .. note:: `Concat` is deprecated. Use `concat` instead. The dimensions of the input arrays should be the same except the axis along - which they will concatenated. +which they will be concatenated. The dimension of the output array along the concatenated axis will be equal to the sum of the corresponding dimensions of the input arrays. diff --git a/src/operator/concat.cu b/src/operator/concat.cu index a410e199637e..06828fcbcd7d 100644 --- a/src/operator/concat.cu +++ b/src/operator/concat.cu @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file concat.cu * \brief * \author Bing Xu diff --git a/src/operator/contrib/count_sketch-inl.h b/src/operator/contrib/count_sketch-inl.h index 566327e3677c..5df00968e4e5 100644 --- a/src/operator/contrib/count_sketch-inl.h +++ b/src/operator/contrib/count_sketch-inl.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file count_sketch-inl.h * \brief count_sketch operator and symbol * \author Chen Zhu diff --git a/src/operator/contrib/count_sketch.cc b/src/operator/contrib/count_sketch.cc index cf89c97bdc9b..6aba8f44b3ad 100644 --- a/src/operator/contrib/count_sketch.cc +++ b/src/operator/contrib/count_sketch.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file count_sketch.cc * \brief count_sketch op * \author Chen Zhu @@ -30,7 +48,7 @@ MXNET_REGISTER_OP_PROPERTY(_contrib_count_sketch, CountSketchProp) Assume input data has shape (N, d), sign hash table s has shape (N, d), index hash table h has shape (N, d) and mapping dimension out_dim = k, -each element in s is either +1 or -1, each element in h is random integer from 0 to k-1. +each element in s is either +1 or -1, each element in h is random integer from 0 to k-1. Then the operator computs: .. math:: diff --git a/src/operator/contrib/count_sketch.cu b/src/operator/contrib/count_sketch.cu index 7cf13e8a4993..0f3d295ae43f 100644 --- a/src/operator/contrib/count_sketch.cu +++ b/src/operator/contrib/count_sketch.cu @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file count_sketch.cu * \brief count_sketch op * \author Chen Zhu, Yang Shi diff --git a/src/operator/contrib/ctc_include/detail/cpu_ctc.h b/src/operator/contrib/ctc_include/detail/cpu_ctc.h index 68d74bbc6f83..f31ef62c384e 100644 --- a/src/operator/contrib/ctc_include/detail/cpu_ctc.h +++ b/src/operator/contrib/ctc_include/detail/cpu_ctc.h @@ -10,6 +10,8 @@ #include "ctc_helper.h" +namespace mxnet_warpctc { + template class CpuCTC { public: @@ -484,3 +486,5 @@ ctcStatus_t CpuCTC::score_forward(const ProbT* const activations, return CTC_STATUS_SUCCESS; } + +} // mxnet_warpctc \ No newline at end of file diff --git a/src/operator/contrib/ctc_include/detail/gpu_ctc.h b/src/operator/contrib/ctc_include/detail/gpu_ctc.h index 30fa2cfdacbb..ef71f3cdf956 100644 --- a/src/operator/contrib/ctc_include/detail/gpu_ctc.h +++ b/src/operator/contrib/ctc_include/detail/gpu_ctc.h @@ -1,8 +1,11 @@ #pragma once + #include "ctc_helper.h" #include "gpu_ctc_kernels.h" +namespace mxnet_warpctc { + template class GpuCTC { public: @@ -481,3 +484,4 @@ GpuCTC::score_forward(const ProbT* const activations, label_lengths, input_lengths, true, false); } +} // mxnet_warpctc \ No newline at end of file diff --git a/src/operator/contrib/ctc_loss-inl.h b/src/operator/contrib/ctc_loss-inl.h index 8431f65088f7..0d0c0bf4cd09 100644 --- a/src/operator/contrib/ctc_loss-inl.h +++ b/src/operator/contrib/ctc_loss-inl.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2016 by Contributors * \file ctc_loss-inl.h * \brief * \author Sebastian Bodenstien diff --git a/src/operator/contrib/ctc_loss.cc b/src/operator/contrib/ctc_loss.cc index 13d280044dee..3727cee10b1c 100644 --- a/src/operator/contrib/ctc_loss.cc +++ b/src/operator/contrib/ctc_loss.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file ctc_loss.cc * \brief * \author Sebastian Bodenstein @@ -18,7 +36,7 @@ ctcStatus_t compute_ctc_cost(const Tensor activations, int minibatch = static_cast(activations.size(1)); int alphabet_size = static_cast(activations.size(2)); int blank_label = 0; - CpuCTC ctc(alphabet_size, minibatch, workspace, blank_label); + mxnet_warpctc::CpuCTC ctc(alphabet_size, minibatch, workspace, blank_label); if (train) return ctc.cost_and_grad(activations.dptr_, grads, costs, labels, label_lengths, input_lengths); @@ -58,22 +76,24 @@ The shapes of the inputs and outputs: - **label**: *(batch_size, label_sequence_length)* - **out**: *(batch_size)*. -``label`` is a tensor of integers between 1 and *alphabet_size*. If a -sequence of labels is shorter than *label_sequence_length*, use the special -padding character 0 at the end of the sequence to conform it to the correct -length. For example, if *label_sequence_length* = 4, and one has two sequences -of labels [2, 1] and [3, 2, 2], the resulting ```label``` tensor should be +``label`` is a tensor of integers between 1 and *alphabet_size*. If a +sequence of labels is shorter than *label_sequence_length*, use the special +padding character 0 at the end of the sequence to conform it to the correct +length. For example, if *label_sequence_length* = 4, and one has two sequences +of labels [2, 1] and [3, 2, 2], the resulting ```label``` tensor should be padded to be:: [[2, 1, 0, 0], [3, 2, 2, 0]] -The ``data`` tensor consists of sequences of activation vectors. The layer -applies a softmax to each vector, which then becomes a vector of probabilities -over the alphabet. Note that the 0th element of this vector is reserved for the +The ``data`` tensor consists of sequences of activation vectors. The layer +applies a softmax to each vector, which then becomes a vector of probabilities +over the alphabet. Note that the 0th element of this vector is reserved for the special blank character. -See *Connectionist Temporal Classification: Labelling Unsegmented -Sequence Data with Recurrent Neural Networks*, A. Graves *et al*. for more +``out`` is a list of CTC loss values, one per example in the batch. + +See *Connectionist Temporal Classification: Labelling Unsegmented +Sequence Data with Recurrent Neural Networks*, A. Graves *et al*. for more information. )code" ADD_FILELINE) diff --git a/src/operator/contrib/ctc_loss.cu b/src/operator/contrib/ctc_loss.cu index 6ef8b4e342bb..4bdef752812b 100644 --- a/src/operator/contrib/ctc_loss.cu +++ b/src/operator/contrib/ctc_loss.cu @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file ctc_loss.cu * \brief * \author Sebastian Bodenstein @@ -18,7 +36,7 @@ ctcStatus_t compute_ctc_cost(const Tensor activations, int minibatch = static_cast(activations.size(1)); int alphabet_size = static_cast(activations.size(2)); int blank_label = 0; - GpuCTC ctc(alphabet_size, minibatch, workspace, + mxnet_warpctc::GpuCTC ctc(alphabet_size, minibatch, workspace, activations.stream_->stream_, blank_label); if (train) return ctc.cost_and_grad(activations.dptr_, grads, costs, labels, diff --git a/src/operator/contrib/deformable_convolution-inl.h b/src/operator/contrib/deformable_convolution-inl.h new file mode 100644 index 000000000000..a8dc6b8f09ed --- /dev/null +++ b/src/operator/contrib/deformable_convolution-inl.h @@ -0,0 +1,509 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +/*! + * Copyright (c) 2017 Microsoft + * Licensed under The Apache-2.0 License [see LICENSE for details] + * \file deformable_convolution-inl.h + * \brief + * \ref: https://github.com/Yangqing/caffe/wiki/Convolution-in-Caffe:-a-memo + * \ref: https://arxiv.org/abs/1703.06211 + * \author Yuwen Xiong, Haozhi Qi, Jifeng Dai +*/ +#ifndef MXNET_OPERATOR_CONTRIB_DEFORMABLE_CONVOLUTION_INL_H_ +#define MXNET_OPERATOR_CONTRIB_DEFORMABLE_CONVOLUTION_INL_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../operator_common.h" +#include "../nn/im2col.h" +#include "./nn/deformable_im2col.h" + + +namespace mxnet { +namespace op { + +namespace conv { + enum DeformableConvolutionOpInputs { kData, kOffset, kWeight, kBias }; + enum DeformableConvolutionOpOutputs { kOut }; + enum DeformableConvolutionOpResource { kTempSpace }; +} + +struct DeformableConvolutionParam : public dmlc::Parameter { + TShape kernel; + TShape stride; + TShape dilate; + TShape pad; + uint32_t num_filter; + uint32_t num_group; + uint32_t num_deformable_group; + uint64_t workspace; + bool no_bias; + dmlc::optional layout; + DMLC_DECLARE_PARAMETER(DeformableConvolutionParam) { + DMLC_DECLARE_FIELD(kernel).describe("convolution kernel size: (h, w) or (d, h, w)"); + DMLC_DECLARE_FIELD(stride).set_default(TShape()) + .describe("convolution stride: (h, w) or (d, h, w)"); + DMLC_DECLARE_FIELD(dilate).set_default(TShape()) + .describe("convolution dilate: (h, w) or (d, h, w)"); + DMLC_DECLARE_FIELD(pad).set_default(TShape()) + .describe("pad for convolution: (h, w) or (d, h, w)"); + DMLC_DECLARE_FIELD(num_filter).set_range(1, 100000) + .describe("convolution filter(channel) number"); + DMLC_DECLARE_FIELD(num_group).set_default(1) + .describe("Number of group partitions."); + DMLC_DECLARE_FIELD(num_deformable_group).set_default(1) + .describe("Number of deformable group partitions."); + DMLC_DECLARE_FIELD(workspace).set_default(1024).set_range(0, 8192) + .describe("Maximum temperal workspace allowed for convolution (MB)."); + DMLC_DECLARE_FIELD(no_bias).set_default(false) + .describe("Whether to disable bias parameter."); + DMLC_DECLARE_FIELD(layout) + .add_enum("NCW", mshadow::kNCW) + .add_enum("NCHW", mshadow::kNCHW) + .add_enum("NCDHW", mshadow::kNCDHW) + .set_default(dmlc::optional()) + .describe("Set layout for input, output and weight. Empty for\n " + "default layout: NCW for 1d, NCHW for 2d and NCDHW for 3d."); + } +}; + +template +class DeformableConvolutionOp : public Operator { + public: + explicit DeformableConvolutionOp(DeformableConvolutionParam p) { + this->param_ = p; + // convert MBytes first to Bytes and then to elements. + param_.workspace = (param_.workspace << 20) / sizeof(DType); + CHECK(param_.layout.value() == mshadow::kNCW || + param_.layout.value() == mshadow::kNCHW || + param_.layout.value() == mshadow::kNCDHW) + << "Only support NCW, NCHW and NCDHW layout"; + } + + virtual void Forward(const OpContext &ctx, + const std::vector &in_data, + const std::vector &req, + const std::vector &out_data, + const std::vector &aux_args) { + using namespace mshadow; + using namespace mshadow::expr; + CHECK_EQ(req[conv::kOut], kWriteTo); + size_t expected = param_.no_bias ? 3 : 4; + CHECK_EQ(in_data.size(), expected); + CHECK_EQ(out_data.size(), 1U); + LayerSetUp(in_data[conv::kData].shape_, + in_data[conv::kOffset].shape_, + out_data[conv::kOut].shape_); + Stream* s = ctx.get_stream(); + // allocate workspace for col_buffer + Tensor workspace = ctx.requested[conv::kTempSpace] + .get_space_typed(Shape1(col_buffer_size_), s); + // calculate the shape of col_buffer + TShape col_buffer_shape(num_spatial_axes_ + 1); + col_buffer_shape[0] = conv_in_channels_ * param_.kernel.Size(); + for (index_t i = 1; i < col_buffer_shape.ndim(); ++i) { + col_buffer_shape[i] = out_data[0].shape_[i + 1]; + } + // create a column buffer using workspace and col_buffer_shape + TBlob col_buffer(workspace.dptr_, col_buffer_shape, xpu::kDevMask, DataType::kFlag); + + // initialize weight and col_buffer 3D tensors for using gemm + index_t M = conv_out_channels_ / group_; + index_t N = conv_out_spatial_dim_; + index_t K = kernel_dim_; + Tensor weight_3d = in_data[conv::kWeight].get_with_shape( + Shape3(group_, M, K), s); + Tensor col_buffer_3d = col_buffer.get_with_shape( + Shape3(group_, K, N), s); + Tensor output_4d = out_data[conv::kOut].get_with_shape( + Shape4(num_, group_, M, N), s); + for (index_t n = 0; n < num_; ++n) { + // transform image to col_buffer in order to use gemm + deformable_im2col(s, in_data[conv::kData].dptr() + n*input_dim_, + in_data[conv::kOffset].dptr() + n*input_offset_dim_, in_data[conv::kData].shape_, + col_buffer.shape_, param_.kernel, param_.pad, param_.stride, param_.dilate, + param_.num_deformable_group, col_buffer.dptr()); + Tensor output_3d = output_4d[n]; + for (index_t g = 0; g < group_; ++g) { + ASSIGN_DISPATCH(output_3d[g], req[conv::kOut], dot(weight_3d[g], col_buffer_3d[g])); + } + } + if (bias_term_) { + Tensor bias = in_data[conv::kBias].get(s); + Tensor output_3d = out_data[conv::kOut].get_with_shape( + Shape3(num_, conv_out_channels_, conv_out_spatial_dim_), s); + // has bias term, broadcast it to the same shape of output_3d in channel dim + output_3d += mshadow::expr::broadcast<1>(bias, output_3d.shape_); + } + } + + virtual void Backward(const OpContext &ctx, + const std::vector& out_grad, + const std::vector& in_data, + const std::vector& out_data, + const std::vector& req, + const std::vector& in_grad, + const std::vector& aux_args) { + using namespace mshadow; + using namespace mshadow::expr; + CHECK_EQ(out_grad.size(), 1U); + size_t expected = param_.no_bias == 0 ? 4 : 3; + CHECK(in_data.size() == expected && in_grad.size() == expected); + CHECK_EQ(req.size(), expected); + CHECK_EQ(in_data[conv::kWeight].CheckContiguous(), true); + LayerSetUp(in_grad[conv::kData].shape_, + in_grad[conv::kOffset].shape_, + out_grad[conv::kOut].shape_); + Stream *s = ctx.get_stream(); + // allocate workspace for col_buffer + Tensor workspace = ctx.requested[conv::kTempSpace] + .get_space_typed(Shape1(col_buffer_size_), s); + // calculate the shape of col_buffer + TShape col_buffer_shape(num_spatial_axes_ + 1); + col_buffer_shape[0] = conv_in_channels_ * param_.kernel.Size(); + for (index_t i = 1; i < col_buffer_shape.ndim(); ++i) { + col_buffer_shape[i] = out_grad[conv::kData].shape_[i + 1]; + } + // create a column buffer using workspace and col_buffer_shape + TBlob col_buffer(workspace.dptr_, col_buffer_shape, xpu::kDevMask, DataType::kFlag); + + // initialize weight and col_buffer 3D tensors for using gemm + // For computing dLoss/d(in_data[kData]) + index_t M = kernel_dim_; + index_t N = conv_out_spatial_dim_; + index_t K = conv_out_channels_ / group_; + Tensor weight_3d = in_data[conv::kWeight].get_with_shape( + Shape3(group_, K, M), s); + Tensor out_grad_4d = out_grad[conv::kOut].get_with_shape( + Shape4(num_, group_, K, N), s); + Tensor col_buffer_3d = col_buffer.get_with_shape( + Shape3(group_, M, N), s); + // For computing dLoss/dWeight + Tensor dweight_3d = in_grad[conv::kWeight].get_with_shape( + Shape3(group_, K, M), s); + + Tensor data_grad = in_grad[conv::kData].FlatTo1D(s); + data_grad = 0; + + + for (index_t n = 0; n < num_; ++n) { + Tensor out_grad_3d = out_grad_4d[n]; + for (index_t g = 0; g < group_; ++g) { + col_buffer_3d[g] = dot(weight_3d[g].T(), out_grad_3d[g]); + } + + // gradient w.r.t. input coordinate data + deformable_col2im_coord(s, col_buffer.dptr(), + in_data[conv::kData].dptr() + n*input_dim_, + in_data[conv::kOffset].dptr() + n*input_offset_dim_, + in_grad[conv::kData].shape_, col_buffer.shape_, + param_.kernel, param_.pad, param_.stride, param_.dilate, param_.num_deformable_group, + in_grad[conv::kOffset].dptr() + n*input_offset_dim_, + req[conv::kData]); + + // gradient w.r.t. input data + deformable_col2im(s, col_buffer.dptr(), + in_data[conv::kOffset].dptr() + n*input_offset_dim_, + in_grad[conv::kData].shape_, col_buffer.shape_, + param_.kernel, param_.pad, param_.stride, param_.dilate, param_.num_deformable_group, + in_grad[conv::kData].dptr() + n*input_dim_, + req[conv::kData]); + + // gradient w.r.t. weight, dWeight should accumulate across the batch and group + deformable_im2col(s, in_data[conv::kData].dptr() + n*input_dim_, + in_data[conv::kOffset].dptr() + n*input_offset_dim_, in_data[conv::kData].shape_, + col_buffer.shape_, param_.kernel, param_.pad, param_.stride, param_.dilate, + param_.num_deformable_group, col_buffer.dptr()); + + for (index_t g = 0; g < group_; ++g) { + if (0 == n) { + ASSIGN_DISPATCH(dweight_3d[g], req[conv::kWeight], + dot(out_grad_3d[g], col_buffer_3d[g].T())); + } else { + dweight_3d[g] += dot(out_grad_3d[g], col_buffer_3d[g].T()); + } + } + } + + // gradient w.r.t bias + if (bias_term_) { + Tensor dbias = in_grad[conv::kBias].get(s); + Tensor dout = out_grad[conv::kOut].get_with_shape( + Shape3(num_, conv_out_channels_, conv_out_spatial_dim_), s); + ASSIGN_DISPATCH(dbias, req[conv::kBias], sumall_except_dim<1>(dout)); + } + } + + private: + void LayerSetUp(const TShape& ishape, const TShape& offset_shape, const TShape& oshape) { + channel_axis_ = 1; // hard code channel axis + const index_t first_spatial_axis = channel_axis_ + 1; + const index_t num_axes = param_.kernel.ndim() + 2; + num_spatial_axes_ = num_axes - first_spatial_axis; + is_1x1_ = true; + for (index_t i = 0; i < param_.kernel.ndim(); ++i) { + is_1x1_ &= param_.kernel[i] == 1 && param_.stride[i] == 1 && param_.pad[i] == 0; + if (!is_1x1_) break; + } + + // batch size + num_ = ishape[0]; + // number of input channels + channels_ = ishape[1]; + group_ = param_.num_group; + conv_out_channels_ = param_.num_filter; + conv_in_channels_ = channels_; + bias_term_ = !param_.no_bias; + kernel_dim_ = conv_in_channels_ / group_ * param_.kernel.Size(); + weight_offset_ = conv_out_channels_ * kernel_dim_ / group_; + conv_out_spatial_dim_ = oshape.ProdShape(2, oshape.ndim()); + col_offset_ = kernel_dim_ * conv_out_spatial_dim_; + output_offset_ = conv_out_channels_ * conv_out_spatial_dim_ / group_; + // size of the column buffer used for storing im2col-ed pixels + col_buffer_size_ = kernel_dim_ * group_ * conv_out_spatial_dim_; + // input/output image size (#channels * height * width) + input_dim_ = ishape.ProdShape(1, ishape.ndim()); + input_offset_dim_ = offset_shape.ProdShape(1, offset_shape.ndim()); + output_dim_ = oshape.ProdShape(1, oshape.ndim()); + num_kernels_im2col_ = conv_in_channels_ * conv_out_spatial_dim_; + num_kernels_col2im_ = input_dim_; + } + + private: + DeformableConvolutionParam param_; + index_t channel_axis_; // channel axis of the input + index_t channels_; // number of channels of input image + index_t num_spatial_axes_; // number of spatial axes + index_t num_; // batch size + index_t group_; // number of groups + index_t conv_out_channels_; // number of output channels (num_filter) + index_t conv_out_spatial_dim_; // number of pixels of output images per channel + index_t conv_in_channels_; // number of input channels + index_t kernel_dim_; // number of input channels per group * kernel size + index_t weight_offset_; // number of output channels per group * kernel_dim_ + index_t col_offset_; + index_t output_offset_; + index_t col_buffer_size_; + index_t input_dim_; + index_t input_offset_dim_; + index_t output_dim_; + index_t num_kernels_im2col_; + index_t num_kernels_col2im_; + bool bias_term_; // has bias term? + bool is_1x1_; +}; // class ConvolutionOp + +template +Operator* CreateOp(DeformableConvolutionParam param, int dtype, + std::vector *in_shape, + std::vector *out_shape, + Context ctx); + +#if DMLC_USE_CXX11 +class DeformableConvolutionProp : public OperatorProperty { + public: + std::vector ListArguments() const override { + if (!param_.no_bias) { + return{ "data", "offset", "weight", "bias" }; + } else { + return{ "data", "offset", "weight" }; + } + } + + void Init(const std::vector >& kwargs) override { + using namespace mshadow; + param_.Init(kwargs); + if (param_.kernel.ndim() == 2) { + param_.layout = param_.layout ? param_.layout.value() : mshadow::kNCHW; + if (param_.stride.ndim() == 0) param_.stride = Shape2(1, 1); + if (param_.dilate.ndim() == 0) param_.dilate = Shape2(1, 1); + if (param_.pad.ndim() == 0) param_.pad = Shape2(0, 0); + } else { + LOG(FATAL) << "not implemented"; + } + } + + std::map GetParams() const override { + return param_.__DICT__(); + } + + bool InferShape(std::vector *in_shape, + std::vector *out_shape, + std::vector *aux_shape) const override { + using namespace mshadow; + if (!param_.no_bias) { + CHECK_EQ(in_shape->size(), 4U) << "Input:[data, offset, weight, bias]"; + } else { + CHECK_EQ(in_shape->size(), 3U) << "Input:[data, offset, weight]"; + } + out_shape->resize(1, TShape()); + const TShape &dshp = (*in_shape)[conv::kData]; + const TShape &oshp = (*in_shape)[conv::kOffset]; + if (dshp.ndim() == 0) return false; + if (param_.kernel.ndim() == 2) { + // 2d conv + CHECK_EQ(dshp.ndim(), 4U) \ + << "Input data should be 4D in batch-num_filter-y-x"; + CHECK_EQ(oshp.ndim(), 4U) \ + << "Input offset should be 4D in batch-num_filter-y-x"; + Shape<4> dshape = ConvertLayout(dshp.get<4>(), param_.layout.value(), kNCHW); + Shape<4> offsetshape = ConvertLayout(oshp.get<4>(), param_.layout.value(), kNCHW); + Shape<4> wshape = Shape4(param_.num_filter / param_.num_group, dshape[1] / param_.num_group, + param_.kernel[0], param_.kernel[1]); + wshape = ConvertLayout(wshape, kNCHW, param_.layout.value()); + wshape[0] *= param_.num_group; + SHAPE_ASSIGN_CHECK(*in_shape, conv::kWeight, wshape); + if (!param_.no_bias) { + SHAPE_ASSIGN_CHECK(*in_shape, conv::kBias, Shape1(param_.num_filter)); + } + + const index_t ksize_y = static_cast(param_.kernel[0]); + const index_t ksize_x = static_cast(param_.kernel[1]); + CHECK_EQ(dshape[1] % param_.num_group, 0U) \ + << "input num_filter must divide group size"; + CHECK_EQ(dshape[1] % param_.num_deformable_group, 0U) \ + << "input num_filter must divide deformable group size"; + CHECK_EQ(param_.num_filter % param_.num_group, 0U) \ + << "output num_filter must divide group size"; + CHECK_GT(param_.kernel.Size(), 0U) \ + << "incorrect kernel size: " << param_.kernel; + CHECK_GT(param_.stride.Size(), 0U) \ + << "incorrect stride size: " << param_.stride; + CHECK_GT(param_.dilate.Size(), 0U) \ + << "incorrect dilate size: " << param_.dilate; + Shape<4> oshape; + oshape[0] = dshape[0]; + oshape[1] = param_.num_filter; + oshape[2] = (dshape[2] + 2 * param_.pad[0] - + (param_.dilate[0] * (ksize_y - 1) + 1)) / param_.stride[0] + 1; + oshape[3] = (dshape[3] + 2 * param_.pad[1] - + (param_.dilate[1] * (ksize_x - 1) + 1)) / param_.stride[1] + 1; + SHAPE_ASSIGN_CHECK(*out_shape, 0, ConvertLayout(oshape, kNCHW, param_.layout.value())); + CHECK_EQ(oshape[1] % param_.num_deformable_group, 0U) \ + << "output num_filter must divide deformable group size"; + CHECK_EQ(oshape[2], offsetshape[2]) \ + << "output height must equal to offset map height"; + CHECK_EQ(oshape[3], offsetshape[3]) \ + << "output width must equal to offset map width"; + CHECK_EQ(offsetshape[1] % (param_.kernel[0] * param_.kernel[1]), 0U) \ + << "offset filter must divide deformable group size"; + CHECK_EQ(offsetshape[1] / (2 * param_.kernel[0] * param_.kernel[1]), \ + param_.num_deformable_group) \ + << "offset filter must divide deformable group size"; + // Perform incomplete shape inference. Fill in the missing values in data shape. + // 1) We can always fill in the batch_size. + // 2) We can back-calculate the input height/width if the corresponding stride is 1. + oshape = ConvertLayout((*out_shape)[0].get<4>(), param_.layout.value(), kNCHW); + dshape[0] = oshape[0]; + if (param_.stride[0] == 1) { + dshape[2] = oshape[2] + param_.dilate[0] * (ksize_y - 1) - 2 * param_.pad[0]; + } + if (param_.stride[1] == 1) { + dshape[3] = oshape[3] + param_.dilate[1] * (ksize_x - 1) - 2 * param_.pad[1]; + } + SHAPE_ASSIGN_CHECK(*in_shape, conv::kData, + ConvertLayout(dshape, kNCHW, param_.layout.value())); + // Check whether the kernel sizes are valid + if (dshape[2] != 0) { + CHECK_LE(ksize_y, dshape[2] + 2 * param_.pad[0]) << "kernel size exceed input"; + } + if (dshape[3] != 0) { + CHECK_LE(ksize_x, dshape[3] + 2 * param_.pad[1]) << "kernel size exceed input"; + } + return true; + } else { + LOG(FATAL) << "not implemented"; + return false; + } + } + + bool InferType(std::vector *in_type, + std::vector *out_type, + std::vector *aux_type) const override { + CHECK_GE(in_type->size(), 1U); + int dtype = (*in_type)[0]; + CHECK_NE(dtype, -1) << "First input must have specified type"; + for (index_t i = 0; i < in_type->size(); ++i) { + if ((*in_type)[i] == -1) { + (*in_type)[i] = dtype; + } else { + CHECK_EQ((*in_type)[i], dtype) << "This layer requires uniform type. " + << "Expected " << dtype << " v.s. given " + << (*in_type)[i] << " at " << ListArguments()[i]; + } + } + out_type->clear(); + out_type->push_back(dtype); + return true; + } + + OperatorProperty* Copy() const override { + auto ptr = new DeformableConvolutionProp(); + ptr->param_ = param_; + return ptr; + } + + std::string TypeString() const override { + return "_contrib_DeformableConvolution"; + } + + std::vector DeclareBackwardDependency( + const std::vector &out_grad, + const std::vector &in_data, + const std::vector &out_data) const override { + return{ out_grad[conv::kOut], in_data[conv::kData], + in_data[conv::kOffset], in_data[conv::kWeight] }; + } + + std::vector ForwardResource( + const std::vector &in_shape) const override { + return{ ResourceRequest::kTempSpace }; + } + + std::vector BackwardResource( + const std::vector &in_shape) const override { + return{ ResourceRequest::kTempSpace }; + } + + Operator* CreateOperator(Context ctx) const override { + LOG(FATAL) << "Not Implemented."; + return NULL; + } + + Operator* CreateOperatorEx(Context ctx, std::vector *in_shape, + std::vector *in_type) const override; + + private: + DeformableConvolutionParam param_; +}; // class ConvolutionProp +#endif // DMLC_USE_CXX11 +} // namespace op +} // namespace mxnet +#endif // MXNET_OPERATOR_CONTRIB_DEFORMABLE_CONVOLUTION_INL_H_ diff --git a/src/operator/contrib/deformable_convolution.cc b/src/operator/contrib/deformable_convolution.cc new file mode 100644 index 000000000000..352baa12fbc1 --- /dev/null +++ b/src/operator/contrib/deformable_convolution.cc @@ -0,0 +1,108 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +/*! + * Copyright (c) 2017 Microsoft + * Licensed under The Apache-2.0 License [see LICENSE for details] + * \file deformable_convolution.cc + * \brief + * \author Yuwen Xiong, Haozhi Qi, Jifeng Dai +*/ + +#include "./deformable_convolution-inl.h" + +namespace mxnet { +namespace op { +DMLC_REGISTER_PARAMETER(DeformableConvolutionParam); + +template<> +Operator* CreateOp(DeformableConvolutionParam param, int dtype, + std::vector *in_shape, + std::vector *out_shape, + Context ctx) { + Operator *op = NULL; + MSHADOW_REAL_TYPE_SWITCH(dtype, DType, { + op = new DeformableConvolutionOp(param); + }) + return op; +} + +// DO_BIND_DISPATCH comes from operator_common.h +Operator *DeformableConvolutionProp::CreateOperatorEx(Context ctx, + std::vector *in_shape, + std::vector *in_type) const { + std::vector out_shape, aux_shape; + std::vector out_type, aux_type; + CHECK(InferType(in_type, &out_type, &aux_type)); + CHECK(InferShape(in_shape, &out_shape, &aux_shape)); + DO_BIND_DISPATCH(CreateOp, param_, (*in_type)[0], in_shape, &out_shape, ctx); +} + +MXNET_REGISTER_OP_PROPERTY(_contrib_DeformableConvolution, DeformableConvolutionProp) +.describe(R"code(Compute 2-D deformable convolution on 4-D input. + +The deformable convolution operation is described in https://arxiv.org/abs/1703.06211 + +For 2-D deformable convolution, the shapes are + +- **data**: *(batch_size, channel, height, width)* +- **offset**: *(batch_size, num_deformable_group * kernel[0] * kernel[1], height, width)* +- **weight**: *(num_filter, channel, kernel[0], kernel[1])* +- **bias**: *(num_filter,)* +- **out**: *(batch_size, num_filter, out_height, out_width)*. + +Define:: + + f(x,k,p,s,d) = floor((x+2*p-d*(k-1)-1)/s)+1 + +then we have:: + + out_height=f(height, kernel[0], pad[0], stride[0], dilate[0]) + out_width=f(width, kernel[1], pad[1], stride[1], dilate[1]) + +If ``no_bias`` is set to be true, then the ``bias`` term is ignored. + +The default data ``layout`` is *NCHW*, namely *(batch_size, channle, height, +width)*. + +If ``num_group`` is larger than 1, denoted by *g*, then split the input ``data`` +evenly into *g* parts along the channel axis, and also evenly split ``weight`` +along the first dimension. Next compute the convolution on the *i*-th part of +the data with the *i*-th weight part. The output is obtained by concating all +the *g* results. + +If ``num_deformable_group`` is larger than 1, denoted by *dg*, then split the +input ``offset`` evenly into *dg* parts along the channel axis, and also evenly +split ``out`` evenly into *dg* parts along the channel axis. Next compute the +deformable convolution, apply the *i*-th part of the offset part on the *i*-th +out. + + +Both ``weight`` and ``bias`` are learnable parameters. + + +)code" ADD_FILELINE) +.add_argument("data", "NDArray-or-Symbol", "Input data to the DeformableConvolutionOp.") +.add_argument("offset", "NDArray-or-Symbol", "Input offset to the DeformableConvolutionOp.") +.add_argument("weight", "NDArray-or-Symbol", "Weight matrix.") +.add_argument("bias", "NDArray-or-Symbol", "Bias parameter.") +.add_arguments(DeformableConvolutionParam::__FIELDS__()); + +} // namespace op +} // namespace mxnet diff --git a/src/operator/contrib/deformable_convolution.cu b/src/operator/contrib/deformable_convolution.cu new file mode 100644 index 000000000000..f2200a9978ca --- /dev/null +++ b/src/operator/contrib/deformable_convolution.cu @@ -0,0 +1,48 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +/*! + * Copyright (c) 2017 Microsoft + * Licensed under The Apache-2.0 License [see LICENSE for details] + * \file deformable_convolution.cu + * \brief + * \author Yuwen Xiong, Haozhi Qi, Jifeng Dai +*/ + +#include "./deformable_convolution-inl.h" +#include + +namespace mxnet { +namespace op { + + template<> + Operator* CreateOp(DeformableConvolutionParam param, int dtype, + std::vector *in_shape, + std::vector *out_shape, + Context ctx) { + Operator *op = NULL; + MSHADOW_REAL_TYPE_SWITCH(dtype, DType, { + op = new DeformableConvolutionOp(param); + }) + return op; + } + +} // namespace op +} // namespace mxnet + diff --git a/src/operator/contrib/deformable_psroi_pooling-inl.h b/src/operator/contrib/deformable_psroi_pooling-inl.h new file mode 100644 index 000000000000..d391f045a1b5 --- /dev/null +++ b/src/operator/contrib/deformable_psroi_pooling-inl.h @@ -0,0 +1,305 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +/*! +* Copyright (c) 2017 Microsoft +* Licensed under The Apache-2.0 License [see LICENSE for details] +* \file deformable_psroi_pooling-inl.h +* \brief deformable psroi pooling operator and symbol +* \author Yi Li, Guodong Zhang, Jifeng Dai +*/ +#ifndef MXNET_OPERATOR_CONTRIB_DEFORMABLE_PSROI_POOLING_INL_H_ +#define MXNET_OPERATOR_CONTRIB_DEFORMABLE_PSROI_POOLING_INL_H_ + +#include +#include +#include +#include +#include +#include +#include +#include "../mshadow_op.h" +#include "../operator_common.h" + + +namespace mxnet { +namespace op { + + // Declare enumeration of input order to make code more intuitive. + // These enums are only visible within this header +namespace deformablepsroipool { + enum DeformablePSROIPoolingOpInputs { kData, kBox, kTrans }; + enum DeformablePSROIPoolingOpOutputs { kOut, kTopCount }; +} // deformablepsroipool + +struct DeformablePSROIPoolingParam : public dmlc::Parameter { + // TShape pooled_size; + float spatial_scale; + int output_dim; + int group_size; + int pooled_size; + int part_size; + int sample_per_part; + float trans_std; + bool no_trans; + DMLC_DECLARE_PARAMETER(DeformablePSROIPoolingParam) { + DMLC_DECLARE_FIELD(spatial_scale).set_range(0.0, 1.0) + .describe("Ratio of input feature map height (or w) to raw image height (or w). " + "Equals the reciprocal of total stride in convolutional layers"); + DMLC_DECLARE_FIELD(output_dim).describe("fix output dim"); + DMLC_DECLARE_FIELD(group_size).describe("fix group size"); + DMLC_DECLARE_FIELD(pooled_size).describe("fix pooled size"); + DMLC_DECLARE_FIELD(part_size).set_default(0).describe("fix part size"); + DMLC_DECLARE_FIELD(sample_per_part).set_default(1).describe("fix samples per part"); + DMLC_DECLARE_FIELD(trans_std).set_default(0.0).set_range(0.0, 1.0) + .describe("fix transition std"); + DMLC_DECLARE_FIELD(no_trans).set_default(false) + .describe("Whether to disable trans parameter."); + } +}; + +template +class DeformablePSROIPoolingOp : public Operator { + public: + explicit DeformablePSROIPoolingOp(DeformablePSROIPoolingParam p) { + this->param_ = p; + } + + virtual void Forward(const OpContext &ctx, + const std::vector &in_data, + const std::vector &req, + const std::vector &out_data, + const std::vector &aux_args) { + using namespace mshadow; + size_t in_expected = param_.no_trans? 2 : 3; + size_t out_expected = 2; + CHECK_EQ(in_data.size(), in_expected); + CHECK_EQ(out_data.size(), out_expected); + CHECK_EQ(out_data[deformablepsroipool::kOut].shape_[0], + in_data[deformablepsroipool::kBox].shape_[0]); + CHECK_EQ(out_data[deformablepsroipool::kTopCount].shape_[0], + in_data[deformablepsroipool::kBox].shape_[0]); + Stream *s = ctx.get_stream(); + + Tensor data = in_data[deformablepsroipool::kData].get(s); + Tensor bbox = in_data[deformablepsroipool::kBox].get(s); + Tensor out = out_data[deformablepsroipool::kOut].get(s); + Tensor top_count = out_data[deformablepsroipool::kTopCount] + .get(s); + CHECK_EQ(data.CheckContiguous(), true); + CHECK_EQ(bbox.CheckContiguous(), true); + CHECK_EQ(out.CheckContiguous(), true); + CHECK_EQ(top_count.CheckContiguous(), true); + out = -FLT_MAX; + top_count = 0.0f; + + Tensor trans; + if (!param_.no_trans) { + trans = in_data[deformablepsroipool::kTrans].get(s); + } + DeformablePSROIPoolForward(out, data, bbox, trans, top_count, param_.no_trans, + param_.spatial_scale, param_.output_dim, param_.group_size, param_.pooled_size, + param_.part_size, param_.sample_per_part, param_.trans_std); + } + + virtual void Backward(const OpContext &ctx, + const std::vector &out_grad, + const std::vector &in_data, + const std::vector &out_data, + const std::vector &req, + const std::vector &in_grad, + const std::vector &aux_args) { + using namespace mshadow; + size_t in_expected = param_.no_trans ? 2 : 3; + size_t out_expected = 2; + CHECK_EQ(in_data.size(), in_expected); + CHECK_EQ(out_data.size(), out_expected); + CHECK_EQ(out_grad[deformablepsroipool::kOut].shape_[0], + in_data[deformablepsroipool::kBox].shape_[0]); + CHECK_EQ(out_data[deformablepsroipool::kTopCount].shape_[0], + in_data[deformablepsroipool::kBox].shape_[0]); + CHECK_NE(req[deformablepsroipool::kData], kWriteInplace) << + "DeformablePSROIPooling: Backward doesn't support kWriteInplace."; + CHECK_NE(req[deformablepsroipool::kBox], kWriteInplace) << + "DeformablePSROIPooling: Backward doesn't support kWriteInplace."; + // CHECK_NE(req[deformablepsroipool::kTrans], kWriteInplace) << + // "DeformablePSROIPooling: Backward doesn't support kWriteInplace."; + Stream *s = ctx.get_stream(); + + Tensor grad_out = out_grad[deformablepsroipool::kOut].get(s); + Tensor data = in_data[deformablepsroipool::kData].get(s); + Tensor bbox = in_data[deformablepsroipool::kBox].get(s); + Tensor top_count = out_data[deformablepsroipool::kTopCount] + .get(s); + Tensor grad_in = in_grad[deformablepsroipool::kData].get(s); + Tensor grad_roi = in_grad[deformablepsroipool::kBox].get(s); + Tensor grad_trans; + Tensor trans; + if (!param_.no_trans) { + CHECK_EQ(in_grad.size(), 3); + trans = in_data[deformablepsroipool::kTrans].get(s); + grad_trans = in_grad[deformablepsroipool::kTrans].get(s); + } + + CHECK_EQ(grad_out.CheckContiguous(), true); + CHECK_EQ(data.CheckContiguous(), true); + CHECK_EQ(bbox.CheckContiguous(), true); + CHECK_EQ(top_count.CheckContiguous(), true); + CHECK_EQ(grad_in.CheckContiguous(), true); + + Assign(grad_in, req[deformablepsroipool::kData], 0); + if (!param_.no_trans) { + Assign(grad_trans, req[deformablepsroipool::kTrans], 0); + } + DeformablePSROIPoolBackwardAcc(grad_in, grad_trans, grad_out, data, bbox, trans, + top_count, param_.no_trans, param_.spatial_scale, param_.output_dim, param_.group_size, + param_.pooled_size, param_.part_size, param_.sample_per_part, param_.trans_std); + Assign(grad_roi, req[deformablepsroipool::kBox], 0); + } + + private: + DeformablePSROIPoolingParam param_; +}; // class DeformablePSROIPoolingOp + +// Decalre Factory function, used for dispatch specialization +template +Operator* CreateOp(DeformablePSROIPoolingParam param, int dtype); + +#if DMLC_USE_CXX11 +class DeformablePSROIPoolingProp : public OperatorProperty { + public: + std::vector ListArguments() const override { + if (param_.no_trans) { + return{ "data", "rois" }; + } else { + return{ "data", "rois", "trans" }; + } + } + + std::vector ListOutputs() const override { + return{ "output", "top_count" }; + } + + int NumOutputs() const override { + return 2; + } + + int NumVisibleOutputs() const override { + return 1; + } + + void Init(const std::vector >& kwargs) override { + param_.Init(kwargs); + if (param_.part_size == 0) { + param_.part_size = param_.pooled_size; + } + } + + std::map GetParams() const override { + return param_.__DICT__(); + } + + bool InferShape(std::vector *in_shape, + std::vector *out_shape, + std::vector *aux_shape) const override { + using namespace mshadow; + if (param_.no_trans) { + CHECK_EQ(in_shape->size(), 2) << "Input:[data, rois]"; + } else { + CHECK_EQ(in_shape->size(), 3) << "Input:[data, rois, trans]"; + // trans: [num_rois, 2, pooled_h, pooled_w] + TShape tshape = in_shape->at(deformablepsroipool::kTrans); + CHECK_EQ(tshape.ndim(), 4) << "trans should be a 4D tensor of shape"; + } + + // data: [batch_size, c, h, w] + TShape dshape = in_shape->at(deformablepsroipool::kData); + CHECK_EQ(dshape.ndim(), 4) << "data should be a 4D tensor"; + + // bbox: [num_rois, 5] + TShape bshape = in_shape->at(deformablepsroipool::kBox); + CHECK_EQ(bshape.ndim(), 2) << "bbox should be a 2D tensor of shape [batch, 5]"; + CHECK_EQ(bshape[1], 5) << "bbox should be a 2D tensor of shape [batch, 5]"; + + // out: [num_rois, c, pooled_h, pooled_w] + // top_count: [num_rois, c, pooled_h, pooled_w] + out_shape->clear(); + out_shape->push_back( + Shape4(bshape[0], param_.output_dim, param_.pooled_size, param_.pooled_size)); + out_shape->push_back( + Shape4(bshape[0], param_.output_dim, param_.pooled_size, param_.pooled_size)); + return true; + } + + bool InferType(std::vector *in_type, + std::vector *out_type, + std::vector *aux_type) const override { + CHECK_GE(in_type->size(), 2); + int dtype = (*in_type)[0]; + CHECK_EQ(dtype, (*in_type)[1]); + CHECK_NE(dtype, -1) << "Input must have specified type"; + + out_type->clear(); + out_type->push_back(dtype); + out_type->push_back(dtype); + return true; + } + + OperatorProperty* Copy() const override { + DeformablePSROIPoolingProp* deformable_psroi_pooling_sym = new DeformablePSROIPoolingProp(); + deformable_psroi_pooling_sym->param_ = this->param_; + return deformable_psroi_pooling_sym; + } + + std::string TypeString() const override { + return "_contrib_DeformablePSROIPooling"; + } + + // decalre dependency and inplace optimization options + std::vector DeclareBackwardDependency( + const std::vector &out_grad, + const std::vector &in_data, + const std::vector &out_data) const override { + if (param_.no_trans) { + return{ out_grad[deformablepsroipool::kOut], in_data[deformablepsroipool::kData], + in_data[deformablepsroipool::kBox], out_data[deformablepsroipool::kTopCount] }; + } else { + return{ out_grad[deformablepsroipool::kOut], in_data[deformablepsroipool::kData], + in_data[deformablepsroipool::kBox], in_data[deformablepsroipool::kTrans], + out_data[deformablepsroipool::kTopCount] }; + } + } + + + Operator* CreateOperator(Context ctx) const override { + LOG(FATAL) << "Not Implemented."; + return NULL; + } + + Operator* CreateOperatorEx(Context ctx, std::vector *in_shape, + std::vector *in_type) const override; + + + private: + DeformablePSROIPoolingParam param_; +}; // class DeformablePSROIPoolingProp +#endif +} // namespace op +} // namespace mxnet +#endif // MXNET_OPERATOR_CONTRIB_DEFORMABLE_PSROI_POOLING_INL_H_ diff --git a/src/operator/contrib/deformable_psroi_pooling.cc b/src/operator/contrib/deformable_psroi_pooling.cc new file mode 100644 index 000000000000..47f369a32d24 --- /dev/null +++ b/src/operator/contrib/deformable_psroi_pooling.cc @@ -0,0 +1,115 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +/*! + * Copyright (c) 2017 Microsoft + * Licensed under The Apache-2.0 License [see LICENSE for details] + * \file deformable_psroi_pooling.cc + * \brief + * \author Yi Li, Guodong Zhang, Jifeng Dai +*/ +#include "./deformable_psroi_pooling-inl.h" +#include +#include +#include +#include +#include + +using std::max; +using std::min; +using std::floor; +using std::ceil; + +namespace mshadow { + template + inline void DeformablePSROIPoolForward(const Tensor &out, + const Tensor &data, + const Tensor &bbox, + const Tensor &trans, + const Tensor &top_count, + const bool no_trans, + const float spatial_scale, + const int output_dim, + const int group_size, + const int pooled_size, + const int part_size, + const int sample_per_part, + const float trans_std) { + // NOT_IMPLEMENTED; + return; + } + + template + inline void DeformablePSROIPoolBackwardAcc(const Tensor &in_grad, + const Tensor &trans_grad, + const Tensor &out_grad, + const Tensor &data, + const Tensor &bbox, + const Tensor &trans, + const Tensor &top_count, + const bool no_trans, + const float spatial_scale, + const int output_dim, + const int group_size, + const int pooled_size, + const int part_size, + const int sample_per_part, + const float trans_std) { + // NOT_IMPLEMENTED; + return; + } +} // namespace mshadow + +namespace mxnet { +namespace op { + + template<> + Operator *CreateOp(DeformablePSROIPoolingParam param, int dtype) { + Operator* op = NULL; + MSHADOW_REAL_TYPE_SWITCH(dtype, DType, { + op = new DeformablePSROIPoolingOp(param); + }); + return op; + } + + Operator *DeformablePSROIPoolingProp::CreateOperatorEx( + Context ctx, std::vector *in_shape, + std::vector *in_type) const { + std::vector out_shape, aux_shape; + std::vector out_type, aux_type; + CHECK(InferType(in_type, &out_type, &aux_type)); + CHECK(InferShape(in_shape, &out_shape, &aux_shape)); + DO_BIND_DISPATCH(CreateOp, param_, in_type->at(0)); + } + + DMLC_REGISTER_PARAMETER(DeformablePSROIPoolingParam); + + MXNET_REGISTER_OP_PROPERTY(_contrib_DeformablePSROIPooling, DeformablePSROIPoolingProp) + .describe("Performs deformable position-sensitive region-of-interest pooling on inputs." + "The DeformablePSROIPooling operation is described in https://arxiv.org/abs/1703.06211 ." + "batch_size will change to the number of region bounding boxes after DeformablePSROIPooling") + .add_argument("data", "Symbol", "Input data to the pooling operator, a 4D Feature maps") + .add_argument("rois", "Symbol", "Bounding box coordinates, a 2D array of " + "[[batch_index, x1, y1, x2, y2]]. (x1, y1) and (x2, y2) are top left and down right corners " + "of designated region of interest. batch_index indicates the index of corresponding image " + "in the input data") + .add_argument("trans", "Symbol", "transition parameter") + .add_arguments(DeformablePSROIPoolingParam::__FIELDS__()); +} // namespace op +} // namespace mxnet diff --git a/src/operator/contrib/deformable_psroi_pooling.cu b/src/operator/contrib/deformable_psroi_pooling.cu new file mode 100644 index 000000000000..71bbd4cd7f2a --- /dev/null +++ b/src/operator/contrib/deformable_psroi_pooling.cu @@ -0,0 +1,434 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +/*! + * Copyright (c) 2017 Microsoft + * Licensed under The Apache-2.0 License [see LICENSE for details] + * \file deformable_psroi_pooling.cu + * \brief + * \author Yi Li, Guodong Zhang, Jifeng Dai +*/ +#include "./deformable_psroi_pooling-inl.h" +#include +#include +#include +#include +#include "../../common/cuda_utils.h" +#include "../mxnet_op.h" + +#define DeformablePSROIPOOLING_CUDA_CHECK(condition) \ + /* Code block avoids redefinition of cudaError_t error */ \ + do { \ + cudaError_t error = condition; \ + CHECK_EQ(error, cudaSuccess) << " " << cudaGetErrorString(error); \ + } while (0) +#define CUDA_KERNEL_LOOP(i, n) \ +for (int i = blockIdx.x * blockDim.x + threadIdx.x; \ + i < (n); \ + i += blockDim.x * gridDim.x) + +namespace mshadow { +namespace cuda { + template + __device__ DType bilinear_interp( + const DType* data, + const DType x, + const DType y, + const int width, + const int height) { + int x1 = floor(x); + int x2 = ceil(x); + int y1 = floor(y); + int y2 = ceil(y); + DType dist_x = static_cast(x - x1); + DType dist_y = static_cast(y - y1); + DType value11 = data[y1*width + x1]; + DType value12 = data[y2*width + x1]; + DType value21 = data[y1*width + x2]; + DType value22 = data[y2*width + x2]; + DType value = (1 - dist_x)*(1 - dist_y)*value11 + (1 - dist_x)*dist_y*value12 + + dist_x*(1 - dist_y)*value21 + dist_x*dist_y*value22; + return value; + } + + template + __global__ void DeformablePSROIPoolForwardKernel( + const int count, + const DType* bottom_data, + const DType spatial_scale, + const int channels, + const int height, const int width, + const int pooled_height, const int pooled_width, + const DType* bottom_rois, const DType* bottom_trans, + const bool no_trans, + const DType trans_std, + const int sample_per_part, + const int output_dim, + const int group_size, + const int part_size, + const int num_classes, + const int channels_each_class, + DType* top_data, + DType* top_count) { + CUDA_KERNEL_LOOP(index, count) { + // The output is in order (n, ctop, ph, pw) + int pw = index % pooled_width; + int ph = (index / pooled_width) % pooled_height; + int ctop = (index / pooled_width / pooled_height) % output_dim; + int n = index / pooled_width / pooled_height / output_dim; + + // [start, end) interval for spatial sampling + const DType* offset_bottom_rois = bottom_rois + n * 5; + int roi_batch_ind = offset_bottom_rois[0]; + DType roi_start_w = static_cast(round(offset_bottom_rois[1])) * spatial_scale - 0.5; + DType roi_start_h = static_cast(round(offset_bottom_rois[2])) * spatial_scale - 0.5; + DType roi_end_w = static_cast(round(offset_bottom_rois[3]) + 1.) * spatial_scale - 0.5; + DType roi_end_h = static_cast(round(offset_bottom_rois[4]) + 1.) * spatial_scale - 0.5; + + // Force too small ROIs to be 1x1 + DType roi_width = max(roi_end_w - roi_start_w, 0.1); // avoid 0 + DType roi_height = max(roi_end_h - roi_start_h, 0.1); + + // Compute w and h at bottom + DType bin_size_h = roi_height / static_cast(pooled_height); + DType bin_size_w = roi_width / static_cast(pooled_width); + + DType sub_bin_size_h = bin_size_h / static_cast(sample_per_part); + DType sub_bin_size_w = bin_size_w / static_cast(sample_per_part); + + int part_h = floor(static_cast(ph) / pooled_height*part_size); + int part_w = floor(static_cast(pw) / pooled_width*part_size); + int class_id = ctop / channels_each_class; + DType trans_x = no_trans ? static_cast(0) : + bottom_trans[(((n * num_classes + class_id) * 2) + * part_size + part_h) + * part_size + part_w] * trans_std; + DType trans_y = no_trans ? static_cast(0) : + bottom_trans[(((n * num_classes + class_id) * 2 + 1) + * part_size + part_h) + * part_size + part_w] * trans_std; + + DType wstart = static_cast(pw)* bin_size_w + + roi_start_w; + wstart += trans_x * roi_width; + DType hstart = static_cast(ph) * bin_size_h + + roi_start_h; + hstart += trans_y * roi_height; + + DType sum = 0; + int count = 0; + int gw = floor(static_cast(pw) * group_size / pooled_width); + int gh = floor(static_cast(ph)* group_size / pooled_height); + gw = min(max(gw, 0), group_size - 1); + gh = min(max(gh, 0), group_size - 1); + + const DType* offset_bottom_data = bottom_data + (roi_batch_ind * channels) * height * width; + for (int ih = 0; ih < sample_per_part; ih++) { + for (int iw = 0; iw < sample_per_part; iw++) { + DType w = wstart + iw*sub_bin_size_w; + DType h = hstart + ih*sub_bin_size_h; + // bilinear interpolation + if (w<-0.5 || w>width - 0.5 || h<-0.5 || h>height - 0.5) { + continue; + } + w = min(max(w, 0.), width - 1.); + h = min(max(h, 0.), height - 1.); + int c = (ctop*group_size + gh)*group_size + gw; + DType val = bilinear_interp(offset_bottom_data + c*height*width, w, h, width, height); + sum += val; + count++; + } + } + top_data[index] = count == 0 ? static_cast(0) : sum / count; + top_count[index] = count; + } + } + + template + inline void DeformablePSROIPoolForward(const Tensor &out, + const Tensor &data, + const Tensor &bbox, + const Tensor &trans, + const Tensor &top_count, + const bool no_trans, + const float spatial_scale, + const int output_dim, + const int group_size, + const int pooled_size, + const int part_size, + const int sample_per_part, + const float trans_std) { + // LOG(INFO) << "DeformablePSROIPoolForward"; + const DType *bottom_data = data.dptr_; + const DType *bottom_rois = bbox.dptr_; + const DType *bottom_trans = no_trans ? NULL : trans.dptr_; + DType *top_data = out.dptr_; + DType *top_count_data = top_count.dptr_; + const int count = out.shape_.Size(); + const int channels = data.size(1); + const int height = data.size(2); + const int width = data.size(3); + const int pooled_height = pooled_size; + const int pooled_width = pooled_size; + const int num_classes = no_trans ? 1 : trans.size(1) / 2; + const int channels_each_class = no_trans ? output_dim : output_dim / num_classes; + + cudaStream_t stream = Stream::GetStream(out.stream_); + DeformablePSROIPoolForwardKernel << > >( + count, bottom_data, spatial_scale, channels, height, width, pooled_height, pooled_width, + bottom_rois, bottom_trans, no_trans, trans_std, sample_per_part, output_dim, + group_size, part_size, num_classes, channels_each_class, top_data, top_count_data); + DeformablePSROIPOOLING_CUDA_CHECK(cudaPeekAtLastError()); + } + + + template + __global__ void DeformablePSROIPoolBackwardAccKernel( + const int count, + const DType* top_diff, + const DType* top_count, + const int num_rois, + const DType spatial_scale, + const int channels, + const int height, const int width, + const int pooled_height, const int pooled_width, + const int output_dim, + DType* bottom_data_diff, DType* bottom_trans_diff, + const DType* bottom_data, + const DType* bottom_rois, + const DType* bottom_trans, + const bool no_trans, + const DType trans_std, + const int sample_per_part, + const int group_size, + const int part_size, + const int num_classes, + const int channels_each_class) { + CUDA_KERNEL_LOOP(index, count) { + // The output is in order (n, ctop, ph, pw) + int pw = index % pooled_width; + int ph = (index / pooled_width) % pooled_height; + int ctop = (index / pooled_width / pooled_height) % output_dim; + int n = index / pooled_width / pooled_height / output_dim; + + // [start, end) interval for spatial sampling + const DType* offset_bottom_rois = bottom_rois + n * 5; + int roi_batch_ind = offset_bottom_rois[0]; + DType roi_start_w = static_cast(round(offset_bottom_rois[1])) * spatial_scale - 0.5; + DType roi_start_h = static_cast(round(offset_bottom_rois[2])) * spatial_scale - 0.5; + DType roi_end_w = static_cast(round(offset_bottom_rois[3]) + 1.) * spatial_scale - 0.5; + DType roi_end_h = static_cast(round(offset_bottom_rois[4]) + 1.) * spatial_scale - 0.5; + + // Force too small ROIs to be 1x1 + DType roi_width = max(roi_end_w - roi_start_w, 0.1); // avoid 0 + DType roi_height = max(roi_end_h - roi_start_h, 0.1); + + // Compute w and h at bottom + DType bin_size_h = roi_height / static_cast(pooled_height); + DType bin_size_w = roi_width / static_cast(pooled_width); + + DType sub_bin_size_h = bin_size_h / static_cast(sample_per_part); + DType sub_bin_size_w = bin_size_w / static_cast(sample_per_part); + + int part_h = floor(static_cast(ph) / pooled_height*part_size); + int part_w = floor(static_cast(pw) / pooled_width*part_size); + int class_id = ctop / channels_each_class; + DType trans_x = no_trans ? static_cast(0) : + bottom_trans[(((n * num_classes + class_id) * 2) + * part_size + part_h) + * part_size + part_w] * trans_std; + DType trans_y = no_trans ? static_cast(0) : + bottom_trans[(((n * num_classes + class_id) * 2 + 1) + * part_size + part_h) + * part_size + part_w] * trans_std; + + DType wstart = static_cast(pw)* bin_size_w + + roi_start_w; + wstart += trans_x * roi_width; + DType hstart = static_cast(ph) * bin_size_h + + roi_start_h; + hstart += trans_y * roi_height; + + if (top_count[index] <= 0) { + continue; + } + DType diff_val = top_diff[index] / top_count[index]; + const DType* offset_bottom_data = bottom_data + roi_batch_ind * channels * height * width; + DType* offset_bottom_data_diff = bottom_data_diff + roi_batch_ind * channels * height * width; + int gw = floor(static_cast(pw)* group_size / pooled_width); + int gh = floor(static_cast(ph)* group_size / pooled_height); + gw = min(max(gw, 0), group_size - 1); + gh = min(max(gh, 0), group_size - 1); + + for (int ih = 0; ih < sample_per_part; ih++) { + for (int iw = 0; iw < sample_per_part; iw++) { + DType w = wstart + iw*sub_bin_size_w; + DType h = hstart + ih*sub_bin_size_h; + // bilinear interpolation + if (w<-0.5 || w>width - 0.5 || h<-0.5 || h>height - 0.5) { + continue; + } + w = min(max(w, 0.), width - 1.); + h = min(max(h, 0.), height - 1.); + int c = (ctop*group_size + gh)*group_size + gw; + // backward on feature + int x0 = floor(w); + int x1 = ceil(w); + int y0 = floor(h); + int y1 = ceil(h); + DType dist_x = w - x0, dist_y = h - y0; + DType q00 = (1 - dist_x)*(1 - dist_y); + DType q01 = (1 - dist_x)*dist_y; + DType q10 = dist_x*(1 - dist_y); + DType q11 = dist_x*dist_y; + int bottom_index_base = c * height *width; + atomicAdd(offset_bottom_data_diff + bottom_index_base + y0*width + x0, q00*diff_val); + atomicAdd(offset_bottom_data_diff + bottom_index_base + y1*width + x0, q01*diff_val); + atomicAdd(offset_bottom_data_diff + bottom_index_base + y0*width + x1, q10*diff_val); + atomicAdd(offset_bottom_data_diff + bottom_index_base + y1*width + x1, q11*diff_val); + + if (no_trans) { + continue; + } + DType U00 = offset_bottom_data[bottom_index_base + y0*width + x0]; + DType U01 = offset_bottom_data[bottom_index_base + y1*width + x0]; + DType U10 = offset_bottom_data[bottom_index_base + y0*width + x1]; + DType U11 = offset_bottom_data[bottom_index_base + y1*width + x1]; + DType diff_x = (U11*dist_y + U10*(1 - dist_y) - U01*dist_y - U00*(1 - dist_y)) + *trans_std*diff_val; + diff_x *= roi_width; + DType diff_y = (U11*dist_x + U01*(1 - dist_x) - U10*dist_x - U00*(1 - dist_x)) + *trans_std*diff_val; + diff_y *= roi_height; + + atomicAdd(bottom_trans_diff + (((n * num_classes + class_id) * 2) + * part_size + part_h) + * part_size + part_w, diff_x); + atomicAdd(bottom_trans_diff + (((n * num_classes + class_id) * 2 + 1) + * part_size + part_h) + * part_size + part_w, diff_y); + } + } + } + } + + + template + inline void DeformablePSROIPoolBackwardAcc(const Tensor &in_grad, + const Tensor &trans_grad, + const Tensor &out_grad, + const Tensor &data, + const Tensor &bbox, + const Tensor &trans, + const Tensor &top_count, + const bool no_trans, + const float spatial_scale, + const int output_dim, + const int group_size, + const int pooled_size, + const int part_size, + const int sample_per_part, + const float trans_std) { + // LOG(INFO) << "DeformablePSROIPoolBackward"; + const DType *top_diff = out_grad.dptr_; + const DType *bottom_data = data.dptr_; + const DType *bottom_rois = bbox.dptr_; + const DType *bottom_trans = no_trans ? NULL : trans.dptr_; + DType *bottom_data_diff = in_grad.dptr_; + DType *bottom_trans_diff = no_trans ? NULL : trans_grad.dptr_; + const DType *top_count_data = top_count.dptr_; + const int count = out_grad.shape_.Size(); + const int num_rois = bbox.size(0); + const int channels = in_grad.size(1); + const int height = in_grad.size(2); + const int width = in_grad.size(3); + const int pooled_height = pooled_size; + const int pooled_width = pooled_size; + const int num_classes = no_trans ? 1 : trans_grad.size(1) / 2; + const int channels_each_class = no_trans ? output_dim : output_dim / num_classes; + + cudaStream_t stream = Stream::GetStream(in_grad.stream_); + DeformablePSROIPoolBackwardAccKernel << > >( + count, top_diff, top_count_data, num_rois, spatial_scale, channels, height, width, + pooled_height, pooled_width, output_dim, bottom_data_diff, bottom_trans_diff, + bottom_data, bottom_rois, bottom_trans, no_trans, trans_std, sample_per_part, + group_size, part_size, num_classes, channels_each_class); + DeformablePSROIPOOLING_CUDA_CHECK(cudaPeekAtLastError()); + } + +} // namespace cuda + + template + inline void DeformablePSROIPoolForward(const Tensor &out, + const Tensor &data, + const Tensor &bbox, + const Tensor &trans, + const Tensor &top_count, + const bool no_trans, + const float spatial_scale, + const int output_dim, + const int group_size, + const int pooled_size, + const int part_size, + const int sample_per_part, + const float trans_std) { + cuda::DeformablePSROIPoolForward(out, data, bbox, trans, top_count, no_trans, spatial_scale, + output_dim, group_size, pooled_size, part_size, sample_per_part, trans_std); + } + + template + inline void DeformablePSROIPoolBackwardAcc(const Tensor &in_grad, + const Tensor &trans_grad, + const Tensor &out_grad, + const Tensor &data, + const Tensor &bbox, + const Tensor &trans, + const Tensor &top_count, + const bool no_trans, + const float spatial_scale, + const int output_dim, + const int group_size, + const int pooled_size, + const int part_size, + const int sample_per_part, + const float trans_std) { + cuda::DeformablePSROIPoolBackwardAcc(in_grad, trans_grad, out_grad, data, bbox, trans, + top_count, no_trans, spatial_scale, output_dim, group_size, pooled_size, part_size, + sample_per_part, trans_std); + } + +} // namespace mshadow + + +namespace mxnet { +namespace op { + + template<> + Operator* CreateOp(DeformablePSROIPoolingParam param, int dtype) { + Operator* op = NULL; + MSHADOW_REAL_TYPE_SWITCH(dtype, DType, { + op = new DeformablePSROIPoolingOp(param); + }); + return op; + } + +} // namespace op +} // namespace mxnet diff --git a/src/operator/contrib/dequantize-inl.h b/src/operator/contrib/dequantize-inl.h index ecd0cb42aafc..61940c016b15 100644 --- a/src/operator/contrib/dequantize-inl.h +++ b/src/operator/contrib/dequantize-inl.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2017 by Contributors * \file dequantize-inl.h * \brief Implementation of dequantize operation */ diff --git a/src/operator/contrib/dequantize.cc b/src/operator/contrib/dequantize.cc index 46e36fa3c891..422a9557dc1d 100644 --- a/src/operator/contrib/dequantize.cc +++ b/src/operator/contrib/dequantize.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2017 by Contributors * \file dequantize.cc * \brief */ diff --git a/src/operator/contrib/dequantize.cu b/src/operator/contrib/dequantize.cu index be09b797b1cd..7081c27c975b 100644 --- a/src/operator/contrib/dequantize.cu +++ b/src/operator/contrib/dequantize.cu @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2017 by Contributors * \file dequantize.cu * \brief */ diff --git a/src/operator/contrib/fft-inl.h b/src/operator/contrib/fft-inl.h index 5996322c0cec..5092f586fdf7 100644 --- a/src/operator/contrib/fft-inl.h +++ b/src/operator/contrib/fft-inl.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file fft-inl.h * \brief * \author Chen Zhu diff --git a/src/operator/contrib/fft.cc b/src/operator/contrib/fft.cc index e2094b3bf9a8..11f8425e07b1 100644 --- a/src/operator/contrib/fft.cc +++ b/src/operator/contrib/fft.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file fft-inl.h * \brief * \author Chen Zhu diff --git a/src/operator/contrib/fft.cu b/src/operator/contrib/fft.cu index 5dbd00c779fd..3017ce76756b 100644 --- a/src/operator/contrib/fft.cu +++ b/src/operator/contrib/fft.cu @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2017 by Contributors * \file fft-inl.h * \brief * \author Chen Zhu diff --git a/src/operator/contrib/ifft-inl.h b/src/operator/contrib/ifft-inl.h index 98b601eeae0c..abd5bb22a389 100644 --- a/src/operator/contrib/ifft-inl.h +++ b/src/operator/contrib/ifft-inl.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file Ifft-inl.h * \brief * \author Chen Zhu diff --git a/src/operator/contrib/ifft.cc b/src/operator/contrib/ifft.cc index b2afd46cb46d..0ea3a7ec112f 100644 --- a/src/operator/contrib/ifft.cc +++ b/src/operator/contrib/ifft.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file Ifft-inl.h * \brief * \author Chen Zhu diff --git a/src/operator/contrib/ifft.cu b/src/operator/contrib/ifft.cu index 93ec1e636a3b..79795d8561bf 100644 --- a/src/operator/contrib/ifft.cu +++ b/src/operator/contrib/ifft.cu @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file Ifft-inl.h * \brief * \author Chen Zhu diff --git a/src/operator/contrib/krprod.h b/src/operator/contrib/krprod.h new file mode 100644 index 000000000000..a54ece79e9d7 --- /dev/null +++ b/src/operator/contrib/krprod.h @@ -0,0 +1,252 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +/*! + * \file krprod.h + * \brief Core function for Khatri-Rao product + * \author Jencir Lee + */ +#ifndef MXNET_OPERATOR_CONTRIB_KRPROD_H_ +#define MXNET_OPERATOR_CONTRIB_KRPROD_H_ +#include +#include "mshadow/tensor.h" +#include "../c_lapack_api.h" + +namespace mxnet { +namespace op { + +using namespace mshadow; +using namespace mshadow::expr; + +/*! + * \brief Computes row-wise Kronecker product + * + * Given input matrices, this function computes the Kronecker product + * row-wise. E.g. if the input matrices are of shape (3, 2), (3, 4), + * (3, 5), the result matrix will be of shape (3, 2 * 4 * 5), which is + * (3, 40). + * + * \param out result matrix + * \param ts_arr vector of input matrices + */ +template +inline void row_wise_kronecker + (Tensor out, + const std::vector > &ts_arr) { + CHECK_GE(ts_arr.size(), 1) << "The input matrices must be non-empty."; + + // Check all input and output matrices have the same number of rows + // and the output matrix has the right number of columns + int nrows = static_cast(out.size(0)); + int ncols = 1; + for (auto & ts : ts_arr) { + CHECK_EQ(nrows, static_cast(ts.size(0))) + << "All input and output matrices must have the same number of rows."; + ncols *= ts.size(1); + } + CHECK_EQ(ncols, static_cast(out.size(1))); + + // Create an intermediate space of the same shape as out + // + // Suppose storage stores the result at step i-1, we'd + // compute and store the result into out for step i; + // we then proceed to compute and store the result in storage + // for step i+1 and so on and so forth, by alternating using + // storage and out to store the given variable and the result variable + Tensor storage(out.shape_); + AllocSpace(&storage); + + // Pointers to the given variable and result variable + // We exchange what given and result point to at every step + Tensor *given = &storage, + *result = &out, *tmp; + + // Compute each intermediate row-wise Kronecker product + storage = 1; + ncols = 1; + for (auto & ts : ts_arr) { + expr::BLASEngine::SetStream + (result->stream_); + + // Compute the current row-wise Kronecker product + *result = 0; + for (int i = 0; i < nrows; ++i) { + // BLAS signature + // + // dger( + // m : ts.size(1), length of each row of current matrix + // n : ncols, length of each row of previous result + // alpha : 1, scaling to the outer product of x and y + // x : ts[i].dptr_, current row of current matrix + // incx : 1, as each element in the row is contiguous + // y : (*given)[i].dptr_, current row of the given variable + // incy : 1, as each element in the row is contiguous + // a : (*result)[i].dptr_, current row of the result variable + // lda : ts.size(1), as the outer product is stored as one row + // which occupies contiguous memory, and as BLASEngine::ger() + // assumes column-major matrix, lda has to be precisely + // the length of x, i.e. ts[i].size(1) + // ) + expr::BLASEngine::ger + (result->stream_, + ts.size(1), ncols, 1, + ts[i].dptr_, 1, + (*given)[i].dptr_, 1, + (*result)[i].dptr_, ts.size(1)); + } + ncols *= ts.size(1); + + tmp = given; + given = result; + result = tmp; + } + + // If the final result is stored in storage, + // copy its value to out + if (given != &out) + Copy(out, storage); + + FreeSpace(&storage); +} + +/*! + * \brief Khatri-Rao product + * + * \param out result matrix + * \param ts_arr vector of input matrices + */ +template +inline void khatri_rao + (Tensor out, + const std::vector > &ts_arr) { + CHECK_GE(ts_arr.size(), 1) << "The input matrices must be non-empty."; + + // Check all input and output matrices have the same number + // of columns and the output matrix has the right number of rows + int ncols = static_cast(out.size(1)); + int nrows = 1; + for (auto & ts : ts_arr) { + CHECK_EQ(ncols, static_cast(ts.size(1))) + << "All input and output matrices must have the same number of columns."; + nrows *= ts.size(0); + } + CHECK_EQ(nrows, static_cast(out.size(0))); + + // Change the layout of matrices to column-major + Tensor out_t(Shape2(out.size(1), out.size(0))); + AllocSpace(&out_t); + flip(out.size(0), out.size(1), out_t.dptr_, out_t.stride_, + out.dptr_, out.stride_); + + std::vector > ts_t_arr; + for (int i = 0; i < static_cast(ts_arr.size()); ++i) { + ts_t_arr.emplace_back(Shape2(ts_arr[i].size(1), ts_arr[i].size(0))); + AllocSpace(&ts_t_arr[i]); + flip(ts_arr[i].size(0), ts_arr[i].size(1), ts_t_arr[i].dptr_, + ts_t_arr[i].stride_, ts_arr[i].dptr_, ts_arr[i].stride_); + } + + // Perform row-wise Kronecker product + row_wise_kronecker(out_t, ts_t_arr); + + // Change the layout of result matrix back to row-major + flip(out.size(1), out.size(0), out.dptr_, out.stride_, + out_t.dptr_, out_t.stride_); + + FreeSpace(&out_t); + for (auto &ts_t : ts_t_arr) + FreeSpace(&ts_t); +} + +/*! + * \brief Moore-Penrose pseudoinverse of the Khatri-Rao product + * + * Given input matrices A_1, ..., A_n, of shape (l_1, k), ..., (l_n, k) respectively, the pseudoinverse of the Khatri-Rao product is + * + * pinv(A_1 khatri-rao A_2 khatri-rao ... khatri-rao A_n) = + * ((A_1^T A_1) hadamard-dot ... hadamard-dot (A_n^T A_n)) + * (A_1 khatri-rao ... khatri-rao A_n)^T + * + * As the first term of the r.h.s is a square matrix, the result is always of the same shape as the transpose of the Khatri-Rao product of the input matrices. The input argument ts_arr could contain the original input matrices, or transposed ones. + * + * \param out result matrix + * \param ts_arr vector of input matrices + * \param input_transposed if every input matrices is transposed + */ +template +inline void inv_khatri_rao + (Tensor out, + const std::vector > &ts_arr, + bool input_transposed = false) { + CHECK_GE(ts_arr.size(), 1) << "Input tensor array must be non-empty"; + + // Initialise the Hadamard product to eye(k) + // where k is the number of "factors" + int k = out.size(0); + Tensor hadamard_prod(Shape2(k, k)); + AllocSpace(&hadamard_prod); + hadamard_prod = 1; + + // Note that out is of the same shape as the transpose of + // the Khatri-Rao product + // + // When input is transposed, we could first put the transpose of + // the Khatri-Rao product in out, then call the linear solver, which + // will update the out's content to the final result; + // + // If the input is not transposed, we need to create an intermediate + // tensor to store the Khatri-Rao product, call the linear solver with + // MXNET_LAPACK_COL_MAJOR as the matrix layout, and transpose + // the final result into out + + int info; + if (input_transposed) { + row_wise_kronecker(out, ts_arr); + for (auto &ts : ts_arr) + hadamard_prod *= implicit_dot(ts, ts.T()); + + info = MXNET_LAPACK_posv(MXNET_LAPACK_ROW_MAJOR, 'U', + k, out.size(1), hadamard_prod.dptr_, hadamard_prod.stride_, + out.dptr_, out.stride_); + } else { + Tensor kr(Shape2(out.size(1), out.size(0))); + AllocSpace(&kr); + khatri_rao(kr, ts_arr); + + for (auto &ts : ts_arr) + hadamard_prod *= implicit_dot(ts.T(), ts); + + info = MXNET_LAPACK_posv(MXNET_LAPACK_COL_MAJOR, 'U', + k, out.size(1), hadamard_prod.dptr_, hadamard_prod.stride_, + kr.dptr_, kr.stride_); + + flip(out.size(1), out.size(0), out.dptr_, out.stride_, + kr.dptr_, kr.stride_); + FreeSpace(&kr); + } + + FreeSpace(&hadamard_prod); + if (info != 0) + LOG(FATAL) << "The linear solver in inv_prod() returns " << info; +} + +} // namespace op +} // namespace mxnet + +#endif // MXNET_OPERATOR_CONTRIB_KRPROD_H_ diff --git a/src/operator/contrib/multi_proposal-inl.h b/src/operator/contrib/multi_proposal-inl.h new file mode 100644 index 000000000000..7cd465e0b09e --- /dev/null +++ b/src/operator/contrib/multi_proposal-inl.h @@ -0,0 +1,319 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +/*! + * Copyright (c) 2017 Microsoft + * Licensed under The Apache-2.0 License [see LICENSE for details] + * \file multi_proposal-inl.h + * \brief MultiProposal Operator + * \author Piotr Teterwak, Bing Xu, Jian Guo, Xizhou Zhu +*/ +#ifndef MXNET_OPERATOR_CONTRIB_MULTI_PROPOSAL_INL_H_ +#define MXNET_OPERATOR_CONTRIB_MULTI_PROPOSAL_INL_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../operator_common.h" +#include "../mshadow_op.h" + +// extend NumericalParam +namespace mxnet { +namespace op { + +/*! +* \brief structure for numerical tuple input +* \tparam VType data type of param +*/ +template +struct NumericalParam { + NumericalParam() {} + explicit NumericalParam(VType *begin, VType *end) { + int32_t size = static_cast(end - begin); + info.resize(size); + for (int i = 0; i < size; ++i) { + info[i] = *(begin + i); + } + } + inline size_t ndim() const { + return info.size(); + } + std::vector info; +}; + +template +inline std::istream &operator>>(std::istream &is, NumericalParam ¶m) { + while (true) { + char ch = is.get(); + if (ch == '(') break; + if (!isspace(ch)) { + is.setstate(std::ios::failbit); + return is; + } + } + VType idx; + std::vector tmp; + // deal with empty case + size_t pos = is.tellg(); + char ch = is.get(); + if (ch == ')') { + param.info = tmp; + return is; + } + is.seekg(pos); + // finish deal + while (is >> idx) { + tmp.push_back(idx); + char ch; + do { + ch = is.get(); + } while (isspace(ch)); + if (ch == ',') { + while (true) { + ch = is.peek(); + if (isspace(ch)) { + is.get(); continue; + } + if (ch == ')') { + is.get(); break; + } + break; + } + if (ch == ')') break; + } else if (ch == ')') { + break; + } else { + is.setstate(std::ios::failbit); + return is; + } + } + param.info = tmp; + return is; +} + +template +inline std::ostream &operator<<(std::ostream &os, const NumericalParam ¶m) { + os << '('; + for (index_t i = 0; i < param.info.size(); ++i) { + if (i != 0) os << ','; + os << param.info[i]; + } + // python style tuple + if (param.info.size() == 1) os << ','; + os << ')'; + return os; +} + +} // namespace op +} // namespace mxnet + +namespace mxnet { +namespace op { + +namespace proposal { +enum MultiProposalOpInputs {kClsProb, kBBoxPred, kImInfo}; +enum MultiProposalOpOutputs {kOut, kScore}; +enum MultiProposalForwardResource {kTempResource}; +} // proposal + +struct MultiProposalParam : public dmlc::Parameter { + int rpn_pre_nms_top_n; + int rpn_post_nms_top_n; + float threshold; + int rpn_min_size; + NumericalParam scales; + NumericalParam ratios; + int feature_stride; + bool output_score; + bool iou_loss; + DMLC_DECLARE_PARAMETER(MultiProposalParam) { + float tmp[] = {0, 0, 0, 0}; + DMLC_DECLARE_FIELD(rpn_pre_nms_top_n).set_default(6000) + .describe("Number of top scoring boxes to keep after applying NMS to RPN proposals"); + DMLC_DECLARE_FIELD(rpn_post_nms_top_n).set_default(300) + .describe("Overlap threshold used for non-maximum" + "suppresion(suppress boxes with IoU >= this threshold"); + DMLC_DECLARE_FIELD(threshold).set_default(0.7) + .describe("NMS value, below which to suppress."); + DMLC_DECLARE_FIELD(rpn_min_size).set_default(16) + .describe("Minimum height or width in proposal"); + tmp[0] = 4.0f; tmp[1] = 8.0f; tmp[2] = 16.0f; tmp[3] = 32.0f; + DMLC_DECLARE_FIELD(scales).set_default(NumericalParam(tmp, tmp + 4)) + .describe("Used to generate anchor windows by enumerating scales"); + tmp[0] = 0.5f; tmp[1] = 1.0f; tmp[2] = 2.0f; + DMLC_DECLARE_FIELD(ratios).set_default(NumericalParam(tmp, tmp + 3)) + .describe("Used to generate anchor windows by enumerating ratios"); + DMLC_DECLARE_FIELD(feature_stride).set_default(16) + .describe("The size of the receptive field each unit in the convolution layer of the rpn," + "for example the product of all stride's prior to this layer."); + DMLC_DECLARE_FIELD(output_score).set_default(false) + .describe("Add score to outputs"); + DMLC_DECLARE_FIELD(iou_loss).set_default(false) + .describe("Usage of IoU Loss"); + } +}; + +template +Operator *CreateOp(MultiProposalParam param); + +#if DMLC_USE_CXX11 +class MultiProposalProp : public OperatorProperty { + public: + void Init(const std::vector >& kwargs) override { + param_.Init(kwargs); + } + + std::map GetParams() const override { + return param_.__DICT__(); + } + + bool InferShape(std::vector *in_shape, + std::vector *out_shape, + std::vector *aux_shape) const override { + using namespace mshadow; + CHECK_EQ(in_shape->size(), 3) << "Input:[cls_prob, bbox_pred, im_info]"; + const TShape &dshape = in_shape->at(proposal::kClsProb); + if (dshape.ndim() == 0) return false; + Shape<4> bbox_pred_shape; + bbox_pred_shape = Shape4(dshape[0], dshape[1] * 2, dshape[2], dshape[3]); + SHAPE_ASSIGN_CHECK(*in_shape, proposal::kBBoxPred, + bbox_pred_shape); + Shape<2> im_info_shape; + im_info_shape = Shape2(dshape[0], 3); + SHAPE_ASSIGN_CHECK(*in_shape, proposal::kImInfo, im_info_shape); + out_shape->clear(); + // output + out_shape->push_back(Shape2(dshape[0] * param_.rpn_post_nms_top_n, 5)); + // score + out_shape->push_back(Shape2(dshape[0] * param_.rpn_post_nms_top_n, 1)); + return true; + } + + OperatorProperty* Copy() const override { + auto ptr = new MultiProposalProp(); + ptr->param_ = param_; + return ptr; + } + + std::string TypeString() const override { + return "_contrib_MultiProposal"; + } + + std::vector ForwardResource( + const std::vector &in_shape) const override { + return {ResourceRequest::kTempSpace}; + } + + std::vector DeclareBackwardDependency( + const std::vector &out_grad, + const std::vector &in_data, + const std::vector &out_data) const override { + return {}; + } + + int NumVisibleOutputs() const override { + if (param_.output_score) { + return 2; + } else { + return 1; + } + } + + int NumOutputs() const override { + return 2; + } + + std::vector ListArguments() const override { + return {"cls_prob", "bbox_pred", "im_info"}; + } + + std::vector ListOutputs() const override { + return {"output", "score"}; + } + + Operator* CreateOperator(Context ctx) const override; + + private: + MultiProposalParam param_; +}; // class MultiProposalProp + +#endif // DMLC_USE_CXX11 +} // namespace op +} // namespace mxnet + +//======================== +// Anchor Generation Utils +//======================== +namespace mxnet { +namespace op { +namespace utils { + +inline void _MakeAnchor(float w, + float h, + float x_ctr, + float y_ctr, + std::vector *out_anchors) { + out_anchors->push_back(x_ctr - 0.5f * (w - 1.0f)); + out_anchors->push_back(y_ctr - 0.5f * (h - 1.0f)); + out_anchors->push_back(x_ctr + 0.5f * (w - 1.0f)); + out_anchors->push_back(y_ctr + 0.5f * (h - 1.0f)); + out_anchors->push_back(0.0f); +} + +inline void _Transform(float scale, + float ratio, + const std::vector& base_anchor, + std::vector *out_anchors) { + float w = base_anchor[2] - base_anchor[1] + 1.0f; + float h = base_anchor[3] - base_anchor[1] + 1.0f; + float x_ctr = base_anchor[0] + 0.5 * (w - 1.0f); + float y_ctr = base_anchor[1] + 0.5 * (h - 1.0f); + float size = w * h; + float size_ratios = std::floor(size / ratio); + float new_w = std::floor(std::sqrt(size_ratios) + 0.5f) * scale; + float new_h = std::floor((new_w / scale * ratio) + 0.5f) * scale; + + _MakeAnchor(new_w, new_h, x_ctr, + y_ctr, out_anchors); +} + +// out_anchors must have shape (n, 5), where n is ratios.size() * scales.size() +inline void GenerateAnchors(const std::vector& base_anchor, + const std::vector& ratios, + const std::vector& scales, + std::vector *out_anchors) { + for (size_t j = 0; j < ratios.size(); ++j) { + for (size_t k = 0; k < scales.size(); ++k) { + _Transform(scales[k], ratios[j], base_anchor, out_anchors); + } + } +} + +} // namespace utils +} // namespace op +} // namespace mxnet + +#endif // MXNET_OPERATOR_CONTRIB_MULTI_PROPOSAL_INL_H_ diff --git a/src/operator/contrib/multi_proposal.cc b/src/operator/contrib/multi_proposal.cc new file mode 100644 index 000000000000..cd00e877a11d --- /dev/null +++ b/src/operator/contrib/multi_proposal.cc @@ -0,0 +1,82 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +/*! + * Copyright (c) 2017 Microsoft + * Licensed under The Apache-2.0 License [see LICENSE for details] + * \file multi_proposal.cc + * \brief + * \author Xizhou Zhu +*/ + +#include "./multi_proposal-inl.h" + + +namespace mxnet { +namespace op { + +template +class MultiProposalOp : public Operator{ + public: + explicit MultiProposalOp(MultiProposalParam param) { + this->param_ = param; + } + + virtual void Forward(const OpContext &ctx, + const std::vector &in_data, + const std::vector &req, + const std::vector &out_data, + const std::vector &aux_states) { + LOG(FATAL) << "not implemented"; + } + + virtual void Backward(const OpContext &ctx, + const std::vector &out_grad, + const std::vector &in_data, + const std::vector &out_data, + const std::vector &req, + const std::vector &in_grad, + const std::vector &aux_states) { + LOG(FATAL) << "not implemented"; + } + + private: + MultiProposalParam param_; +}; // class MultiProposalOp + +template<> +Operator *CreateOp(MultiProposalParam param) { + return new MultiProposalOp(param); +} + +Operator* MultiProposalProp::CreateOperator(Context ctx) const { + DO_BIND_DISPATCH(CreateOp, param_); +} + +DMLC_REGISTER_PARAMETER(MultiProposalParam); + +MXNET_REGISTER_OP_PROPERTY(_contrib_MultiProposal, MultiProposalProp) +.describe("Generate region proposals via RPN") +.add_argument("cls_score", "NDArray-or-Symbol", "Score of how likely proposal is object.") +.add_argument("bbox_pred", "NDArray-or-Symbol", "BBox Predicted deltas from anchors for proposals") +.add_argument("im_info", "NDArray-or-Symbol", "Image size and scale.") +.add_arguments(MultiProposalParam::__FIELDS__()); + +} // namespace op +} // namespace mxnet diff --git a/src/operator/contrib/multi_proposal.cu b/src/operator/contrib/multi_proposal.cu new file mode 100644 index 000000000000..cb9996344e3e --- /dev/null +++ b/src/operator/contrib/multi_proposal.cu @@ -0,0 +1,611 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +/*! + * Copyright (c) 2017 Microsoft + * Licensed under The Apache-2.0 License [see LICENSE for details] + * \file multi_proposal.cu + * \brief MultiProposal Operator + * \author Shaoqing Ren, Xizhou Zhu, Jian Guo +*/ +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "../operator_common.h" +#include "../mshadow_op.h" +#include "./multi_proposal-inl.h" + +#define DIVUP(m, n) ((m) / (n) + ((m) % (n) > 0)) + +#define FRCNN_CUDA_CHECK(condition) \ + /* Code block avoids redefinition of cudaError_t error */ \ + do { \ + cudaError_t error = condition; \ + CHECK_EQ(error, cudaSuccess) << " " << cudaGetErrorString(error); \ +} while (0) + +namespace mshadow { +namespace cuda { +namespace multi_proposal { + +// scores are (b, 2 * anchor, h, w) +// workspace_proposals are (b, h * w * anchor, 5) +// w defines "x" and h defines "y" +// count should be total anchors numbers, h * w * anchors +template +__global__ void ProposalGridKernel(const int count, + const int num_anchors, + const int height, + const int width, + const int feature_stride, + const Dtype* scores, + Dtype* workspace_proposals) { + for (int index = blockIdx.x * blockDim.x + threadIdx.x; + index < count; + index += blockDim.x * gridDim.x) { + int a = index % num_anchors; + int w = (index / num_anchors) % width; + int h = (index / num_anchors / width) % height; + int b = index / num_anchors / width / height; + + workspace_proposals[index * 5 + 0] = workspace_proposals[a * 5 + 0] + w * feature_stride; + workspace_proposals[index * 5 + 1] = workspace_proposals[a * 5 + 1] + h * feature_stride; + workspace_proposals[index * 5 + 2] = workspace_proposals[a * 5 + 2] + w * feature_stride; + workspace_proposals[index * 5 + 3] = workspace_proposals[a * 5 + 3] + h * feature_stride; + workspace_proposals[index * 5 + 4] = + scores[((b * (2 * num_anchors) + a + num_anchors) * height + h) * width + w]; + } +} + +// boxes are (b, h * w * anchor, 5) +// deltas are (b, 4 * anchor, h, w) +// out_pred_boxes are (b, h * w * anchor, 5) +// count should be total anchors numbers, b * h * w * anchors +// in-place write: boxes and out_pred_boxes are the same location +template +__global__ void BBoxPredKernel(const int count, + const int num_anchors, + const int feat_height, + const int feat_width, + const int feature_stride, + const Dtype* im_infos, + const Dtype* boxes, + const Dtype* deltas, + Dtype* out_pred_boxes) { + for (int index = blockIdx.x * blockDim.x + threadIdx.x; + index < count; + index += blockDim.x * gridDim.x) { + int a = index % num_anchors; + int w = (index / num_anchors) % feat_width; + int h = (index / num_anchors / feat_width) % feat_height; + int b = index / num_anchors / feat_width / feat_height; + + float im_height = im_infos[b * 3]; + float im_width = im_infos[b * 3 + 1]; + int real_height = static_cast(im_height / feature_stride); + int real_width = static_cast(im_width / feature_stride); + + float width = boxes[index * 5 + 2] - boxes[index * 5 + 0] + 1.0f; + float height = boxes[index * 5 + 3] - boxes[index * 5 + 1] + 1.0f; + float ctr_x = boxes[index * 5 + 0] + 0.5f * (width - 1.0f); + float ctr_y = boxes[index * 5 + 1] + 0.5f * (height - 1.0f); + + int ba = (b * num_anchors + a); + float dx = deltas[((ba * 4) * feat_height + h) * feat_width + w]; + float dy = deltas[((ba * 4 + 1) * feat_height + h) * feat_width + w]; + float dw = deltas[((ba * 4 + 2) * feat_height + h) * feat_width + w]; + float dh = deltas[((ba * 4 + 3) * feat_height + h) * feat_width + w]; + + float pred_ctr_x = dx * width + ctr_x; + float pred_ctr_y = dy * height + ctr_y; + float pred_w = exp(dw) * width; + float pred_h = exp(dh) * height; + + float pred_x1 = pred_ctr_x - 0.5f * (pred_w - 1.0f); + float pred_y1 = pred_ctr_y - 0.5f * (pred_h - 1.0f); + float pred_x2 = pred_ctr_x + 0.5f * (pred_w - 1.0f); + float pred_y2 = pred_ctr_y + 0.5f * (pred_h - 1.0f); + + pred_x1 = max(min(pred_x1, im_width - 1.0f), 0.0f); + pred_y1 = max(min(pred_y1, im_height - 1.0f), 0.0f); + pred_x2 = max(min(pred_x2, im_width - 1.0f), 0.0f); + pred_y2 = max(min(pred_y2, im_height - 1.0f), 0.0f); + + out_pred_boxes[index * 5 + 0] = pred_x1; + out_pred_boxes[index * 5 + 1] = pred_y1; + out_pred_boxes[index * 5 + 2] = pred_x2; + out_pred_boxes[index * 5 + 3] = pred_y2; + + if (h >= real_height || w >= real_width) { + out_pred_boxes[index * 5 + 4] = -1.0f; + } + } +} + +// boxes are (b, h * w * anchor, 5) +// deltas are (b, 4 * anchor, h, w) +// out_pred_boxes are (b, h * w * anchor, 5) +// count should be total anchors numbers, b * h * w * anchors +// in-place write: boxes and out_pred_boxes are the same location +template +__global__ void IoUPredKernel(const int count, + const int num_anchors, + const int feat_height, + const int feat_width, + const int feature_stride, + const Dtype* im_infos, + const Dtype* boxes, + const Dtype* deltas, + Dtype* out_pred_boxes) { + for (int index = blockIdx.x * blockDim.x + threadIdx.x; + index < count; + index += blockDim.x * gridDim.x) { + int a = index % num_anchors; + int w = (index / num_anchors) % feat_width; + int h = (index / num_anchors / feat_width) % feat_height; + int b = index / num_anchors / feat_width / feat_height; + + float im_height = im_infos[b * 3]; + float im_width = im_infos[b * 3 + 1]; + int real_height = static_cast(im_height / feature_stride); + int real_width = static_cast(im_width / feature_stride); + + float x1 = boxes[index * 5 + 0]; + float y1 = boxes[index * 5 + 1]; + float x2 = boxes[index * 5 + 2]; + float y2 = boxes[index * 5 + 3]; + + int ba = (b * num_anchors + a); + float dx1 = deltas[((ba * 4) * feat_height + h) * feat_width + w]; + float dy1 = deltas[((ba * 4 + 1) * feat_height + h) * feat_width + w]; + float dx2 = deltas[((ba * 4 + 2) * feat_height + h) * feat_width + w]; + float dy2 = deltas[((ba * 4 + 3) * feat_height + h) * feat_width + w]; + + float pred_x1 = max(min(x1 + dx1, im_width - 1.0f), 0.0f); + float pred_y1 = max(min(y1 + dy1, im_height - 1.0f), 0.0f); + float pred_x2 = max(min(x2 + dx2, im_width - 1.0f), 0.0f); + float pred_y2 = max(min(y2 + dy2, im_height - 1.0f), 0.0f); + + out_pred_boxes[index * 5 + 0] = pred_x1; + out_pred_boxes[index * 5 + 1] = pred_y1; + out_pred_boxes[index * 5 + 2] = pred_x2; + out_pred_boxes[index * 5 + 3] = pred_y2; + + if (h >= real_height || w >= real_width) { + out_pred_boxes[index * 5 + 4] = -1.0f; + } + } +} + +// filter box with stride less than rpn_min_size +// filter: set score to zero +// dets (b, n, 5) +template +__global__ void FilterBoxKernel(const int count, + const int count_anchors, + const float original_min_size, + const Dtype* im_infos, + Dtype* dets) { + for (int index = blockIdx.x * blockDim.x + threadIdx.x; + index < count; + index += blockDim.x * gridDim.x) { + int b = index / count_anchors; + float iw = dets[index * 5 + 2] - dets[index * 5 + 0] + 1.0f; + float ih = dets[index * 5 + 3] - dets[index * 5 + 1] + 1.0f; + float min_size = original_min_size * im_infos[b * 3 + 2]; + if (iw < min_size || ih < min_size) { + dets[index * 5 + 0] -= min_size / 2; + dets[index * 5 + 1] -= min_size / 2; + dets[index * 5 + 2] += min_size / 2; + dets[index * 5 + 3] += min_size / 2; + dets[index * 5 + 4] = -1.0f; + } + } +} + +// copy score and init order +// dets (n, 5); score (n, ); order (n, ) +// count should be n (total anchors or proposals) +template +__global__ void CopyScoreKernel(const int count, + const Dtype* dets, + Dtype* score, + int* order) { + for (int index = blockIdx.x * blockDim.x + threadIdx.x; + index < count; + index += blockDim.x * gridDim.x) { + score[index] = dets[index * 5 + 4]; + order[index] = index; + } +} + +// reorder proposals according to order and keep the top_n proposals +// prev_dets (n, 5); order (n, ); dets (n, 5) +// count should be output anchor numbers (top_n) +template +__global__ void ReorderProposalsKernel(const int count, + const Dtype* prev_dets, + const int* order, + Dtype* dets) { + for (int index = blockIdx.x * blockDim.x + threadIdx.x; + index < count; + index += blockDim.x * gridDim.x) { + const int order_i = order[index]; + for (int j = 0; j < 5; j ++) { + dets[index * 5 + j] = prev_dets[order_i * 5 + j]; + } + } +} + +__device__ inline float devIoU(float const * const a, float const * const b) { + float left = max(a[0], b[0]), right = min(a[2], b[2]); + float top = max(a[1], b[1]), bottom = min(a[3], b[3]); + float width = max(right - left + 1, 0.f), height = max(bottom - top + 1, 0.f); + float interS = width * height; + float Sa = (a[2] - a[0] + 1) * (a[3] - a[1] + 1); + float Sb = (b[2] - b[0] + 1) * (b[3] - b[1] + 1); + return interS / (Sa + Sb - interS); +} + +__global__ void nms_kernel(const int n_boxes, const float nms_overlap_thresh, + const float *dev_boxes, uint64_t *dev_mask) { + const int threadsPerBlock = sizeof(uint64_t) * 8; + const int row_start = blockIdx.y; + const int col_start = blockIdx.x; + + // if (row_start > col_start) return; + + const int row_size = + min(n_boxes - row_start * threadsPerBlock, threadsPerBlock); + const int col_size = + min(n_boxes - col_start * threadsPerBlock, threadsPerBlock); + + __shared__ float block_boxes[threadsPerBlock * 5]; + if (threadIdx.x < col_size) { + block_boxes[threadIdx.x * 5 + 0] = + dev_boxes[(threadsPerBlock * col_start + threadIdx.x) * 5 + 0]; + block_boxes[threadIdx.x * 5 + 1] = + dev_boxes[(threadsPerBlock * col_start + threadIdx.x) * 5 + 1]; + block_boxes[threadIdx.x * 5 + 2] = + dev_boxes[(threadsPerBlock * col_start + threadIdx.x) * 5 + 2]; + block_boxes[threadIdx.x * 5 + 3] = + dev_boxes[(threadsPerBlock * col_start + threadIdx.x) * 5 + 3]; + block_boxes[threadIdx.x * 5 + 4] = + dev_boxes[(threadsPerBlock * col_start + threadIdx.x) * 5 + 4]; + } + __syncthreads(); + + if (threadIdx.x < row_size) { + const int cur_box_idx = threadsPerBlock * row_start + threadIdx.x; + const float *cur_box = dev_boxes + cur_box_idx * 5; + int i = 0; + uint64_t t = 0; + int start = 0; + if (row_start == col_start) { + start = threadIdx.x + 1; + } + for (i = start; i < col_size; i++) { + if (devIoU(cur_box, block_boxes + i * 5) > nms_overlap_thresh) { + t |= 1ULL << i; + } + } + const int col_blocks = DIVUP(n_boxes, threadsPerBlock); + dev_mask[cur_box_idx * col_blocks + col_start] = t; + } +} + +void _nms(const mshadow::Tensor& boxes, + const float nms_overlap_thresh, + int *keep, + int *num_out) { + const int threadsPerBlock = sizeof(uint64_t) * 8; + const int boxes_num = boxes.size(0); + const int boxes_dim = boxes.size(1); + + float* boxes_dev = boxes.dptr_; + uint64_t* mask_dev = NULL; + + const int col_blocks = DIVUP(boxes_num, threadsPerBlock); + FRCNN_CUDA_CHECK(cudaMalloc(&mask_dev, + boxes_num * col_blocks * sizeof(uint64_t))); + + dim3 blocks(DIVUP(boxes_num, threadsPerBlock), + DIVUP(boxes_num, threadsPerBlock)); + dim3 threads(threadsPerBlock); + nms_kernel<<>>(boxes_num, + nms_overlap_thresh, + boxes_dev, + mask_dev); + FRCNN_CUDA_CHECK(cudaPeekAtLastError()); + std::vector mask_host(boxes_num * col_blocks); + FRCNN_CUDA_CHECK(cudaMemcpy(&mask_host[0], + mask_dev, + sizeof(uint64_t) * boxes_num * col_blocks, + cudaMemcpyDeviceToHost)); + + std::vector remv(col_blocks); + memset(&remv[0], 0, sizeof(uint64_t) * col_blocks); + + int num_to_keep = 0; + for (int i = 0; i < boxes_num; i++) { + int nblock = i / threadsPerBlock; + int inblock = i % threadsPerBlock; + + if (!(remv[nblock] & (1ULL << inblock))) { + keep[num_to_keep++] = i; + uint64_t *p = &mask_host[0] + i * col_blocks; + for (int j = nblock; j < col_blocks; j++) { + remv[j] |= p[j]; + } + } + } + *num_out = num_to_keep; + + FRCNN_CUDA_CHECK(cudaFree(mask_dev)); +} + +// copy proposals to output +// dets (top_n, 5); keep (top_n, ); out (top_n, ) +// count should be top_n (total anchors or proposals) +template +__global__ void PrepareOutput(const int count, + const Dtype* dets, + const int* keep, + const int out_size, + const int image_index, + Dtype* out, + Dtype* score) { + for (int index = blockIdx.x * blockDim.x + threadIdx.x; + index < count; + index += blockDim.x * gridDim.x) { + out[index * 5] = image_index; + if (index < out_size) { + int keep_i = keep[index]; + for (int j = 0; j < 4; ++j) { + out[index * 5 + j + 1] = dets[keep_i * 5 + j]; + } + score[index] = dets[keep_i * 5 + 4]; + } else { + int keep_i = keep[index % out_size]; + for (int j = 0; j < 4; ++j) { + out[index * 5 + j + 1] = dets[keep_i * 5 + j]; + } + score[index] = dets[keep_i * 5 + 4]; + } + } +} +} // namespace multi_proposal +} // namespace cuda +} // namespace mshadow + +namespace mxnet { +namespace op { + +template +class MultiProposalGPUOp : public Operator{ + public: + explicit MultiProposalGPUOp(MultiProposalParam param) { + this->param_ = param; + } + + virtual void Forward(const OpContext &ctx, + const std::vector &in_data, + const std::vector &req, + const std::vector &out_data, + const std::vector &aux_states) { + using namespace mshadow; + using namespace mshadow::expr; + using namespace mshadow::cuda; + using namespace mshadow::cuda::multi_proposal; + CHECK_EQ(in_data.size(), 3); + CHECK_EQ(out_data.size(), 2); + CHECK_GT(req.size(), 1); + CHECK_EQ(req[proposal::kOut], kWriteTo); + /*CHECK_EQ(in_data[proposal::kClsProb].shape_[0], 1) + << "Sorry, multiple images each device is not implemented.";*/ + + Stream *s = ctx.get_stream(); + + Tensor scores = in_data[proposal::kClsProb].get(s); + Tensor bbox_deltas = in_data[proposal::kBBoxPred].get(s); + Tensor im_info = in_data[proposal::kImInfo].get(s); + + Tensor out = out_data[proposal::kOut].get(s); + Tensor out_score = out_data[proposal::kScore].get(s); + + int num_images = scores.size(0); + int num_anchors = scores.size(1) / 2; + int height = scores.size(2); + int width = scores.size(3); + int count_anchors = num_anchors * height * width; // count of total anchors + int count = num_images * count_anchors; + // set to -1 for max + int rpn_pre_nms_top_n = (param_.rpn_pre_nms_top_n > 0) ? param_.rpn_pre_nms_top_n + : count_anchors; + rpn_pre_nms_top_n = std::min(rpn_pre_nms_top_n, count_anchors); + int rpn_post_nms_top_n = std::min(param_.rpn_post_nms_top_n, rpn_pre_nms_top_n); + + // Generate first anchors based on base anchor + std::vector base_anchor(4); + base_anchor[0] = 0.0; + base_anchor[1] = 0.0; + base_anchor[2] = param_.feature_stride - 1.0; + base_anchor[3] = param_.feature_stride - 1.0; + CHECK_EQ(num_anchors, param_.ratios.info.size() * param_.scales.info.size()); + std::vector anchors; + utils::GenerateAnchors(base_anchor, + param_.ratios.info, + param_.scales.info, + &anchors); + + // Copy generated anchors to GPU + float* workspace_proposals_ptr = NULL; + FRCNN_CUDA_CHECK(cudaMalloc(&workspace_proposals_ptr, + sizeof(float) * num_images * count_anchors * 5)); + Tensor workspace_proposals(workspace_proposals_ptr, + Shape3(num_images, count_anchors, 5)); + FRCNN_CUDA_CHECK(cudaMemcpy(workspace_proposals.dptr_, &anchors[0], + sizeof(float) * anchors.size(), cudaMemcpyHostToDevice)); + + // Copy proposals to a mesh grid + dim3 dimGrid((count + kMaxThreadsPerBlock - 1) / kMaxThreadsPerBlock); + dim3 dimBlock(kMaxThreadsPerBlock); + CheckLaunchParam(dimGrid, dimBlock, "ProposalGrid"); + ProposalGridKernel<<>>( + count, num_anchors, height, width, param_.feature_stride, + scores.dptr_, workspace_proposals.dptr_); + FRCNN_CUDA_CHECK(cudaPeekAtLastError()); + + // Transform anchors and bbox_deltas into bboxes + CheckLaunchParam(dimGrid, dimBlock, "BBoxPred"); + if (param_.iou_loss) { + IoUPredKernel<<>>( + count, num_anchors, height, width, param_.feature_stride, im_info.dptr_, + workspace_proposals.dptr_, bbox_deltas.dptr_, workspace_proposals.dptr_); + } else { + BBoxPredKernel<<>>( + count, num_anchors, height, width, param_.feature_stride, im_info.dptr_, + workspace_proposals.dptr_, bbox_deltas.dptr_, workspace_proposals.dptr_); + } + FRCNN_CUDA_CHECK(cudaPeekAtLastError()); + + // filter boxes with less than rpn_min_size + CheckLaunchParam(dimGrid, dimBlock, "FilterBox"); + FilterBoxKernel<<>>( + count, count_anchors, param_.rpn_min_size, im_info.dptr_, workspace_proposals.dptr_); + FRCNN_CUDA_CHECK(cudaPeekAtLastError()); + + + + dimGrid = dim3((count_anchors + kMaxThreadsPerBlock - 1) / kMaxThreadsPerBlock); + dimBlock = dim3(kMaxThreadsPerBlock); + // Copy score to a continuous memory + float* score_ptr = NULL; + FRCNN_CUDA_CHECK(cudaMalloc(&score_ptr, sizeof(float) * count_anchors)); + Tensor score(score_ptr, Shape1(count_anchors)); + int* order_ptr = NULL; + FRCNN_CUDA_CHECK(cudaMalloc(&order_ptr, sizeof(int) * count_anchors)); + Tensor order(order_ptr, Shape1(count_anchors)); + + float* workspace_ordered_proposals_ptr = NULL; + FRCNN_CUDA_CHECK(cudaMalloc(&workspace_ordered_proposals_ptr, + sizeof(float) * rpn_pre_nms_top_n * 5)); + Tensor workspace_ordered_proposals(workspace_ordered_proposals_ptr, + Shape2(rpn_pre_nms_top_n, 5)); + + int* keep; + FRCNN_CUDA_CHECK(cudaMalloc(&keep, sizeof(int) * rpn_pre_nms_top_n)); + + for (int b = 0; b < num_images; b++) { + CheckLaunchParam(dimGrid, dimBlock, "CopyScore"); + CopyScoreKernel << > >( + count_anchors, workspace_proposals.dptr_ + b * count_anchors * 5, + score.dptr_, order.dptr_); + FRCNN_CUDA_CHECK(cudaPeekAtLastError()); + + // argsort score, save order + thrust::stable_sort_by_key(thrust::device, + score.dptr_, + score.dptr_ + score.size(0), + order.dptr_, + thrust::greater()); + FRCNN_CUDA_CHECK(cudaPeekAtLastError()); + + // Reorder proposals according to order + + dimGrid.x = (rpn_pre_nms_top_n + kMaxThreadsPerBlock - 1) / kMaxThreadsPerBlock; + CheckLaunchParam(dimGrid, dimBlock, "ReorderProposals"); + ReorderProposalsKernel << > >( + rpn_pre_nms_top_n, workspace_proposals.dptr_ + b * count_anchors * 5, + order.dptr_, workspace_ordered_proposals.dptr_); + FRCNN_CUDA_CHECK(cudaPeekAtLastError()); + + // perform nms + std::vector _keep(workspace_ordered_proposals.size(0)); + int out_size = 0; + _nms(workspace_ordered_proposals, + param_.threshold, + &_keep[0], + &out_size); + + // copy nms result to gpu + FRCNN_CUDA_CHECK(cudaMemcpy(keep, &_keep[0], sizeof(int) * _keep.size(), + cudaMemcpyHostToDevice)); + + // copy results after nms + dimGrid.x = (rpn_post_nms_top_n + kMaxThreadsPerBlock - 1) / kMaxThreadsPerBlock; + CheckLaunchParam(dimGrid, dimBlock, "PrepareOutput"); + PrepareOutput << > >( + rpn_post_nms_top_n, workspace_ordered_proposals.dptr_, keep, out_size, b, + out.dptr_ + b * rpn_post_nms_top_n * 5, out_score.dptr_ + b * rpn_post_nms_top_n); + FRCNN_CUDA_CHECK(cudaPeekAtLastError()); + } + // free temporary memory + FRCNN_CUDA_CHECK(cudaFree(keep)); + FRCNN_CUDA_CHECK(cudaFree(workspace_ordered_proposals_ptr)); + FRCNN_CUDA_CHECK(cudaFree(workspace_proposals_ptr)); + FRCNN_CUDA_CHECK(cudaFree(score_ptr)); + FRCNN_CUDA_CHECK(cudaFree(order_ptr)); + } + + virtual void Backward(const OpContext &ctx, + const std::vector &out_grad, + const std::vector &in_data, + const std::vector &out_data, + const std::vector &req, + const std::vector &in_grad, + const std::vector &aux_states) { + using namespace mshadow; + using namespace mshadow::expr; + CHECK_EQ(in_grad.size(), 3); + + Stream *s = ctx.get_stream(); + Tensor gscores = in_grad[proposal::kClsProb].get(s); + Tensor gbbox = in_grad[proposal::kBBoxPred].get(s); + Tensor ginfo = in_grad[proposal::kImInfo].get(s); + + // can not assume the grad would be zero + Assign(gscores, req[proposal::kClsProb], 0); + Assign(gbbox, req[proposal::kBBoxPred], 0); + Assign(ginfo, req[proposal::kImInfo], 0); + } + + private: + MultiProposalParam param_; +}; // class MultiProposalGPUOp + +template<> +Operator* CreateOp(MultiProposalParam param) { + return new MultiProposalGPUOp(param); +} +} // namespace op +} // namespace mxnet diff --git a/src/operator/contrib/multibox_detection-inl.h b/src/operator/contrib/multibox_detection-inl.h index 3507281eba10..34099a3d6978 100644 --- a/src/operator/contrib/multibox_detection-inl.h +++ b/src/operator/contrib/multibox_detection-inl.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2016 by Contributors * \file multibox_detection-inl.h * \brief post-process multibox detection predictions * \author Joshua Zhang diff --git a/src/operator/contrib/multibox_detection.cc b/src/operator/contrib/multibox_detection.cc index 6a4bfdd189b1..0f6982890f4f 100644 --- a/src/operator/contrib/multibox_detection.cc +++ b/src/operator/contrib/multibox_detection.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2016 by Contributors * \file multibox_detection.cc * \brief MultiBoxDetection op * \author Joshua Zhang @@ -176,7 +194,7 @@ MXNET_REGISTER_OP_PROPERTY(_contrib_MultiBoxDetection, MultiBoxDetectionProp) .describe("Convert multibox detection predictions.") .add_argument("cls_prob", "NDArray-or-Symbol", "Class probabilities.") .add_argument("loc_pred", "NDArray-or-Symbol", "Location regression predictions.") -.add_argument("anchors", "NDArray-or-Symbol", "Multibox prior anchor boxes") +.add_argument("anchor", "NDArray-or-Symbol", "Multibox prior anchor boxes") .add_arguments(MultiBoxDetectionParam::__FIELDS__()); } // namespace op } // namespace mxnet diff --git a/src/operator/contrib/multibox_detection.cu b/src/operator/contrib/multibox_detection.cu index dab11ffbe701..56a1e88dfee0 100644 --- a/src/operator/contrib/multibox_detection.cu +++ b/src/operator/contrib/multibox_detection.cu @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2016 by Contributors * \file multibox_detection.cu * \brief MultiBoxDetection op * \author Joshua Zhang diff --git a/src/operator/contrib/multibox_prior-inl.h b/src/operator/contrib/multibox_prior-inl.h index ee83fe462ce4..88ca3dc8de6f 100644 --- a/src/operator/contrib/multibox_prior-inl.h +++ b/src/operator/contrib/multibox_prior-inl.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2016 by Contributors * \file multibox_prior-inl.h * \brief generate multibox prior boxes * \author Joshua Zhang diff --git a/src/operator/contrib/multibox_prior.cc b/src/operator/contrib/multibox_prior.cc index a9c747e7c6f5..af77fdaa8015 100644 --- a/src/operator/contrib/multibox_prior.cc +++ b/src/operator/contrib/multibox_prior.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2016 by Contributors * \file multibox_prior.cc * \brief generate multibox prior boxes cpu implementation * \author Joshua Zhang diff --git a/src/operator/contrib/multibox_prior.cu b/src/operator/contrib/multibox_prior.cu index a3f2cc22f552..b041b90d1d05 100644 --- a/src/operator/contrib/multibox_prior.cu +++ b/src/operator/contrib/multibox_prior.cu @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2016 by Contributors * \file multibox_prior.cu * \brief generate multibox prior boxes cuda kernels * \author Joshua Zhang diff --git a/src/operator/contrib/multibox_target-inl.h b/src/operator/contrib/multibox_target-inl.h index 7185c9a1d2ff..f76df3504a28 100644 --- a/src/operator/contrib/multibox_target-inl.h +++ b/src/operator/contrib/multibox_target-inl.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2016 by Contributors * \file multibox_target-inl.h * \brief * \author Joshua Zhang diff --git a/src/operator/contrib/multibox_target.cc b/src/operator/contrib/multibox_target.cc index 56c6ceefdc43..095613d4a938 100644 --- a/src/operator/contrib/multibox_target.cc +++ b/src/operator/contrib/multibox_target.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2016 by Contributors * \file multibox_target.cc * \brief MultiBoxTarget op * \author Joshua Zhang diff --git a/src/operator/contrib/multibox_target.cu b/src/operator/contrib/multibox_target.cu index adcfcf249eea..3d0da6ce6f5b 100644 --- a/src/operator/contrib/multibox_target.cu +++ b/src/operator/contrib/multibox_target.cu @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2016 by Contributors * \file multibox_target.cu * \brief MultiBoxTarget op * \author Joshua Zhang diff --git a/src/operator/contrib/nn/deformable_im2col.cuh b/src/operator/contrib/nn/deformable_im2col.cuh new file mode 100644 index 000000000000..0238921933c5 --- /dev/null +++ b/src/operator/contrib/nn/deformable_im2col.cuh @@ -0,0 +1,544 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +/*! + ******************* BEGIN Caffe Copyright Notice and Disclaimer **************** + * + * COPYRIGHT + * + * All contributions by the University of California: + * Copyright (c) 2014-2017 The Regents of the University of California (Regents) + * All rights reserved. + * + * All other contributions: + * Copyright (c) 2014-2017, the respective contributors + * All rights reserved. + * + * Caffe uses a shared copyright model: each contributor holds copyright over + * their contributions to Caffe. The project versioning records all such + * contribution and copyright details. If a contributor wants to further mark + * their specific copyright on a particular contribution, they should indicate + * their copyright solely in the commit message of the change when it is + * committed. + * + * LICENSE + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * CONTRIBUTION AGREEMENT + * + * By contributing to the BVLC/caffe repository through pull-request, comment, + * or otherwise, the contributor releases their content to the + * license and copyright terms herein. + * + ***************** END Caffe Copyright Notice and Disclaimer ******************** + * + * Copyright (c) 2017 Microsoft + * Licensed under The Apache-2.0 License [see LICENSE for details] + * \file deformable_im2col.cuh + * \brief Function definitions of converting an image to + * column matrix based on kernel, padding, dilation, and offset. + * These functions are mainly used in deformable convolution operators. + * \ref: https://arxiv.org/abs/1703.06211 + * \author Yuwen Xiong, Haozhi Qi, Jifeng Dai + */ + +#ifndef MXNET_OPERATOR_CONTRIB_NN_DEFORMABLE_IM2COL_CUH_ +#define MXNET_OPERATOR_CONTRIB_NN_DEFORMABLE_IM2COL_CUH_ + +#include +#include +#include +#include +#include +#include "../../mxnet_op.h" +#include "../../../common/cuda_utils.h" + + + +namespace mxnet { +namespace op { + +template +__device__ DType deformable_im2col_bilinear(const DType* bottom_data, const int data_width, + const int height, const int width, DType h, DType w) { + + int h_low = floor(h); + int w_low = floor(w); + int h_high; + int w_high; + if (h_low >= height - 1) { + h_high = h_low = height - 1; + h = (DType)h_low; + } + else { + h_high = h_low + 1; + } + + if (w_low >= width - 1) { + w_high = w_low = width - 1; + w = (DType)w_low; + } + else { + w_high = w_low + 1; + } + + DType lh = h - h_low; + DType lw = w - w_low; + DType hh = 1 - lh, hw = 1 - lw; + + DType v1 = bottom_data[h_low * data_width + w_low]; + DType v2 = bottom_data[h_low * data_width + w_high]; + DType v3 = bottom_data[h_high * data_width + w_low]; + DType v4 = bottom_data[h_high * data_width + w_high]; + DType w1 = hh * hw, w2 = hh * lw, w3 = lh * hw, w4 = lh * lw; + + DType val = (w1 * v1 + w2 * v2 + w3 * v3 + w4 * v4); + return val; +} + + +template +__device__ DType get_gradient_weight(DType argmax_h, DType argmax_w, + const int h, const int w, const int height, const int width) { + + if (argmax_h < 0 || argmax_h > height || argmax_w < 0 || argmax_w > width) { + //empty + return 0; + } + + argmax_h = max(argmax_h, (DType)0.0f); + argmax_w = max(argmax_w, (DType)0.0f); + + int argmax_h_low = (int)argmax_h; + int argmax_w_low = (int)argmax_w; + int argmax_h_high; + int argmax_w_high; + if (argmax_h_low >= height - 1) { + argmax_h_high = argmax_h_low = height - 1; + argmax_h = (DType)argmax_h_low; + } else { + argmax_h_high = argmax_h_low + 1; + } + if (argmax_w_low >= width - 1) + { + argmax_w_high = argmax_w_low = width - 1; + argmax_w = (DType)argmax_w_low; + } else { + argmax_w_high = argmax_w_low + 1; + } + DType weight = 0; + if (h == argmax_h_low) { + if (w == argmax_w_low) { + weight = (h + 1 - argmax_h) * (w + 1 - argmax_w); + } else if (w == argmax_w_high) { + weight = (h + 1 - argmax_h) * (argmax_w + 1 - w); + } + } else if (h == argmax_h_high) { + if (w == argmax_w_low) { + weight = (argmax_h + 1 - h) * (w + 1 - argmax_w); + } else if (w == argmax_w_high) { + weight = (argmax_h + 1 - h) * (argmax_w + 1 - w); + } + } + return weight; +} + + +template +__device__ DType get_coordinate_weight(DType argmax_h, DType argmax_w, + const int height, const int width, const DType* im_data, + const int data_width, const int bp_dir) { + + if (argmax_h < 0 || argmax_h > height || argmax_w < 0 || argmax_w > width) + { + //empty + return 0; + } + + if (argmax_h < 0) argmax_h = 0; + if (argmax_w < 0) argmax_w = 0; + + int argmax_h_low = (int)argmax_h; + int argmax_w_low = (int)argmax_w; + int argmax_h_high; + int argmax_w_high; + if (argmax_h_low >= height - 1) { + argmax_h_high = argmax_h_low = height - 1; + argmax_h = (DType)argmax_h_low; + } else { + argmax_h_high = argmax_h_low + 1; + } + if (argmax_w_low >= width - 1) { + argmax_w_high = argmax_w_low = width - 1; + argmax_w = (DType)argmax_w_low; + } else { + argmax_w_high = argmax_w_low + 1; + } + DType weight = 0; + + if (bp_dir == 0) { + weight += -1 * (argmax_w_low + 1 - argmax_w) * im_data[argmax_h_low * data_width + argmax_w_low]; + weight += -1 * (argmax_w - argmax_w_low) * im_data[argmax_h_low * data_width + argmax_w_high]; + weight += (argmax_w_low + 1 - argmax_w) * im_data[argmax_h_high * data_width + argmax_w_low]; + weight += (argmax_w - argmax_w_low) * im_data[argmax_h_high * data_width + argmax_w_high]; + } else if (bp_dir == 1) { + weight += -1 * (argmax_h_low + 1 - argmax_h) * im_data[argmax_h_low * data_width + argmax_w_low]; + weight += (argmax_h_low + 1 - argmax_h) * im_data[argmax_h_low * data_width + argmax_w_high]; + weight += -1 * (argmax_h - argmax_h_low) * im_data[argmax_h_high * data_width + argmax_w_low]; + weight += (argmax_h - argmax_h_low) * im_data[argmax_h_high * data_width + argmax_w_high]; + } + + return weight; +} + + +/*! + * \brief deformable_im2col gpu kernel. + * DO NOT call this directly. Use wrapper function im2col() instead; + */ +template +__global__ void deformable_im2col_gpu_kernel(const int n, const DType* data_im, const DType* data_offset, + const int height, const int width, const int kernel_h, const int kernel_w, + const int pad_h, const int pad_w, + const int stride_h, const int stride_w, + const int dilation_h, const int dilation_w, + const int channel_per_deformable_group, + const int height_col, const int width_col, + DType* data_col) { + CUDA_KERNEL_LOOP(index, n) { + // index index of output matrix + const int w_col = index % width_col; + const int h_col = (index / width_col) % height_col; + const int c_im = (index / width_col) / height_col; + const int c_col = c_im * kernel_h * kernel_w; + + // compute deformable group index + const int deformable_group_index = c_im / channel_per_deformable_group; + + const int h_in = h_col * stride_h - pad_h; + const int w_in = w_col * stride_w - pad_w; + DType* data_col_ptr = data_col + (c_col * height_col + h_col) * width_col + w_col; + const DType* data_im_ptr = data_im + (c_im * height + h_in) * width + w_in; + const DType* data_offset_ptr = data_offset + deformable_group_index * 2 * kernel_h * kernel_w * height_col * width_col; + + + for (int i = 0; i < kernel_h; ++i) { + for (int j = 0; j < kernel_w; ++j) { + const int data_offset_h_ptr = ((2 * (i * kernel_w + j)) * height_col + h_col) * width_col + w_col; + const int data_offset_w_ptr = ((2 * (i * kernel_w + j) + 1) * height_col + h_col) * width_col + w_col; + const DType offset_h = data_offset_ptr[data_offset_h_ptr]; + const DType offset_w = data_offset_ptr[data_offset_w_ptr]; + DType val = static_cast(0); + const DType h_im = h_in + i * dilation_h + offset_h; + const DType w_im = w_in + j * dilation_w + offset_w; + if (h_im >= 0 && w_im >= 0 && h_im < height && w_im < width) { + const DType map_h = i * dilation_h + offset_h; + const DType map_w = j * dilation_w + offset_w; + const int cur_height = height - h_in; + const int cur_width = width - w_in; + val = deformable_im2col_bilinear(data_im_ptr, width, cur_height, cur_width, map_h, map_w); + } + *data_col_ptr = val; + data_col_ptr += height_col * width_col; + } + } + } +} + + + + + + +/*!\brief + * cpu function of deformable_im2col algorithm + * \param s device stream + * \param data_im pointer of an image (C, H, W, ...) in the image batch + * \param data_offset pointer of offset (C, H, W, ...) in the offset batch + * \param im_shape input image shape in dimensions (N, C, H, W,) + * \param col_shape column buffer shape (#channels, output_im_height, output_im_width, ...) + * \param kernel_shape kernel filter shape + * \param pad pad shape + * \param stride stride shape + * \param dilation dilation shape + * \param deformable_group #offset group that deformable convolution use + * \param data_col column buffer pointer + */ +template +inline void deformable_im2col(mshadow::Stream* s, + const DType* data_im, const DType* data_offset, + const TShape& im_shape, const TShape& col_shape, const TShape& kernel_shape, + const TShape& pad, const TShape& stride, const TShape& dilation, + const uint32_t deformable_group, DType* data_col) { + // num_axes should be smaller than block size + index_t num_spatial_axes = kernel_shape.ndim(); + CHECK_LT(num_spatial_axes, mshadow::cuda::kBaseThreadNum); + index_t channel_per_deformable_group = im_shape[1] / deformable_group; + index_t num_kernels = im_shape[1] * col_shape.ProdShape(1, col_shape.ndim()); + using namespace mxnet_op; + switch (num_spatial_axes) { + case 2: + deformable_im2col_gpu_kernel // NOLINT_NEXT_LINE(whitespace/operators) + <<::GetStream(s)>>>( + num_kernels, data_im, data_offset, im_shape[2], im_shape[3], kernel_shape[0], kernel_shape[1], + pad[0], pad[1], stride[0], stride[1], dilation[0], dilation[1], channel_per_deformable_group, + col_shape[1], col_shape[2], data_col); + MSHADOW_CUDA_POST_KERNEL_CHECK(deformable_im2col_gpu_kernel); + break; + default: + LOG(FATAL) << "im2col_nd_gpu does not support computation with " + << num_spatial_axes << " spatial axes"; + } +} + + +/*! +* \brief deformable_col2im gpu kernel. +* \brief DO NOT call this directly. Use wrapper function deformable_col2im() instead; +*/ +template +__global__ void deformable_col2im_gpu_kernel(const int n, const DType* data_col, const DType* data_offset, + const int channels, const int height, const int width, + const int kernel_h, const int kernel_w, + const int pad_h, const int pad_w, + const int stride_h, const int stride_w, + const int dilation_h, const int dilation_w, + const int channel_per_deformable_group, + const int height_col, const int width_col, + DType* grad_im, OpReqType req) { + CUDA_KERNEL_LOOP(index, n) { + const int j = (index / width_col / height_col) % kernel_w; + const int i = (index / width_col / height_col / kernel_w) % kernel_h; + const int c = index / width_col / height_col / kernel_w / kernel_h; + // compute the start and end of the output + + const int deformable_group_index = c / channel_per_deformable_group; + + int w_out = index % width_col; + int h_out = (index / width_col) % height_col; + int w_in = w_out * stride_w - pad_w; + int h_in = h_out * stride_h - pad_h; + + const DType* data_offset_ptr = data_offset + deformable_group_index * 2 * kernel_h * kernel_w * height_col * width_col; + const int data_offset_h_ptr = ((2 * (i * kernel_w + j)) * height_col + h_out) * width_col + w_out; + const int data_offset_w_ptr = ((2 * (i * kernel_w + j) + 1) * height_col + h_out) * width_col + w_out; + const DType offset_h = data_offset_ptr[data_offset_h_ptr]; + const DType offset_w = data_offset_ptr[data_offset_w_ptr]; + const DType cur_inv_h_data = h_in + i * dilation_h + offset_h; + const DType cur_inv_w_data = w_in + j * dilation_w + offset_w; + + const DType cur_top_grad = data_col[index]; + const int cur_h = (int)cur_inv_h_data; + const int cur_w = (int)cur_inv_w_data; + for (int dy = -2; dy <= 2; dy++) { + for (int dx = -2; dx <= 2; dx++) { + if (cur_h + dy >= 0 && cur_h + dy < height && + cur_w + dx >= 0 && cur_w + dx < width && + abs(cur_inv_h_data - (cur_h + dy)) < 1 && + abs(cur_inv_w_data - (cur_w + dx)) < 1 + ) { + int cur_bottom_grad_pos = (c * height + cur_h + dy) * width + cur_w + dx; + DType weight = get_gradient_weight(cur_inv_h_data, cur_inv_w_data, cur_h + dy, cur_w + dx, height, width); + atomicAdd(grad_im + cur_bottom_grad_pos, weight * cur_top_grad); + } + } + } + } +} + + +/*!\brief + * gpu function of deformable_col2im algorithm + * \param s device stream + * \param data_col start pointer of the column buffer to be filled + * \param data_offset pointer of offset (C, H, W, ...) in the offset batch + * \param im_shape input image shape in dimensions (N, C, H, W,) + * \param col_shape column buffer shape + * \param kernel_shape kernel filter shape + * \param pad pad shape + * \param stride stride shape + * \param dilation dilation shape + * \param deformable_group #offset group that deformable convolution use + * \param grad_im pointer of a image (C, H, W,...) in the image batch + */ +template +inline void deformable_col2im(mshadow::Stream* s, + const DType* data_col, const DType* data_offset, + const TShape& im_shape, const TShape& col_shape, const TShape& kernel_shape, + const TShape& pad, const TShape& stride, + const TShape& dilation, const uint32_t deformable_group, + DType* grad_im, OpReqType req) { + index_t num_spatial_axes = kernel_shape.ndim(); + index_t im_size = im_shape.ProdShape(1, im_shape.ndim()); + index_t channel_per_deformable_group = im_shape[1] / deformable_group; + index_t num_kernels = col_shape.ProdShape(0, col_shape.ndim()); + // num_axes should be smaller than block size + CHECK_LT(num_spatial_axes, mshadow::cuda::kBaseThreadNum); + using namespace mxnet_op; + switch (num_spatial_axes) { + case 2: + // To avoid involving atomic operations, we will launch one kernel per + // bottom dimension, and then in the kernel add up the top dimensions. + // NOLINT_NEXT_LINE(whitespace/operators) + deformable_col2im_gpu_kernel<<::GetStream(s)>>>( + num_kernels, data_col, data_offset, im_shape[1], im_shape[2], im_shape[3], + kernel_shape[0], kernel_shape[1], pad[0], pad[1], stride[0], stride[1], + dilation[0], dilation[1], channel_per_deformable_group, col_shape[1], col_shape[2], grad_im, req); + MSHADOW_CUDA_POST_KERNEL_CHECK(deformable_col2im_gpu_kernel); + break; + default: + LOG(FATAL) << "col2im_nd_gpu does not support computation with " + << num_spatial_axes << " spatial axes"; + } +} + + +/*! + * \brief deformable_col2im_coord gpu kernel. + * \brief DO NOT call this directly. Use wrapper function deformable_col2im_coord() instead; + */ +template +__global__ void deformable_col2im_coord_gpu_kernel(const int n, const DType* data_col, + const DType* data_im, const DType* data_offset, + const int channels, const int height, const int width, + const int kernel_h, const int kernel_w, + const int pad_h, const int pad_w, + const int stride_h, const int stride_w, + const int dilation_h, const int dilation_w, + const int channel_per_deformable_group, + const int height_col, const int width_col, + DType* grad_offset, OpReqType req) { + CUDA_KERNEL_LOOP(index, n) { + DType val = 0; + int w = index % width_col; + int h = (index / width_col) % height_col; + int c = index / width_col / height_col; + // compute the start and end of the output + + const int deformable_group_index = c / (2 * kernel_h * kernel_w); + const int col_step = kernel_h * kernel_w; + int cnt = 0; + const DType* data_col_ptr = data_col + deformable_group_index * channel_per_deformable_group * width_col * height_col; + const DType* data_im_ptr = data_im + deformable_group_index * channel_per_deformable_group / kernel_h / kernel_w * height * width; + const DType* data_offset_ptr = data_offset + deformable_group_index * 2 * kernel_h * kernel_w * height_col * width_col; + + const int offset_c = c - deformable_group_index * 2 * kernel_h * kernel_w; + + for (int col_c = (offset_c / 2); col_c < channel_per_deformable_group; col_c += col_step) { + const int col_pos = ((col_c * height_col) + h) * width_col + w; + const int bp_dir = offset_c % 2; + + int j = (col_pos / width_col / height_col) % kernel_w; + int i = (col_pos / width_col / height_col / kernel_w) % kernel_h; + int w_out = col_pos % width_col; + int h_out = (col_pos / width_col) % height_col; + int w_in = w_out * stride_w - pad_w; + int h_in = h_out * stride_h - pad_h; + const int data_offset_h_ptr = (((2 * (i * kernel_w + j)) * height_col + h_out) * width_col + w_out); + const int data_offset_w_ptr = (((2 * (i * kernel_w + j) + 1) * height_col + h_out) * width_col + w_out); + const DType offset_h = data_offset_ptr[data_offset_h_ptr]; + const DType offset_w = data_offset_ptr[data_offset_w_ptr]; + DType inv_h = h_in + i * dilation_h + offset_h; + DType inv_w = w_in + j * dilation_w + offset_w; + if (inv_h < 0 || inv_w < 0 || inv_h >= height || inv_w >= width) { + inv_h = inv_w = -1; + } + const DType weight = get_coordinate_weight( + inv_h, inv_w, + height, width, data_im_ptr + cnt * height * width, width, bp_dir); + val += weight * data_col_ptr[col_pos]; + cnt += 1; + } + + grad_offset[index] = val; + } +} + +/*!\brief + * gpu function of deformable_col2im_coord algorithm + * \param s device stream + * \param data_col start pointer of the column buffer to be filled + * \param data_im pointer of an image (C, H, W, ...) in the image batch + * \param data_offset pointer of offset (C, H, W, ...) in the offset batch + * \param im_shape input image shape in dimensions (N, C, H, W,) + * \param col_shape column buffer shape + * \param kernel_shape kernel filter shape + * \param pad pad shape + * \param stride stride shape + * \param dilation dilation shape + * \param deformable_group #offset group that deformable convolution use + * \param grad_offset pointer of the offset (C, H, W,...) in the offset batch + */ +template +inline void deformable_col2im_coord(mshadow::Stream* s, + const DType* data_col, const DType* data_im, const DType* data_offset, const TShape& im_shape, + const TShape& col_shape, const TShape& kernel_shape, + const TShape& pad, const TShape& stride, + const TShape& dilation, const uint32_t deformable_group, DType* grad_offset, OpReqType req) { + index_t num_spatial_axes = kernel_shape.ndim(); + index_t num_kernels = col_shape[1] * col_shape[2] * 2 * kernel_shape[0] * kernel_shape[1] * deformable_group; + index_t channel_per_deformable_group = col_shape[0] / deformable_group; + // num_axes should be smaller than block size + CHECK_LT(num_spatial_axes, mshadow::cuda::kBaseThreadNum); + using namespace mxnet_op; + switch (num_spatial_axes) { + case 2: + // To avoid involving atomic operations, we will launch one kernel per + // bottom dimension, and then in the kernel add up the top dimensions. + // NOLINT_NEXT_LINE(whitespace/operators) + + deformable_col2im_coord_gpu_kernel << ::GetStream(s) >> >( + num_kernels, data_col, data_im, data_offset, im_shape[1], im_shape[2], im_shape[3], + kernel_shape[0], kernel_shape[1], pad[0], pad[1], stride[0], stride[1], + dilation[0], dilation[1], channel_per_deformable_group, col_shape[1], col_shape[2], grad_offset, req); + MSHADOW_CUDA_POST_KERNEL_CHECK(deformable_col2im_gpu_kernel); + break; + default: + LOG(FATAL) << "col2im_nd_gpu does not support computation with " + << num_spatial_axes << " spatial axes"; + } +} + + +} // namespace op +} // namespace mxnet + +#endif // MXNET_OPERATOR_CONTRIB_NN_DEFORMABLE_IM2COL_CUH_ diff --git a/src/operator/contrib/nn/deformable_im2col.h b/src/operator/contrib/nn/deformable_im2col.h new file mode 100644 index 000000000000..b477acb4c876 --- /dev/null +++ b/src/operator/contrib/nn/deformable_im2col.h @@ -0,0 +1,176 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +/*! + ******************* BEGIN Caffe Copyright Notice and Disclaimer **************** + * + * COPYRIGHT + * + * All contributions by the University of California: + * Copyright (c) 2014-2017 The Regents of the University of California (Regents) + * All rights reserved. + * + * All other contributions: + * Copyright (c) 2014-2017, the respective contributors + * All rights reserved. + * + * Caffe uses a shared copyright model: each contributor holds copyright over + * their contributions to Caffe. The project versioning records all such + * contribution and copyright details. If a contributor wants to further mark + * their specific copyright on a particular contribution, they should indicate + * their copyright solely in the commit message of the change when it is + * committed. + * + * LICENSE + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * CONTRIBUTION AGREEMENT + * + * By contributing to the BVLC/caffe repository through pull-request, comment, + * or otherwise, the contributor releases their content to the + * license and copyright terms herein. + * + ***************** END Caffe Copyright Notice and Disclaimer ******************** + * + * Copyright (c) 2017 Microsoft + * Licensed under The Apache-2.0 License [see LICENSE for details] + * \file deformable_im2col.h + * \brief Function definitions of converting an image to + * column matrix based on kernel, padding, dilation, and offset. + * These functions are mainly used in deformable convolution operators. + * \ref: https://arxiv.org/abs/1703.06211 + * \author Yuwen Xiong, Haozhi Qi, Jifeng Dai + */ + +#ifndef MXNET_OPERATOR_CONTRIB_NN_DEFORMABLE_IM2COL_H_ +#define MXNET_OPERATOR_CONTRIB_NN_DEFORMABLE_IM2COL_H_ + +#include +#include +#include +#include +#include "../../mxnet_op.h" + +namespace mxnet { +namespace op { + +/*!\brief + * cpu function of deformable_im2col algorithm + * \param s device stream + * \param data_im pointer of an image (C, H, W, ...) in the image batch + * \param data_offset pointer of offset (C, H, W, ...) in the offset batch + * \param im_shape input image shape in dimensions (N, C, H, W,) + * \param col_shape column buffer shape (#channels, output_im_height, output_im_width, ...) + * \param kernel_shape kernel filter shape + * \param pad pad shape + * \param stride stride shape + * \param dilation dilation shape + * \param deformable_group #offset group that deformable convolution use + * \param data_col column buffer pointer + */ +template +inline void deformable_im2col(mshadow::Stream* s, + const DType* data_im, const DType* data_offset, + const TShape& im_shape, const TShape& col_shape, const TShape& kernel_shape, + const TShape& pad, const TShape& stride, const TShape& dilation, + const uint32_t deformable_group, DType* data_col) { + if (2 == kernel_shape.ndim()) { + LOG(FATAL) << "only implemented in GPU"; + } else { + LOG(FATAL) << "not implemented"; + } +} + + +/*!\brief + * cpu function of deformable_col2im algorithm + * \param s device stream + * \param data_col start pointer of the column buffer to be filled + * \param data_offset pointer of offset (C, H, W, ...) in the offset batch + * \param im_shape input image shape in dimensions (N, C, H, W,) + * \param col_shape column buffer shape + * \param kernel_shape kernel filter shape + * \param pad pad shape + * \param stride stride shape + * \param dilation dilation shape + * \param deformable_group #offset group that deformable convolution use + * \param grad_im pointer of a image (C, H, W,...) in the image batch + */ +template +inline void deformable_col2im(mshadow::Stream* s, + const DType* data_col, const DType* data_offset, + const TShape& im_shape, const TShape& col_shape, const TShape& kernel_shape, + const TShape& pad, const TShape& stride, + const TShape& dilation, const uint32_t deformable_group, + DType* grad_im, OpReqType req) { + index_t num_spatial_axes = kernel_shape.ndim(); + LOG(FATAL) << "only implemented in GPU"; +} + + +/*!\brief + * cpu function of deformable_col2im_coord algorithm + * \param s device stream + * \param data_col start pointer of the column buffer to be filled + * \param data_im pointer of an image (C, H, W, ...) in the image batch + * \param data_offset pointer of offset (C, H, W, ...) in the offset batch + * \param im_shape input image shape in dimensions (N, C, H, W,) + * \param col_shape column buffer shape + * \param kernel_shape kernel filter shape + * \param pad pad shape + * \param stride stride shape + * \param dilation dilation shape + * \param deformable_group #offset group that deformable convolution use + * \param grad_offset pointer of the offset (C, H, W,...) in the offset batch + */ + +template +inline void deformable_col2im_coord(mshadow::Stream* s, + const DType* data_col, const DType* data_im, const DType* data_offset, const TShape& im_shape, + const TShape& col_shape, const TShape& kernel_shape, + const TShape& pad, const TShape& stride, + const TShape& dilation, const uint32_t deformable_group, DType* grad_offset, OpReqType req) { + LOG(FATAL) << "only implemented in GPU"; +} + +} // namespace op +} // namespace mxnet +#ifdef __CUDACC__ +#include "./deformable_im2col.cuh" +#endif +#endif // MXNET_OPERATOR_CONTRIB_NN_DEFORMABLE_IM2COL_H_ diff --git a/src/operator/contrib/proposal-inl.h b/src/operator/contrib/proposal-inl.h index 40d59aade18a..0b33b2d79b31 100644 --- a/src/operator/contrib/proposal-inl.h +++ b/src/operator/contrib/proposal-inl.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file proposal-inl.h * \brief Proposal Operator * \author Piotr Teterwak, Bing Xu, Jian Guo, Pengfei Chen @@ -267,7 +285,7 @@ inline void _Transform(float scale, float ratio, const std::vector& base_anchor, std::vector *out_anchors) { - float w = base_anchor[2] - base_anchor[1] + 1.0f; + float w = base_anchor[2] - base_anchor[0] + 1.0f; float h = base_anchor[3] - base_anchor[1] + 1.0f; float x_ctr = base_anchor[0] + 0.5 * (w - 1.0f); float y_ctr = base_anchor[1] + 0.5 * (h - 1.0f); diff --git a/src/operator/contrib/proposal.cc b/src/operator/contrib/proposal.cc index a118385a5f6f..9aff80ddf2f6 100644 --- a/src/operator/contrib/proposal.cc +++ b/src/operator/contrib/proposal.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file proposal.cc * \brief * \author Piotr Teterwak, Bing Xu, Jian Guo diff --git a/src/operator/contrib/proposal.cu b/src/operator/contrib/proposal.cu index 9f7acb7fd4a5..bee05e32c524 100644 --- a/src/operator/contrib/proposal.cu +++ b/src/operator/contrib/proposal.cu @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file proposal.cu * \brief Proposal Operator * \author Shaoqing Ren, Jian Guo, Pengfei Chen diff --git a/src/operator/contrib/psroi_pooling-inl.h b/src/operator/contrib/psroi_pooling-inl.h new file mode 100644 index 000000000000..b4929725279d --- /dev/null +++ b/src/operator/contrib/psroi_pooling-inl.h @@ -0,0 +1,240 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +/*! + * Copyright (c) 2017 Microsoft + * Licensed under The Apache-2.0 License [see LICENSE for details] + * \file psroi_pooling-inl.h + * \brief psroi pooling operator and symbol + * \author Yi Li, Tairui Chen, Guodong Zhang, Haozhi Qi, Jifeng Dai +*/ +#ifndef MXNET_OPERATOR_CONTRIB_PSROI_POOLING_INL_H_ +#define MXNET_OPERATOR_CONTRIB_PSROI_POOLING_INL_H_ + +#include +#include +#include +#include +#include +#include +#include +#include "../mshadow_op.h" +#include "../operator_common.h" + + +namespace mxnet { +namespace op { + +// Declare enumeration of input order to make code more intuitive. +// These enums are only visible within this header +namespace psroipool { +enum PSROIPoolingOpInputs {kData, kBox}; +enum PSROIPoolingOpOutputs {kOut}; +} // psroipool + +struct PSROIPoolingParam : public dmlc::Parameter { + // TShape pooled_size; + float spatial_scale; + int output_dim; + int pooled_size; + int group_size; + DMLC_DECLARE_PARAMETER(PSROIPoolingParam) { + DMLC_DECLARE_FIELD(spatial_scale).set_range(0.0, 1.0) + .describe("Ratio of input feature map height (or w) to raw image height (or w). " + "Equals the reciprocal of total stride in convolutional layers"); + DMLC_DECLARE_FIELD(output_dim).describe("fix output dim"); + DMLC_DECLARE_FIELD(pooled_size).describe("fix pooled size"); + DMLC_DECLARE_FIELD(group_size).set_default(0).describe("fix group size"); + } +}; + +template +class PSROIPoolingOp : public Operator { + public: + explicit PSROIPoolingOp(PSROIPoolingParam p) { + this->param_ = p; + } + + virtual void Forward(const OpContext &ctx, + const std::vector &in_data, + const std::vector &req, + const std::vector &out_data, + const std::vector &aux_args) { + using namespace mshadow; + CHECK_EQ(in_data.size(), 2); + CHECK_EQ(out_data.size(), 1); + CHECK_EQ(out_data[psroipool::kOut].shape_[0], in_data[psroipool::kBox].shape_[0]); + Stream *s = ctx.get_stream(); + + Tensor data = in_data[psroipool::kData].get(s); + Tensor bbox = in_data[psroipool::kBox].get(s); + Tensor out = out_data[psroipool::kOut].get(s); + CHECK_EQ(data.CheckContiguous(), true); + CHECK_EQ(bbox.CheckContiguous(), true); + CHECK_EQ(out.CheckContiguous(), true); + out = -FLT_MAX; + PSROIPoolForward(out, data, bbox, param_.spatial_scale, param_.output_dim, param_.group_size); + } + + virtual void Backward(const OpContext &ctx, + const std::vector &out_grad, + const std::vector &in_data, + const std::vector &out_data, + const std::vector &req, + const std::vector &in_grad, + const std::vector &aux_args) { + using namespace mshadow; + CHECK_EQ(in_data.size(), 2); + CHECK_EQ(out_data.size(), 1); + CHECK_EQ(out_grad[psroipool::kOut].shape_[0], in_data[psroipool::kBox].shape_[0]); + CHECK_NE(req[psroipool::kData], kWriteInplace) << + "ROIPooling: Backward doesn't support kWriteInplace."; + CHECK_NE(req[psroipool::kBox], kWriteInplace) << + "ROIPooling: Backward doesn't support kWriteInplace."; + Stream *s = ctx.get_stream(); + + Tensor grad_out = out_grad[psroipool::kOut].get(s); + Tensor bbox = in_data[psroipool::kBox].get(s); + Tensor grad_in = in_grad[psroipool::kData].get(s); + Tensor grad_roi = in_grad[psroipool::kBox].get(s); + + CHECK_EQ(grad_out.CheckContiguous(), true); + CHECK_EQ(bbox.CheckContiguous(), true); + CHECK_EQ(grad_in.CheckContiguous(), true); + + if (kAddTo == req[psroipool::kData] || kWriteTo == req[psroipool::kData]) { + if (kWriteTo == req[psroipool::kData]) { + grad_in = 0.0f; + } + PSROIPoolBackwardAcc(grad_in, grad_out, bbox, param_.spatial_scale, + param_.output_dim, param_.group_size); + } + if (kWriteTo == req[psroipool::kBox]) { + grad_roi = 0.0f; + } + } + + private: + PSROIPoolingParam param_; +}; // class PSROIPoolingOp + +// Decalre Factory function, used for dispatch specialization +template +Operator* CreateOp(PSROIPoolingParam param, int dtype); + +#if DMLC_USE_CXX11 +class PSROIPoolingProp : public OperatorProperty { + public: + std::vector ListArguments() const override { + return {"data", "rois"}; + } + + std::vector ListOutputs() const override { + return {"output"}; + } + + int NumOutputs() const override { + return 1; + } + + int NumVisibleOutputs() const override { + return 1; + } + + void Init(const std::vector >& kwargs) override { + param_.Init(kwargs); + if (param_.group_size == 0) { + param_.group_size = param_.pooled_size; + } + } + + std::map GetParams() const override { + return param_.__DICT__(); + } + + bool InferShape(std::vector *in_shape, + std::vector *out_shape, + std::vector *aux_shape) const override { + using namespace mshadow; + CHECK_EQ(in_shape->size(), 2) << "Input:[data, rois]"; + + // data: [batch_size, c, h, w] + TShape dshape = in_shape->at(psroipool::kData); + CHECK_EQ(dshape.ndim(), 4) << "data should be a 4D tensor"; + + // bbox: [num_rois, 5] + TShape bshape = in_shape->at(psroipool::kBox); + CHECK_EQ(bshape.ndim(), 2) << "bbox should be a 2D tensor of shape [batch, 5]"; + CHECK_EQ(bshape[1], 5) << "bbox should be a 2D tensor of shape [batch, 5]"; + + // out: [num_rois, c, pooled_h, pooled_w] + out_shape->clear(); + out_shape->push_back( + Shape4(bshape[0], param_.output_dim, param_.pooled_size, param_.pooled_size)); + return true; + } + + bool InferType(std::vector *in_type, + std::vector *out_type, + std::vector *aux_type) const override { + CHECK_EQ(in_type->size(), 2); + int dtype = (*in_type)[0]; + CHECK_EQ(dtype, (*in_type)[1]); + CHECK_NE(dtype, -1) << "Input must have specified type"; + + out_type->clear(); + out_type->push_back(dtype); + return true; + } + + OperatorProperty* Copy() const override { + PSROIPoolingProp* psroi_pooling_sym = new PSROIPoolingProp(); + psroi_pooling_sym->param_ = this->param_; + return psroi_pooling_sym; + } + + std::string TypeString() const override { + return "_contrib_PSROIPooling"; + } + + // decalre dependency and inplace optimization options + std::vector DeclareBackwardDependency( + const std::vector &out_grad, + const std::vector &in_data, + const std::vector &out_data) const override { + return {out_grad[psroipool::kOut], in_data[psroipool::kBox]}; + } + + + Operator* CreateOperator(Context ctx) const override { + LOG(FATAL) << "Not Implemented."; + return NULL; + } + + Operator* CreateOperatorEx(Context ctx, std::vector *in_shape, + std::vector *in_type) const override; + + + private: + PSROIPoolingParam param_; +}; // class PSROIPoolingProp +#endif +} // namespace op +} // namespace mxnet +#endif // MXNET_OPERATOR_CONTRIB_PSROI_POOLING_INL_H_ diff --git a/src/operator/contrib/psroi_pooling.cc b/src/operator/contrib/psroi_pooling.cc new file mode 100644 index 000000000000..dd3a9e08895d --- /dev/null +++ b/src/operator/contrib/psroi_pooling.cc @@ -0,0 +1,98 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +/*! + * Copyright (c) 2017 Microsoft + * Licensed under The Apache-2.0 License [see LICENSE for details] + * \file psroi_pooling.cc + * \brief psroi pooling operator + * \author Yi Li, Tairui Chen, Guodong Zhang, Haozhi Qi, Jifeng Dai +*/ +#include "./psroi_pooling-inl.h" +#include +#include +#include +#include +#include + +using std::max; +using std::min; +using std::floor; +using std::ceil; + +namespace mshadow { +template +inline void PSROIPoolForward(const Tensor &out, + const Tensor &data, + const Tensor &bbox, + const float spatial_scale_, + const int output_dim_, + const int group_size_) { + // NOT_IMPLEMENTED; + return; +} + +template +inline void PSROIPoolBackwardAcc(const Tensor &in_grad, + const Tensor &out_grad, + const Tensor &bbox, + const float spatial_scale_, + const int output_dim_, + const int group_size_) { + // NOT_IMPLEMENTED; + return; +} +} // namespace mshadow + +namespace mxnet { +namespace op { + +template<> +Operator *CreateOp(PSROIPoolingParam param, int dtype) { + Operator* op = NULL; + MSHADOW_REAL_TYPE_SWITCH(dtype, DType, { + op = new PSROIPoolingOp(param); + }); + return op; +} + +Operator *PSROIPoolingProp::CreateOperatorEx(Context ctx, std::vector *in_shape, + std::vector *in_type) const { + std::vector out_shape, aux_shape; + std::vector out_type, aux_type; + CHECK(InferType(in_type, &out_type, &aux_type)); + CHECK(InferShape(in_shape, &out_shape, &aux_shape)); + DO_BIND_DISPATCH(CreateOp, param_, in_type->at(0)); +} + +DMLC_REGISTER_PARAMETER(PSROIPoolingParam); + +MXNET_REGISTER_OP_PROPERTY(_contrib_PSROIPooling, PSROIPoolingProp) +.describe("Performs region-of-interest pooling on inputs. Resize bounding box coordinates by " +"spatial_scale and crop input feature maps accordingly. The cropped feature maps are pooled " +"by max pooling to a fixed size output indicated by pooled_size. batch_size will change to " +"the number of region bounding boxes after PSROIPooling") +.add_argument("data", "Symbol", "Input data to the pooling operator, a 4D Feature maps") +.add_argument("rois", "Symbol", "Bounding box coordinates, a 2D array of " +"[[batch_index, x1, y1, x2, y2]]. (x1, y1) and (x2, y2) are top left and down right corners " +"of designated region of interest. batch_index indicates the index of corresponding image " +"in the input data") +.add_arguments(PSROIPoolingParam::__FIELDS__()); +} // namespace op +} // namespace mxnet diff --git a/src/operator/contrib/psroi_pooling.cu b/src/operator/contrib/psroi_pooling.cu new file mode 100644 index 000000000000..6df64a1948d6 --- /dev/null +++ b/src/operator/contrib/psroi_pooling.cu @@ -0,0 +1,278 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +/*! + * Copyright (c) 2017 Microsoft + * Licensed under The Apache-2.0 License [see LICENSE for details] + * \file psroi_pooling.cu + * \brief psroi pooling operator + * \author Yi Li, Tairui Chen, Guodong Zhang, Haozhi Qi, Jifeng Dai +*/ +#include "./psroi_pooling-inl.h" +#include +#include +#include +#include +#include "../../common/cuda_utils.h" +#include "../mxnet_op.h" + +#define PSROIPOOLING_CUDA_CHECK(condition) \ + /* Code block avoids redefinition of cudaError_t error */ \ + do { \ + cudaError_t error = condition; \ + CHECK_EQ(error, cudaSuccess) << " " << cudaGetErrorString(error); \ + } while (0) +#define CUDA_KERNEL_LOOP(i, n) \ +for (int i = blockIdx.x * blockDim.x + threadIdx.x; \ + i < (n); \ + i += blockDim.x * gridDim.x) + +namespace mshadow { +namespace cuda { + +template +__global__ void PSROIPoolForwardKernel( + const int count, + const DType* bottom_data, + const DType spatial_scale, + const int channels, + const int height, const int width, + const int pooled_height, const int pooled_width, + const DType* bottom_rois, + const int output_dim, + const int group_size, + DType* top_data) { + CUDA_KERNEL_LOOP(index, count) { + // The output is in order (n, ctop, ph, pw) + int pw = index % pooled_width; + int ph = (index / pooled_width) % pooled_height; + int ctop = (index / pooled_width / pooled_height) % output_dim; + int n = index / pooled_width / pooled_height / output_dim; + + // [start, end) interval for spatial sampling + const DType* offset_bottom_rois = bottom_rois + n * 5; + int roi_batch_ind = offset_bottom_rois[0]; + DType roi_start_w = static_cast(round(offset_bottom_rois[1])) * spatial_scale; + DType roi_start_h = static_cast(round(offset_bottom_rois[2])) * spatial_scale; + DType roi_end_w = static_cast(round(offset_bottom_rois[3]) + 1.) * spatial_scale; + DType roi_end_h = static_cast(round(offset_bottom_rois[4]) + 1.) * spatial_scale; + + // Force too small ROIs to be 1x1 + DType roi_width = max(roi_end_w - roi_start_w, 0.1); // avoid 0 + DType roi_height = max(roi_end_h - roi_start_h, 0.1); + + // Compute w and h at bottom + DType bin_size_h = roi_height / static_cast(pooled_height); + DType bin_size_w = roi_width / static_cast(pooled_width); + + int hstart = floor(static_cast(ph) * bin_size_h + + roi_start_h); + int wstart = floor(static_cast(pw)* bin_size_w + + roi_start_w); + int hend = ceil(static_cast(ph + 1) * bin_size_h + + roi_start_h); + int wend = ceil(static_cast(pw + 1) * bin_size_w + + roi_start_w); + // Add roi offsets and clip to input boundaries + hstart = min(max(hstart, 0), height); + hend = min(max(hend, 0), height); + wstart = min(max(wstart, 0), width); + wend = min(max(wend, 0), width); + bool is_empty = (hend <= hstart) || (wend <= wstart); + + int gw = floor(static_cast(pw)* group_size / pooled_width); + int gh = floor(static_cast(ph)* group_size / pooled_height); + gw = min(max(gw, 0), group_size - 1); + gh = min(max(gh, 0), group_size - 1); + int c = (ctop*group_size + gh)*group_size + gw; + + const DType* offset_bottom_data = bottom_data + (roi_batch_ind * channels + c) * height * width; + DType out_sum = 0; + for (int h = hstart; h < hend; ++h) { + for (int w = wstart; w < wend; ++w) { + int bottom_index = h*width + w; + out_sum += offset_bottom_data[bottom_index]; + } + } + + DType bin_area = (hend - hstart)*(wend - wstart); + top_data[index] = is_empty? (DType)0. : out_sum/bin_area; + } +} + +template +inline void PSROIPoolForward(const Tensor &out, + const Tensor &data, + const Tensor &bbox, + const float spatial_scale, + const int output_dim_, + const int group_size_) { + const DType *bottom_data = data.dptr_; + const DType *bottom_rois = bbox.dptr_; + DType *top_data = out.dptr_; + const int count = out.shape_.Size(); + const int channels = data.size(1); + const int height = data.size(2); + const int width = data.size(3); + const int pooled_height = out.size(2); + const int pooled_width = out.size(3); + cudaStream_t stream = Stream::GetStream(out.stream_); + PSROIPoolForwardKernel << > >( + count, bottom_data, spatial_scale, channels, height, width, + pooled_height, pooled_width, bottom_rois, output_dim_, group_size_, top_data); + PSROIPOOLING_CUDA_CHECK(cudaPeekAtLastError()); +} + + +template +__global__ void PSROIPoolBackwardAccKernel( + const int count, + const DType* top_diff, + const int num_rois, + const DType spatial_scale, + const int channels, + const int height, const int width, + const int pooled_height, const int pooled_width, + const int group_size, + const int output_dim, + DType* bottom_diff, + const DType* bottom_rois) { + CUDA_KERNEL_LOOP(index, count) { + // The output is in order (n, ctop, ph, pw) + int pw = index % pooled_width; + int ph = (index / pooled_width) % pooled_height; + int ctop = (index / pooled_width / pooled_height) % output_dim; + int n = index / pooled_width / pooled_height / output_dim; + + // [start, end) interval for spatial sampling + const DType* offset_bottom_rois = bottom_rois + n * 5; + int roi_batch_ind = offset_bottom_rois[0]; + DType roi_start_w = static_cast(round(offset_bottom_rois[1])) * spatial_scale; + DType roi_start_h = static_cast(round(offset_bottom_rois[2])) * spatial_scale; + DType roi_end_w = static_cast(round(offset_bottom_rois[3]) + 1.) * spatial_scale; + DType roi_end_h = static_cast(round(offset_bottom_rois[4]) + 1.) * spatial_scale; + + // Force too small ROIs to be 1x1 + DType roi_width = max(roi_end_w - roi_start_w, 0.1); // avoid 0 + DType roi_height = max(roi_end_h - roi_start_h, 0.1); + + // Compute w and h at bottom + DType bin_size_h = roi_height / static_cast(pooled_height); + DType bin_size_w = roi_width / static_cast(pooled_width); + + int hstart = floor(static_cast(ph)* bin_size_h + + roi_start_h); + int wstart = floor(static_cast(pw)* bin_size_w + + roi_start_w); + int hend = ceil(static_cast(ph + 1) * bin_size_h + + roi_start_h); + int wend = ceil(static_cast(pw + 1) * bin_size_w + + roi_start_w); + // Add roi offsets and clip to input boundaries + hstart = min(max(hstart, 0), height); + hend = min(max(hend, 0), height); + wstart = min(max(wstart, 0), width); + wend = min(max(wend, 0), width); + bool is_empty = (hend <= hstart) || (wend <= wstart); + + // Compute c at bottom + int gw = floor(static_cast(pw)* group_size / pooled_width); + int gh = floor(static_cast(ph)* group_size / pooled_height); + gw = min(max(gw, 0), group_size - 1); + gh = min(max(gh, 0), group_size - 1); + int c = (ctop*group_size + gh)*group_size + gw; + DType* offset_bottom_diff = bottom_diff + (roi_batch_ind * channels + c) * height * width; + DType bin_area = (hend - hstart)*(wend - wstart); + DType diff_val = is_empty ? (DType)0. : top_diff[index] / bin_area; + for (int h = hstart; h < hend; ++h) { + for (int w = wstart; w < wend; ++w) { + int bottom_index = h*width + w; + atomicAdd(offset_bottom_diff + bottom_index, diff_val); + } + } + } +} + + +template +inline void PSROIPoolBackwardAcc(const Tensor &in_grad, + const Tensor &out_grad, + const Tensor &bbox, + const float spatial_scale, + const int output_dim_, + const int group_size_) { + // LOG(INFO) << "PSROIPoolBackward"; + const DType *top_diff = out_grad.dptr_; + const DType *bottom_rois = bbox.dptr_; + DType *bottom_diff = in_grad.dptr_; + const int count = out_grad.shape_.Size(); + const int num_rois = bbox.size(0); + const int channels = in_grad.size(1); + const int height = in_grad.size(2); + const int width = in_grad.size(3); + const int pooled_height = out_grad.size(2); + const int pooled_width = out_grad.size(3); + cudaStream_t stream = Stream::GetStream(in_grad.stream_); + PSROIPoolBackwardAccKernel << > >( + count, top_diff, num_rois, spatial_scale, channels, height, width, + pooled_height, pooled_width, group_size_, output_dim_, bottom_diff, bottom_rois); + PSROIPOOLING_CUDA_CHECK(cudaPeekAtLastError()); +} + +} // namespace cuda + +template +inline void PSROIPoolForward(const Tensor &out, + const Tensor &data, + const Tensor &bbox, + const float spatial_scale, + const int output_dim_, + const int group_size_) { + cuda::PSROIPoolForward(out, data, bbox, spatial_scale, output_dim_, group_size_); +} + +template +inline void PSROIPoolBackwardAcc(const Tensor &in_grad, + const Tensor &out_grad, + const Tensor &bbox, + const float spatial_scale, + const int output_dim_, + const int group_size_) { + cuda::PSROIPoolBackwardAcc(in_grad, out_grad, bbox, spatial_scale, output_dim_, group_size_); +} + +} // namespace mshadow + + +namespace mxnet { +namespace op { + +template<> +Operator* CreateOp(PSROIPoolingParam param, int dtype) { + Operator* op = NULL; + MSHADOW_REAL_TYPE_SWITCH(dtype, DType, { + op = new PSROIPoolingOp(param); + }); + return op; +} + +} // namespace op +} // namespace mxnet diff --git a/src/operator/contrib/quantize-inl.h b/src/operator/contrib/quantize-inl.h index e005762cf0eb..1274a7ded58a 100644 --- a/src/operator/contrib/quantize-inl.h +++ b/src/operator/contrib/quantize-inl.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2017 by Contributors * \file quantize-inl.h * \brief implementation of quantize operation */ diff --git a/src/operator/contrib/quantize.cc b/src/operator/contrib/quantize.cc index 86f35e117882..dbb8985c72f2 100644 --- a/src/operator/contrib/quantize.cc +++ b/src/operator/contrib/quantize.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2017 by Contributors * \file quantize.cc * \brief */ diff --git a/src/operator/contrib/quantize.cu b/src/operator/contrib/quantize.cu index c6d9035c9687..6c9db9aeecf4 100644 --- a/src/operator/contrib/quantize.cu +++ b/src/operator/contrib/quantize.cu @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2017 by Contributors * \file quantize.cu * \brief */ diff --git a/src/operator/convolution-inl.h b/src/operator/convolution-inl.h index 0036befcdb6a..0a2522cccb65 100644 --- a/src/operator/convolution-inl.h +++ b/src/operator/convolution-inl.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2017 by Contributors * \file convolution-inl.h * \brief * \ref: https://github.com/Yangqing/caffe/wiki/Convolution-in-Caffe:-a-memo @@ -59,7 +77,7 @@ struct ConvolutionParam : public dmlc::Parameter { DMLC_DECLARE_FIELD(num_group).set_default(1) .describe("Number of group partitions."); DMLC_DECLARE_FIELD(workspace).set_default(1024).set_range(0, 8192) - .describe("Maximum temperal workspace allowed for convolution (MB)."); + .describe("Maximum temporary workspace allowed for convolution (MB)."); DMLC_DECLARE_FIELD(no_bias).set_default(false) .describe("Whether to disable bias parameter."); DMLC_DECLARE_FIELD(cudnn_tune) diff --git a/src/operator/convolution.cc b/src/operator/convolution.cc index 04e4fef2053a..55cfe4e085dc 100644 --- a/src/operator/convolution.cc +++ b/src/operator/convolution.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2017 by Contributors * \file convolution.cc * \brief * \author Bing Xu, Jun Wu @@ -44,7 +62,6 @@ Operator* CreateOp(ConvolutionParam param, int dtype, break; } } - LOG(INFO) << MKLConvolutionOp::getName() << " Skip MKL optimization"; #endif #if MXNET_USE_NNPACK == 1 const size_t batch_size = (*in_shape)[0][0]; @@ -72,8 +89,6 @@ Operator *ConvolutionProp::CreateOperatorEx(Context ctx, std::vector *in_shape, std::vector *in_type) const { std::vector out_shape, aux_shape; - std::vector out_type, aux_type; - CHECK(InferType(in_type, &out_type, &aux_type)); CHECK(InferShape(in_shape, &out_shape, &aux_shape)); DO_BIND_DISPATCH(CreateOp, param_, (*in_type)[0], in_shape, &out_shape, ctx); } @@ -86,7 +101,7 @@ channel, height, width)*, the output is computed by .. math:: - out[n,i,:,:] = bias[i] + \sum_{j=0}^{num\_filter} data[n,j,:,:] \star + out[n,i,:,:] = bias[i] + \sum_{j=0}^{channel} data[n,j,:,:] \star weight[i,j,:,:] where :math:`\star` is the 2-D cross-correlation operator. @@ -109,13 +124,13 @@ then we have:: If ``no_bias`` is set to be true, then the ``bias`` term is ignored. -The default data ``layout`` is *NCHW*, namely *(batch_size, channle, height, +The default data ``layout`` is *NCHW*, namely *(batch_size, channel, height, width)*. We can choose other layouts such as *NHWC*. If ``num_group`` is larger than 1, denoted by *g*, then split the input ``data`` evenly into *g* parts along the channel axis, and also evenly split ``weight`` along the first dimension. Next compute the convolution on the *i*-th part of -the data with the *i*-th weight part. The output is obtained by concating all +the data with the *i*-th weight part. The output is obtained by concatenating all the *g* results. 1-D convolution does not have *height* dimension but only *width* in space. diff --git a/src/operator/convolution.cu b/src/operator/convolution.cu index 34ae42f31af8..f5777c1714a4 100644 --- a/src/operator/convolution.cu +++ b/src/operator/convolution.cu @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2017 by Contributors * \file convolution.cu * \brief * \author Bing Xu, Jun Wu @@ -11,6 +29,8 @@ #include "./cudnn_convolution-inl.h" #endif // MXNET_USE_CUDNN +#include "./depthwise_convolution-inl.h" + namespace mxnet { namespace op { @@ -27,6 +47,18 @@ Operator* CreateOp(ConvolutionParam param, int dtype, }) return op; } + + // depth wise conv + if (param.num_filter == param.num_group && + param.layout.value() == mshadow::kNCHW && + param.num_filter == (*in_shape)[conv::kData][1] && + param.kernel.ndim() == 2 && + param.dilate == mshadow::Shape2(1, 1) && + dtype == mshadow::kFloat32) { + op = new DepthwiseConvolutionOp(param, *in_shape, *out_shape); + return op; + } + #if MXNET_USE_CUDNN == 1 // The NVIDIA Pascal architecture was the first to include 16-bit ALUs. // Thus, when the framework is compiled with MSHADOW_USE_PASCAL == 1, we @@ -53,14 +85,14 @@ Operator* CreateOp(ConvolutionParam param, int dtype, int backward_compute_type = desired_backward_compute_type; bool convolutionIsSupported = CuDNNConvolutionOp::Supports(param, forward_compute_type, - backward_compute_type); + backward_compute_type, ctx); // If cuDNN can't handle this case with fp16 backprop kernels, try fp32 backprop. if (!convolutionIsSupported && backward_compute_type == mshadow::kFloat16) { backward_compute_type = mshadow::kFloat32; convolutionIsSupported = CuDNNConvolutionOp::Supports(param, forward_compute_type, - backward_compute_type); + backward_compute_type, ctx); } // If cuDNN can't handle this case with fp16 forward kernels, try fp32 @@ -68,16 +100,16 @@ Operator* CreateOp(ConvolutionParam param, int dtype, forward_compute_type = mshadow::kFloat32; convolutionIsSupported = CuDNNConvolutionOp::Supports(param, forward_compute_type, - backward_compute_type); + backward_compute_type, ctx); } if (!convolutionIsSupported) { LOG(WARNING) << "This convolution is not supported by cudnn, MXNET convolution is applied."; op = new ConvolutionOp(param); } else { - if ((forward_compute_type != desired_forward_compute_type) || - (backward_compute_type != desired_backward_compute_type)) - LOG(WARNING) << "True fp16 convolution by cudnn not supported in this configuration. " << - "Falling back to pseudo fp16."; + if (forward_compute_type != desired_forward_compute_type) + LOG(WARNING) << "Requested forward compute precision not supported, using fp32."; + if (backward_compute_type != desired_backward_compute_type) + LOG(WARNING) << "Requested backward compute precision not supported, using fp32."; op = new CuDNNConvolutionOp(param, forward_compute_type, backward_compute_type, diff --git a/src/operator/convolution_v1-inl.h b/src/operator/convolution_v1-inl.h index ee8c8c0462b3..f39d8e0804bc 100644 --- a/src/operator/convolution_v1-inl.h +++ b/src/operator/convolution_v1-inl.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file convolution_v1-inl.h * \brief * \author Bing Xu diff --git a/src/operator/convolution_v1.cc b/src/operator/convolution_v1.cc index a1d115fdae51..cb47ed11b5c9 100644 --- a/src/operator/convolution_v1.cc +++ b/src/operator/convolution_v1.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file convolution_v1.cc * \brief * \author Bing Xu diff --git a/src/operator/convolution_v1.cu b/src/operator/convolution_v1.cu index 83a0f1d0f7df..b20b4b249224 100644 --- a/src/operator/convolution_v1.cu +++ b/src/operator/convolution_v1.cu @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file convolution_v1.cu * \brief * \author Bing Xu diff --git a/src/operator/correlation-inl.h b/src/operator/correlation-inl.h index 6ba209bfb28e..02507cb1d96c 100644 --- a/src/operator/correlation-inl.h +++ b/src/operator/correlation-inl.h @@ -1,236 +1,254 @@ -/*! - * Copyright (c) 2015 by Contributors - * \file correlation-inl.h - * \brief correlation operator and symbol - * \author Xu Dong -*/ -#ifndef MXNET_OPERATOR_CORRELATION_INL_H_ -#define MXNET_OPERATOR_CORRELATION_INL_H_ -#include -#include -#include -#include -#include -#include -#include -#include "./mshadow_op.h" -#include "./operator_common.h" -namespace mxnet { -namespace op { -// Declare enumeration of input order to make code more intuitive. -// These enums are only visible within this header -namespace Correlation { -enum CorrelationOpInputs{kData1, kData2}; -enum CorrelationOpOutputs{kOut, kTemp1, kTemp2}; -} // namespace Correlation -struct CorrelationParam : public dmlc::Parameter { - uint32_t max_displacement; - uint32_t kernel_size; - uint32_t pad_size; - uint32_t stride1; - uint32_t stride2; - bool is_multiply; - DMLC_DECLARE_PARAMETER(CorrelationParam) { - DMLC_DECLARE_FIELD(kernel_size).set_default(1) - .describe("kernel size for Correlation must be an odd number"); - DMLC_DECLARE_FIELD(max_displacement).set_default(1) - .describe("Max displacement of Correlation "); - DMLC_DECLARE_FIELD(stride1).set_default(1) - .describe("stride1 quantize data1 globally"); - DMLC_DECLARE_FIELD(stride2).set_default(1) - .describe("stride2 quantize data2 within the neighborhood centered around data1"); - DMLC_DECLARE_FIELD(pad_size).set_default(0) - .describe("pad for Correlation"); - DMLC_DECLARE_FIELD(is_multiply).set_default(true) - .describe("operation type is either multiplication or subduction"); - } -}; -template -class CorrelationOp : public Operator { - public: - explicit CorrelationOp(CorrelationParam param) { - this->param_ = param; - } - virtual void Forward(const OpContext &ctx, - const std::vector &in_data, - const std::vector &req, - const std::vector &out_data, - const std::vector &aux_args) { - using namespace mshadow; - CHECK_EQ(in_data.size(), 2U); - CHECK_EQ(out_data.size(), 3U); - Stream *s = ctx.get_stream(); - Tensor data1 = in_data[Correlation::kData1].get(s); - Tensor data2 = in_data[Correlation::kData2].get(s); - Tensor out = out_data[Correlation::kOut].get(s); - Tensor tmp1 = out_data[Correlation::kTemp1].get(s); - Tensor tmp2 = out_data[Correlation::kTemp2].get(s); - tmp1 = 0.0f; - tmp2 = 0.0f; - out = 0.0f; - CHECK_EQ(data1.CheckContiguous(), true); - CHECK_EQ(data2.CheckContiguous(), true); - CHECK_EQ(out.CheckContiguous(), true); - CHECK_EQ(tmp1.CheckContiguous(), true); - CHECK_EQ(tmp2.CheckContiguous(), true); - paddedbottomheight = data1.shape_[2] + 2 * param_.pad_size; - paddedbottomwidth = data1.shape_[3] + 2 * param_.pad_size; - kernel_radius_ = (param_.kernel_size - 1) / 2; - border_size_ = param_.max_displacement + kernel_radius_; - stride1 = param_.stride1; - stride2 = param_.stride2; - top_width_ = ceil(static_cast(paddedbottomwidth - border_size_ * 2)\ - / static_cast(stride1)); - top_height_ = ceil(static_cast(paddedbottomheight - border_size_ * 2)\ - / static_cast(stride1)); - neighborhood_grid_radius_ = param_.max_displacement / stride2; - neighborhood_grid_width_ = neighborhood_grid_radius_ * 2 + 1; - top_channels_ = neighborhood_grid_width_ * neighborhood_grid_width_; - num = data1.shape_[0]; - channels = data1.shape_[1]; - height = data1.shape_[2]; - width = data1.shape_[3]; - CorrelationForward(out, data1, data2, tmp1, tmp2, top_channels_, top_height_, top_width_, - param_.pad_size, param_.is_multiply, - param_.max_displacement, param_.kernel_size, - neighborhood_grid_radius_, neighborhood_grid_width_, - kernel_radius_, param_.stride1, param_.stride2); - } - virtual void Backward(const OpContext &ctx, - const std::vector &out_grad, - const std::vector &in_data, - const std::vector &out_data, - const std::vector &req, - const std::vector &in_grad, - const std::vector &aux_args) { - using namespace mshadow; - Stream *s = ctx.get_stream(); - Tensor grad_data1 = in_grad[Correlation::kData1].get(s); - Tensor grad_data2 = in_grad[Correlation::kData2].get(s); - Tensor out_g = out_grad[Correlation::kOut].get(s); - Tensor tmp1 = out_data[Correlation::kTemp1].get(s); - Tensor tmp2 = out_data[Correlation::kTemp2].get(s); - if (req[0] != kAddTo) grad_data1 = 0.0f; - if (req[1] != kAddTo) grad_data2 = 0.0f; - CHECK_EQ(grad_data1.CheckContiguous(), true); - CHECK_EQ(grad_data2.CheckContiguous(), true); - CHECK_EQ(out_g.CheckContiguous(), true); - CHECK_EQ(tmp1.CheckContiguous(), true); - CHECK_EQ(tmp2.CheckContiguous(), true); - CorrelationBackward(out_g, grad_data1, grad_data2, tmp1, tmp2, top_channels_, - top_height_, top_width_, param_.pad_size, param_.is_multiply, - param_.max_displacement, param_.kernel_size, neighborhood_grid_radius_, - neighborhood_grid_width_, kernel_radius_, param_.stride1, param_.stride2, - num, channels, height, width); - } - - private: - CorrelationParam param_; - int paddedbottomheight; - int paddedbottomwidth; - uint32_t kernel_radius_; - uint32_t border_size_; - uint32_t stride1; - uint32_t stride2; - uint32_t top_width_; - uint32_t top_height_; - uint32_t neighborhood_grid_radius_; - uint32_t neighborhood_grid_width_; - uint32_t top_channels_; - int num; - int channels; - int height; - int width; -}; // class CorrelationOp -// Decalre Factory function -template -Operator* CreateOp(CorrelationParam param); -#if DMLC_USE_CXX11 -class CorrelationProp : public OperatorProperty { - public: - std::vector ListArguments() const override { - return {"data1", "data2"}; - } - std::vector ListOutputs() const override { - return {"output", "tmp1", "tmp2"}; - } - int NumOutputs() const override { - return 3; - } - int NumVisibleOutputs() const override { - return 1; - } -void Init(const std::vector >& kwargs) override { - param_.Init(kwargs); - } - std::map GetParams() const override { - return param_.__DICT__(); - } - bool InferShape(std::vector *in_shape, - std::vector *out_shape, - std::vector *aux_shape) const override { - using namespace mshadow; - CHECK_EQ(in_shape->size(), 2U) << "Input:[data1, data2]"; - TShape dshape1 = in_shape->at(Correlation::kData1); - TShape dshape2 = in_shape->at(Correlation::kData2); - CHECK_EQ(dshape1.ndim(), 4U) << "data should be a 4D tensor"; - CHECK_EQ(dshape2.ndim(), 4U) << "data should be a 4D tensor"; - int paddedbottomheight; - int paddedbottomwidth; - uint32_t kernel_radius_; - uint32_t stride1; - uint32_t stride2; - uint32_t top_width_; - uint32_t top_height_; - uint32_t neighborhood_grid_radius_; - uint32_t neighborhood_grid_width_; - uint32_t top_channels_; - uint32_t border_size_; - paddedbottomheight = dshape1[2] + 2*param_.pad_size; - paddedbottomwidth = dshape1[3] + 2*param_.pad_size; - kernel_radius_ = (param_.kernel_size -1)/2; - border_size_ = param_.max_displacement + kernel_radius_; - stride1 = param_.stride1; - stride2 = param_.stride2; - top_width_ = ceil(static_cast(paddedbottomwidth - border_size_ * 2)\ - / static_cast(stride1)); - top_height_ = ceil(static_cast(paddedbottomheight - border_size_ * 2)\ - / static_cast(stride1)); - neighborhood_grid_radius_ = param_.max_displacement / stride2; - neighborhood_grid_width_ = neighborhood_grid_radius_ * 2 + 1; - top_channels_ = neighborhood_grid_width_ * neighborhood_grid_width_; - CHECK_GE(top_width_, 1U) << - "Correlation cannot be done with current settings.Neighborhood and kernel don't fit in blob"; - CHECK_GE(top_height_, 1U) << - "Correlation cannot be done with current settings.Neighborhood and kernel don't fit in blob"; - out_shape->clear(); - out_shape->push_back(Shape4(dshape1[0], top_channels_, top_height_, top_width_)); - out_shape->push_back(Shape4(dshape1[0], paddedbottomheight, paddedbottomwidth, dshape1[1])); - out_shape->push_back(Shape4(dshape1[0], paddedbottomheight, paddedbottomwidth, dshape1[1])); - return true; - } - OperatorProperty* Copy() const override { - CorrelationProp* Correlation_sym = new CorrelationProp(); - Correlation_sym->param_ = this->param_; - return Correlation_sym; - } - std::string TypeString() const override { - return "Correlation"; - } - // decalre dependency and inplace optimization options - std::vector DeclareBackwardDependency( - const std::vector &out_grad, - const std::vector &in_data, - const std::vector &out_data) const override { - return {out_grad[Correlation::kOut], - out_data[Correlation::kTemp1], out_data[Correlation::kTemp2]}; -} - Operator* CreateOperator(Context ctx) const override; - - private: - CorrelationParam param_; -}; // class CorrelationProp -#endif -} // namespace op -} // namespace mxnet -#endif // MXNET_OPERATOR_CORRELATION_INL_H_ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +/*! + * \file correlation-inl.h + * \brief correlation operator and symbol + * \author Xu Dong +*/ +#ifndef MXNET_OPERATOR_CORRELATION_INL_H_ +#define MXNET_OPERATOR_CORRELATION_INL_H_ +#include +#include +#include +#include +#include +#include +#include +#include "./mshadow_op.h" +#include "./operator_common.h" +namespace mxnet { +namespace op { +// Declare enumeration of input order to make code more intuitive. +// These enums are only visible within this header +namespace Correlation { +enum CorrelationOpInputs{kData1, kData2}; +enum CorrelationOpOutputs{kOut, kTemp1, kTemp2}; +} // namespace Correlation +struct CorrelationParam : public dmlc::Parameter { + uint32_t max_displacement; + uint32_t kernel_size; + uint32_t pad_size; + uint32_t stride1; + uint32_t stride2; + bool is_multiply; + DMLC_DECLARE_PARAMETER(CorrelationParam) { + DMLC_DECLARE_FIELD(kernel_size).set_default(1) + .describe("kernel size for Correlation must be an odd number"); + DMLC_DECLARE_FIELD(max_displacement).set_default(1) + .describe("Max displacement of Correlation "); + DMLC_DECLARE_FIELD(stride1).set_default(1) + .describe("stride1 quantize data1 globally"); + DMLC_DECLARE_FIELD(stride2).set_default(1) + .describe("stride2 quantize data2 within the neighborhood centered around data1"); + DMLC_DECLARE_FIELD(pad_size).set_default(0) + .describe("pad for Correlation"); + DMLC_DECLARE_FIELD(is_multiply).set_default(true) + .describe("operation type is either multiplication or subduction"); + } +}; +template +class CorrelationOp : public Operator { + public: + explicit CorrelationOp(CorrelationParam param) { + this->param_ = param; + } + virtual void Forward(const OpContext &ctx, + const std::vector &in_data, + const std::vector &req, + const std::vector &out_data, + const std::vector &aux_args) { + using namespace mshadow; + CHECK_EQ(in_data.size(), 2U); + CHECK_EQ(out_data.size(), 3U); + Stream *s = ctx.get_stream(); + Tensor data1 = in_data[Correlation::kData1].get(s); + Tensor data2 = in_data[Correlation::kData2].get(s); + Tensor out = out_data[Correlation::kOut].get(s); + Tensor tmp1 = out_data[Correlation::kTemp1].get(s); + Tensor tmp2 = out_data[Correlation::kTemp2].get(s); + tmp1 = 0.0f; + tmp2 = 0.0f; + out = 0.0f; + CHECK_EQ(data1.CheckContiguous(), true); + CHECK_EQ(data2.CheckContiguous(), true); + CHECK_EQ(out.CheckContiguous(), true); + CHECK_EQ(tmp1.CheckContiguous(), true); + CHECK_EQ(tmp2.CheckContiguous(), true); + paddedbottomheight = data1.shape_[2] + 2 * param_.pad_size; + paddedbottomwidth = data1.shape_[3] + 2 * param_.pad_size; + kernel_radius_ = (param_.kernel_size - 1) / 2; + border_size_ = param_.max_displacement + kernel_radius_; + stride1 = param_.stride1; + stride2 = param_.stride2; + top_width_ = ceil(static_cast(paddedbottomwidth - border_size_ * 2)\ + / static_cast(stride1)); + top_height_ = ceil(static_cast(paddedbottomheight - border_size_ * 2)\ + / static_cast(stride1)); + neighborhood_grid_radius_ = param_.max_displacement / stride2; + neighborhood_grid_width_ = neighborhood_grid_radius_ * 2 + 1; + top_channels_ = neighborhood_grid_width_ * neighborhood_grid_width_; + num = data1.shape_[0]; + channels = data1.shape_[1]; + height = data1.shape_[2]; + width = data1.shape_[3]; + CorrelationForward(out, data1, data2, tmp1, tmp2, top_channels_, top_height_, top_width_, + param_.pad_size, param_.is_multiply, + param_.max_displacement, param_.kernel_size, + neighborhood_grid_radius_, neighborhood_grid_width_, + kernel_radius_, param_.stride1, param_.stride2); + } + virtual void Backward(const OpContext &ctx, + const std::vector &out_grad, + const std::vector &in_data, + const std::vector &out_data, + const std::vector &req, + const std::vector &in_grad, + const std::vector &aux_args) { + using namespace mshadow; + Stream *s = ctx.get_stream(); + Tensor grad_data1 = in_grad[Correlation::kData1].get(s); + Tensor grad_data2 = in_grad[Correlation::kData2].get(s); + Tensor out_g = out_grad[Correlation::kOut].get(s); + Tensor tmp1 = out_data[Correlation::kTemp1].get(s); + Tensor tmp2 = out_data[Correlation::kTemp2].get(s); + if (req[0] != kAddTo) grad_data1 = 0.0f; + if (req[1] != kAddTo) grad_data2 = 0.0f; + CHECK_EQ(grad_data1.CheckContiguous(), true); + CHECK_EQ(grad_data2.CheckContiguous(), true); + CHECK_EQ(out_g.CheckContiguous(), true); + CHECK_EQ(tmp1.CheckContiguous(), true); + CHECK_EQ(tmp2.CheckContiguous(), true); + CorrelationBackward(out_g, grad_data1, grad_data2, tmp1, tmp2, top_channels_, + top_height_, top_width_, param_.pad_size, param_.is_multiply, + param_.max_displacement, param_.kernel_size, neighborhood_grid_radius_, + neighborhood_grid_width_, kernel_radius_, param_.stride1, param_.stride2, + num, channels, height, width); + } + + private: + CorrelationParam param_; + int paddedbottomheight; + int paddedbottomwidth; + uint32_t kernel_radius_; + uint32_t border_size_; + uint32_t stride1; + uint32_t stride2; + uint32_t top_width_; + uint32_t top_height_; + uint32_t neighborhood_grid_radius_; + uint32_t neighborhood_grid_width_; + uint32_t top_channels_; + int num; + int channels; + int height; + int width; +}; // class CorrelationOp +// Decalre Factory function +template +Operator* CreateOp(CorrelationParam param); +#if DMLC_USE_CXX11 +class CorrelationProp : public OperatorProperty { + public: + std::vector ListArguments() const override { + return {"data1", "data2"}; + } + std::vector ListOutputs() const override { + return {"output", "tmp1", "tmp2"}; + } + int NumOutputs() const override { + return 3; + } + int NumVisibleOutputs() const override { + return 1; + } +void Init(const std::vector >& kwargs) override { + param_.Init(kwargs); + } + std::map GetParams() const override { + return param_.__DICT__(); + } + bool InferShape(std::vector *in_shape, + std::vector *out_shape, + std::vector *aux_shape) const override { + using namespace mshadow; + CHECK_EQ(in_shape->size(), 2U) << "Input:[data1, data2]"; + TShape dshape1 = in_shape->at(Correlation::kData1); + TShape dshape2 = in_shape->at(Correlation::kData2); + CHECK_EQ(dshape1.ndim(), 4U) << "data should be a 4D tensor"; + CHECK_EQ(dshape2.ndim(), 4U) << "data should be a 4D tensor"; + int paddedbottomheight; + int paddedbottomwidth; + uint32_t kernel_radius_; + uint32_t stride1; + uint32_t stride2; + uint32_t top_width_; + uint32_t top_height_; + uint32_t neighborhood_grid_radius_; + uint32_t neighborhood_grid_width_; + uint32_t top_channels_; + uint32_t border_size_; + paddedbottomheight = dshape1[2] + 2*param_.pad_size; + paddedbottomwidth = dshape1[3] + 2*param_.pad_size; + kernel_radius_ = (param_.kernel_size -1)/2; + border_size_ = param_.max_displacement + kernel_radius_; + stride1 = param_.stride1; + stride2 = param_.stride2; + top_width_ = ceil(static_cast(paddedbottomwidth - border_size_ * 2)\ + / static_cast(stride1)); + top_height_ = ceil(static_cast(paddedbottomheight - border_size_ * 2)\ + / static_cast(stride1)); + neighborhood_grid_radius_ = param_.max_displacement / stride2; + neighborhood_grid_width_ = neighborhood_grid_radius_ * 2 + 1; + top_channels_ = neighborhood_grid_width_ * neighborhood_grid_width_; + CHECK_GE(top_width_, 1U) << + "Correlation cannot be done with current settings.Neighborhood and kernel don't fit in blob"; + CHECK_GE(top_height_, 1U) << + "Correlation cannot be done with current settings.Neighborhood and kernel don't fit in blob"; + out_shape->clear(); + out_shape->push_back(Shape4(dshape1[0], top_channels_, top_height_, top_width_)); + out_shape->push_back(Shape4(dshape1[0], paddedbottomheight, paddedbottomwidth, dshape1[1])); + out_shape->push_back(Shape4(dshape1[0], paddedbottomheight, paddedbottomwidth, dshape1[1])); + return true; + } + OperatorProperty* Copy() const override { + CorrelationProp* Correlation_sym = new CorrelationProp(); + Correlation_sym->param_ = this->param_; + return Correlation_sym; + } + std::string TypeString() const override { + return "Correlation"; + } + // decalre dependency and inplace optimization options + std::vector DeclareBackwardDependency( + const std::vector &out_grad, + const std::vector &in_data, + const std::vector &out_data) const override { + return {out_grad[Correlation::kOut], + out_data[Correlation::kTemp1], out_data[Correlation::kTemp2]}; +} + Operator* CreateOperator(Context ctx) const override; + + private: + CorrelationParam param_; +}; // class CorrelationProp +#endif +} // namespace op +} // namespace mxnet +#endif // MXNET_OPERATOR_CORRELATION_INL_H_ diff --git a/src/operator/correlation.cc b/src/operator/correlation.cc index 7b762af78149..2522cd45c414 100644 --- a/src/operator/correlation.cc +++ b/src/operator/correlation.cc @@ -1,144 +1,193 @@ -/*! - * Copyright (c) 2015 by Contributors - * \file correlation.cc - * \brief correlation op - * \author Xu Dong -*/ -#include "./correlation-inl.h" -#include "./mshadow_op.h" - -namespace mshadow { -template -void AddPad(const Tensor &original, - const Tensor &out, - int pad_size) -{ for (index_t nbatch = 0 ; nbatch < original.size(0) ; nbatch++) - for (index_t channel = 0 ; channel < original.size(1) ; channel++) - for (index_t h = 0 ; h < original.size(2) ; h++) - for (index_t w = 0 ; w < original.size(3) ; w++) - out[nbatch][h+pad_size][w+pad_size][channel] = original[nbatch][channel][h][w]; -} -template -inline void CorrelationForward(const Tensor &out, - const Tensor &data1, - const Tensor &data2, - const Tensor &tmp1, - const Tensor &tmp2, - int top_channels_, int top_height_, int top_width_, - int pad_size_, bool is_multiply, - int max_displacement_, int kernel_size_, - int neighborhood_grid_radius_, int neighborhood_grid_width_, - int kernel_radius_, int stride1_, int stride2_) { - const index_t bnum = data1.size(0); - const int bchannels = data1.size(1); - const int sumelems = kernel_size_ * kernel_size_ * bchannels; - AddPad(data1, tmp1, pad_size_); - AddPad(data2, tmp2, pad_size_); - for (index_t i = 0 ; i < static_cast(top_height_) ; i++) - for (index_t j = 0 ; j < static_cast(top_width_); j++) - for (index_t nbatch = 0 ; nbatch < bnum ; nbatch++) { - int x1 = j*stride1_+max_displacement_; - int y1 = i*stride1_+max_displacement_; - for (index_t top_channel = 0 ; top_channel < top_channels_ ; top_channel++) { - int s2o = (top_channel % neighborhood_grid_width_ -\ - neighborhood_grid_radius_) * stride2_; - int s2p = (top_channel / neighborhood_grid_width_ -\ - neighborhood_grid_radius_) * stride2_; - int x2 = x1 + s2o; - int y2 = y1 + s2p; - for (index_t h = 0; h < kernel_size_; h++) - for (index_t w = 0; w < kernel_size_; w++) - for (index_t channel = 0; channel < bchannels; channel++) { - if (is_multiply == true) - out[nbatch][top_channel][i][j] += \ - tmp1[nbatch][y1+h][x1+w][channel]*tmp2[nbatch][y2+h][x2+w][channel]; - else - out[nbatch][top_channel][i][j] += \ - fabsf(tmp1[nbatch][y1+h][x1+w][channel]-tmp2[nbatch][y2+h][x2+w][channel]); - } - out[nbatch][top_channel][i][j] /= sumelems; - } - } -} -template -inline void CorrelationBackward(const Tensor &out_grad, - const Tensor &in_grad1, - const Tensor &in_grad2, - const Tensor &tmp1, - const Tensor &tmp2, - int top_channels_, int top_height_, - int top_width_, int pad_size_, - bool is_multiply, int max_displacement_, - int kernel_size_, int neighborhood_grid_radius_, - int neighborhood_grid_width_, - int kernel_radius_, int stride1_, - int stride2_, int num, - int channels, int height, int width - ) { - const float sumelems = kernel_size_ * kernel_size_ * channels; - for (int i = 0 ; i < static_cast(top_height_) ; i++) - for (int j = 0 ; j < static_cast(top_width_); j++) - for (int nbatch = 0 ; nbatch < static_cast(num) ; nbatch++) { - int x1 = j*stride1_+max_displacement_; - int y1 = i*stride1_+max_displacement_; - for (int top_channel = 0 ; top_channel < top_channels_ ; top_channel++) { - int s2o = (top_channel % neighborhood_grid_width_ - \ - neighborhood_grid_radius_) * stride2_; - int s2p = (top_channel / neighborhood_grid_width_ - \ - neighborhood_grid_radius_) * stride2_; - int x2 = x1 + s2o; - int y2 = y1 + s2p; - for (int h = 0; h < kernel_size_; h++) - for (int w = 0; w < kernel_size_; w++) - for (int channel = 0 ; channel < channels; channel++) { - if (is_multiply == true) { - if ((y1 + h - pad_size_ >= 0) && (x1 + w - pad_size_ >= 0) && \ - (y1 + h < height +pad_size_) && (x1 + w < width + pad_size_)) { - in_grad1[nbatch][channel][y1+h-pad_size_][x1+w-pad_size_] += \ - out_grad[nbatch][top_channel][i][j] * \ - tmp2[nbatch][y2+h][x2+w][channel]/sumelems; - } - if ((y2 + h - pad_size_ >= 0) && (x2 + w -pad_size_ >=0) && \ - (y2 + h < height +pad_size_) && (x2 + w < width + pad_size_)) { - in_grad2[nbatch][channel][y2+h-pad_size_][x2+w-pad_size_] += \ - out_grad[nbatch][top_channel][i][j] * \ - tmp1[nbatch][y1+h][x1+w][channel]/sumelems; - } - } else { - if ((y1 + h - pad_size_ >= 0) && (x1 + w -pad_size_ >=0) && \ - (y1 + h < height + pad_size_) && (x1 + w < width + pad_size_)) { - Dtype sign = (tmp1[nbatch][y1+h][x1+w][channel] >= \ - tmp2[nbatch][y2+h][x2+w][channel])? Dtype(1.0) : Dtype(-1.0); - in_grad1[nbatch][channel][y1+h-pad_size_][x1+w-pad_size_] +=\ - out_grad[nbatch][top_channel][i][j]*sign/sumelems; - } - if ((y2 + h - pad_size_ >= 0) && (x2 + w - pad_size_ >=0) && \ - (y2 + h < height + pad_size_) && (x2 + w < width + pad_size_)) { - Dtype sign = (tmp1[nbatch][y1+h][x1+w][channel] >= \ - tmp2[nbatch][y2+h][x2+w][channel])? Dtype(-1.0) : Dtype(1.0); - in_grad2[nbatch][channel][y2+h-pad_size_][x2+w-pad_size_] +=\ - out_grad[nbatch][top_channel][i][j]*sign/sumelems; - } - } - } - } - } -} -} // namespace mshadow -namespace mxnet { -namespace op { -template<> -Operator *CreateOp(CorrelationParam param) { - return new CorrelationOp(param); -} -Operator* CorrelationProp::CreateOperator(Context ctx) const { - DO_BIND_DISPATCH(CreateOp, param_); -} -DMLC_REGISTER_PARAMETER(CorrelationParam); -MXNET_REGISTER_OP_PROPERTY(Correlation, CorrelationProp) -.describe("Applies correlation to inputs.") -.add_argument("data1", "NDArray-or-Symbol", "Input data1 to the correlation.") -.add_argument("data2", "NDArray-or-Symbol", "Input data2 to the correlation.") -.add_arguments(CorrelationParam::__FIELDS__()); -} // namespace op -} // namespace mxnet +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +/*! + * \file correlation.cc + * \brief correlation op + * \author Xu Dong +*/ +#include "./correlation-inl.h" +#include "./mshadow_op.h" + +namespace mshadow { +template +void AddPad(const Tensor &original, + const Tensor &out, + int pad_size) +{ for (index_t nbatch = 0 ; nbatch < original.size(0) ; nbatch++) + for (index_t channel = 0 ; channel < original.size(1) ; channel++) + for (index_t h = 0 ; h < original.size(2) ; h++) + for (index_t w = 0 ; w < original.size(3) ; w++) + out[nbatch][h+pad_size][w+pad_size][channel] = original[nbatch][channel][h][w]; +} +template +inline void CorrelationForward(const Tensor &out, + const Tensor &data1, + const Tensor &data2, + const Tensor &tmp1, + const Tensor &tmp2, + int top_channels_, int top_height_, int top_width_, + int pad_size_, bool is_multiply, + int max_displacement_, int kernel_size_, + int neighborhood_grid_radius_, int neighborhood_grid_width_, + int kernel_radius_, int stride1_, int stride2_) { + const index_t bnum = data1.size(0); + const int bchannels = data1.size(1); + const int sumelems = kernel_size_ * kernel_size_ * bchannels; + AddPad(data1, tmp1, pad_size_); + index_t top_channels_unsigned_ = static_cast(top_channels_); + AddPad(data2, tmp2, pad_size_); + for (index_t i = 0 ; i < static_cast(top_height_) ; i++) + for (index_t j = 0 ; j < static_cast(top_width_); j++) + for (index_t nbatch = 0 ; nbatch < bnum ; nbatch++) { + int x1 = j*stride1_+max_displacement_; + int y1 = i*stride1_+max_displacement_; + for (index_t top_channel = 0 ; top_channel < top_channels_unsigned_ ; top_channel++) { + int s2o = (top_channel % neighborhood_grid_width_ -\ + neighborhood_grid_radius_) * stride2_; + int s2p = (top_channel / neighborhood_grid_width_ -\ + neighborhood_grid_radius_) * stride2_; + int x2 = x1 + s2o; + int y2 = y1 + s2p; + for (index_t h = 0; h < static_cast(kernel_size_); h++) + for (index_t w = 0; w < static_cast(kernel_size_); w++) + for (index_t channel = 0; channel < static_cast(bchannels); channel++) { + if (is_multiply == true) + out[nbatch][top_channel][i][j] += \ + tmp1[nbatch][y1+h][x1+w][channel]*tmp2[nbatch][y2+h][x2+w][channel]; + else + out[nbatch][top_channel][i][j] += \ + fabsf(tmp1[nbatch][y1+h][x1+w][channel]-tmp2[nbatch][y2+h][x2+w][channel]); + } + out[nbatch][top_channel][i][j] /= sumelems; + } + } +} +template +inline void CorrelationBackward(const Tensor &out_grad, + const Tensor &in_grad1, + const Tensor &in_grad2, + const Tensor &tmp1, + const Tensor &tmp2, + int top_channels_, int top_height_, + int top_width_, int pad_size_, + bool is_multiply, int max_displacement_, + int kernel_size_, int neighborhood_grid_radius_, + int neighborhood_grid_width_, + int kernel_radius_, int stride1_, + int stride2_, int num, + int channels, int height, int width + ) { + const float sumelems = kernel_size_ * kernel_size_ * channels; + for (index_t i = 0 ; i < static_cast(top_height_) ; i++) + for (index_t j = 0 ; j < static_cast(top_width_); j++) + for (index_t nbatch = 0 ; nbatch < static_cast(num) ; nbatch++) { + int x1 = j*stride1_+max_displacement_; + int y1 = i*stride1_+max_displacement_; + for (int top_channel = 0 ; top_channel < top_channels_ ; top_channel++) { + int s2o = (top_channel % neighborhood_grid_width_ - \ + neighborhood_grid_radius_) * stride2_; + int s2p = (top_channel / neighborhood_grid_width_ - \ + neighborhood_grid_radius_) * stride2_; + int x2 = x1 + s2o; + int y2 = y1 + s2p; + for (int h = 0; h < kernel_size_; h++) + for (int w = 0; w < kernel_size_; w++) + for (int channel = 0 ; channel < channels; channel++) { + if (is_multiply == true) { + if ((y1 + h - pad_size_ >= 0) && (x1 + w - pad_size_ >= 0) && \ + (y1 + h < height +pad_size_) && (x1 + w < width + pad_size_)) { + in_grad1[nbatch][channel][y1+h-pad_size_][x1+w-pad_size_] += \ + out_grad[nbatch][top_channel][i][j] * \ + tmp2[nbatch][y2+h][x2+w][channel]/sumelems; + } + if ((y2 + h - pad_size_ >= 0) && (x2 + w -pad_size_ >=0) && \ + (y2 + h < height +pad_size_) && (x2 + w < width + pad_size_)) { + in_grad2[nbatch][channel][y2+h-pad_size_][x2+w-pad_size_] += \ + out_grad[nbatch][top_channel][i][j] * \ + tmp1[nbatch][y1+h][x1+w][channel]/sumelems; + } + } else { + if ((y1 + h - pad_size_ >= 0) && (x1 + w -pad_size_ >=0) && \ + (y1 + h < height + pad_size_) && (x1 + w < width + pad_size_)) { + Dtype sign = (tmp1[nbatch][y1+h][x1+w][channel] >= \ + tmp2[nbatch][y2+h][x2+w][channel])? Dtype(1.0) : Dtype(-1.0); + in_grad1[nbatch][channel][y1+h-pad_size_][x1+w-pad_size_] +=\ + out_grad[nbatch][top_channel][i][j]*sign/sumelems; + } + if ((y2 + h - pad_size_ >= 0) && (x2 + w - pad_size_ >=0) && \ + (y2 + h < height + pad_size_) && (x2 + w < width + pad_size_)) { + Dtype sign = (tmp1[nbatch][y1+h][x1+w][channel] >= \ + tmp2[nbatch][y2+h][x2+w][channel])? Dtype(-1.0) : Dtype(1.0); + in_grad2[nbatch][channel][y2+h-pad_size_][x2+w-pad_size_] +=\ + out_grad[nbatch][top_channel][i][j]*sign/sumelems; + } + } + } + } + } +} +} // namespace mshadow +namespace mxnet { +namespace op { +template<> +Operator *CreateOp(CorrelationParam param) { + return new CorrelationOp(param); +} +Operator* CorrelationProp::CreateOperator(Context ctx) const { + DO_BIND_DISPATCH(CreateOp, param_); +} +DMLC_REGISTER_PARAMETER(CorrelationParam); +MXNET_REGISTER_OP_PROPERTY(Correlation, CorrelationProp) +.add_argument("data1", "NDArray-or-Symbol", "Input data1 to the correlation.") +.add_argument("data2", "NDArray-or-Symbol", "Input data2 to the correlation.") +.add_arguments(CorrelationParam::__FIELDS__()) +.describe(R"code(Applies correlation to inputs. + +The correlation layer performs multiplicative patch comparisons between two feature maps. + +Given two multi-channel feature maps :math:`f_{1}, f_{2}`, with :math:`w`, :math:`h`, and :math:`c` being their width, height, and number of channels, +the correlation layer lets the network compare each patch from :math:`f_{1}` with each patch from :math:`f_{2}`. + +For now we consider only a single comparison of two patches. The 'correlation' of two patches centered at :math:`x_{1}` in the first map and +:math:`x_{2}` in the second map is then defined as: + +.. math:: + c(x_{1}, x_{2}) = \sum_{o \in [-k,k] \times [-k,k]} + +for a square patch of size :math:`K:=2k+1`. + +Note that the equation above is identical to one step of a convolution in neural networks, but instead of convolving data with a filter, it convolves data with other +data. For this reason, it has no training weights. + +Computing :math:`c(x_{1}, x_{2})` involves :math:`c * K^{2}` multiplications. Comparing all patch combinations involves :math:`w^{2}*h^{2}` such computations. + +Given a maximum displacement :math:`d`, for each location :math:`x_{1}` it computes correlations :math:`c(x_{1}, x_{2})` only in a neighborhood of size :math:`D:=2d+1`, +by limiting the range of :math:`x_{2}`. We use strides :math:`s_{1}, s_{2}`, to quantize :math:`x_{1}` globally and to quantize :math:`x_{2}` within the neighborhood +centered around :math:`x_{1}`. + +The final output is defined by the following expression: + +.. math:: + out[n, q, i, j] = c(x_{i, j}, x_{q}) + +where :math:`i` and :math:`j` enumerate spatial locations in :math:`f_{1}`, and :math:`q` denotes the :math:`q^{th}` neighborhood of :math:`x_{i,j}`. +)code" ADD_FILELINE); +} // namespace op +} // namespace mxnet diff --git a/src/operator/correlation.cu b/src/operator/correlation.cu index b26ae04f2d0b..149d73f2e83a 100644 --- a/src/operator/correlation.cu +++ b/src/operator/correlation.cu @@ -1,609 +1,628 @@ -/*! - * Copyright [2016] - * \file Correation.cu - * \brief Correlation operator - * \author Xu Dong -*/ -#include "./correlation-inl.h" -#include -#include -#include -#include - -#define ROUND_OFF 50000 -#define WARPS_PER_BLOCK 1 -#define THREADS_PER_WARP 32 -#define CORRELATION_CUDA_CHECK(condition) \ - /* Code block avoids redefinition of cudaError_t error */ \ - do { \ - cudaError_t error = condition; \ - CHECK_EQ(error, cudaSuccess) << " " << cudaGetErrorString(error); \ - } while (0) -#define CUDA_KERNEL_LOOP(i, n) \ -for (int i = blockIdx.x * blockDim.x + threadIdx.x; \ - i < (n); \ - i += blockDim.x * gridDim.x) -namespace mshadow { -namespace cuda { -// == Correlation Kernel -template -__global__ void CorrelateData(const int nthreads, int num, int topwidth, - int topheight, int topchannels, int topcount, - int max_displacement, int neighborhood_grid_radius, - int neighborhood_grid_width, int kernel_radius, int kernel_size, int stride1, int stride2, - int bottomwidth, int bottomheight, int bottomchannels, - const Dtype *bottom0, const Dtype *bottom1, Dtype *top) { - extern __shared__ char patch_data_char[]; - Dtype *patch_data = reinterpret_cast(patch_data_char); - // First (upper left) position of kernel upper-left corner - // in current center position of neighborhood in image 1 - int x1 = blockIdx.x * stride1 + max_displacement; - int y1 = blockIdx.y * stride1 + max_displacement; - int item = blockIdx.z; - int ch_off = threadIdx.x; - // Load 3D patch into shared shared memory - for (int j = 0; j < kernel_size; j++) { // HEIGHT - for (int i = 0; i < kernel_size; i++) { // WIDTH - int ji_off = ((j * kernel_size) + i) * bottomchannels; - for (int ch = ch_off; ch < bottomchannels; ch += (THREADS_PER_WARP * WARPS_PER_BLOCK)) { - // CHANNELS - int idx1 = ((item * bottomheight + y1+j) * bottomwidth + x1+i) * bottomchannels + ch; - int idxPatchData = ji_off + ch; - patch_data[idxPatchData] = bottom0[idx1]; - } - } - } - __syncthreads(); - __shared__ Dtype sum[THREADS_PER_WARP * WARPS_PER_BLOCK]; - // Compute correlation - for (int top_channel = 0; top_channel < topchannels; top_channel++) { - sum[ch_off] = 0; - int s2o = (top_channel % neighborhood_grid_width - neighborhood_grid_radius) * stride2; - int s2p = (top_channel / neighborhood_grid_width - neighborhood_grid_radius) * stride2; - for (int j = 0; j < kernel_size; j++) { // HEIGHT - for (int i = 0; i < kernel_size; i++) { // WIDTH - int ji_off = ((j * kernel_size) + i) * bottomchannels; - for (int ch = ch_off; ch < bottomchannels; ch += (THREADS_PER_WARP * WARPS_PER_BLOCK)) { - // CHANNELS - int x2 = x1 + s2o; - int y2 = y1 + s2p; - int idxPatchData = ji_off + ch; - int idx2 = ((item * bottomheight + y2 + j) * bottomwidth + x2 + i) * bottomchannels + ch; - sum[ch_off] += patch_data[idxPatchData] * bottom1[idx2]; - } - } - } - __syncthreads(); - if (ch_off == 0) { - Dtype total_sum = 0; - for (int idx = 0; idx < THREADS_PER_WARP * WARPS_PER_BLOCK; idx++) { - total_sum += sum[idx]; - } - const int sumelems = kernel_size * kernel_size * bottomchannels; - const int index = ((top_channel * topheight + blockIdx.y) * topwidth) + blockIdx.x; - top[index + item*topcount] = total_sum / static_cast(sumelems); - } // Aggregate result of different threads - } -} -// == Correlation Backward Pass Kernel (For data1) -template -__global__ void CorrelateDataBackward0(const int nthreads, int num, int item, - int topwidth, int topheight, int topchannels, - int max_displacement, int neighborhood_grid_radius, - int neighborhood_grid_width, int kernel_radius, int stride1, int stride2, - int bottomwidth, int bottomheight, int pbottomwidth, int pbottomheight, - int bottomchannels, int bottomcount, int pad_size, - Dtype *bottom0diff, const Dtype *bottom1, const Dtype *topdiff) { - CUDA_KERNEL_LOOP(index, nthreads) { - int n = index % bottomchannels; // channels - int l = (index / bottomchannels) % bottomwidth + pad_size; // w-pos - int m = (index / bottomchannels / bottomwidth) % bottomheight + pad_size; // h-pos - // Get X,Y ranges and clamp - // round_off is a trick to enable integer division with ceil, even for negative numbers - // We use a large offset, for the inner part not to become negative. - const int round_off = ROUND_OFF; - const int round_off_s1 = stride1 * round_off; - // We add round_off before_s1 the int division and subtract round_off after it, - // to ensure the formula matches ceil behavior: - int xmin = (l - 2*kernel_radius - max_displacement + round_off_s1 - 1)\ - / stride1 + 1 - round_off; // ceil (l - 2*kernel_radius - max_displacement) / stride1 - int ymin = (m - 2*kernel_radius - max_displacement + round_off_s1 - 1)\ - / stride1 + 1 - round_off; // ceil (l - 2*kernel_radius - max_displacement) / stride1 - // Same here: - int xmax = (l - max_displacement + round_off_s1) / stride1 - round_off; - // floor (l - max_displacement) / stride1 - int ymax = (m - max_displacement + round_off_s1) / stride1 - round_off; - // floor (m - max_displacement) / stride1 - Dtype sum = 0; - if (xmax >= 0 && ymax >= 0 && (xmin <= topwidth-1) && (ymin <= topheight-1)) { - xmin = max(0, xmin); - xmax = min(topwidth-1, xmax); - ymin = max(0, ymin); - ymax = min(topheight-1, ymax); - for (int p = -neighborhood_grid_radius; p <= neighborhood_grid_radius; p++) { - for (int o = -neighborhood_grid_radius; o <= neighborhood_grid_radius; o++) { - // Get bottom1 data: - int s2o = stride2 * o; - int s2p = stride2 * p; - int idxbot1 = ((item * pbottomheight + (m + s2p)) * pbottomwidth + (l + s2o))\ - * bottomchannels + n; - Dtype bot1tmp = bottom1[idxbot1]; // bottom1[l+s2o,m+s2p,n] - // Index offset for topdiff in following loops: - int op = (p+neighborhood_grid_radius) * neighborhood_grid_width\ - + (o + neighborhood_grid_radius); // index [o,p] - int idxopoffset = (item * topchannels + op); - for (int y = ymin; y <= ymax; y++) { - for (int x = xmin; x <= xmax; x++) { - int idxtopdiff = (idxopoffset * topheight + y) * topwidth + x; // topdiff[x,y,o,p] - sum += topdiff[idxtopdiff] * bot1tmp; - } - } - } - } - } - const int sumelems = (kernel_radius * 2 + 1) * (kernel_radius * 2+1) * bottomchannels; - const int bot0index = ((n * bottomheight) + (m-pad_size)) * bottomwidth + (l-pad_size); - bottom0diff[bot0index + item * bottomcount] = sum / static_cast(sumelems); - } -} -// == Correlation Backward Pass Kernel (For Blob 1) -template -__global__ void CorrelateDataBackward1(const int nthreads, - int num, int item, int topwidth, int topheight, int topchannels, - int max_displacement, int neighborhood_grid_radius, - int neighborhood_grid_width, int kernel_radius, int stride1, int stride2, - int bottomwidth, int bottomheight, int pbottomwidth, int pbottomheight, - int bottomchannels, int bottomcount, int pad_size, - const Dtype *bottom0, Dtype *bottom1diff, const Dtype *topdiff) { - CUDA_KERNEL_LOOP(index, nthreads) { - // int l = index % bottomwidth + pad_size; //w-pos - // int m = (index / bottomwidth) % bottomheight + pad_size; // h-pos - // int n = (index / bottomwidth / bottomheight) % bottomchannels; // channels - int n = index % bottomchannels; // channels - int l = (index / bottomchannels) % bottomwidth + pad_size; // w-pos - int m = (index / bottomchannels / bottomwidth) % bottomheight + pad_size; // h-pos - // round_off is a trick to enable integer division with ceil, even for negative numbers - // We use a large offset, for the inner part not to become negative. - const int round_off = ROUND_OFF; - const int round_off_s1 = stride1 * round_off; - Dtype sum = 0; - for (int p = -neighborhood_grid_radius; p <= neighborhood_grid_radius; p++) { - for (int o = -neighborhood_grid_radius; o <= neighborhood_grid_radius; o++) { - int s2o = stride2 * o; - int s2p = stride2 * p; - // Get X,Y ranges and clamp - // We add round_off before_s1 the int division and subtract round_off after it, - // to ensure the formula matches ceil behavior: - int xmin = (l - 2*kernel_radius - max_displacement - s2o + round_off_s1 - 1)\ - / stride1 + 1 - round_off; - // ceil (l - 2*kernel_radius - max_displacement - s2o) / stride1 - int ymin = (m - 2*kernel_radius - max_displacement - s2p + round_off_s1 - 1)\ - / stride1 + 1 - round_off; - // ceil (l - 2*kernel_radius - max_displacement - s2o) / stride1 - // Same here: - int xmax = (l - max_displacement - s2o + round_off_s1) / stride1 - round_off; - // floor (l - max_displacement - s2o) / stride1 - int ymax = (m - max_displacement - s2p + round_off_s1) / stride1 - round_off; - // floor (m - max_displacement - s2p) / stride1 - if (xmax >= 0 && ymax >= 0 && (xmin <= topwidth - 1) && (ymin <= topheight - 1)) { - xmin = max(0, xmin); - xmax = min(topwidth-1, xmax); - ymin = max(0, ymin); - ymax = min(topheight-1, ymax); - // Get bottom0 data: - int idxbot0 = ((item * pbottomheight + (m - s2p)) \ - * pbottomwidth + (l - s2o)) * bottomchannels + n; - Dtype bot0tmp = bottom0[idxbot0]; // bottom1[l+s2o,m+s2p,n] - // Index offset for topdiff in following loops: - int op = (p+neighborhood_grid_radius) * \ - neighborhood_grid_width + (o+neighborhood_grid_radius); // index [o,p] - int idxOpOffset = (item * topchannels + op); - for (int y = ymin; y <= ymax; y++) { - for (int x = xmin; x <= xmax; x++) { - int idxtopdiff = (idxOpOffset * topheight + y)\ - * topwidth + x; // topdiff[x,y,o,p] - sum += topdiff[idxtopdiff] * bot0tmp; - } - } - } - } - } - const int sumelems = (kernel_radius*2+1)*(kernel_radius*2+1)*bottomchannels; - const int bot1index = ((n * bottomheight) + (m - pad_size)) * bottomwidth + (l - pad_size); - bottom1diff[bot1index + item * bottomcount] = sum / static_cast(sumelems); - } -} -// == Correlation Kernel Subtraction -template -__global__ void CorrelateDataSubtract(const int nthreads, int num, int item, - int topwidth, int topheight, int topchannels, int topcount, - int max_displacement, int neighborhood_grid_radius, - int neighborhood_grid_width, int kernel_radius, int stride1, int stride2, - int bottomwidth, int bottomheight, int bottomchannels, - const Dtype *bottom0, const Dtype *bottom1, Dtype *top) { - CUDA_KERNEL_LOOP(index, nthreads) { - int x = index % topwidth; // w-pos - int y = (index / topwidth) % topheight; // h-pos - int c = (index / topwidth / topheight) % topchannels; // channels - // Offset of patch in image 2 - int s2o = (c % neighborhood_grid_width - neighborhood_grid_radius) * stride2; - int s2p = (c / neighborhood_grid_width - neighborhood_grid_radius) * stride2; - // First (upper left) position of kernel center in current neighborhood in image 1 - int x1 = x*stride1 + kernel_radius + max_displacement; - int y1 = y*stride1 + kernel_radius + max_displacement; - // Iterate through 3D patch - Dtype sum = 0; - for (int j = -kernel_radius; j <= kernel_radius; j++) { // HEIGHT - for (int i = -kernel_radius; i <= kernel_radius; i++) { // WIDTH - for (int l = 0; l < bottomchannels; l++) { // CHANNELS - // Calculate position in image 2 - int x2 = x1 + s2o; - int y2 = y1 + s2p; - // Indices in bottom data: (CH=l,W=x2,H=y2,N) - int idx1 = ((item * bottomheight + y1 + j) * bottomwidth + x1 + i) \ - * bottomchannels + l; - int idx2 = ((item * bottomheight + y2 + j) * bottomwidth + x2 + i) \ - * bottomchannels + l; - // Do the correlation: - sum += fabsf(bottom0[idx1] - bottom1[idx2]); - } - } - } - const int sumelems = (kernel_radius * 2 + 1) * (kernel_radius * 2 + 1) * bottomchannels; - top[index + item * topcount] = sum / static_cast(sumelems); - } -} -// == Correlation Backward Pass Kernel (For Blob 0) -template -__global__ void CorrelateDataBackward0Subtract(const int nthreads, int num, - int item, int topwidth, int topheight, int topchannels, - int max_displacement, int neighborhood_grid_radius, - int neighborhood_grid_width, int kernel_radius, - int stride1, int stride2, int bottomwidth, int bottomheight, - int pbottomwidth, int pbottomheight, - int bottomchannels, int bottomcount, int pad_size, - Dtype *bottom0diff, const Dtype *bottom0, const Dtype *bottom1, const Dtype *topdiff) { - CUDA_KERNEL_LOOP(index, nthreads) { - int n = index % bottomchannels; // channels - int l = (index / bottomchannels) % bottomwidth + pad_size; // w-pos - int m = (index / bottomchannels / bottomwidth) % bottomheight + pad_size; // h-pos - // Get X,Y ranges and clamp - // round_off is a trick to enable integer division with ceil, even for negative numbers - // We use a large offset, for the inner part not to become negative. - const int round_off = ROUND_OFF; - const int round_off_s1 = stride1 * round_off; - int idxbot0 = ((item * pbottomheight + m) * pbottomwidth + l)\ - * bottomchannels + n; - // We add round_off before_s1 the int division and subtract round_off after it, - // to ensure the formula matches ceil behavior: - int xmin = (l - 2*kernel_radius - max_displacement + round_off_s1 - 1)\ - / stride1 + 1 - round_off; // ceil (l - 2*kernel_radius - max_displacement) / stride1 - int ymin = (m - 2*kernel_radius - max_displacement + round_off_s1 - 1)\ - / stride1 + 1 - round_off; // ceil (l - 2*kernel_radius - max_displacement) / stride1 - // Same here: - int xmax = (l - max_displacement + round_off_s1) / stride1 - round_off; - // floor (l - max_displacement) / stride1 - int ymax = (m - max_displacement + round_off_s1) / stride1 - round_off; - // floor (m - max_displacement) / stride1 - Dtype sum = 0; - if (xmax >= 0 && ymax >= 0 && (xmin <= topwidth-1) && (ymin <= topheight-1)) { - xmin = max(0, xmin); - xmax = min(topwidth-1, xmax); - ymin = max(0, ymin); - ymax = min(topheight-1, ymax); - for (int p = -neighborhood_grid_radius; p <= neighborhood_grid_radius; p++) { - for (int o = -neighborhood_grid_radius; o <= neighborhood_grid_radius; o++) { - // Get bottom1 data: - int s2o = stride2 * o; - int s2p = stride2 * p; - int idxbot1 = ((item * pbottomheight + (m+s2p)) * pbottomwidth\ - + (l+s2o)) * bottomchannels + n; - Dtype bot0tmp = bottom0[idxbot0]; - Dtype bot1tmp = bottom1[idxbot1]; - Dtype sign = (bot0tmp >= bot1tmp) ? Dtype(1.0) : Dtype(-1.0); - // Index offset for topdiff in following loops: - int op = (p+neighborhood_grid_radius) * neighborhood_grid_width\ - + (o + neighborhood_grid_radius); // index [o,p] - int idxopoffset = (item * topchannels + op); - for (int y = ymin; y <= ymax; y++) { - for (int x = xmin; x <= xmax; x++) { - int idxtopdiff = (idxopoffset * topheight + y) * topwidth + x; // topdiff[x,y,o,p] - sum += topdiff[idxtopdiff] * sign; - } - } - } - } - } - const int sumelems = (kernel_radius * 2 + 1) * (kernel_radius * 2+1) * bottomchannels; - const int bot0index = ((n * bottomheight) + (m-pad_size)) * bottomwidth + (l-pad_size); - bottom0diff[bot0index + item * bottomcount] = sum / static_cast(sumelems); - } -} -// == Correlation Backward Pass Kernel (For Blob 1) -template -__global__ void CorrelateDataBackward1Subtract(const int nthreads, int num, - int item, int topwidth, int topheight, int topchannels, - int max_displacement, int neighborhood_grid_radius, - int neighborhood_grid_width, int kernel_radius, - int stride1, int stride2, int bottomwidth, int bottomheight, - int pbottomwidth, int pbottomheight, int bottomchannels, - int bottomcount, int pad_size, const Dtype *bottom0, - const Dtype *bottom1, Dtype *bottom1diff, const Dtype *topdiff) { - CUDA_KERNEL_LOOP(index, nthreads) { - // int l = index % bottomwidth + pad_size; //w-pos - // int m = (index / bottomwidth) % bottomheight + pad_size; // h-pos - // int n = (index / bottomwidth / bottomheight) % bottomchannels; // channels - int n = index % bottomchannels; // channels - int l = (index / bottomchannels) % bottomwidth + pad_size; // w-pos - int m = (index / bottomchannels / bottomwidth) % bottomheight + pad_size; // h-pos - // round_off is a trick to enable integer division with ceil, even for negative numbers - // We use a large offset, for the inner part not to become negative. - const int round_off = ROUND_OFF; - const int round_off_s1 = stride1 * round_off; - Dtype sum = 0; - int idxbot1 = ((item * pbottomheight + m) * pbottomwidth + l)\ - * bottomchannels + n; - for (int p = -neighborhood_grid_radius; p <= neighborhood_grid_radius; p++) { - for (int o = -neighborhood_grid_radius; o <= neighborhood_grid_radius; o++) { - int s2o = stride2 * o; - int s2p = stride2 * p; - // Get X,Y ranges and clamp - // We add round_off before_s1 the int division and subtract round_off after it, - // to ensure the formula matches ceil behavior: - int xmin = (l - 2*kernel_radius - max_displacement - s2o + round_off_s1 - 1)\ - / stride1 + 1 - round_off; - // ceil (l - 2*kernel_radius - max_displacement - s2o) / stride1 - int ymin = (m - 2*kernel_radius - max_displacement - s2p + round_off_s1 - 1)\ - / stride1 + 1 - round_off; - // ceil (l - 2*kernel_radius - max_displacement - s2o) / stride1 - // Same here: - int xmax = (l - max_displacement - s2o + round_off_s1) / stride1 - round_off; - // floor (l - max_displacement - s2o) / stride1 - int ymax = (m - max_displacement - s2p + round_off_s1) / stride1 - round_off; - // floor (m - max_displacement - s2p) / stride1 - if (xmax >= 0 && ymax >= 0 && (xmin <= topwidth - 1) && (ymin <= topheight - 1)) { - xmin = max(0, xmin); - xmax = min(topwidth-1, xmax); - ymin = max(0, ymin); - ymax = min(topheight-1, ymax); - // Get bottom0 data: - int idxbot0 = ((item * pbottomheight + (m - s2p)) * pbottomwidth + (l - s2o))\ - * bottomchannels + n; - // bottom0[l+s2o,m+s2p,n] - Dtype bot0tmp = bottom0[idxbot0]; - Dtype bot1tmp = bottom1[idxbot1]; - Dtype sign = (bot0tmp >= bot1tmp) ? Dtype(-1.0) : Dtype(1.0); - // Index offset for topdiff in following loops: - int op = (p+neighborhood_grid_radius) * \ - neighborhood_grid_width + (o+neighborhood_grid_radius); // index [o,p] - int idxOpOffset = (item * topchannels + op); - for (int y = ymin; y <= ymax; y++) { - for (int x = xmin; x <= xmax; x++) { - int idxtopdiff = (idxOpOffset * topheight + y)\ - * topwidth + x; // topdiff[x,y,o,p] - sum += topdiff[idxtopdiff] * sign; - } - } - } - } - } - const int sumelems = (kernel_radius*2+1)*(kernel_radius*2+1)*bottomchannels; - const int bot1index = ((n * bottomheight) + (m - pad_size)) * bottomwidth + (l - pad_size); - bottom1diff[bot1index + item * bottomcount] = sum / static_cast(sumelems); - } -} -// == Forward -// == Dimension rearrangement Kernel -template -__global__ void blob_rearrange_kernel2(const Dtype* in, Dtype* out, int num, -int channels, int width, int height, int widthheight, int padding, int pwidthheight) { - // change shape from [batchsize,channel,y,x] to [batchsize,y,x,channel] - int xy = blockIdx.x * blockDim.x + threadIdx.x; - if (xy >= widthheight ) - return; - int ch = blockIdx.y; - int n = blockIdx.z; - Dtype value = in[(n * channels + ch) * widthheight + xy]; - __syncthreads(); - int xpad = (xy % width + padding); - int ypad = (xy / width + padding); - int xypad = ypad * (width + 2 * padding) + xpad; - out[(n * pwidthheight + xypad) * channels + ch] = value; -} -template -void Forward_gpu( - const Tensor &out, - const Tensor &data1, - const Tensor &data2, - const Tensor &tmp1, - const Tensor &tmp2, - int top_channels_, int top_height_, int top_width_, int pad_size_, - bool is_multiply, int max_displacement_, int kernel_size_, - int neighborhood_grid_radius_, int neighborhood_grid_width_, - int kernel_radius_, int stride1_, int stride2_, cudaStream_t stream, - cudaStream_t stream_tmp1, cudaStream_t stream_tmp2) { - const Dtype *bottom_data1 = data1.dptr_; - const Dtype *bottom_data2 = data2.dptr_; - Dtype *rbot1 = tmp1.dptr_; - Dtype *rbot2 = tmp2.dptr_; - Dtype *top = out.dptr_; - const int bnum = data1.size(0); - const int bchannels = data1.size(1); - const int bheight = data1.size(2); - const int bwidth = data1.size(3); - const int bwidthheight = bwidth * bheight; - const int topcount = top_width_ * top_height_ * top_channels_; - dim3 threadsPerBlock(THREADS_PER_WARP * WARPS_PER_BLOCK); - int threads_per_block = 16; - dim3 totalBlocksRearr((bwidthheight - 1) / threads_per_block + 1, bchannels, bnum); - const int pwidthheight = (bwidth + 2 * pad_size_) * (bheight + 2 * pad_size_); - blob_rearrange_kernel2<<>> - (bottom_data1, rbot1, bnum, bchannels, bwidth, bheight, bwidthheight, pad_size_, pwidthheight); - blob_rearrange_kernel2<<>> - (bottom_data2, rbot2, bnum, bchannels, bwidth, bheight, bwidthheight, pad_size_, pwidthheight); - const int num = bnum; - const int channels = bchannels; - const int height = bheight + 2 * pad_size_; - const int width = bwidth + 2 * pad_size_; - const int shared_memory_per_block = (kernel_size_ * kernel_size_) * bchannels; - if (is_multiply == true) { - // CorrelationLayer - int topThreadCount = topcount; - dim3 totalBlocksCorr(top_width_, top_height_, num); - CorrelateData<<>>( - topThreadCount, - num, top_width_, top_height_, top_channels_, topcount, - max_displacement_, neighborhood_grid_radius_, - neighborhood_grid_width_, kernel_radius_, kernel_size_, - stride1_, stride2_, - width, height, channels, - rbot1, rbot2, top); - CORRELATION_CUDA_CHECK(cudaPeekAtLastError()); - } else { - // CorrelationLayer - for (int n = 0; n < num; n++) { - int topThreadCount = topcount; - const int gridSize = (topThreadCount + kMaxThreadsPerBlock - 1)\ - / kMaxThreadsPerBlock; - CorrelateDataSubtract<<>>( - topThreadCount, - num, n, top_width_, top_height_, top_channels_, topcount, - max_displacement_, neighborhood_grid_radius_, - neighborhood_grid_width_, kernel_radius_, - stride1_, stride2_, width, height, channels, rbot1, rbot2, top); - CORRELATION_CUDA_CHECK(cudaPeekAtLastError()); - } - } -} -template -void Backward_gpu( - const Tensor &out_grad, - const Tensor &in_grad1, - const Tensor &in_grad2, - const Tensor &tmp1, - const Tensor &tmp2, - int top_channels_, int top_height_, - int top_width_, int pad_size_, bool is_multiply, - int max_displacement_, int kernel_size_, - int neighborhood_grid_radius_, int neighborhood_grid_width_, - int kernel_radius_, int stride1_, int stride2_, - cudaStream_t stream0, cudaStream_t stream1, - int num, int channels, int height, int width) { - // Get top diff, compute bottom diff - const Dtype* top_diff = out_grad.dptr_; - Dtype* bottom0_diff = in_grad1.dptr_; - Dtype* bottom1_diff = in_grad2.dptr_; - const Dtype* rbot1 = tmp1.dptr_; - const Dtype* rbot2 = tmp2.dptr_; - const int paddedheight = height + 2 * pad_size_; - const int paddedwidth = width + 2 * pad_size_; - const int bottomcount = channels * height * width; - int botThreadCount = bottomcount; - const int gridSize = (botThreadCount + kMaxThreadsPerBlock - 1) / kMaxThreadsPerBlock; - // CorrelationLayerBackward - if (is_multiply == true) { - // == Run kernel Backward 0 - dim3 totalBlocksBackward0(width, height, channels * num); // First dim is fastest - const int buffer_size_backw0 = \ - (static_cast(ceil(static_cast(2 * kernel_radius_)\ - / static_cast(stride1_))) + 1) * top_channels_; - // == Run kernel Backward 0 - for (int n = 0; n < num; n++) { - CorrelateDataBackward0<<>>( - botThreadCount, - num, n, top_width_, top_height_, top_channels_, - max_displacement_, neighborhood_grid_radius_, neighborhood_grid_width_, kernel_radius_, - stride1_, stride2_, - width, height, paddedwidth, paddedheight, channels, bottomcount, pad_size_, - bottom0_diff, rbot2, top_diff); - CORRELATION_CUDA_CHECK(cudaPeekAtLastError()); - } - // == Run kernel Backward 1 - for (int n = 0; n < num; n++) { - CorrelateDataBackward1<<>>( - botThreadCount, - num, n, top_width_, top_height_, top_channels_, - max_displacement_, neighborhood_grid_radius_, neighborhood_grid_width_, kernel_radius_, - stride1_, stride2_, - width, height, paddedwidth, paddedheight, channels, bottomcount, pad_size_, - rbot1, bottom1_diff, top_diff); - CORRELATION_CUDA_CHECK(cudaPeekAtLastError()); - } - } else { - for (int n = 0; n < num; n++) { - // Bottom0: - CorrelateDataBackward0Subtract<<>>( - botThreadCount, - num, n, top_width_, top_height_, top_channels_, - max_displacement_, neighborhood_grid_radius_, neighborhood_grid_width_, kernel_radius_, - stride1_, stride2_, - width, height, paddedwidth, paddedheight, channels, bottomcount, pad_size_, - bottom0_diff, rbot1, rbot2, top_diff); - CORRELATION_CUDA_CHECK(cudaPeekAtLastError()); - } - for (int n = 0; n < num; n++) { - // Bottom1: - CorrelateDataBackward1Subtract<<>>( - botThreadCount, - num, n, top_width_, top_height_, top_channels_, - max_displacement_, neighborhood_grid_radius_, neighborhood_grid_width_, kernel_radius_, - stride1_, stride2_, - width, height, paddedwidth, paddedheight, channels, bottomcount, pad_size_, - rbot1, rbot2, bottom1_diff, top_diff); - CORRELATION_CUDA_CHECK(cudaPeekAtLastError()); - } - } -} -} // namespace cuda -template -inline void CorrelationForward(const Tensor &out, - const Tensor &data1, - const Tensor &data2, - const Tensor &tmp1, - const Tensor &tmp2, - int top_channels_, int top_height_, - int top_width_, int pad_size_, bool is_multiply, - int max_displacement_, int kernel_size_, - int neighborhood_grid_radius_, int neighborhood_grid_width_, - int kernel_radius_, int stride1_, int stride2_ - ) { - cudaStream_t stream = Stream::GetStream(out.stream_); - cudaStream_t stream_tmp1 = Stream::GetStream(tmp1.stream_); - cudaStream_t stream_tmp2 = Stream::GetStream(tmp2.stream_); - cuda::Forward_gpu(out, data1, data2, tmp1, tmp2, top_channels_, top_height_, - top_width_, pad_size_, is_multiply, max_displacement_, kernel_size_, - neighborhood_grid_radius_, neighborhood_grid_width_, kernel_radius_, - stride1_, stride2_, stream, stream_tmp1, stream_tmp2); -} - -template -inline void CorrelationBackward(const Tensor &out_grad, - const Tensor &in_grad1, - const Tensor &in_grad2, - const Tensor &tmp1, - const Tensor &tmp2, - int top_channels_, int top_height_, - int top_width_, int pad_size_, bool is_multiply, - int max_displacement_, int kernel_size_, - int neighborhood_grid_radius_, int neighborhood_grid_width_, - int kernel_radius_, int stride1_, - int stride2_, int num, int channels, int height, int width - ) { - cudaStream_t stream0 = Stream::GetStream(in_grad1.stream_); - cudaStream_t stream1 = Stream::GetStream(in_grad2.stream_); - cuda::Backward_gpu(out_grad, in_grad1, in_grad2, tmp1, tmp2, top_channels_, - top_height_, top_width_, pad_size_, is_multiply, - max_displacement_, kernel_size_, neighborhood_grid_radius_, - neighborhood_grid_width_, kernel_radius_, stride1_, stride2_, - stream0, stream1, num, channels, height, width); -} -} // namespace mshadow -namespace mxnet { -namespace op { -template<> -Operator* CreateOp(CorrelationParam param) { - return new CorrelationOp(param); -} -} // namespace op -} // namespace mxnet +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +/*! + * Copyright [2016] + * \file Correation.cu + * \brief Correlation operator + * \author Xu Dong +*/ +#include "./correlation-inl.h" +#include +#include +#include +#include + +#define ROUND_OFF 50000 +#define WARPS_PER_BLOCK 1 +#define THREADS_PER_WARP 32 +#define CORRELATION_CUDA_CHECK(condition) \ + /* Code block avoids redefinition of cudaError_t error */ \ + do { \ + cudaError_t error = condition; \ + CHECK_EQ(error, cudaSuccess) << " " << cudaGetErrorString(error); \ + } while (0) +#define CUDA_KERNEL_LOOP(i, n) \ +for (int i = blockIdx.x * blockDim.x + threadIdx.x; \ + i < (n); \ + i += blockDim.x * gridDim.x) +namespace mshadow { +namespace cuda { +// == Correlation Kernel +template +__global__ void CorrelateData(const int nthreads, int num, int topwidth, + int topheight, int topchannels, int topcount, + int max_displacement, int neighborhood_grid_radius, + int neighborhood_grid_width, int kernel_radius, int kernel_size, int stride1, int stride2, + int bottomwidth, int bottomheight, int bottomchannels, + const Dtype *bottom0, const Dtype *bottom1, Dtype *top) { + extern __shared__ char patch_data_char[]; + Dtype *patch_data = reinterpret_cast(patch_data_char); + // First (upper left) position of kernel upper-left corner + // in current center position of neighborhood in image 1 + int x1 = blockIdx.x * stride1 + max_displacement; + int y1 = blockIdx.y * stride1 + max_displacement; + int item = blockIdx.z; + int ch_off = threadIdx.x; + // Load 3D patch into shared shared memory + for (int j = 0; j < kernel_size; j++) { // HEIGHT + for (int i = 0; i < kernel_size; i++) { // WIDTH + int ji_off = ((j * kernel_size) + i) * bottomchannels; + for (int ch = ch_off; ch < bottomchannels; ch += (THREADS_PER_WARP * WARPS_PER_BLOCK)) { + // CHANNELS + int idx1 = ((item * bottomheight + y1+j) * bottomwidth + x1+i) * bottomchannels + ch; + int idxPatchData = ji_off + ch; + patch_data[idxPatchData] = bottom0[idx1]; + } + } + } + __syncthreads(); + __shared__ Dtype sum[THREADS_PER_WARP * WARPS_PER_BLOCK]; + // Compute correlation + for (int top_channel = 0; top_channel < topchannels; top_channel++) { + sum[ch_off] = 0; + int s2o = (top_channel % neighborhood_grid_width - neighborhood_grid_radius) * stride2; + int s2p = (top_channel / neighborhood_grid_width - neighborhood_grid_radius) * stride2; + for (int j = 0; j < kernel_size; j++) { // HEIGHT + for (int i = 0; i < kernel_size; i++) { // WIDTH + int ji_off = ((j * kernel_size) + i) * bottomchannels; + for (int ch = ch_off; ch < bottomchannels; ch += (THREADS_PER_WARP * WARPS_PER_BLOCK)) { + // CHANNELS + int x2 = x1 + s2o; + int y2 = y1 + s2p; + int idxPatchData = ji_off + ch; + int idx2 = ((item * bottomheight + y2 + j) * bottomwidth + x2 + i) * bottomchannels + ch; + sum[ch_off] += patch_data[idxPatchData] * bottom1[idx2]; + } + } + } + __syncthreads(); + if (ch_off == 0) { + Dtype total_sum = 0; + for (int idx = 0; idx < THREADS_PER_WARP * WARPS_PER_BLOCK; idx++) { + total_sum += sum[idx]; + } + const int sumelems = kernel_size * kernel_size * bottomchannels; + const int index = ((top_channel * topheight + blockIdx.y) * topwidth) + blockIdx.x; + top[index + item*topcount] = total_sum / static_cast(sumelems); + } // Aggregate result of different threads + } +} +// == Correlation Backward Pass Kernel (For data1) +template +__global__ void CorrelateDataBackward0(const int nthreads, int num, int item, + int topwidth, int topheight, int topchannels, + int max_displacement, int neighborhood_grid_radius, + int neighborhood_grid_width, int kernel_radius, int stride1, int stride2, + int bottomwidth, int bottomheight, int pbottomwidth, int pbottomheight, + int bottomchannels, int bottomcount, int pad_size, + Dtype *bottom0diff, const Dtype *bottom1, const Dtype *topdiff) { + CUDA_KERNEL_LOOP(index, nthreads) { + int n = index % bottomchannels; // channels + int l = (index / bottomchannels) % bottomwidth + pad_size; // w-pos + int m = (index / bottomchannels / bottomwidth) % bottomheight + pad_size; // h-pos + // Get X,Y ranges and clamp + // round_off is a trick to enable integer division with ceil, even for negative numbers + // We use a large offset, for the inner part not to become negative. + const int round_off = ROUND_OFF; + const int round_off_s1 = stride1 * round_off; + // We add round_off before_s1 the int division and subtract round_off after it, + // to ensure the formula matches ceil behavior: + int xmin = (l - 2*kernel_radius - max_displacement + round_off_s1 - 1)\ + / stride1 + 1 - round_off; // ceil (l - 2*kernel_radius - max_displacement) / stride1 + int ymin = (m - 2*kernel_radius - max_displacement + round_off_s1 - 1)\ + / stride1 + 1 - round_off; // ceil (l - 2*kernel_radius - max_displacement) / stride1 + // Same here: + int xmax = (l - max_displacement + round_off_s1) / stride1 - round_off; + // floor (l - max_displacement) / stride1 + int ymax = (m - max_displacement + round_off_s1) / stride1 - round_off; + // floor (m - max_displacement) / stride1 + Dtype sum = 0; + if (xmax >= 0 && ymax >= 0 && (xmin <= topwidth-1) && (ymin <= topheight-1)) { + xmin = max(0, xmin); + xmax = min(topwidth-1, xmax); + ymin = max(0, ymin); + ymax = min(topheight-1, ymax); + for (int p = -neighborhood_grid_radius; p <= neighborhood_grid_radius; p++) { + for (int o = -neighborhood_grid_radius; o <= neighborhood_grid_radius; o++) { + // Get bottom1 data: + int s2o = stride2 * o; + int s2p = stride2 * p; + int idxbot1 = ((item * pbottomheight + (m + s2p)) * pbottomwidth + (l + s2o))\ + * bottomchannels + n; + Dtype bot1tmp = bottom1[idxbot1]; // bottom1[l+s2o,m+s2p,n] + // Index offset for topdiff in following loops: + int op = (p+neighborhood_grid_radius) * neighborhood_grid_width\ + + (o + neighborhood_grid_radius); // index [o,p] + int idxopoffset = (item * topchannels + op); + for (int y = ymin; y <= ymax; y++) { + for (int x = xmin; x <= xmax; x++) { + int idxtopdiff = (idxopoffset * topheight + y) * topwidth + x; // topdiff[x,y,o,p] + sum += topdiff[idxtopdiff] * bot1tmp; + } + } + } + } + } + const int sumelems = (kernel_radius * 2 + 1) * (kernel_radius * 2+1) * bottomchannels; + const int bot0index = ((n * bottomheight) + (m-pad_size)) * bottomwidth + (l-pad_size); + bottom0diff[bot0index + item * bottomcount] = sum / static_cast(sumelems); + } +} +// == Correlation Backward Pass Kernel (For Blob 1) +template +__global__ void CorrelateDataBackward1(const int nthreads, + int num, int item, int topwidth, int topheight, int topchannels, + int max_displacement, int neighborhood_grid_radius, + int neighborhood_grid_width, int kernel_radius, int stride1, int stride2, + int bottomwidth, int bottomheight, int pbottomwidth, int pbottomheight, + int bottomchannels, int bottomcount, int pad_size, + const Dtype *bottom0, Dtype *bottom1diff, const Dtype *topdiff) { + CUDA_KERNEL_LOOP(index, nthreads) { + // int l = index % bottomwidth + pad_size; //w-pos + // int m = (index / bottomwidth) % bottomheight + pad_size; // h-pos + // int n = (index / bottomwidth / bottomheight) % bottomchannels; // channels + int n = index % bottomchannels; // channels + int l = (index / bottomchannels) % bottomwidth + pad_size; // w-pos + int m = (index / bottomchannels / bottomwidth) % bottomheight + pad_size; // h-pos + // round_off is a trick to enable integer division with ceil, even for negative numbers + // We use a large offset, for the inner part not to become negative. + const int round_off = ROUND_OFF; + const int round_off_s1 = stride1 * round_off; + Dtype sum = 0; + for (int p = -neighborhood_grid_radius; p <= neighborhood_grid_radius; p++) { + for (int o = -neighborhood_grid_radius; o <= neighborhood_grid_radius; o++) { + int s2o = stride2 * o; + int s2p = stride2 * p; + // Get X,Y ranges and clamp + // We add round_off before_s1 the int division and subtract round_off after it, + // to ensure the formula matches ceil behavior: + int xmin = (l - 2*kernel_radius - max_displacement - s2o + round_off_s1 - 1)\ + / stride1 + 1 - round_off; + // ceil (l - 2*kernel_radius - max_displacement - s2o) / stride1 + int ymin = (m - 2*kernel_radius - max_displacement - s2p + round_off_s1 - 1)\ + / stride1 + 1 - round_off; + // ceil (l - 2*kernel_radius - max_displacement - s2o) / stride1 + // Same here: + int xmax = (l - max_displacement - s2o + round_off_s1) / stride1 - round_off; + // floor (l - max_displacement - s2o) / stride1 + int ymax = (m - max_displacement - s2p + round_off_s1) / stride1 - round_off; + // floor (m - max_displacement - s2p) / stride1 + if (xmax >= 0 && ymax >= 0 && (xmin <= topwidth - 1) && (ymin <= topheight - 1)) { + xmin = max(0, xmin); + xmax = min(topwidth-1, xmax); + ymin = max(0, ymin); + ymax = min(topheight-1, ymax); + // Get bottom0 data: + int idxbot0 = ((item * pbottomheight + (m - s2p)) \ + * pbottomwidth + (l - s2o)) * bottomchannels + n; + Dtype bot0tmp = bottom0[idxbot0]; // bottom1[l+s2o,m+s2p,n] + // Index offset for topdiff in following loops: + int op = (p+neighborhood_grid_radius) * \ + neighborhood_grid_width + (o+neighborhood_grid_radius); // index [o,p] + int idxOpOffset = (item * topchannels + op); + for (int y = ymin; y <= ymax; y++) { + for (int x = xmin; x <= xmax; x++) { + int idxtopdiff = (idxOpOffset * topheight + y)\ + * topwidth + x; // topdiff[x,y,o,p] + sum += topdiff[idxtopdiff] * bot0tmp; + } + } + } + } + } + const int sumelems = (kernel_radius*2+1)*(kernel_radius*2+1)*bottomchannels; + const int bot1index = ((n * bottomheight) + (m - pad_size)) * bottomwidth + (l - pad_size); + bottom1diff[bot1index + item * bottomcount] = sum / static_cast(sumelems); + } +} +// == Correlation Kernel Subtraction +template +__global__ void CorrelateDataSubtract(const int nthreads, int num, int item, + int topwidth, int topheight, int topchannels, int topcount, + int max_displacement, int neighborhood_grid_radius, + int neighborhood_grid_width, int kernel_radius, int stride1, int stride2, + int bottomwidth, int bottomheight, int bottomchannels, + const Dtype *bottom0, const Dtype *bottom1, Dtype *top) { + CUDA_KERNEL_LOOP(index, nthreads) { + int x = index % topwidth; // w-pos + int y = (index / topwidth) % topheight; // h-pos + int c = (index / topwidth / topheight) % topchannels; // channels + // Offset of patch in image 2 + int s2o = (c % neighborhood_grid_width - neighborhood_grid_radius) * stride2; + int s2p = (c / neighborhood_grid_width - neighborhood_grid_radius) * stride2; + // First (upper left) position of kernel center in current neighborhood in image 1 + int x1 = x*stride1 + kernel_radius + max_displacement; + int y1 = y*stride1 + kernel_radius + max_displacement; + // Iterate through 3D patch + Dtype sum = 0; + for (int j = -kernel_radius; j <= kernel_radius; j++) { // HEIGHT + for (int i = -kernel_radius; i <= kernel_radius; i++) { // WIDTH + for (int l = 0; l < bottomchannels; l++) { // CHANNELS + // Calculate position in image 2 + int x2 = x1 + s2o; + int y2 = y1 + s2p; + // Indices in bottom data: (CH=l,W=x2,H=y2,N) + int idx1 = ((item * bottomheight + y1 + j) * bottomwidth + x1 + i) \ + * bottomchannels + l; + int idx2 = ((item * bottomheight + y2 + j) * bottomwidth + x2 + i) \ + * bottomchannels + l; + // Do the correlation: + sum += fabsf(bottom0[idx1] - bottom1[idx2]); + } + } + } + const int sumelems = (kernel_radius * 2 + 1) * (kernel_radius * 2 + 1) * bottomchannels; + top[index + item * topcount] = sum / static_cast(sumelems); + } +} +// == Correlation Backward Pass Kernel (For Blob 0) +template +__global__ void CorrelateDataBackward0Subtract(const int nthreads, int num, + int item, int topwidth, int topheight, int topchannels, + int max_displacement, int neighborhood_grid_radius, + int neighborhood_grid_width, int kernel_radius, + int stride1, int stride2, int bottomwidth, int bottomheight, + int pbottomwidth, int pbottomheight, + int bottomchannels, int bottomcount, int pad_size, + Dtype *bottom0diff, const Dtype *bottom0, const Dtype *bottom1, const Dtype *topdiff) { + CUDA_KERNEL_LOOP(index, nthreads) { + int n = index % bottomchannels; // channels + int l = (index / bottomchannels) % bottomwidth + pad_size; // w-pos + int m = (index / bottomchannels / bottomwidth) % bottomheight + pad_size; // h-pos + // Get X,Y ranges and clamp + // round_off is a trick to enable integer division with ceil, even for negative numbers + // We use a large offset, for the inner part not to become negative. + const int round_off = ROUND_OFF; + const int round_off_s1 = stride1 * round_off; + int idxbot0 = ((item * pbottomheight + m) * pbottomwidth + l)\ + * bottomchannels + n; + // We add round_off before_s1 the int division and subtract round_off after it, + // to ensure the formula matches ceil behavior: + int xmin = (l - 2*kernel_radius - max_displacement + round_off_s1 - 1)\ + / stride1 + 1 - round_off; // ceil (l - 2*kernel_radius - max_displacement) / stride1 + int ymin = (m - 2*kernel_radius - max_displacement + round_off_s1 - 1)\ + / stride1 + 1 - round_off; // ceil (l - 2*kernel_radius - max_displacement) / stride1 + // Same here: + int xmax = (l - max_displacement + round_off_s1) / stride1 - round_off; + // floor (l - max_displacement) / stride1 + int ymax = (m - max_displacement + round_off_s1) / stride1 - round_off; + // floor (m - max_displacement) / stride1 + Dtype sum = 0; + if (xmax >= 0 && ymax >= 0 && (xmin <= topwidth-1) && (ymin <= topheight-1)) { + xmin = max(0, xmin); + xmax = min(topwidth-1, xmax); + ymin = max(0, ymin); + ymax = min(topheight-1, ymax); + for (int p = -neighborhood_grid_radius; p <= neighborhood_grid_radius; p++) { + for (int o = -neighborhood_grid_radius; o <= neighborhood_grid_radius; o++) { + // Get bottom1 data: + int s2o = stride2 * o; + int s2p = stride2 * p; + int idxbot1 = ((item * pbottomheight + (m+s2p)) * pbottomwidth\ + + (l+s2o)) * bottomchannels + n; + Dtype bot0tmp = bottom0[idxbot0]; + Dtype bot1tmp = bottom1[idxbot1]; + Dtype sign = (bot0tmp >= bot1tmp) ? Dtype(1.0) : Dtype(-1.0); + // Index offset for topdiff in following loops: + int op = (p+neighborhood_grid_radius) * neighborhood_grid_width\ + + (o + neighborhood_grid_radius); // index [o,p] + int idxopoffset = (item * topchannels + op); + for (int y = ymin; y <= ymax; y++) { + for (int x = xmin; x <= xmax; x++) { + int idxtopdiff = (idxopoffset * topheight + y) * topwidth + x; // topdiff[x,y,o,p] + sum += topdiff[idxtopdiff] * sign; + } + } + } + } + } + const int sumelems = (kernel_radius * 2 + 1) * (kernel_radius * 2+1) * bottomchannels; + const int bot0index = ((n * bottomheight) + (m-pad_size)) * bottomwidth + (l-pad_size); + bottom0diff[bot0index + item * bottomcount] = sum / static_cast(sumelems); + } +} +// == Correlation Backward Pass Kernel (For Blob 1) +template +__global__ void CorrelateDataBackward1Subtract(const int nthreads, int num, + int item, int topwidth, int topheight, int topchannels, + int max_displacement, int neighborhood_grid_radius, + int neighborhood_grid_width, int kernel_radius, + int stride1, int stride2, int bottomwidth, int bottomheight, + int pbottomwidth, int pbottomheight, int bottomchannels, + int bottomcount, int pad_size, const Dtype *bottom0, + const Dtype *bottom1, Dtype *bottom1diff, const Dtype *topdiff) { + CUDA_KERNEL_LOOP(index, nthreads) { + // int l = index % bottomwidth + pad_size; //w-pos + // int m = (index / bottomwidth) % bottomheight + pad_size; // h-pos + // int n = (index / bottomwidth / bottomheight) % bottomchannels; // channels + int n = index % bottomchannels; // channels + int l = (index / bottomchannels) % bottomwidth + pad_size; // w-pos + int m = (index / bottomchannels / bottomwidth) % bottomheight + pad_size; // h-pos + // round_off is a trick to enable integer division with ceil, even for negative numbers + // We use a large offset, for the inner part not to become negative. + const int round_off = ROUND_OFF; + const int round_off_s1 = stride1 * round_off; + Dtype sum = 0; + int idxbot1 = ((item * pbottomheight + m) * pbottomwidth + l)\ + * bottomchannels + n; + for (int p = -neighborhood_grid_radius; p <= neighborhood_grid_radius; p++) { + for (int o = -neighborhood_grid_radius; o <= neighborhood_grid_radius; o++) { + int s2o = stride2 * o; + int s2p = stride2 * p; + // Get X,Y ranges and clamp + // We add round_off before_s1 the int division and subtract round_off after it, + // to ensure the formula matches ceil behavior: + int xmin = (l - 2*kernel_radius - max_displacement - s2o + round_off_s1 - 1)\ + / stride1 + 1 - round_off; + // ceil (l - 2*kernel_radius - max_displacement - s2o) / stride1 + int ymin = (m - 2*kernel_radius - max_displacement - s2p + round_off_s1 - 1)\ + / stride1 + 1 - round_off; + // ceil (l - 2*kernel_radius - max_displacement - s2o) / stride1 + // Same here: + int xmax = (l - max_displacement - s2o + round_off_s1) / stride1 - round_off; + // floor (l - max_displacement - s2o) / stride1 + int ymax = (m - max_displacement - s2p + round_off_s1) / stride1 - round_off; + // floor (m - max_displacement - s2p) / stride1 + if (xmax >= 0 && ymax >= 0 && (xmin <= topwidth - 1) && (ymin <= topheight - 1)) { + xmin = max(0, xmin); + xmax = min(topwidth-1, xmax); + ymin = max(0, ymin); + ymax = min(topheight-1, ymax); + // Get bottom0 data: + int idxbot0 = ((item * pbottomheight + (m - s2p)) * pbottomwidth + (l - s2o))\ + * bottomchannels + n; + // bottom0[l+s2o,m+s2p,n] + Dtype bot0tmp = bottom0[idxbot0]; + Dtype bot1tmp = bottom1[idxbot1]; + Dtype sign = (bot0tmp >= bot1tmp) ? Dtype(-1.0) : Dtype(1.0); + // Index offset for topdiff in following loops: + int op = (p+neighborhood_grid_radius) * \ + neighborhood_grid_width + (o+neighborhood_grid_radius); // index [o,p] + int idxOpOffset = (item * topchannels + op); + for (int y = ymin; y <= ymax; y++) { + for (int x = xmin; x <= xmax; x++) { + int idxtopdiff = (idxOpOffset * topheight + y)\ + * topwidth + x; // topdiff[x,y,o,p] + sum += topdiff[idxtopdiff] * sign; + } + } + } + } + } + const int sumelems = (kernel_radius*2+1)*(kernel_radius*2+1)*bottomchannels; + const int bot1index = ((n * bottomheight) + (m - pad_size)) * bottomwidth + (l - pad_size); + bottom1diff[bot1index + item * bottomcount] = sum / static_cast(sumelems); + } +} +// == Forward +// == Dimension rearrangement Kernel +template +__global__ void blob_rearrange_kernel2(const Dtype* in, Dtype* out, int num, +int channels, int width, int height, int widthheight, int padding, int pwidthheight) { + // change shape from [batchsize,channel,y,x] to [batchsize,y,x,channel] + int xy = blockIdx.x * blockDim.x + threadIdx.x; + if (xy >= widthheight ) + return; + int ch = blockIdx.y; + int n = blockIdx.z; + Dtype value = in[(n * channels + ch) * widthheight + xy]; + __syncthreads(); + int xpad = (xy % width + padding); + int ypad = (xy / width + padding); + int xypad = ypad * (width + 2 * padding) + xpad; + out[(n * pwidthheight + xypad) * channels + ch] = value; +} +template +void Forward_gpu( + const Tensor &out, + const Tensor &data1, + const Tensor &data2, + const Tensor &tmp1, + const Tensor &tmp2, + int top_channels_, int top_height_, int top_width_, int pad_size_, + bool is_multiply, int max_displacement_, int kernel_size_, + int neighborhood_grid_radius_, int neighborhood_grid_width_, + int kernel_radius_, int stride1_, int stride2_, cudaStream_t stream, + cudaStream_t stream_tmp1, cudaStream_t stream_tmp2) { + const Dtype *bottom_data1 = data1.dptr_; + const Dtype *bottom_data2 = data2.dptr_; + Dtype *rbot1 = tmp1.dptr_; + Dtype *rbot2 = tmp2.dptr_; + Dtype *top = out.dptr_; + const int bnum = data1.size(0); + const int bchannels = data1.size(1); + const int bheight = data1.size(2); + const int bwidth = data1.size(3); + const int bwidthheight = bwidth * bheight; + const int topcount = top_width_ * top_height_ * top_channels_; + dim3 threadsPerBlock(THREADS_PER_WARP * WARPS_PER_BLOCK); + int threads_per_block = 16; + dim3 totalBlocksRearr((bwidthheight - 1) / threads_per_block + 1, bchannels, bnum); + const int pwidthheight = (bwidth + 2 * pad_size_) * (bheight + 2 * pad_size_); + blob_rearrange_kernel2<<>> + (bottom_data1, rbot1, bnum, bchannels, bwidth, bheight, bwidthheight, pad_size_, pwidthheight); + blob_rearrange_kernel2<<>> + (bottom_data2, rbot2, bnum, bchannels, bwidth, bheight, bwidthheight, pad_size_, pwidthheight); + const int num = bnum; + const int channels = bchannels; + const int height = bheight + 2 * pad_size_; + const int width = bwidth + 2 * pad_size_; + const int shared_memory_per_block = (kernel_size_ * kernel_size_) * bchannels; + if (is_multiply == true) { + // CorrelationLayer + int topThreadCount = topcount; + dim3 totalBlocksCorr(top_width_, top_height_, num); + CorrelateData<<>>( + topThreadCount, + num, top_width_, top_height_, top_channels_, topcount, + max_displacement_, neighborhood_grid_radius_, + neighborhood_grid_width_, kernel_radius_, kernel_size_, + stride1_, stride2_, + width, height, channels, + rbot1, rbot2, top); + CORRELATION_CUDA_CHECK(cudaPeekAtLastError()); + } else { + // CorrelationLayer + for (int n = 0; n < num; n++) { + int topThreadCount = topcount; + const int gridSize = (topThreadCount + kMaxThreadsPerBlock - 1)\ + / kMaxThreadsPerBlock; + CorrelateDataSubtract<<>>( + topThreadCount, + num, n, top_width_, top_height_, top_channels_, topcount, + max_displacement_, neighborhood_grid_radius_, + neighborhood_grid_width_, kernel_radius_, + stride1_, stride2_, width, height, channels, rbot1, rbot2, top); + CORRELATION_CUDA_CHECK(cudaPeekAtLastError()); + } + } +} +template +void Backward_gpu( + const Tensor &out_grad, + const Tensor &in_grad1, + const Tensor &in_grad2, + const Tensor &tmp1, + const Tensor &tmp2, + int top_channels_, int top_height_, + int top_width_, int pad_size_, bool is_multiply, + int max_displacement_, int kernel_size_, + int neighborhood_grid_radius_, int neighborhood_grid_width_, + int kernel_radius_, int stride1_, int stride2_, + cudaStream_t stream0, cudaStream_t stream1, + int num, int channels, int height, int width) { + // Get top diff, compute bottom diff + const Dtype* top_diff = out_grad.dptr_; + Dtype* bottom0_diff = in_grad1.dptr_; + Dtype* bottom1_diff = in_grad2.dptr_; + const Dtype* rbot1 = tmp1.dptr_; + const Dtype* rbot2 = tmp2.dptr_; + const int paddedheight = height + 2 * pad_size_; + const int paddedwidth = width + 2 * pad_size_; + const int bottomcount = channels * height * width; + int botThreadCount = bottomcount; + const int gridSize = (botThreadCount + kMaxThreadsPerBlock - 1) / kMaxThreadsPerBlock; + // CorrelationLayerBackward + if (is_multiply == true) { + // == Run kernel Backward 0 + dim3 totalBlocksBackward0(width, height, channels * num); // First dim is fastest + const int buffer_size_backw0 = \ + (static_cast(ceil(static_cast(2 * kernel_radius_)\ + / static_cast(stride1_))) + 1) * top_channels_; + // == Run kernel Backward 0 + for (int n = 0; n < num; n++) { + CorrelateDataBackward0<<>>( + botThreadCount, + num, n, top_width_, top_height_, top_channels_, + max_displacement_, neighborhood_grid_radius_, neighborhood_grid_width_, kernel_radius_, + stride1_, stride2_, + width, height, paddedwidth, paddedheight, channels, bottomcount, pad_size_, + bottom0_diff, rbot2, top_diff); + CORRELATION_CUDA_CHECK(cudaPeekAtLastError()); + } + // == Run kernel Backward 1 + for (int n = 0; n < num; n++) { + CorrelateDataBackward1<<>>( + botThreadCount, + num, n, top_width_, top_height_, top_channels_, + max_displacement_, neighborhood_grid_radius_, neighborhood_grid_width_, kernel_radius_, + stride1_, stride2_, + width, height, paddedwidth, paddedheight, channels, bottomcount, pad_size_, + rbot1, bottom1_diff, top_diff); + CORRELATION_CUDA_CHECK(cudaPeekAtLastError()); + } + } else { + for (int n = 0; n < num; n++) { + // Bottom0: + CorrelateDataBackward0Subtract<<>>( + botThreadCount, + num, n, top_width_, top_height_, top_channels_, + max_displacement_, neighborhood_grid_radius_, neighborhood_grid_width_, kernel_radius_, + stride1_, stride2_, + width, height, paddedwidth, paddedheight, channels, bottomcount, pad_size_, + bottom0_diff, rbot1, rbot2, top_diff); + CORRELATION_CUDA_CHECK(cudaPeekAtLastError()); + } + for (int n = 0; n < num; n++) { + // Bottom1: + CorrelateDataBackward1Subtract<<>>( + botThreadCount, + num, n, top_width_, top_height_, top_channels_, + max_displacement_, neighborhood_grid_radius_, neighborhood_grid_width_, kernel_radius_, + stride1_, stride2_, + width, height, paddedwidth, paddedheight, channels, bottomcount, pad_size_, + rbot1, rbot2, bottom1_diff, top_diff); + CORRELATION_CUDA_CHECK(cudaPeekAtLastError()); + } + } +} +} // namespace cuda +template +inline void CorrelationForward(const Tensor &out, + const Tensor &data1, + const Tensor &data2, + const Tensor &tmp1, + const Tensor &tmp2, + int top_channels_, int top_height_, + int top_width_, int pad_size_, bool is_multiply, + int max_displacement_, int kernel_size_, + int neighborhood_grid_radius_, int neighborhood_grid_width_, + int kernel_radius_, int stride1_, int stride2_ + ) { + cudaStream_t stream = Stream::GetStream(out.stream_); + cudaStream_t stream_tmp1 = Stream::GetStream(tmp1.stream_); + cudaStream_t stream_tmp2 = Stream::GetStream(tmp2.stream_); + cuda::Forward_gpu(out, data1, data2, tmp1, tmp2, top_channels_, top_height_, + top_width_, pad_size_, is_multiply, max_displacement_, kernel_size_, + neighborhood_grid_radius_, neighborhood_grid_width_, kernel_radius_, + stride1_, stride2_, stream, stream_tmp1, stream_tmp2); +} + +template +inline void CorrelationBackward(const Tensor &out_grad, + const Tensor &in_grad1, + const Tensor &in_grad2, + const Tensor &tmp1, + const Tensor &tmp2, + int top_channels_, int top_height_, + int top_width_, int pad_size_, bool is_multiply, + int max_displacement_, int kernel_size_, + int neighborhood_grid_radius_, int neighborhood_grid_width_, + int kernel_radius_, int stride1_, + int stride2_, int num, int channels, int height, int width + ) { + cudaStream_t stream0 = Stream::GetStream(in_grad1.stream_); + cudaStream_t stream1 = Stream::GetStream(in_grad2.stream_); + cuda::Backward_gpu(out_grad, in_grad1, in_grad2, tmp1, tmp2, top_channels_, + top_height_, top_width_, pad_size_, is_multiply, + max_displacement_, kernel_size_, neighborhood_grid_radius_, + neighborhood_grid_width_, kernel_radius_, stride1_, stride2_, + stream0, stream1, num, channels, height, width); +} +} // namespace mshadow +namespace mxnet { +namespace op { +template<> +Operator* CreateOp(CorrelationParam param) { + return new CorrelationOp(param); +} +} // namespace op +} // namespace mxnet diff --git a/src/operator/crop-inl.h b/src/operator/crop-inl.h index 5b5adbf15874..5a8709633f21 100644 --- a/src/operator/crop-inl.h +++ b/src/operator/crop-inl.h @@ -1,214 +1,232 @@ -/*! - * Copyright (c) 2015 by Contributors - * \file crop-inl.h - * \brief - * \author Wei Wu -*/ -#ifndef MXNET_OPERATOR_CROP_INL_H_ -#define MXNET_OPERATOR_CROP_INL_H_ -#include -#include -#include -#include -#include -#include -#include -#include -#include "./operator_common.h" - -namespace mxnet { -namespace op { - -namespace crop_enum { -enum CropOpInputs {kData, kCropLike}; -enum CropOpOutputs {kOut}; -} // namespace crop_enum - -struct CropParam : public dmlc::Parameter { - int num_args; - TShape offset; - TShape h_w; - bool center_crop; - DMLC_DECLARE_PARAMETER(CropParam) { - DMLC_DECLARE_FIELD(num_args).set_range(1, 3) - .describe("Number of inputs for crop, if equals one, then we will use the h_w" - "for crop height and width, else if equals two, then we will use the height" - "and width of the second input symbol, we name crop_like here"); - int shape[] = {0, 0}; - DMLC_DECLARE_FIELD(offset).set_default(TShape(shape, shape + 2)) - .describe("crop offset coordinate: (y, x)"); - DMLC_DECLARE_FIELD(h_w).set_default(TShape(shape, shape + 2)) - .describe("crop height and width: (h, w)"); - DMLC_DECLARE_FIELD(center_crop).set_default(false) - .describe("If set to true, then it will use be the center_crop," - "or it will crop using the shape of crop_like"); - } -}; // struct CropParam - -template -class CropOp : public Operator { - public: - explicit CropOp(CropParam param) { - this->param_ = param; - } - - virtual void Forward(const OpContext &ctx, - const std::vector &in_data, - const std::vector &req, - const std::vector &out_data, - const std::vector &aux_args) { - using namespace mshadow; - using namespace mshadow::expr; - CHECK_EQ(static_cast(in_data.size()), param_.num_args); - CHECK_EQ(out_data.size(), 1U); - CHECK_EQ(req[crop_enum::kOut], kWriteTo); - Stream *s = ctx.get_stream(); - Tensor data = in_data[crop_enum::kData].get(s); - Tensor out = out_data[crop_enum::kOut].get(s); - offset_hw_ = InferCropOfferset(data.shape_, out.shape_); - out = crop(data, Shape2(out.size(2), out.size(3)), offset_hw_[0], offset_hw_[1]); - } - - // because the crop_like input is only used with it's shape, so we should be - // careful setting its backwrd grad value to zeros, so that it will not hurt - // the connection of crop_like. - virtual void Backward(const OpContext &ctx, - const std::vector &out_grad, - const std::vector &in_data, - const std::vector &out_data, - const std::vector &req, - const std::vector &in_grad, - const std::vector &aux_states) { - using namespace mshadow; - using namespace mshadow::expr; - CHECK_EQ(in_grad.size(), static_cast(param_.num_args)) << in_grad.size(); - CHECK_EQ(out_grad.size(), 1U) << out_grad.size(); - Stream *s = ctx.get_stream(); - Tensor grad = out_grad[crop_enum::kOut].get(s); - Tensor gdata = in_grad[crop_enum::kData].get(s); - if (param_.num_args > 1) { - // here backward grad is set to zero for crop_like - // however, this should only be done when num_args > 1, i.e., crop_like exists - Tensor gcrop_like = in_grad[crop_enum::kCropLike].get(s); - gcrop_like = (real_t)0.0f; - } - offset_hw_ = InferCropOfferset(gdata.shape_, grad.shape_); - gdata = (real_t)0.0f; - slice<3>(slice<2>(gdata, offset_hw_[0], offset_hw_[0]+grad.size(2)), - offset_hw_[1], offset_hw_[1]+grad.size(3)) = grad; - } - - private: - CropParam param_; - std::vector offset_hw_; - std::vector InferCropOfferset(const mshadow::Shape<4> &data_shape, - const mshadow::Shape<4> &out_shape) { - std::vector offset_hw; - CHECK_GE(data_shape[2], out_shape[2]) << - "data_shape'height should be larger than that of out_shape"; - CHECK_GE(data_shape[3], out_shape[3]) << - "data_shape'weight should be larger than that of out_shape"; - if (param_.center_crop) { - offset_hw.push_back(static_cast((data_shape[2]-out_shape[2])/2)); - offset_hw.push_back(static_cast((data_shape[3]-out_shape[3])/2)); - } else { - CHECK_GE(static_cast(param_.offset[0]), 0) << - "offset[0] should be larger than 0"; - CHECK_LE(param_.offset[0], data_shape[2]-out_shape[2]) << - "offset[0] should be less than the residual space of height"; - CHECK_GE(static_cast(param_.offset[1]), 0) << - "offset[1] should be larger than 0"; - CHECK_LE(param_.offset[1], data_shape[3]-out_shape[3]) << - "offset[1] should be less than the residual space of width"; - offset_hw.push_back(static_cast(param_.offset[0])); - offset_hw.push_back(static_cast(param_.offset[1])); - } - return offset_hw; - } -}; // class CropOp - -template -Operator *CreateOp(CropParam param); - -#if DMLC_USE_CXX11 -class CropProp : public OperatorProperty { - public: - void Init(const std::vector >& kwargs) override { - param_.Init(kwargs); - } - - std::map GetParams() const override { - return param_.__DICT__(); - } - - std::vector ListArguments() const override { - // return {"data", "crop_like"}; - std::vector ret; - for (int i = 0; i < param_.num_args; ++i) { - ret.push_back(std::string("arg") + std::to_string(i)); - } - return ret; - } - - bool InferShape(std::vector *in_shape, - std::vector *out_shape, - std::vector *aux_shape) const override { - using namespace mshadow; - CHECK_EQ(in_shape->size(), static_cast(param_.num_args)); - TShape data_shape = in_shape->at(crop_enum::kData); - if (data_shape.ndim() == 0) return false; - CHECK_EQ(data_shape.ndim(), 4U) << \ - "Input data should be 4D in batch-num_filter-y-x"; - std::vector crop_shape; - if (param_.num_args == 1) { - CHECK_GE(static_cast(param_.h_w[0]), 1) << - "the crop height(h_w[0]) should be larger than 1"; - CHECK_LE(static_cast(param_.h_w[0]), static_cast(data_shape[2])) << - "the crop height(h_w[0]) should be less than the input data's height"; - CHECK_GE(static_cast(param_.h_w[1]), 1) << - "the crop width(h_w[1]) should be larger than 1"; - CHECK_LE(static_cast(param_.h_w[1]), static_cast(data_shape[3])) << - "the crop width(h_w[1]) should be less than the input data's width"; - crop_shape.push_back(param_.h_w[0]); - crop_shape.push_back(param_.h_w[1]); - } else if (param_.num_args == 2) { - TShape crop_like_shape = in_shape->at(crop_enum::kCropLike); - crop_shape.push_back(crop_like_shape[2]); - crop_shape.push_back(crop_like_shape[3]); - } - if (crop_shape.size() == 0) return false; - CHECK_EQ(crop_shape.size(), 2U) << \ - "Input crop_like should be 2D in height-width"; - out_shape->clear(); - data_shape[2] = crop_shape[0]; - data_shape[3] = crop_shape[1]; - out_shape->push_back(data_shape); - return true; - } - - OperatorProperty* Copy() const override { - auto ptr = new CropProp(); - ptr->param_ = param_; - return ptr; - } - - std::string TypeString() const override { - return "Crop"; - } - - std::vector DeclareBackwardDependency( - const std::vector &out_grad, - const std::vector &in_data, - const std::vector &out_data) const override { - return out_grad; - } - - Operator* CreateOperator(Context ctx) const override; - - private: - CropParam param_; -}; // class CropProp -#endif // DMLC_USE_CXX11 -} // namespace op -} // namespace mxnet -#endif // MXNET_OPERATOR_CROP_INL_H_ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +/*! + * \file crop-inl.h + * \brief + * \author Wei Wu +*/ +#ifndef MXNET_OPERATOR_CROP_INL_H_ +#define MXNET_OPERATOR_CROP_INL_H_ +#include +#include +#include +#include +#include +#include +#include +#include +#include "./operator_common.h" + +namespace mxnet { +namespace op { + +namespace crop_enum { +enum CropOpInputs {kData, kCropLike}; +enum CropOpOutputs {kOut}; +} // namespace crop_enum + +struct CropParam : public dmlc::Parameter { + int num_args; + TShape offset; + TShape h_w; + bool center_crop; + DMLC_DECLARE_PARAMETER(CropParam) { + DMLC_DECLARE_FIELD(num_args).set_range(1, 3) + .describe("Number of inputs for crop, if equals one, then we will use the h_w" + "for crop height and width, else if equals two, then we will use the height" + "and width of the second input symbol, we name crop_like here"); + int shape[] = {0, 0}; + DMLC_DECLARE_FIELD(offset).set_default(TShape(shape, shape + 2)) + .describe("crop offset coordinate: (y, x)"); + DMLC_DECLARE_FIELD(h_w).set_default(TShape(shape, shape + 2)) + .describe("crop height and width: (h, w)"); + DMLC_DECLARE_FIELD(center_crop).set_default(false) + .describe("If set to true, then it will use be the center_crop," + "or it will crop using the shape of crop_like"); + } +}; // struct CropParam + +template +class CropOp : public Operator { + public: + explicit CropOp(CropParam param) { + this->param_ = param; + } + + virtual void Forward(const OpContext &ctx, + const std::vector &in_data, + const std::vector &req, + const std::vector &out_data, + const std::vector &aux_args) { + using namespace mshadow; + using namespace mshadow::expr; + CHECK_EQ(static_cast(in_data.size()), param_.num_args); + CHECK_EQ(out_data.size(), 1U); + CHECK_EQ(req[crop_enum::kOut], kWriteTo); + Stream *s = ctx.get_stream(); + Tensor data = in_data[crop_enum::kData].get(s); + Tensor out = out_data[crop_enum::kOut].get(s); + offset_hw_ = InferCropOfferset(data.shape_, out.shape_); + out = crop(data, Shape2(out.size(2), out.size(3)), offset_hw_[0], offset_hw_[1]); + } + + // because the crop_like input is only used with it's shape, so we should be + // careful setting its backwrd grad value to zeros, so that it will not hurt + // the connection of crop_like. + virtual void Backward(const OpContext &ctx, + const std::vector &out_grad, + const std::vector &in_data, + const std::vector &out_data, + const std::vector &req, + const std::vector &in_grad, + const std::vector &aux_states) { + using namespace mshadow; + using namespace mshadow::expr; + CHECK_EQ(in_grad.size(), static_cast(param_.num_args)) << in_grad.size(); + CHECK_EQ(out_grad.size(), 1U) << out_grad.size(); + Stream *s = ctx.get_stream(); + Tensor grad = out_grad[crop_enum::kOut].get(s); + Tensor gdata = in_grad[crop_enum::kData].get(s); + if (param_.num_args > 1) { + // here backward grad is set to zero for crop_like + // however, this should only be done when num_args > 1, i.e., crop_like exists + Tensor gcrop_like = in_grad[crop_enum::kCropLike].get(s); + gcrop_like = (real_t)0.0f; + } + offset_hw_ = InferCropOfferset(gdata.shape_, grad.shape_); + gdata = (real_t)0.0f; + slice<3>(slice<2>(gdata, offset_hw_[0], offset_hw_[0]+grad.size(2)), + offset_hw_[1], offset_hw_[1]+grad.size(3)) = grad; + } + + private: + CropParam param_; + std::vector offset_hw_; + std::vector InferCropOfferset(const mshadow::Shape<4> &data_shape, + const mshadow::Shape<4> &out_shape) { + std::vector offset_hw; + CHECK_GE(data_shape[2], out_shape[2]) << + "data_shape'height should be larger than that of out_shape"; + CHECK_GE(data_shape[3], out_shape[3]) << + "data_shape'weight should be larger than that of out_shape"; + if (param_.center_crop) { + offset_hw.push_back(static_cast((data_shape[2]-out_shape[2])/2)); + offset_hw.push_back(static_cast((data_shape[3]-out_shape[3])/2)); + } else { + CHECK_GE(static_cast(param_.offset[0]), 0) << + "offset[0] should be larger than 0"; + CHECK_LE(param_.offset[0], data_shape[2]-out_shape[2]) << + "offset[0] should be less than the residual space of height"; + CHECK_GE(static_cast(param_.offset[1]), 0) << + "offset[1] should be larger than 0"; + CHECK_LE(param_.offset[1], data_shape[3]-out_shape[3]) << + "offset[1] should be less than the residual space of width"; + offset_hw.push_back(static_cast(param_.offset[0])); + offset_hw.push_back(static_cast(param_.offset[1])); + } + return offset_hw; + } +}; // class CropOp + +template +Operator *CreateOp(CropParam param); + +#if DMLC_USE_CXX11 +class CropProp : public OperatorProperty { + public: + void Init(const std::vector >& kwargs) override { + param_.Init(kwargs); + } + + std::map GetParams() const override { + return param_.__DICT__(); + } + + std::vector ListArguments() const override { + // return {"data", "crop_like"}; + std::vector ret; + for (int i = 0; i < param_.num_args; ++i) { + ret.push_back(std::string("arg") + std::to_string(i)); + } + return ret; + } + + bool InferShape(std::vector *in_shape, + std::vector *out_shape, + std::vector *aux_shape) const override { + using namespace mshadow; + CHECK_EQ(in_shape->size(), static_cast(param_.num_args)); + TShape data_shape = in_shape->at(crop_enum::kData); + if (data_shape.ndim() == 0) return false; + CHECK_EQ(data_shape.ndim(), 4U) << \ + "Input data should be 4D in batch-num_filter-y-x"; + std::vector crop_shape; + if (param_.num_args == 1) { + CHECK_GE(static_cast(param_.h_w[0]), 1) << + "the crop height(h_w[0]) should be larger than 1"; + CHECK_LE(static_cast(param_.h_w[0]), static_cast(data_shape[2])) << + "the crop height(h_w[0]) should be less than the input data's height"; + CHECK_GE(static_cast(param_.h_w[1]), 1) << + "the crop width(h_w[1]) should be larger than 1"; + CHECK_LE(static_cast(param_.h_w[1]), static_cast(data_shape[3])) << + "the crop width(h_w[1]) should be less than the input data's width"; + crop_shape.push_back(param_.h_w[0]); + crop_shape.push_back(param_.h_w[1]); + } else if (param_.num_args == 2) { + TShape crop_like_shape = in_shape->at(crop_enum::kCropLike); + crop_shape.push_back(crop_like_shape[2]); + crop_shape.push_back(crop_like_shape[3]); + } + if (crop_shape.size() == 0) return false; + CHECK_EQ(crop_shape.size(), 2U) << \ + "Input crop_like should be 2D in height-width"; + out_shape->clear(); + data_shape[2] = crop_shape[0]; + data_shape[3] = crop_shape[1]; + out_shape->push_back(data_shape); + return true; + } + + OperatorProperty* Copy() const override { + auto ptr = new CropProp(); + ptr->param_ = param_; + return ptr; + } + + std::string TypeString() const override { + return "Crop"; + } + + std::vector DeclareBackwardDependency( + const std::vector &out_grad, + const std::vector &in_data, + const std::vector &out_data) const override { + return out_grad; + } + + Operator* CreateOperator(Context ctx) const override; + + private: + CropParam param_; +}; // class CropProp +#endif // DMLC_USE_CXX11 +} // namespace op +} // namespace mxnet +#endif // MXNET_OPERATOR_CROP_INL_H_ diff --git a/src/operator/crop.cc b/src/operator/crop.cc index f1233ba8a135..8465819903ce 100644 --- a/src/operator/crop.cc +++ b/src/operator/crop.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file crop.cc * \brief * \author Wei Wu diff --git a/src/operator/crop.cu b/src/operator/crop.cu index 64f8cb219f30..0b51b1449581 100644 --- a/src/operator/crop.cu +++ b/src/operator/crop.cu @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file concat.cu * \brief * \author Wei Wu diff --git a/src/operator/cross_device_copy.cc b/src/operator/cross_device_copy.cc index ce618c97fa05..b32a68d3038c 100644 --- a/src/operator/cross_device_copy.cc +++ b/src/operator/cross_device_copy.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file cross_device_copy.cc * \brief Special operator that copys NDArray */ @@ -20,12 +38,6 @@ class CrossDeviceCopyOp : public Operator { // We still re-use things such as InferShape in OperatorProperty LOG(FATAL) << "Not Reached"; } - - ExecType exec_type() const override { - // TODO(tianqi) Think of other way to blend cross device op into operator interface. - // declare the op as cross device, - return kCrossDeviceCopy; - } }; class CrossDeviceCopyProp : public OperatorProperty { @@ -58,6 +70,12 @@ class CrossDeviceCopyProp : public OperatorProperty { Operator* CreateOperator(Context ctx) const override { return new CrossDeviceCopyOp(); } + + ExecType exec_type() const override { + // TODO(tianqi) Think of other way to blend cross device op into operator interface. + // declare the op as cross device, + return ExecType::kCrossDeviceCopy; + } }; diff --git a/src/operator/cudnn_activation-inl.h b/src/operator/cudnn_activation-inl.h index 68f68b6225be..317ef47c126a 100644 --- a/src/operator/cudnn_activation-inl.h +++ b/src/operator/cudnn_activation-inl.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file cudnn_activation-inl.h * \brief * \author Bing Xu diff --git a/src/operator/cudnn_algoreg-inl.h b/src/operator/cudnn_algoreg-inl.h index 0d1c3948186c..dc5db6bbc8b7 100644 --- a/src/operator/cudnn_algoreg-inl.h +++ b/src/operator/cudnn_algoreg-inl.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file cudnn_algoreg-inl.h * \brief * \author Bing Xu @@ -14,11 +32,35 @@ #include "../common/cuda_utils.h" #include "./convolution-inl.h" #include "./deconvolution-inl.h" - namespace mxnet { namespace op { #if MXNET_USE_CUDNN == 1 +/*! + * \brief A cuDNN algorithm: an algo number and whether it should be run in TENSOR CORE mode. + */ +template +class CuDNNAlgo { + public: + CuDNNAlgo() : + algo_number_(static_cast(0)), + is_tensor_core_algo_(false) { } + void Set(CuDNNAlgoType algo, bool is_tensor_core) { + algo_number_ = algo; + is_tensor_core_algo_ = is_tensor_core; + } + CuDNNAlgoType AlgoNumber() const { return algo_number_; } + bool IsTensorCoreAlgo() const { return is_tensor_core_algo_; } + #if CUDNN_MAJOR >= 7 + cudnnMathType_t MathType() { + return IsTensorCoreAlgo() ? CUDNN_TENSOR_OP_MATH : CUDNN_DEFAULT_MATH; + } + #endif + private: + CuDNNAlgoType algo_number_; + bool is_tensor_core_algo_; +}; + class CuDNNAlgoReg { public: template @@ -26,7 +68,8 @@ class CuDNNAlgoReg { const std::vector &out_shape, cudnnDataType_t cudnn_data_type, cudnnDataType_t cudnn_forward_compute_type, - cudnnDataType_t cudnn_backward_compute_type) { + cudnnDataType_t cudnn_backward_compute_type, + int sm_arch) { std::ostringstream oss; oss << "inputs="; for (auto &i : in_shape) @@ -40,12 +83,15 @@ class CuDNNAlgoReg { oss << "cudnn_data_type=" << cudnn_data_type << ";"; oss << "cudnn_forward_compute_type=" << cudnn_forward_compute_type << ";"; oss << "cudnn_backward_compute_type=" << cudnn_backward_compute_type << ";"; + // All GPUs of the same compute capability (SM arch) share an algo selection. + oss << "sm_arch=" << sm_arch << ";"; return oss.str(); } - bool Find(std::string key, cudnnConvolutionFwdAlgo_t *fwd, - cudnnConvolutionBwdDataAlgo_t *bwd, - cudnnConvolutionBwdFilterAlgo_t *flt) { + bool Find(std::string key, + CuDNNAlgo *fwd, + CuDNNAlgo *bwd, + CuDNNAlgo *flt) { std::lock_guard guard(lock_); auto i = reg_.find(key); if (i != reg_.end()) { @@ -57,9 +103,10 @@ class CuDNNAlgoReg { return false; } - void Register(std::string key, cudnnConvolutionFwdAlgo_t fwd, - cudnnConvolutionBwdDataAlgo_t bwd, - cudnnConvolutionBwdFilterAlgo_t flt) { + void Register(std::string key, + const CuDNNAlgo &fwd, + const CuDNNAlgo &bwd, + const CuDNNAlgo &flt) { std::lock_guard guard(lock_); if (reg_.size() % 50 == 0) { LOG(INFO) << "Running performance tests to find the best convolution " @@ -82,9 +129,9 @@ class CuDNNAlgoReg { private: struct CudnnAlgorithms { - cudnnConvolutionFwdAlgo_t fwd; - cudnnConvolutionBwdDataAlgo_t bwd; - cudnnConvolutionBwdFilterAlgo_t flt; + CuDNNAlgo fwd; + CuDNNAlgo bwd; + CuDNNAlgo flt; }; std::mutex lock_; diff --git a/src/operator/cudnn_algoreg.cc b/src/operator/cudnn_algoreg.cc index 103c4819d951..5aa8688c8148 100644 --- a/src/operator/cudnn_algoreg.cc +++ b/src/operator/cudnn_algoreg.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file cudnn_algoreg.cc * \brief * \author Junyuan Xie diff --git a/src/operator/cudnn_batch_norm-inl.h b/src/operator/cudnn_batch_norm-inl.h old mode 100755 new mode 100644 index 90e76581fa86..b0c5f43157d0 --- a/src/operator/cudnn_batch_norm-inl.h +++ b/src/operator/cudnn_batch_norm-inl.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file cudnn_batch_norm-inl.h * \brief * \author Junyuan Xie @@ -28,8 +46,8 @@ class CuDNNBatchNormOp : public Operator { public: explicit CuDNNBatchNormOp(BatchNormParam param) { using namespace mshadow; - CHECK_GT(param.eps, CUDNN_BN_MIN_EPSILON) - << "CuDNN requires eps to be greater than " << CUDNN_BN_MIN_EPSILON; + CHECK_GE(param.eps, CUDNN_BN_MIN_EPSILON) + << "CuDNN requires eps to be no less than " << CUDNN_BN_MIN_EPSILON; this->param_ = param; init_cudnn_ = false; dtype_ = DataType::kCudnnFlag; @@ -94,6 +112,11 @@ class CuDNNBatchNormOp : public Operator { Tensor y = out_data[cudnnbatchnorm::kOut].get_with_shape(shape_, s); +#if CUDNN_VERSION >= 7000 + auto mode = CUDNN_BATCHNORM_SPATIAL_PERSISTENT; +#else + auto mode = CUDNN_BATCHNORM_SPATIAL; +#endif MSHADOW_REAL_TYPE_SWITCH(dtype_param_, DTypeParam, { Tensor gamma = @@ -118,7 +141,7 @@ class CuDNNBatchNormOp : public Operator { out_data[cudnnbatchnorm::kInvVar] .get_with_shape(Shape1(shape_[1]), s); CUDNN_CALL(cudnnBatchNormalizationForwardTraining(s->dnn_handle_, - CUDNN_BATCHNORM_SPATIAL, + mode, &a, &b, io_desc_, @@ -178,6 +201,11 @@ class CuDNNBatchNormOp : public Operator { out_grad[cudnnbatchnorm::kOut].get_with_shape(shape_, s); #if CUDNN_VERSION >= 4007 +#if CUDNN_VERSION >= 7000 + auto mode = CUDNN_BATCHNORM_SPATIAL_PERSISTENT; +#else + auto mode = CUDNN_BATCHNORM_SPATIAL; +#endif MSHADOW_REAL_TYPE_SWITCH(dtype_param_, DTypeParam, { Tensor gamma = in_data[cudnnbatchnorm::kGamma].get_with_shape(Shape1(shape_[1]), s); @@ -199,7 +227,7 @@ class CuDNNBatchNormOp : public Operator { CUDNN_CALL(cudnnBatchNormalizationBackward( s->dnn_handle_, - CUDNN_BATCHNORM_SPATIAL, + mode, &a, &b, &a, diff --git a/src/operator/cudnn_batch_norm.cc b/src/operator/cudnn_batch_norm.cc index 424299d93260..28c592b78ccf 100644 --- a/src/operator/cudnn_batch_norm.cc +++ b/src/operator/cudnn_batch_norm.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file cudnn_batch_norm.cc * \brief * \author Junyuan Xie diff --git a/src/operator/cudnn_batch_norm.cu b/src/operator/cudnn_batch_norm.cu old mode 100755 new mode 100644 index 3ab43cabd6cd..c16fc0cac25b --- a/src/operator/cudnn_batch_norm.cu +++ b/src/operator/cudnn_batch_norm.cu @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file cudnn_batch_norm.cu * \brief * \author Junyuan Xie diff --git a/src/operator/cudnn_bilinear_sampler-inl.h b/src/operator/cudnn_bilinear_sampler-inl.h index 8b012b71723b..57592dabd891 100644 --- a/src/operator/cudnn_bilinear_sampler-inl.h +++ b/src/operator/cudnn_bilinear_sampler-inl.h @@ -1,167 +1,185 @@ -/*! - * Copyright (c) 2016 by Contributors - * \file cudnn_bilinear_sampler-inl.h - * \brief - * \author Xu Dong -*/ -#ifndef MXNET_OPERATOR_CUDNN_BILINEAR_SAMPLER_INL_H_ -#define MXNET_OPERATOR_CUDNN_BILINEAR_SAMPLER_INL_H_ - -#include -#include -#include "./bilinear_sampler-inl.h" -namespace mxnet { -namespace op { -#if defined(__CUDACC__) && MXNET_USE_CUDNN == 1 && CUDNN_MAJOR >= 5 -template -class CuDNNBilinearSamplerOp : public Operator { - public: - explicit CuDNNBilinearSamplerOp(BilinearSamplerParam param) { - this->param_ = param; - init_cudnn_ = false; - dtype_ = mshadow::DataType::kCudnnFlag; - sampler_ = CUDNN_SAMPLER_BILINEAR; - } - - ~CuDNNBilinearSamplerOp() { - if (init_cudnn_) { - CUDNN_CALL(cudnnDestroySpatialTransformerDescriptor(st_desc_)); - CUDNN_CALL(cudnnDestroyTensorDescriptor(in_desc_)); - CUDNN_CALL(cudnnDestroyTensorDescriptor(out_desc_)); - } - } - - virtual void Forward(const OpContext &ctx, - const std::vector &in_data, - const std::vector &req, - const std::vector &out_data, - const std::vector &aux_args) { - using namespace mshadow; - CHECK_EQ(req[bs::kOut], kWriteTo); - CHECK_EQ(in_data.size(), 2U); - CHECK_EQ(out_data.size(), 2U); - Stream *s = ctx.get_stream(); - - Tensor data = in_data[bs::kData].get(s); - Tensor grid = in_data[bs::kGrid].get(s); - Tensor grid_tmp = out_data[bs::kTmp].get(s); - Tensor out = out_data[bs::kOut].get(s); - // grid_tmp : (batch, h, w, 2) - grid_tmp = transpose(grid, Shape4(0, 2, 3, 1)); - if (!init_cudnn_) { - Init(s, in_data, out_data); - } - CHECK_EQ(data.CheckContiguous(), true); - CHECK_EQ(out.CheckContiguous(), true); - CHECK_EQ(grid_tmp.CheckContiguous(), true); - typename DataType::ScaleType alpha = 1.0f; - typename DataType::ScaleType beta = 0.0f; - CUDNN_CALL(cudnnSpatialTfSamplerForward(s->dnn_handle_, - st_desc_, - &alpha, - in_desc_, - data.dptr_, - grid_tmp.dptr_, - &beta, - out_desc_, - out.dptr_)); - } - - virtual void Backward(const OpContext &ctx, - const std::vector &out_grad, - const std::vector &in_data, - const std::vector &out_data, - const std::vector &req, - const std::vector &in_grad, - const std::vector &aux_args) { - using namespace mshadow; - CHECK_NE(req[bs::kData], kWriteInplace); - CHECK_NE(req[bs::kGrid], kWriteInplace); - CHECK_EQ(in_data.size(), 2U); - CHECK_EQ(out_data.size(), 2U); - CHECK_EQ(out_grad.size(), 1U); - Stream *s = ctx.get_stream(); - Tensor data = in_data[bs::kData].get(s); - Tensor grid_tmp = out_data[bs::kTmp].get(s); - Tensor gdata = in_grad[bs::kData].get(s); - Tensor ggrid = in_grad[bs::kGrid].get(s); - Tensor grad = out_grad[bs::kOut].get(s); - - typename DataType::ScaleType alpha = (req[bs::kData] == kNullOp) ? 0.0f : 1.0f; - typename DataType::ScaleType beta = (req[bs::kData] == kAddTo) ? 1.0f : 0.0f; - typename DataType::ScaleType alpha_dgrid = 1.0f; - typename DataType::ScaleType beta_dgrid = 0.0f; - CUDNN_CALL(cudnnSpatialTfSamplerBackward(s->dnn_handle_, - st_desc_, - &alpha, - in_desc_, - data.dptr_, - &beta, - in_desc_/*reuse in_desc_*/, - gdata.dptr_/*output*/, - &alpha_dgrid, - out_desc_/*reuse out_desc_*/, - grad.dptr_, - grid_tmp.dptr_, - &beta_dgrid, - grid_tmp.dptr_)); - Assign(ggrid, req[bs::kGrid], transpose(grid_tmp, Shape4(0, 3, 1, 2))); - } - - private: - inline void Init(mshadow::Stream *s, - const std::vector &in_data, - const std::vector &out_data) { - using namespace mshadow; - #if CUDNN_MAJOR >= 5 - format_ = CUDNN_TENSOR_NCHW; - #endif - CHECK_EQ(in_data.size(), 2U); - CHECK_EQ(out_data.size(), 2U); - if (!init_cudnn_) { - init_cudnn_ = true; - Tensor data = in_data[bs::kData].get(s); - Tensor out = out_data[bs::kOut].get(s); - CUDNN_CALL(cudnnCreateSpatialTransformerDescriptor(&st_desc_)); - CUDNN_CALL(cudnnCreateTensorDescriptor(&in_desc_)); - CUDNN_CALL(cudnnCreateTensorDescriptor(&out_desc_)); - CUDNN_CALL(cudnnSetTensor4dDescriptor(in_desc_, - format_, - dtype_, - data.size(0), - data.size(1), - data.size(2), - data.size(3))); - CUDNN_CALL(cudnnSetTensor4dDescriptor(out_desc_, - format_, - dtype_, - out.size(0), - out.size(1), - out.size(2), - out.size(3))); - int dim[] = {static_cast(out.size(0)), static_cast(out.size(1)), - static_cast(out.size(2)), static_cast(out.size(3))}; - CUDNN_CALL(cudnnSetSpatialTransformerNdDescriptor(st_desc_, - sampler_, - dtype_, - 4, - dim)); - } - } - - bool init_cudnn_; - cudnnDataType_t dtype_; - cudnnSpatialTransformerDescriptor_t st_desc_; - cudnnTensorDescriptor_t in_desc_; - cudnnTensorDescriptor_t out_desc_; - cudnnSamplerType_t sampler_; - #if CUDNN_MAJOR >= 5 - cudnnTensorFormat_t format_; - #endif - BilinearSamplerParam param_; -}; -#endif // __CUDACC__ && CUDNN -} // namespace op -} // namespace mxnet - -#endif // MXNET_OPERATOR_CUDNN_BILINEAR_SAMPLER_INL_H_ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +/*! + * \file cudnn_bilinear_sampler-inl.h + * \brief + * \author Xu Dong +*/ +#ifndef MXNET_OPERATOR_CUDNN_BILINEAR_SAMPLER_INL_H_ +#define MXNET_OPERATOR_CUDNN_BILINEAR_SAMPLER_INL_H_ + +#include +#include +#include "./bilinear_sampler-inl.h" +namespace mxnet { +namespace op { +#if defined(__CUDACC__) && MXNET_USE_CUDNN == 1 && CUDNN_MAJOR >= 5 +template +class CuDNNBilinearSamplerOp : public Operator { + public: + explicit CuDNNBilinearSamplerOp(BilinearSamplerParam param) { + this->param_ = param; + init_cudnn_ = false; + dtype_ = mshadow::DataType::kCudnnFlag; + sampler_ = CUDNN_SAMPLER_BILINEAR; + } + + ~CuDNNBilinearSamplerOp() { + if (init_cudnn_) { + CUDNN_CALL(cudnnDestroySpatialTransformerDescriptor(st_desc_)); + CUDNN_CALL(cudnnDestroyTensorDescriptor(in_desc_)); + CUDNN_CALL(cudnnDestroyTensorDescriptor(out_desc_)); + } + } + + virtual void Forward(const OpContext &ctx, + const std::vector &in_data, + const std::vector &req, + const std::vector &out_data, + const std::vector &aux_args) { + using namespace mshadow; + CHECK_EQ(req[bs::kOut], kWriteTo); + CHECK_EQ(in_data.size(), 2U); + CHECK_EQ(out_data.size(), 2U); + Stream *s = ctx.get_stream(); + + Tensor data = in_data[bs::kData].get(s); + Tensor grid = in_data[bs::kGrid].get(s); + Tensor grid_tmp = out_data[bs::kTmp].get(s); + Tensor out = out_data[bs::kOut].get(s); + // grid_tmp : (batch, h, w, 2) + grid_tmp = transpose(grid, Shape4(0, 2, 3, 1)); + if (!init_cudnn_) { + Init(s, in_data, out_data); + } + CHECK_EQ(data.CheckContiguous(), true); + CHECK_EQ(out.CheckContiguous(), true); + CHECK_EQ(grid_tmp.CheckContiguous(), true); + typename DataType::ScaleType alpha = 1.0f; + typename DataType::ScaleType beta = 0.0f; + CUDNN_CALL(cudnnSpatialTfSamplerForward(s->dnn_handle_, + st_desc_, + &alpha, + in_desc_, + data.dptr_, + grid_tmp.dptr_, + &beta, + out_desc_, + out.dptr_)); + } + + virtual void Backward(const OpContext &ctx, + const std::vector &out_grad, + const std::vector &in_data, + const std::vector &out_data, + const std::vector &req, + const std::vector &in_grad, + const std::vector &aux_args) { + using namespace mshadow; + CHECK_NE(req[bs::kData], kWriteInplace); + CHECK_NE(req[bs::kGrid], kWriteInplace); + CHECK_EQ(in_data.size(), 2U); + CHECK_EQ(out_data.size(), 2U); + CHECK_EQ(out_grad.size(), 1U); + Stream *s = ctx.get_stream(); + Tensor data = in_data[bs::kData].get(s); + Tensor grid_tmp = out_data[bs::kTmp].get(s); + Tensor gdata = in_grad[bs::kData].get(s); + Tensor ggrid = in_grad[bs::kGrid].get(s); + Tensor grad = out_grad[bs::kOut].get(s); + + typename DataType::ScaleType alpha = (req[bs::kData] == kNullOp) ? 0.0f : 1.0f; + typename DataType::ScaleType beta = (req[bs::kData] == kAddTo) ? 1.0f : 0.0f; + typename DataType::ScaleType alpha_dgrid = 1.0f; + typename DataType::ScaleType beta_dgrid = 0.0f; + CUDNN_CALL(cudnnSpatialTfSamplerBackward(s->dnn_handle_, + st_desc_, + &alpha, + in_desc_, + data.dptr_, + &beta, + in_desc_/*reuse in_desc_*/, + gdata.dptr_/*output*/, + &alpha_dgrid, + out_desc_/*reuse out_desc_*/, + grad.dptr_, + grid_tmp.dptr_, + &beta_dgrid, + grid_tmp.dptr_)); + Assign(ggrid, req[bs::kGrid], transpose(grid_tmp, Shape4(0, 3, 1, 2))); + } + + private: + inline void Init(mshadow::Stream *s, + const std::vector &in_data, + const std::vector &out_data) { + using namespace mshadow; + #if CUDNN_MAJOR >= 5 + format_ = CUDNN_TENSOR_NCHW; + #endif + CHECK_EQ(in_data.size(), 2U); + CHECK_EQ(out_data.size(), 2U); + if (!init_cudnn_) { + init_cudnn_ = true; + Tensor data = in_data[bs::kData].get(s); + Tensor out = out_data[bs::kOut].get(s); + CUDNN_CALL(cudnnCreateSpatialTransformerDescriptor(&st_desc_)); + CUDNN_CALL(cudnnCreateTensorDescriptor(&in_desc_)); + CUDNN_CALL(cudnnCreateTensorDescriptor(&out_desc_)); + CUDNN_CALL(cudnnSetTensor4dDescriptor(in_desc_, + format_, + dtype_, + data.size(0), + data.size(1), + data.size(2), + data.size(3))); + CUDNN_CALL(cudnnSetTensor4dDescriptor(out_desc_, + format_, + dtype_, + out.size(0), + out.size(1), + out.size(2), + out.size(3))); + int dim[] = {static_cast(out.size(0)), static_cast(out.size(1)), + static_cast(out.size(2)), static_cast(out.size(3))}; + CUDNN_CALL(cudnnSetSpatialTransformerNdDescriptor(st_desc_, + sampler_, + dtype_, + 4, + dim)); + } + } + + bool init_cudnn_; + cudnnDataType_t dtype_; + cudnnSpatialTransformerDescriptor_t st_desc_; + cudnnTensorDescriptor_t in_desc_; + cudnnTensorDescriptor_t out_desc_; + cudnnSamplerType_t sampler_; + #if CUDNN_MAJOR >= 5 + cudnnTensorFormat_t format_; + #endif + BilinearSamplerParam param_; +}; +#endif // __CUDACC__ && CUDNN +} // namespace op +} // namespace mxnet + +#endif // MXNET_OPERATOR_CUDNN_BILINEAR_SAMPLER_INL_H_ diff --git a/src/operator/cudnn_convolution-inl.h b/src/operator/cudnn_convolution-inl.h index 96eadcdcca4c..428278498337 100644 --- a/src/operator/cudnn_convolution-inl.h +++ b/src/operator/cudnn_convolution-inl.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file cudnn_convolution-inl.h * \brief * \author Bing Xu @@ -33,6 +51,7 @@ class CuDNNConvolutionOp : public Operator { const Context& ctx) { using namespace mshadow; this->param_ = param; + InitBufferForParam(); auto cudnn_forward_compute_type = convertToCuDNNDataType(forward_compute_type); auto cudnn_backward_compute_type = convertToCuDNNDataType(backward_compute_type); // convert MB to words @@ -40,6 +59,8 @@ class CuDNNConvolutionOp : public Operator { init_cudnn_ = false; init_temp_size_ = false; dtype_ = DataType::kCudnnFlag; + // TensorCore algos only allowed on fp16-I/O convolutions if permitted by the global policy. + cudnn_tensor_core_ = DataType::kFlag == kFloat16 && GetEnvAllowTensorCore(); #if CUDNN_MAJOR >= 5 MSHADOW_LAYOUT_SWITCH(param_.layout.value(), Layout, { @@ -50,7 +71,7 @@ class CuDNNConvolutionOp : public Operator { << "Need CuDNN > 5.0 for layout support"; #endif // Double check to make sure this class supports the operation - if (!Supports(param, forward_compute_type, backward_compute_type)) + if (!Supports(param, forward_compute_type, backward_compute_type, ctx)) LOG(FATAL) << "Need CuDNN >= 6.0 for dilated convolution."; InitDescriptors(ctx, in_shape, out_shape, @@ -76,7 +97,8 @@ class CuDNNConvolutionOp : public Operator { CUDNN_CALL(cudnnDestroyTensorDescriptor(bias_desc_)); CUDNN_CALL(cudnnDestroyFilterDescriptor(filter_desc_)); CUDNN_CALL(cudnnDestroyConvolutionDescriptor(forward_conv_desc_)); - CUDNN_CALL(cudnnDestroyConvolutionDescriptor(backward_conv_desc_)); + CUDNN_CALL(cudnnDestroyConvolutionDescriptor(back_conv_desc_)); + CUDNN_CALL(cudnnDestroyConvolutionDescriptor(back_conv_desc_w_)); } } @@ -94,9 +116,8 @@ class CuDNNConvolutionOp : public Operator { CHECK_EQ(out_data.size(), 1U); Stream *s = ctx.get_stream(); GetTempSize(ctx); - Tensor workspace = - ctx.requested[conv::kTempSpace].get_space_typed( - mshadow::Shape1(forward_workspace_), s); + Tensor workspace = AllocateTempWorkspace(ctx, forward_workspace_byte_); + size_t workspace_size = TensorSizeBytes(workspace); if (param_.kernel.ndim() == 2) { Tensor data = in_data[conv::kData].get(s); @@ -130,9 +151,9 @@ class CuDNNConvolutionOp : public Operator { filter_desc_, wmat_ptr + weight_offset_ * g, forward_conv_desc_, - algo_, + forward_algo_.AlgoNumber(), workspace.dptr_, - forward_workspace_byte_, + workspace_size, req[conv::kOut] == kAddTo? &beta_add : &beta, out_desc_, out_ptr + out_offset_ * g)); @@ -202,9 +223,8 @@ class CuDNNConvolutionOp : public Operator { data_ptr = data.dptr_; gdata_ptr = gdata.dptr_; } - Tensor workspace = - ctx.requested[conv::kTempSpace].get_space_typed( - mshadow::Shape1(backward_workspace_), s); + Tensor workspace = AllocateTempWorkspace(ctx, backward_workspace_byte_); + size_t workspace_size = TensorSizeBytes(workspace); for (uint32_t g = 0; g < param_.num_group; ++g) { typename DataType::ScaleType alpha = 1.0f; typename DataType::ScaleType beta = 0.0f; @@ -227,10 +247,10 @@ class CuDNNConvolutionOp : public Operator { data_ptr + data_offset_ * g, out_desc_, grad_ptr + out_offset_ * g, - backward_conv_desc_, - back_algo_w_, + back_conv_desc_w_, + back_algo_w_.AlgoNumber(), workspace.dptr_, - backward_workspace_byte_, + workspace_size, req[conv::kWeight] == kAddTo? &beta_add : &beta, filter_desc_, gwmat_ptr + weight_offset_ * g)); @@ -241,10 +261,10 @@ class CuDNNConvolutionOp : public Operator { data_ptr + data_offset_ * g, out_desc_, grad_ptr + out_offset_ * g, - backward_conv_desc_, - back_algo_w_, + back_conv_desc_w_, + back_algo_w_.AlgoNumber(), workspace.dptr_, - backward_workspace_byte_, + workspace_size, req[conv::kWeight] == kAddTo? &beta_add : &beta, filter_desc_, gwmat_ptr + weight_offset_ * g)); @@ -258,10 +278,10 @@ class CuDNNConvolutionOp : public Operator { wmat_ptr + weight_offset_ * g, out_desc_, grad_ptr + out_offset_ * g, - backward_conv_desc_, - back_algo_, + back_conv_desc_, + back_algo_.AlgoNumber(), workspace.dptr_, - backward_workspace_byte_, + workspace_size, req[conv::kData] == kAddTo? &beta_add : &beta, in_desc_, gdata_ptr + data_offset_ * g)); @@ -272,10 +292,10 @@ class CuDNNConvolutionOp : public Operator { wmat_ptr + weight_offset_ * g, out_desc_, grad_ptr + out_offset_ * g, - backward_conv_desc_, - back_algo_, + back_conv_desc_, + back_algo_.AlgoNumber(), workspace.dptr_, - backward_workspace_byte_, + workspace_size, req[conv::kData] == kAddTo? &beta_add : &beta, in_desc_, gdata_ptr + data_offset_ * g)); @@ -291,7 +311,8 @@ class CuDNNConvolutionOp : public Operator { */ static bool Supports(ConvolutionParam param, int forward_compute_type, - int backward_compute_type) { + int backward_compute_type, + const Context &ctx) { using namespace mshadow; // NDHWC not supported, NHWC not supported in true fp16 @@ -301,6 +322,12 @@ class CuDNNConvolutionOp : public Operator { if (layout_val == kNDHWC || layout_val == kNHWC && true_fp16) return false; + // Permits graceful fallback to pseudo-fp16 on heterogenous systems + if (!SupportsFloat16Compute(ctx.dev_id) && + (forward_compute_type == kFloat16 || backward_compute_type == kFloat16)) { + return false; + } + // The factor by which the effective filter size grows based on dilation. auto filterDilationFactor = param.dilate.Size(); @@ -338,7 +365,8 @@ class CuDNNConvolutionOp : public Operator { CUDNN_CALL(cudnnCreateTensorDescriptor(&bias_desc_)); CUDNN_CALL(cudnnCreateFilterDescriptor(&filter_desc_)); CUDNN_CALL(cudnnCreateConvolutionDescriptor(&forward_conv_desc_)); - CUDNN_CALL(cudnnCreateConvolutionDescriptor(&backward_conv_desc_)); + CUDNN_CALL(cudnnCreateConvolutionDescriptor(&back_conv_desc_)); + CUDNN_CALL(cudnnCreateConvolutionDescriptor(&back_conv_desc_w_)); TShape dshape = in_shape[conv::kData]; TShape wshape = in_shape[conv::kWeight]; @@ -362,7 +390,16 @@ class CuDNNConvolutionOp : public Operator { param_.dilate[1], CUDNN_CROSS_CORRELATION, cudnn_forward_compute_type)); - CUDNN_CALL(cudnnSetConvolution2dDescriptor(backward_conv_desc_, + CUDNN_CALL(cudnnSetConvolution2dDescriptor(back_conv_desc_, + param_.pad[0], + param_.pad[1], + param_.stride[0], + param_.stride[1], + param_.dilate[0], + param_.dilate[1], + CUDNN_CROSS_CORRELATION, + cudnn_backward_compute_type)); + CUDNN_CALL(cudnnSetConvolution2dDescriptor(back_conv_desc_w_, param_.pad[0], param_.pad[1], param_.stride[0], @@ -380,7 +417,15 @@ class CuDNNConvolutionOp : public Operator { param_.dilate[0], param_.dilate[1], CUDNN_CROSS_CORRELATION)); - CUDNN_CALL(cudnnSetConvolution2dDescriptor(backward_conv_desc_, + CUDNN_CALL(cudnnSetConvolution2dDescriptor(back_conv_desc_, + param_.pad[0], + param_.pad[1], + param_.stride[0], + param_.stride[1], + param_.dilate[0], + param_.dilate[1], + CUDNN_CROSS_CORRELATION)); + CUDNN_CALL(cudnnSetConvolution2dDescriptor(back_conv_desc_w_, param_.pad[0], param_.pad[1], param_.stride[0], @@ -426,27 +471,36 @@ class CuDNNConvolutionOp : public Operator { // 3d conv #if CUDNN_MAJOR >= 5 CHECK_EQ(param_.layout.value(), kNCDHW) << "CuDNN only support 3D conv with NCDHW layout"; + std::vector wshape_buffer(wshape.ndim()); CUDNN_CALL(cudnnSetFilterNdDescriptor(filter_desc_, dtype_, CUDNN_TENSOR_NCHW, static_cast(wshape.ndim()), - reinterpret_cast(&wshape[0]))); + CastTShapeToIntPtr(wshape, &wshape_buffer))); #else LOG(FATAL) << "Only support CUDNN V5 for 3D convolution"; #endif CUDNN_CALL(cudnnSetConvolutionNdDescriptor(forward_conv_desc_, 3, - reinterpret_cast(¶m_.pad[0]), - reinterpret_cast(¶m_.stride[0]), - reinterpret_cast(¶m_.dilate[0]), + param_pad_.data(), + param_stride_.data(), + param_dilate_.data(), CUDNN_CROSS_CORRELATION, cudnn_forward_compute_type)); - CUDNN_CALL(cudnnSetConvolutionNdDescriptor(backward_conv_desc_, + CUDNN_CALL(cudnnSetConvolutionNdDescriptor(back_conv_desc_, + 3, + param_pad_.data(), + param_stride_.data(), + param_dilate_.data(), + CUDNN_CROSS_CORRELATION, + cudnn_backward_compute_type)); + + CUDNN_CALL(cudnnSetConvolutionNdDescriptor(back_conv_desc_w_, 3, - reinterpret_cast(¶m_.pad[0]), - reinterpret_cast(¶m_.stride[0]), - reinterpret_cast(¶m_.dilate[0]), + param_pad_.data(), + param_stride_.data(), + param_dilate_.data(), CUDNN_CROSS_CORRELATION, cudnn_backward_compute_type)); @@ -466,23 +520,40 @@ class CuDNNConvolutionOp : public Operator { param_.layout.value(), kNCDHW); oshape = ConvertLayout(oshape.get<5>(), param_.layout.value(), kNCDHW); } + // Set "allow tensor core" flag in convolution descriptors, if available. + #if CUDNN_MAJOR >= 7 + cudnnMathType_t math_type = cudnn_tensor_core_ ? CUDNN_TENSOR_OP_MATH + : CUDNN_DEFAULT_MATH; + CUDNN_CALL(cudnnSetConvolutionMathType(forward_conv_desc_, math_type)); + CUDNN_CALL(cudnnSetConvolutionMathType(back_conv_desc_, math_type)); + CUDNN_CALL(cudnnSetConvolutionMathType(back_conv_desc_w_, math_type)); + #endif dshape[1] /= param_.num_group; oshape[1] /= param_.num_group; weight_offset_ = wshape.Size(); data_offset_ = dstride[1] * dshape[1]; out_offset_ = ostride[1] * oshape[1]; - CUDNN_CALL(cudnnSetTensorNdDescriptor(in_desc_, - dtype_, - static_cast(dshape.ndim()), - reinterpret_cast(&dshape[0]), - reinterpret_cast(&dstride[0]))); + std::vector dshape_buffer(dshape.ndim()); + nnvm::ShapeTypeCast(dshape.begin(), dshape.end(), dshape_buffer.data()); + std::vector dstride_buffer(dstride.ndim()); + nnvm::ShapeTypeCast(dstride.begin(), dstride.end(), dstride_buffer.data()); + CUDNN_CALL(cudnnSetTensorNdDescriptor(in_desc_, + dtype_, + static_cast(dshape.ndim()), + dshape_buffer.data(), + dstride_buffer.data())); + + std::vector oshape_buffer(oshape.ndim()); + nnvm::ShapeTypeCast(oshape.begin(), oshape.end(), oshape_buffer.data()); + std::vector ostride_buffer(ostride.ndim()); + nnvm::ShapeTypeCast(ostride.begin(), ostride.end(), ostride_buffer.data()); CUDNN_CALL(cudnnSetTensorNdDescriptor(out_desc_, - dtype_, - static_cast(oshape.ndim()), - reinterpret_cast(&oshape[0]), - reinterpret_cast(&ostride[0]))); + dtype_, + static_cast(oshape.ndim()), + oshape_buffer.data(), + ostride_buffer.data())); if (!param_.no_bias) { TShape bias = in_shape[conv::kBias]; @@ -511,122 +582,234 @@ class CuDNNConvolutionOp : public Operator { cudnnDataType_t cudnn_backward_compute_type) { std::string key = CuDNNAlgoReg::Get()->GetKey(param_, in_shape, out_shape, dtype_, cudnn_forward_compute_type, - cudnn_backward_compute_type); - if (CuDNNAlgoReg::Get()->Find(key, &algo_, &back_algo_, &back_algo_w_)) - return; - - Engine::VarHandle var = Engine::Get()->NewVariable(); - Engine::Get()->PushSync([=](RunContext rctx) { - mshadow::Stream *s = rctx.get_stream(); - CHECK_EQ(s->dnn_handle_ownership_, mshadow::Stream::OwnHandle); - size_t workspace_byte = static_cast(param_.workspace * sizeof(DType)); - if (!param_.cudnn_tune.value()) { - // In cuDNNv6, for kNHWC, only CUDNN_CONVOLUTION_FWD_ALGO_IMPLICIT_GEMM is - // supported. Hard-coded this since the algo find() or get() throws an FPE. - if (CUDNN_MAJOR == 6 && param_.layout.value() == mshadow::kNHWC) { - algo_ = CUDNN_CONVOLUTION_FWD_ALGO_IMPLICIT_GEMM; - } else { - CUDNN_CALL(cudnnGetConvolutionForwardAlgorithm(s->dnn_handle_, - in_desc_, - filter_desc_, - forward_conv_desc_, - out_desc_, - CUDNN_CONVOLUTION_FWD_SPECIFY_WORKSPACE_LIMIT, - workspace_byte, - &(this->algo_))); - } - CUDNN_CALL(cudnnGetConvolutionBackwardFilterAlgorithm(s->dnn_handle_, - in_desc_, - out_desc_, - backward_conv_desc_, - filter_desc_, - CUDNN_CONVOLUTION_BWD_FILTER_SPECIFY_WORKSPACE_LIMIT, - workspace_byte, - &(this->back_algo_w_))); - CUDNN_CALL(cudnnGetConvolutionBackwardDataAlgorithm(s->dnn_handle_, - filter_desc_, - out_desc_, - backward_conv_desc_, - in_desc_, - CUDNN_CONVOLUTION_BWD_DATA_SPECIFY_WORKSPACE_LIMIT, - workspace_byte, - &(this->back_algo_))); - } else { - const int kMaxAlgos = 10; - int nalgo = kMaxAlgos; - int i; - - // In cuDNNv6, for kNHWC, only CUDNN_CONVOLUTION_FWD_ALGO_IMPLICIT_GEMM is - // supported. Hard-coded this since the algo find() or get() throws an FPE. - if (CUDNN_MAJOR == 6 && param_.layout.value() == mshadow::kNHWC) { - algo_ = CUDNN_CONVOLUTION_FWD_ALGO_IMPLICIT_GEMM; - } else { - cudnnConvolutionFwdAlgoPerf_t fwd_algo[kMaxAlgos]; - CUDNN_CALL(cudnnFindConvolutionForwardAlgorithm(s->dnn_handle_, - in_desc_, - filter_desc_, - forward_conv_desc_, - out_desc_, - kMaxAlgos, - &nalgo, - fwd_algo)); - i = 0; - while (i < nalgo - && (fwd_algo[i].status != CUDNN_STATUS_SUCCESS - || (param_.cudnn_tune.value() == conv::kLimited - && fwd_algo[i].memory > workspace_byte))) ++i; - if (i == nalgo) { - LOG(FATAL) << "Failed to find a forward convolution algorithm."; + cudnn_backward_compute_type, + SMArch(ctx.dev_id)); + if (!CuDNNAlgoReg::Get()->Find(key, &forward_algo_, &back_algo_, &back_algo_w_)) { + // Not in algo registry, must determine via *Get*() or *Find*() + Engine::VarHandle var = Engine::Get()->NewVariable(); + Engine::Get()->PushSync([=](RunContext rctx) { + mshadow::Stream *s = rctx.get_stream(); + CHECK_EQ(s->dnn_handle_ownership_, mshadow::Stream::OwnHandle); + size_t workspace_byte = static_cast(param_.workspace * sizeof(DType)); + #if CUDNN_MAJOR >= 7 + // Starting with cuDNNv7, the algo number returned by *Get*() is not the entire + // story: the notion of whether the algo ran in Tensor Core mode is not known. + // Since we want to report the Tensor Core mode in the verbose output, we switch + // to using the new *Get*_v7() call. Since the function signature of *Get*_v7() matches + // that of *Find*(), we can unify the find-vs-get logic by using function pointers. + + // Forward Algorithm Find/Get() v7 + std::vector fwd_results(MaxForwardAlgos(s->dnn_handle_)); + int actual_fwd_algos = 0; + auto fwd_algo_discoverer = + param_.cudnn_tune.value() == conv::kOff ? cudnnGetConvolutionForwardAlgorithm_v7 + : cudnnFindConvolutionForwardAlgorithm; + CUDNN_CALL((*fwd_algo_discoverer)(s->dnn_handle_, + in_desc_, + filter_desc_, + forward_conv_desc_, + out_desc_, + fwd_results.size(), + &actual_fwd_algos, + fwd_results.data())); + fwd_results.resize(actual_fwd_algos); + AlgoFinalSelect(fwd_results, "forward", + workspace_byte, &forward_algo_); + + // Backprop-to-Filter Algorithm Find/Get() v7 + auto max_bwd_filt_algos = MaxBackwardFilterAlgos(s->dnn_handle_); + std::vector bwd_filt_results(max_bwd_filt_algos); + int actual_bwd_filter_algos = 0; + auto bwd_filter_algo_discoverer = + param_.cudnn_tune.value() == conv::kOff ? cudnnGetConvolutionBackwardFilterAlgorithm_v7 + : cudnnFindConvolutionBackwardFilterAlgorithm; + CUDNN_CALL((*bwd_filter_algo_discoverer)(s->dnn_handle_, + in_desc_, + out_desc_, + back_conv_desc_w_, + filter_desc_, + bwd_filt_results.size(), + &actual_bwd_filter_algos, + bwd_filt_results.data())); + bwd_filt_results.resize(actual_bwd_filter_algos); + AlgoFinalSelect(bwd_filt_results, "backprop-to-filter", + workspace_byte, &back_algo_w_); + + // Backprop-to-Data Algorithm Find/Get() v7 + auto max_bwd_data_algos = MaxBackwardDataAlgos(s->dnn_handle_); + std::vector bwd_data_results(max_bwd_data_algos); + int actual_bwd_data_algos = 0; + auto bwd_data_algo_discoverer = + param_.cudnn_tune.value() == conv::kOff ? cudnnGetConvolutionBackwardDataAlgorithm_v7 + : cudnnFindConvolutionBackwardDataAlgorithm; + CUDNN_CALL((*bwd_data_algo_discoverer)(s->dnn_handle_, + filter_desc_, + out_desc_, + back_conv_desc_, + in_desc_, + bwd_data_results.size(), + &actual_bwd_data_algos, + bwd_data_results.data())); + bwd_data_results.resize(actual_bwd_data_algos); + AlgoFinalSelect(bwd_data_results, "backprop-to-data", + workspace_byte, &back_algo_); + #else + // CUDNN_MAJOR < 7 + const int kMaxAlgos = 10; + int nalgo = kMaxAlgos; + int i = 0; + // Forward Algorithm Find/Get, v6 and earlier + if (CUDNN_MAJOR == 6 && param_.layout.value() == mshadow::kNHWC) { + // In cuDNNv6, for kNHWC, only CUDNN_CONVOLUTION_FWD_ALGO_IMPLICIT_GEMM is + // supported. Hard-coded this since the algo find() or get() throws an FPE. + forward_algo_.Set(CUDNN_CONVOLUTION_FWD_ALGO_IMPLICIT_GEMM, false); + } else if (!param_.cudnn_tune.value()) { + cudnnConvolutionFwdAlgo_t fastest_fwd_algo; + CUDNN_CALL(cudnnGetConvolutionForwardAlgorithm(s->dnn_handle_, + in_desc_, + filter_desc_, + forward_conv_desc_, + out_desc_, + CUDNN_CONVOLUTION_FWD_SPECIFY_WORKSPACE_LIMIT, + workspace_byte, + &fastest_fwd_algo)); + forward_algo_.Set(fastest_fwd_algo, false); + } else { + cudnnConvolutionFwdAlgoPerf_t fwd_algo[kMaxAlgos]; + CUDNN_CALL(cudnnFindConvolutionForwardAlgorithm(s->dnn_handle_, + in_desc_, + filter_desc_, + forward_conv_desc_, + out_desc_, + kMaxAlgos, + &nalgo, + fwd_algo)); + i = 0; + while (i < nalgo + && (fwd_algo[i].status != CUDNN_STATUS_SUCCESS + || (param_.cudnn_tune.value() == conv::kLimited + && fwd_algo[i].memory > workspace_byte))) + ++i; + if (i == nalgo) { + LOG(FATAL) << "Failed to find a forward convolution algorithm."; + } else { + forward_algo_.Set(fwd_algo[i].algo, false); + } + } + // Backprop-to-Filter Algorithm Find/Get, v6 and earlier + if (!param_.cudnn_tune.value()) { + cudnnConvolutionBwdFilterAlgo_t fastest_bwd_filt_algo; + CUDNN_CALL(cudnnGetConvolutionBackwardFilterAlgorithm(s->dnn_handle_, + in_desc_, + out_desc_, + back_conv_desc_w_, + filter_desc_, + CUDNN_CONVOLUTION_BWD_FILTER_SPECIFY_WORKSPACE_LIMIT, + workspace_byte, + &fastest_bwd_filt_algo)); + back_algo_w_.Set(fastest_bwd_filt_algo, false); + } else { + cudnnConvolutionBwdFilterAlgoPerf_t bwd_filter_algo[kMaxAlgos]; + CUDNN_CALL(cudnnFindConvolutionBackwardFilterAlgorithm(s->dnn_handle_, + in_desc_, + out_desc_, + back_conv_desc_w_, + filter_desc_, + kMaxAlgos, + &nalgo, + bwd_filter_algo)); + i = 0; + while (i < nalgo + && (bwd_filter_algo[i].status != CUDNN_STATUS_SUCCESS + || (param_.cudnn_tune.value() == conv::kLimited + && bwd_filter_algo[i].memory > workspace_byte))) + ++i; + if (i == nalgo) { + LOG(FATAL) << "Failed to find a backward filter convolution algorithm."; + } else { + back_algo_w_.Set(bwd_filter_algo[i].algo, false); + } + } + // Backprop-to-Data Algorithm Get(), v6 and earlier + if (!param_.cudnn_tune.value()) { + cudnnConvolutionBwdDataAlgo_t fastest_bwd_data_algo; + CUDNN_CALL(cudnnGetConvolutionBackwardDataAlgorithm(s->dnn_handle_, + filter_desc_, + out_desc_, + back_conv_desc_, + in_desc_, + CUDNN_CONVOLUTION_BWD_DATA_SPECIFY_WORKSPACE_LIMIT, + workspace_byte, + &fastest_bwd_data_algo)); + back_algo_.Set(fastest_bwd_data_algo, false); } else { - this->algo_ = fwd_algo[i].algo; + cudnnConvolutionBwdDataAlgoPerf_t bwd_data_algo[kMaxAlgos]; + CUDNN_CALL(cudnnFindConvolutionBackwardDataAlgorithm(s->dnn_handle_, + filter_desc_, + out_desc_, + back_conv_desc_, + in_desc_, + kMaxAlgos, + &nalgo, + bwd_data_algo)); + i = 0; + while (i < nalgo + && (bwd_data_algo[i].status != CUDNN_STATUS_SUCCESS + || (param_.cudnn_tune.value() == conv::kLimited + && bwd_data_algo[i].memory > workspace_byte))) + ++i; + if (i == nalgo) { + LOG(FATAL) << "Failed to find a backward data convolution algorithm."; + } else { + back_algo_.Set(bwd_data_algo[i].algo, false); + } } - } - - cudnnConvolutionBwdFilterAlgoPerf_t bwd_filter_algo[kMaxAlgos]; - CUDNN_CALL(cudnnFindConvolutionBackwardFilterAlgorithm(s->dnn_handle_, - in_desc_, - out_desc_, - backward_conv_desc_, - filter_desc_, - kMaxAlgos, - &nalgo, - bwd_filter_algo)); - i = 0; - while (i < nalgo - && (bwd_filter_algo[i].status != CUDNN_STATUS_SUCCESS - || (param_.cudnn_tune.value() == conv::kLimited - && bwd_filter_algo[i].memory > workspace_byte))) ++i; - if (i == nalgo) { - LOG(FATAL) << "Failed to find a backward filter convolution algorithm."; - } else { - this->back_algo_w_ = bwd_filter_algo[i].algo; - } - - cudnnConvolutionBwdDataAlgoPerf_t bwd_data_algo[kMaxAlgos]; - CUDNN_CALL(cudnnFindConvolutionBackwardDataAlgorithm(s->dnn_handle_, - filter_desc_, - out_desc_, - backward_conv_desc_, - in_desc_, - kMaxAlgos, - &nalgo, - bwd_data_algo)); - i = 0; - while (i < nalgo - && (bwd_data_algo[i].status != CUDNN_STATUS_SUCCESS - || (param_.cudnn_tune.value() == conv::kLimited - && bwd_data_algo[i].memory > workspace_byte))) ++i; - if (i == nalgo) { - LOG(FATAL) << "Failed to find a backward data convolution algorithm."; - } else { - this->back_algo_ = bwd_data_algo[i].algo; - } - CuDNNAlgoReg::Get()->Register(key, this->algo_, this->back_algo_, + #endif // CUDNN_MAJOR < 7 + // An algo specification by the user may be cached here, but another + // convolution will match only if identically specified. + // We're caching results of *Get* as well as *Find*, but these records + // will be held distinctly because param_.cudnn_tune is part of the key. + CuDNNAlgoReg::Get()->Register(key, this->forward_algo_, this->back_algo_, this->back_algo_w_); + }, ctx, {}, {var}); + Engine::Get()->WaitForVar(var); + Engine::Get()->DeleteVariable([](RunContext s) {}, ctx, var); + } + // If we're allowing Tensor Core variants of the algos to be considered in + // *Find*() or *Get*(), but a non-Tensor-Core algo variant is the fastest, + // we must change the descriptor to preclude Tensor Core. Simplest is to + // once again set the mathType in all cases. + #if CUDNN_MAJOR >= 7 + CUDNN_CALL(cudnnSetConvolutionMathType(forward_conv_desc_, forward_algo_.MathType())); + CUDNN_CALL(cudnnSetConvolutionMathType(back_conv_desc_, back_algo_.MathType())); + CUDNN_CALL(cudnnSetConvolutionMathType(back_conv_desc_w_, back_algo_w_.MathType())); + #endif + } + + // Look over the results from *Find*() or *Get*() and pick the fastest algo given possible + // workspace constraints. + template + void AlgoFinalSelect(const std::vector &perf_results, std::string kernel_name, + size_t workspace_byte, CuDNNAlgo *algo) { + // Determine the fastest acceptable algo that matches the algo_preference (-1 = any), + // regardless of mathType. + for (decltype(perf_results.size()) i = 0; i != perf_results.size(); ++i) { + const auto &result = perf_results[i]; + bool algo_is_tensor_core = false; + #if CUDNN_MAJOR >= 7 + algo_is_tensor_core = result.mathType == CUDNN_TENSOR_OP_MATH; + #endif + if (result.status == CUDNN_STATUS_SUCCESS && + (param_.cudnn_tune.value() != conv::kLimited || result.memory <= workspace_byte)) { + algo->Set(result.algo, algo_is_tensor_core); + return; } - }, ctx, {}, {var}); - Engine::Get()->WaitForVar(var); - Engine::Get()->DeleteVariable([](RunContext s) {}, ctx, var); + } + auto mode = param_.cudnn_tune.value() == conv::kOff ? " get " : " find "; + LOG(FATAL) << "Failed to" << mode << "any " << kernel_name << " convolution algorithm."; } void GetTempSize(const OpContext& ctx) { @@ -636,16 +819,16 @@ class CuDNNConvolutionOp : public Operator { CUDNN_CALL(cudnnGetConvolutionBackwardDataWorkspaceSize(s->dnn_handle_, filter_desc_, out_desc_, - backward_conv_desc_, + back_conv_desc_, in_desc_, - back_algo_, + back_algo_.AlgoNumber(), &back_size)); CUDNN_CALL(cudnnGetConvolutionBackwardFilterWorkspaceSize(s->dnn_handle_, in_desc_, out_desc_, - backward_conv_desc_, + back_conv_desc_w_, filter_desc_, - back_algo_w_, + back_algo_w_.AlgoNumber(), &back_size_w)); backward_workspace_byte_ = std::max(back_size, back_size_w); CUDNN_CALL(cudnnGetConvolutionForwardWorkspaceSize(s->dnn_handle_, @@ -653,19 +836,47 @@ class CuDNNConvolutionOp : public Operator { filter_desc_, forward_conv_desc_, out_desc_, - algo_, + forward_algo_.AlgoNumber(), &forward_workspace_byte_)); - forward_workspace_ = forward_workspace_byte_ / sizeof(DType) + 1; - backward_workspace_ = backward_workspace_byte_ / sizeof(DType) + 1; init_temp_size_ = true; } + int *CastTShapeToIntPtr(const TShape& s, std::vector *buffer) { + buffer->resize(s.ndim()); + nnvm::ShapeTypeCast(s.begin(), s.end(), buffer->data()); + return buffer->data(); + } + + void InitBufferForParam() { + CastTShapeToIntPtr(param_.stride, ¶m_stride_); + CastTShapeToIntPtr(param_.dilate, ¶m_dilate_); + CastTShapeToIntPtr(param_.pad, ¶m_pad_); + } + + // Allocates a 1D Tensor of words with size in bytes >= `size_bytes`. + // Always allocates at least one word. + mshadow::Tensor AllocateTempWorkspace(const OpContext &ctx, size_t size_bytes) { + mshadow::Stream *s = ctx.get_stream(); + size_t size_words = size_bytes / sizeof(DType) + 1; + return ctx.requested[conv::kTempSpace].get_space_typed( + mshadow::Shape1(size_words), s); + } + + // Returns the size in bytes of the 1D Tensor of words. + size_t TensorSizeBytes(const mshadow::Tensor &tensor) { + return tensor.MSize() * sizeof(DType); + } + + std::vector param_stride_; + std::vector param_dilate_; + std::vector param_pad_; + bool init_cudnn_; bool init_temp_size_; - size_t forward_workspace_; - size_t backward_workspace_; + // Temp workspace size in bytes needed for Forward() operation. size_t forward_workspace_byte_; + // Temp workspace size in bytes needed for Backward() operation. size_t backward_workspace_byte_; size_t data_offset_; size_t out_offset_; @@ -678,15 +889,19 @@ class CuDNNConvolutionOp : public Operator { cudnnFilterDescriptor_t filter_desc_; // Convolution descriptor for forward inference operation cudnnConvolutionDescriptor_t forward_conv_desc_; - // Convolution descriptor for back-prop operations to data and filter - cudnnConvolutionDescriptor_t backward_conv_desc_; + // Convolution descriptor for back-prop operations to the data + cudnnConvolutionDescriptor_t back_conv_desc_; + // Convolution descriptor for back-prop operations to the weights + cudnnConvolutionDescriptor_t back_conv_desc_w_; // Algorithm for the forward inference operation - cudnnConvolutionFwdAlgo_t algo_; + CuDNNAlgo forward_algo_; // Algorithm for the back-prop operation to the data - cudnnConvolutionBwdDataAlgo_t back_algo_; + CuDNNAlgo back_algo_; // Algorithm for the back-prop operation to the weights - cudnnConvolutionBwdFilterAlgo_t back_algo_w_; + CuDNNAlgo back_algo_w_; cudnnTensorFormat_t format_; + // Allow TensorCore algo policy + bool cudnn_tensor_core_; ConvolutionParam param_; }; #endif // __CUDACC__ && CUDNN diff --git a/src/operator/cudnn_deconvolution-inl.h b/src/operator/cudnn_deconvolution-inl.h index 8405c2399897..de3e70c7d6a7 100644 --- a/src/operator/cudnn_deconvolution-inl.h +++ b/src/operator/cudnn_deconvolution-inl.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2017 by Contributors * \file cudnn_deconvolution-inl.h * \brief * \author Wei Wu, Leonard Lausen @@ -30,6 +48,7 @@ class CuDNNDeconvolutionOp : public Operator { const Context& ctx) { using namespace mshadow; this->param_ = param; + InitBufferForParam(); auto cudnn_forward_compute_type = convertToCuDNNDataType(forward_compute_type); auto cudnn_backward_compute_type = convertToCuDNNDataType(backward_compute_type); // convert MB to words @@ -37,6 +56,8 @@ class CuDNNDeconvolutionOp : public Operator { init_cudnn_ = false; init_temp_size_ = false; dtype_ = mshadow::DataType::kCudnnFlag; + // TensorCore algos only allowed on fp16-I/O deconvolutions if permitted by the global policy. + cudnn_tensor_core_ = DataType::kFlag == kFloat16 && GetEnvAllowTensorCore(); #if CUDNN_MAJOR >= 5 MSHADOW_LAYOUT_SWITCH(param_.layout.value(), Layout, { @@ -47,7 +68,7 @@ class CuDNNDeconvolutionOp : public Operator { << "Need CuDNN > 5.0 for layout support"; #endif // Double check to make sure this class supports the operation - if (!Supports(param, forward_compute_type, backward_compute_type)) + if (!Supports(param, forward_compute_type, backward_compute_type, ctx)) LOG(FATAL) << "Need CuDNN >= 6.0 for dilated convolution."; InitDescriptors(ctx, in_shape, out_shape, @@ -73,7 +94,8 @@ class CuDNNDeconvolutionOp : public Operator { CUDNN_CALL(cudnnDestroyTensorDescriptor(bias_desc_)); CUDNN_CALL(cudnnDestroyFilterDescriptor(filter_desc_)); CUDNN_CALL(cudnnDestroyConvolutionDescriptor(forward_conv_desc_)); - CUDNN_CALL(cudnnDestroyConvolutionDescriptor(backward_conv_desc_)); + CUDNN_CALL(cudnnDestroyConvolutionDescriptor(back_conv_desc_)); + CUDNN_CALL(cudnnDestroyConvolutionDescriptor(back_conv_desc_w_)); } } @@ -91,9 +113,8 @@ class CuDNNDeconvolutionOp : public Operator { CHECK_EQ(out_data.size(), 1U); Stream *s = ctx.get_stream(); GetTempSize(ctx); - Tensor workspace = - ctx.requested[deconv::kTempSpace].get_space_typed( - mshadow::Shape1(forward_workspace_), s); + Tensor workspace = AllocateTempWorkspace(ctx, forward_workspace_byte_); + size_t workspace_size = TensorSizeBytes(workspace); if (param_.kernel.ndim() == 2) { Tensor data = in_data[deconv::kData].get(s); @@ -128,9 +149,9 @@ class CuDNNDeconvolutionOp : public Operator { in_desc_, data_ptr + data_offset_ * g, forward_conv_desc_, // this backward algorithm used for inference - back_algo_, + back_algo_.AlgoNumber(), workspace.dptr_, - backward_workspace_byte_, + workspace_size, &beta, out_desc_, out.dptr_ + out_offset_ * g)); @@ -142,9 +163,9 @@ class CuDNNDeconvolutionOp : public Operator { in_desc_, data_ptr + data_offset_ * g, forward_conv_desc_, // this backward algorithm used for inference - back_algo_, + back_algo_.AlgoNumber(), workspace.dptr_, - backward_workspace_byte_, + workspace_size, &beta, out_desc_, out_ptr + out_offset_ * g)); @@ -217,15 +238,18 @@ class CuDNNDeconvolutionOp : public Operator { gdata_ptr = gdata.dptr_; } CHECK_NE(req[deconv::kWeight], kWriteInplace); - CHECK_NE(req[deconv::kBias], kWriteInplace); + if (!param_.no_bias) { + CHECK_NE(req[deconv::kBias], kWriteInplace); + } CHECK_NE(req[deconv::kData], kWriteInplace); - Tensor workspace = - ctx.requested[deconv::kTempSpace].get_space_typed( - mshadow::Shape1(backward_workspace_), s); + Tensor workspace = AllocateTempWorkspace(ctx, backward_workspace_byte_); + size_t workspace_size = TensorSizeBytes(workspace); for (uint32_t g = 0; g < param_.num_group; ++g) { typename DataType::ScaleType alpha = 1.0f; - typename DataType::ScaleType bias_beta = - req[deconv::kBias] == kAddTo ? 1.0f : 0.0f; + typename DataType::ScaleType bias_beta = 0.0f; + if (!param_.no_bias && req[deconv::kBias] == kAddTo) { + bias_beta = 1.0f; + } typename DataType::ScaleType data_beta = req[deconv::kData] == kAddTo ? 1.0f : 0.0f; typename DataType::ScaleType weight_beta = @@ -249,10 +273,10 @@ class CuDNNDeconvolutionOp : public Operator { grad_ptr + out_offset_ * g, in_desc_, data_ptr + data_offset_ * g, - backward_conv_desc_, - back_algo_w_, + back_conv_desc_, + back_algo_w_.AlgoNumber(), workspace.dptr_, - backward_workspace_byte_, + workspace_size, &weight_beta, filter_desc_, gwmat.dptr_ + weight_offset_ * g)); @@ -264,10 +288,10 @@ class CuDNNDeconvolutionOp : public Operator { grad_ptr + out_offset_ * g, in_desc_, data_ptr + data_offset_ * g, - backward_conv_desc_, - back_algo_w_, + back_conv_desc_, + back_algo_w_.AlgoNumber(), workspace.dptr_, - backward_workspace_byte_, + workspace_size, &weight_beta, filter_desc_, gwmat_ptr + weight_offset_ * g)); @@ -280,10 +304,10 @@ class CuDNNDeconvolutionOp : public Operator { grad_ptr + out_offset_ * g, filter_desc_, wmat_ptr + weight_offset_ * g, - backward_conv_desc_, - algo_, + back_conv_desc_, + forward_algo_.AlgoNumber(), workspace.dptr_, - forward_workspace_byte_, + workspace_size, &data_beta, in_desc_, gdata_ptr + data_offset_ * g)); @@ -298,7 +322,8 @@ class CuDNNDeconvolutionOp : public Operator { */ static bool Supports(DeconvolutionParam param, int forward_compute_type, - int backward_compute_type) { + int backward_compute_type, + const Context &ctx) { using namespace mshadow; // NDHWC not supported, NHWC not supported in true fp16 @@ -308,6 +333,12 @@ class CuDNNDeconvolutionOp : public Operator { if (layout_val == kNDHWC || layout_val == kNHWC && true_fp16) return false; + // Permits graceful fallback to pseudo-fp16 on heterogenous systems + if (!SupportsFloat16Compute(ctx.dev_id) && + (forward_compute_type == kFloat16 || backward_compute_type == kFloat16)) { + return false; + } + // The factor by which the effective filter size grows based on dilation. auto filterDilationFactor = param.dilate.Size(); @@ -353,7 +384,8 @@ class CuDNNDeconvolutionOp : public Operator { CUDNN_CALL(cudnnCreateTensorDescriptor(&bias_desc_)); CUDNN_CALL(cudnnCreateFilterDescriptor(&filter_desc_)); CUDNN_CALL(cudnnCreateConvolutionDescriptor(&forward_conv_desc_)); - CUDNN_CALL(cudnnCreateConvolutionDescriptor(&backward_conv_desc_)); + CUDNN_CALL(cudnnCreateConvolutionDescriptor(&back_conv_desc_)); + CUDNN_CALL(cudnnCreateConvolutionDescriptor(&back_conv_desc_w_)); TShape dshape = in_shape[deconv::kData]; TShape wshape = in_shape[deconv::kWeight]; @@ -377,7 +409,16 @@ class CuDNNDeconvolutionOp : public Operator { param_.dilate[1], CUDNN_CROSS_CORRELATION, cudnn_forward_compute_type)); - CUDNN_CALL(cudnnSetConvolution2dDescriptor(backward_conv_desc_, + CUDNN_CALL(cudnnSetConvolution2dDescriptor(back_conv_desc_, + o_pad[0], + o_pad[1], + param_.stride[0], + param_.stride[1], + param_.dilate[0], + param_.dilate[1], + CUDNN_CROSS_CORRELATION, + cudnn_backward_compute_type)); + CUDNN_CALL(cudnnSetConvolution2dDescriptor(back_conv_desc_w_, o_pad[0], o_pad[1], param_.stride[0], @@ -395,7 +436,15 @@ class CuDNNDeconvolutionOp : public Operator { param_.dilate[0], param_.dilate[1], CUDNN_CROSS_CORRELATION)); - CUDNN_CALL(cudnnSetConvolution2dDescriptor(backward_conv_desc_, + CUDNN_CALL(cudnnSetConvolution2dDescriptor(back_conv_desc_, + o_pad[0], + o_pad[1], + param_.stride[0], + param_.stride[1], + param_.dilate[0], + param_.dilate[1], + CUDNN_CROSS_CORRELATION)); + CUDNN_CALL(cudnnSetConvolution2dDescriptor(back_conv_desc_w_, o_pad[0], o_pad[1], param_.stride[0], @@ -445,27 +494,36 @@ class CuDNNDeconvolutionOp : public Operator { #if CUDNN_MAJOR >= 5 CHECK_EQ(param_.layout.value(), kNCDHW) << "CuDNN only support 3D conv with NCDHW layout"; + std::vector wshape_buffer(wshape.ndim()); CUDNN_CALL(cudnnSetFilterNdDescriptor(filter_desc_, dtype_, CUDNN_TENSOR_NCHW, static_cast(wshape.ndim()), - reinterpret_cast(&wshape[0]))); + CastTShapeToIntPtr(wshape, &wshape_buffer))); #else LOG(FATAL) << "Only support CUDNN V5 for 3D convolution"; #endif CUDNN_CALL(cudnnSetConvolutionNdDescriptor(forward_conv_desc_, 3, reinterpret_cast(&o_pad[0]), - reinterpret_cast(¶m_.stride[0]), - reinterpret_cast(¶m_.dilate[0]), + param_stride_.data(), + param_dilate_.data(), CUDNN_CROSS_CORRELATION, cudnn_forward_compute_type)); - CUDNN_CALL(cudnnSetConvolutionNdDescriptor(backward_conv_desc_, + CUDNN_CALL(cudnnSetConvolutionNdDescriptor(back_conv_desc_, + 3, + reinterpret_cast(&o_pad[0]), + param_stride_.data(), + param_dilate_.data(), + CUDNN_CROSS_CORRELATION, + cudnn_backward_compute_type)); + + CUDNN_CALL(cudnnSetConvolutionNdDescriptor(back_conv_desc_w_, 3, reinterpret_cast(&o_pad[0]), - reinterpret_cast(¶m_.stride[0]), - reinterpret_cast(¶m_.dilate[0]), + param_stride_.data(), + param_dilate_.data(), CUDNN_CROSS_CORRELATION, cudnn_backward_compute_type)); @@ -485,23 +543,35 @@ class CuDNNDeconvolutionOp : public Operator { param_.layout.value(), kNCDHW); oshape = ConvertLayout(oshape.get<5>(), param_.layout.value(), kNCDHW); } + // Set "allow tensor core" flag in convolution descriptors, if available. +#if CUDNN_MAJOR >= 7 + cudnnMathType_t math_type = cudnn_tensor_core_ ? CUDNN_TENSOR_OP_MATH + : CUDNN_DEFAULT_MATH; + CUDNN_CALL(cudnnSetConvolutionMathType(forward_conv_desc_, math_type)); + CUDNN_CALL(cudnnSetConvolutionMathType(back_conv_desc_, math_type)); + CUDNN_CALL(cudnnSetConvolutionMathType(back_conv_desc_w_, math_type)); +#endif dshape[1] /= param_.num_group; oshape[1] /= param_.num_group; weight_offset_ = wshape.Size(); data_offset_ = dstride[1] * dshape[1]; out_offset_ = ostride[1] * oshape[1]; + std::vector dshape_buffer(dshape.ndim()); + std::vector dstride_buffer(dstride.ndim()); CUDNN_CALL(cudnnSetTensorNdDescriptor(in_desc_, dtype_, static_cast(dshape.ndim()), - reinterpret_cast(&dshape[0]), - reinterpret_cast(&dstride[0]))); + CastTShapeToIntPtr(dshape, &dshape_buffer), + CastTShapeToIntPtr(dstride, &dstride_buffer))) + std::vector oshape_buffer(oshape.ndim()); + std::vector ostride_buffer(ostride.ndim()); CUDNN_CALL(cudnnSetTensorNdDescriptor(out_desc_, dtype_, static_cast(oshape.ndim()), - reinterpret_cast(&oshape[0]), - reinterpret_cast(&ostride[0]))); + CastTShapeToIntPtr(oshape, &oshape_buffer), + CastTShapeToIntPtr(ostride, &ostride_buffer))); if (!param_.no_bias) { TShape bias = in_shape[deconv::kBias]; @@ -530,164 +600,314 @@ class CuDNNDeconvolutionOp : public Operator { cudnnDataType_t cudnn_backward_compute_type) { std::string key = CuDNNAlgoReg::Get()->GetKey(param_, in_shape, out_shape, dtype_, cudnn_forward_compute_type, - cudnn_backward_compute_type); - if (CuDNNAlgoReg::Get()->Find(key, &algo_, &back_algo_, &back_algo_w_)) - return; - - Engine::VarHandle var = Engine::Get()->NewVariable(); - Engine::Get()->PushSync([=](RunContext rctx) { - mshadow::Stream *s = rctx.get_stream(); - CHECK_EQ(s->dnn_handle_ownership_, mshadow::Stream::OwnHandle); - size_t workspace_byte = static_cast(param_.workspace * sizeof(DType)); - if (!param_.cudnn_tune.value()) { - // In cuDNNv6, for kNHWC, only CUDNN_CONVOLUTION_FWD_ALGO_IMPLICIT_GEMM is - // supported. Hard-coded this since the algo find() or get() throws an FPE. - if (CUDNN_MAJOR == 6 && param_.layout.value() == mshadow::kNHWC) { - algo_ = CUDNN_CONVOLUTION_FWD_ALGO_IMPLICIT_GEMM; - } else { - CUDNN_CALL(cudnnGetConvolutionForwardAlgorithm(s->dnn_handle_, - out_desc_, - filter_desc_, - backward_conv_desc_, // forward algorithm used to backprop-to-data - in_desc_, - CUDNN_CONVOLUTION_FWD_SPECIFY_WORKSPACE_LIMIT, - workspace_byte, - &(this->algo_))); - } - CUDNN_CALL(cudnnGetConvolutionBackwardFilterAlgorithm(s->dnn_handle_, - out_desc_, - in_desc_, - backward_conv_desc_, - filter_desc_, - CUDNN_CONVOLUTION_BWD_FILTER_SPECIFY_WORKSPACE_LIMIT, - workspace_byte, - &(this->back_algo_w_))); - CUDNN_CALL(cudnnGetConvolutionBackwardDataAlgorithm(s->dnn_handle_, - filter_desc_, - in_desc_, - forward_conv_desc_, // this backward algorithm used for inference - out_desc_, - CUDNN_CONVOLUTION_BWD_DATA_SPECIFY_WORKSPACE_LIMIT, - workspace_byte, - &(this->back_algo_))); - } else { + cudnn_backward_compute_type, + SMArch(ctx.dev_id)); + if (!CuDNNAlgoReg::Get()->Find(key, &forward_algo_, &back_algo_, &back_algo_w_)) { + // Not in algo registry, must determine via *Get*() or *Find*() + Engine::VarHandle var = Engine::Get()->NewVariable(); + Engine::Get()->PushSync([=](RunContext rctx) { + mshadow::Stream *s = rctx.get_stream(); + CHECK_EQ(s->dnn_handle_ownership_, mshadow::Stream::OwnHandle); + size_t workspace_byte = static_cast(param_.workspace * sizeof(DType)); + #if CUDNN_MAJOR >= 7 + // Starting with cuDNNv7, the algo number returned by *Get*() is not the entire + // story: the notion of whether the algo ran in Tensor Core mode is not known. + // Since we want to report the Tensor Core mode in the verbose output, we switch + // to using the new *Get*_v7() call. Since the function signature of *Get*_v7() matches + // that of *Find*(), we can unify the find-vs-get logic by using function pointers. + + // Forward Algorithm Find/Get() v7 + std::vector fwd_results(MaxForwardAlgos(s->dnn_handle_)); + int actual_fwd_algos = 0; + auto fwd_algo_discoverer = + param_.cudnn_tune.value() == conv::kOff ? cudnnGetConvolutionForwardAlgorithm_v7 + : cudnnFindConvolutionForwardAlgorithm; + CUDNN_CALL((*fwd_algo_discoverer)(s->dnn_handle_, + out_desc_, + filter_desc_, + back_conv_desc_, // fwd algo used to backprop-to-data + in_desc_, + fwd_results.size(), + &actual_fwd_algos, + fwd_results.data())); + fwd_results.resize(actual_fwd_algos); + AlgoFinalSelect(fwd_results, "forward", + workspace_byte, &forward_algo_); + + // Backprop-to-Filter Algorithm Find/Get() v7 + auto max_bwd_filt_algos = MaxBackwardFilterAlgos(s->dnn_handle_); + std::vector bwd_filt_results(max_bwd_filt_algos); + int actual_bwd_filter_algos = 0; + auto bwd_filter_algo_discoverer = + param_.cudnn_tune.value() == conv::kOff ? cudnnGetConvolutionBackwardFilterAlgorithm_v7 + : cudnnFindConvolutionBackwardFilterAlgorithm; + CUDNN_CALL((*bwd_filter_algo_discoverer)(s->dnn_handle_, + out_desc_, + in_desc_, + back_conv_desc_, + filter_desc_, + bwd_filt_results.size(), + &actual_bwd_filter_algos, + bwd_filt_results.data())); + bwd_filt_results.resize(actual_bwd_filter_algos); + AlgoFinalSelect(bwd_filt_results, "backprop-to-filter", + workspace_byte, &back_algo_w_); + + // Backprop-to-Data Algorithm Find/Get() v7 + auto max_bwd_data_algos = MaxBackwardDataAlgos(s->dnn_handle_); + std::vector bwd_data_results(max_bwd_data_algos); + int actual_bwd_data_algos = 0; + auto bwd_data_algo_discoverer = + param_.cudnn_tune.value() == conv::kOff ? cudnnGetConvolutionBackwardDataAlgorithm_v7 + : cudnnFindConvolutionBackwardDataAlgorithm; + CUDNN_CALL((*bwd_data_algo_discoverer)(s->dnn_handle_, + filter_desc_, + in_desc_, + forward_conv_desc_, // bwd algo used in inference + out_desc_, + bwd_data_results.size(), + &actual_bwd_data_algos, + bwd_data_results.data())); + bwd_data_results.resize(actual_bwd_data_algos); + AlgoFinalSelect(bwd_data_results, "backprop-to-data", + workspace_byte, &back_algo_); + #else + // CUDNN_MAJOR < 7 const int kMaxAlgos = 10; int nalgo = kMaxAlgos; - int i; - - // In cuDNNv6, for kNHWC, only CUDNN_CONVOLUTION_FWD_ALGO_IMPLICIT_GEMM is - // supported. Hard-coded this since the algo find() or get() throws an FPE. + int i = 0; + // Forward Algorithm Find/Get, v6 and earlier if (CUDNN_MAJOR == 6 && param_.layout.value() == mshadow::kNHWC) { - algo_ = CUDNN_CONVOLUTION_FWD_ALGO_IMPLICIT_GEMM; + // In cuDNNv6, for kNHWC, only CUDNN_CONVOLUTION_FWD_ALGO_IMPLICIT_GEMM is + // supported. Hard-coded this since the algo find() or get() throws an FPE. + forward_algo_.Set(CUDNN_CONVOLUTION_FWD_ALGO_IMPLICIT_GEMM, false); + } else if (!param_.cudnn_tune.value()) { + cudnnConvolutionFwdAlgo_t fastest_fwd_algo; + CUDNN_CALL(cudnnGetConvolutionForwardAlgorithm(s->dnn_handle_, + out_desc_, + filter_desc_, + back_conv_desc_, // fwd algo used in dgrad + in_desc_, + CUDNN_CONVOLUTION_FWD_SPECIFY_WORKSPACE_LIMIT, + workspace_byte, + &fastest_fwd_algo)); + forward_algo_.Set(fastest_fwd_algo, false); } else { cudnnConvolutionFwdAlgoPerf_t fwd_algo[kMaxAlgos]; CUDNN_CALL(cudnnFindConvolutionForwardAlgorithm(s->dnn_handle_, - out_desc_, - filter_desc_, - backward_conv_desc_, // forward algorithm used to backprop-to-data - in_desc_, - kMaxAlgos, - &nalgo, - fwd_algo)); + out_desc_, + filter_desc_, + back_conv_desc_, // fwd algo used in dgrad + in_desc_, + kMaxAlgos, + &nalgo, + fwd_algo)); i = 0; while (i < nalgo - && (fwd_algo[i].status != CUDNN_STATUS_SUCCESS - || (param_.cudnn_tune.value() == deconv::kLimited - && fwd_algo[i].memory > workspace_byte))) ++i; + && (fwd_algo[i].status != CUDNN_STATUS_SUCCESS + || (param_.cudnn_tune.value() == deconv::kLimited + && fwd_algo[i].memory > workspace_byte))) + ++i; if (i == nalgo) { LOG(FATAL) << "Failed to find a 'forward' convolution algorithm " << - "(for use in deconvolution operator backprop-to-data)."; + "(for use in deconvolution operator backprop-to-data)."; } else { - this->algo_ = fwd_algo[i].algo; + forward_algo_.Set(fwd_algo[i].algo, false); } } - - cudnnConvolutionBwdFilterAlgoPerf_t bwd_filter_algo[kMaxAlgos]; - CUDNN_CALL(cudnnFindConvolutionBackwardFilterAlgorithm(s->dnn_handle_, - out_desc_, - in_desc_, - backward_conv_desc_, - filter_desc_, - kMaxAlgos, - &nalgo, - bwd_filter_algo)); - i = 0; - while (i < nalgo - && (bwd_filter_algo[i].status != CUDNN_STATUS_SUCCESS - || (param_.cudnn_tune.value() == deconv::kLimited - && bwd_filter_algo[i].memory > workspace_byte))) ++i; - if (i == nalgo) { - LOG(FATAL) << "Failed to find a backward filter convolution algorithm " << - "(for use in deconvolution operator backprop-to-filter)."; + // Backprop-to-Filter Algorithm Find/Get, v6 and earlier + if (!param_.cudnn_tune.value()) { + cudnnConvolutionBwdFilterAlgo_t fastest_bwd_filt_algo; + CUDNN_CALL(cudnnGetConvolutionBackwardFilterAlgorithm(s->dnn_handle_, + out_desc_, + in_desc_, + back_conv_desc_, + filter_desc_, + CUDNN_CONVOLUTION_BWD_FILTER_SPECIFY_WORKSPACE_LIMIT, + workspace_byte, + &fastest_bwd_filt_algo)); + back_algo_w_.Set(fastest_bwd_filt_algo, false); } else { - this->back_algo_w_ = bwd_filter_algo[i].algo; + cudnnConvolutionBwdFilterAlgoPerf_t bwd_filter_algo[kMaxAlgos]; + CUDNN_CALL(cudnnFindConvolutionBackwardFilterAlgorithm(s->dnn_handle_, + out_desc_, + in_desc_, + back_conv_desc_, + filter_desc_, + kMaxAlgos, + &nalgo, + bwd_filter_algo)); + i = 0; + while (i < nalgo + && (bwd_filter_algo[i].status != CUDNN_STATUS_SUCCESS + || (param_.cudnn_tune.value() == deconv::kLimited + && bwd_filter_algo[i].memory > workspace_byte))) + ++i; + if (i == nalgo) { + LOG(FATAL) << "Failed to find a backward filter convolution algorithm " << + "(for use in deconvolution operator backprop-to-filter)."; + } else { + back_algo_w_.Set(bwd_filter_algo[i].algo, false); + } } - - cudnnConvolutionBwdDataAlgoPerf_t bwd_data_algo[kMaxAlgos]; - CUDNN_CALL(cudnnFindConvolutionBackwardDataAlgorithm(s->dnn_handle_, - filter_desc_, - in_desc_, - forward_conv_desc_, // this backward algorithm used for inference - out_desc_, - kMaxAlgos, - &nalgo, - bwd_data_algo)); - i = 0; - while (i < nalgo - && (bwd_data_algo[i].status != CUDNN_STATUS_SUCCESS - || (param_.cudnn_tune.value() == deconv::kLimited - && bwd_data_algo[i].memory > workspace_byte))) ++i; - if (i == nalgo) { - LOG(FATAL) << "Failed to find a backward data convolution algorithm." << - "(for use in deconvolution operator forward inference)."; + // Backprop-to-Data Algorithm Get(), v6 and earlier + if (!param_.cudnn_tune.value()) { + cudnnConvolutionBwdDataAlgo_t fastest_bwd_data_algo; + CUDNN_CALL(cudnnGetConvolutionBackwardDataAlgorithm(s->dnn_handle_, + filter_desc_, + in_desc_, + forward_conv_desc_, // bwd algo used for inference + out_desc_, + CUDNN_CONVOLUTION_BWD_DATA_SPECIFY_WORKSPACE_LIMIT, + workspace_byte, + &fastest_bwd_data_algo)); + back_algo_.Set(fastest_bwd_data_algo, false); } else { - this->back_algo_ = bwd_data_algo[i].algo; + cudnnConvolutionBwdDataAlgoPerf_t bwd_data_algo[kMaxAlgos]; + CUDNN_CALL(cudnnFindConvolutionBackwardDataAlgorithm(s->dnn_handle_, + filter_desc_, + in_desc_, + forward_conv_desc_, // bwd algo used in inference + out_desc_, + kMaxAlgos, + &nalgo, + bwd_data_algo)); + i = 0; + while (i < nalgo + && (bwd_data_algo[i].status != CUDNN_STATUS_SUCCESS + || (param_.cudnn_tune.value() == deconv::kLimited + && bwd_data_algo[i].memory > workspace_byte))) + ++i; + if (i == nalgo) { + LOG(FATAL) << "Failed to find a backward data convolution algorithm." << + "(for use in deconvolution operator forward inference)."; + } else { + back_algo_.Set(bwd_data_algo[i].algo, false); + } } - CuDNNAlgoReg::Get()->Register(key, this->algo_, this->back_algo_, + #endif // CUDNN_MAJOR < 7 + // An algo specification by the user may be cached here, but another + // convolution will match only if identically specified. + // We're caching results of *Get* as well as *Find*, but these records + // will be held distinctly because param_.cudnn_tune is part of the key. + CuDNNAlgoReg::Get()->Register(key, this->forward_algo_, this->back_algo_, this->back_algo_w_); + }, ctx, {}, {var}); + Engine::Get()->WaitForVar(var); + Engine::Get()->DeleteVariable([](RunContext s) {}, ctx, var); + } + // If we're allowing Tensor Core variants of the algos to be considered in + // *Find*() or *Get*(), but a non-Tensor-Core algo variant is the fastest, + // we must change the descriptor to preclude Tensor Core. Simplest is to + // once again set the mathType in all cases. + #if CUDNN_MAJOR >= 7 + // The next two code lines will look like they have typos, but they don't! + // The forward_conv_desc_ is used during inference, which invokes the back_algo_. + // Thus, the mathType of the back_algo_ should be stored in the forward_conv_desc_. + // Conversely, the back_conv_desc_ is used during training backprop, which invokes + // the forward_algo_. Thus, the mathType of the forward_algo_ should be stored + // in the back_conv_desc_. + CUDNN_CALL(cudnnSetConvolutionMathType(forward_conv_desc_, back_algo_.MathType())); + CUDNN_CALL(cudnnSetConvolutionMathType(back_conv_desc_, forward_algo_.MathType())); + CUDNN_CALL(cudnnSetConvolutionMathType(back_conv_desc_w_, back_algo_w_.MathType())); + #endif + } + + // Look over the results from *Find*() or *Get*() and pick the fastest algo given possible + // workspace constraints and a possible user algo preference. + template + void AlgoFinalSelect(const std::vector &perf_results, std::string kernel_name, + size_t workspace_byte, CuDNNAlgo *algo) { + // Determine the fastest acceptable algo regardless of mathType. + for (decltype(perf_results.size()) i = 0; i != perf_results.size(); ++i) { + const auto &result = perf_results[i]; + bool algo_is_tensor_core = false; + #if CUDNN_MAJOR >= 7 + algo_is_tensor_core = result.mathType == CUDNN_TENSOR_OP_MATH; + #endif + if (result.status == CUDNN_STATUS_SUCCESS && + (param_.cudnn_tune.value() != conv::kLimited || result.memory <= workspace_byte)) { + algo->Set(result.algo, algo_is_tensor_core); + return; } - }, ctx, {}, {var}); - Engine::Get()->WaitForVar(var); - Engine::Get()->DeleteVariable([](RunContext s) {}, ctx, var); + } + auto mode = param_.cudnn_tune.value() == conv::kOff ? " get " : " find "; + LOG(FATAL) << "Failed to" << mode << "any " << kernel_name << " deconvolution algorithm."; } void GetTempSize(const OpContext& ctx) { if (init_temp_size_) return; mshadow::Stream *s = ctx.get_stream(); - size_t back_size = 0, back_size_w = 0; + size_t back_data_algo_workspace_size = 0; + size_t back_filter_algo_workspace_size = 0; + size_t forward_algo_workspace_size = 0; CUDNN_CALL(cudnnGetConvolutionBackwardDataWorkspaceSize(s->dnn_handle_, filter_desc_, in_desc_, forward_conv_desc_, out_desc_, - back_algo_, - &back_size)); + back_algo_.AlgoNumber(), + &back_data_algo_workspace_size)); CUDNN_CALL(cudnnGetConvolutionBackwardFilterWorkspaceSize(s->dnn_handle_, out_desc_, in_desc_, - backward_conv_desc_, + back_conv_desc_, filter_desc_, - back_algo_w_, - &back_size_w)); - backward_workspace_byte_ = std::max(back_size, back_size_w); + back_algo_w_.AlgoNumber(), + &back_filter_algo_workspace_size)); CUDNN_CALL(cudnnGetConvolutionForwardWorkspaceSize(s->dnn_handle_, out_desc_, filter_desc_, - backward_conv_desc_, + back_conv_desc_, in_desc_, - algo_, - &forward_workspace_byte_)); + forward_algo_.AlgoNumber(), + &forward_algo_workspace_size)); - forward_workspace_ = forward_workspace_byte_ / sizeof(DType) + 1; - backward_workspace_ = backward_workspace_byte_ / sizeof(DType) + 1; + forward_workspace_byte_ = back_data_algo_workspace_size; + backward_workspace_byte_ = std::max(forward_algo_workspace_size, + back_filter_algo_workspace_size); init_temp_size_ = true; } + int *CastTShapeToIntPtr(const TShape& s, std::vector *buffer) { + buffer->resize(s.ndim()); + nnvm::ShapeTypeCast(s.begin(), s.end(), buffer->data()); + return buffer->data(); + } + + void InitBufferForParam() { + CastTShapeToIntPtr(param_.stride, ¶m_stride_); + CastTShapeToIntPtr(param_.dilate, ¶m_dilate_); + } + + // Allocates a 1D Tensor of words with size in bytes >= `size_bytes`. + // Always allocates at least one word. + mshadow::Tensor AllocateTempWorkspace(const OpContext &ctx, size_t size_bytes) { + mshadow::Stream *s = ctx.get_stream(); + size_t size_words = size_bytes / sizeof(DType) + 1; + return ctx.requested[deconv::kTempSpace].get_space_typed( + mshadow::Shape1(size_words), s); + } + + // Returns the size in bytes of the 1D Tensor of words. + size_t TensorSizeBytes(const mshadow::Tensor &tensor) { + return tensor.MSize() * sizeof(DType); + } + + std::vector param_stride_; + std::vector param_dilate_; + bool init_cudnn_; bool init_temp_size_; - size_t forward_workspace_; - size_t backward_workspace_; + // Temp workspace size in bytes needed for Forward() operation. Note that + // in deconvolution, this is handled by the cuDNN backprop-to-data kernel. size_t forward_workspace_byte_; + // Temp workspace size in bytes needed for Backward() operation. Note that + // in deconvolution, this is handled by the cuDNN forward kernel and the + // the cuDNN backprop-to-filter kernel. size_t backward_workspace_byte_; size_t data_offset_; size_t out_offset_; @@ -702,19 +922,24 @@ class CuDNNDeconvolutionOp : public Operator { // Note that in deconvolution, the forward operation is handled // by the cuDNN backprop-to-data kernel. cudnnConvolutionDescriptor_t forward_conv_desc_; - // Convolution descriptor for "back-prop" operations to data and filter. + // Convolution descriptor for "back-prop" operations to data . + // Note that in deconvolution, the backprop-to-data operation is handled + // by the cuDNN forward kernel. + cudnnConvolutionDescriptor_t back_conv_desc_; + // Convolution descriptor for "back-prop" operations to filter. // Note that in deconvolution, the backprop-to-data operation is handled - // by the cuDNN forward kernel, while the backprop-to-filter operation - // stays consistent with the convolution operator and is handled by - // the backprop-to-filter kernel. - cudnnConvolutionDescriptor_t backward_conv_desc_; + // by the backprop-to-filter kernel (so consistent with the treatment + // in convolution). + cudnnConvolutionDescriptor_t back_conv_desc_w_; // Algorithm for the cuDNN forward kernel (used in gradient backprop to input) - cudnnConvolutionFwdAlgo_t algo_; + CuDNNAlgo forward_algo_; // Algorithm for the cuDNN backprop-to-data kernel (used in inference) - cudnnConvolutionBwdDataAlgo_t back_algo_; + CuDNNAlgo back_algo_; // Algorithm for the cuDNN backprop-to-filter kernel - cudnnConvolutionBwdFilterAlgo_t back_algo_w_; + CuDNNAlgo back_algo_w_; cudnnTensorFormat_t format_; + // Allow TensorCore algo policy + bool cudnn_tensor_core_; DeconvolutionParam param_; }; #endif // CUDNN diff --git a/src/operator/cudnn_lrn-inl.h b/src/operator/cudnn_lrn-inl.h old mode 100755 new mode 100644 index d65a678bc07d..241ec704a904 --- a/src/operator/cudnn_lrn-inl.h +++ b/src/operator/cudnn_lrn-inl.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file cudnn_lrn-inl.h * \brief * \author Bing Xu diff --git a/src/operator/cudnn_pooling-inl.h b/src/operator/cudnn_pooling-inl.h index 3c9344ec5aeb..5b03fe5ee6f3 100644 --- a/src/operator/cudnn_pooling-inl.h +++ b/src/operator/cudnn_pooling-inl.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file cudnn_pooling-inl.h * \brief * \author Bing Xu diff --git a/src/operator/cudnn_rnn-inl.h b/src/operator/cudnn_rnn-inl.h index a4ce10edd886..a260cb4ca0e3 100644 --- a/src/operator/cudnn_rnn-inl.h +++ b/src/operator/cudnn_rnn-inl.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2016 by Contributors * \file cudnn_rnn-inl.h * \brief * \author Sebastian Bodenstein @@ -25,6 +43,12 @@ class CuDNNRNNOp : public Operator { this->param_ = param; init_cudnn_ = false; dtype_ = mshadow::DataType::kCudnnFlag; + // TensorCore algos only allowed on fp16-I/O convolutions if permitted by the global policy. + // No tests in place for fp16 RNNs, so leave TensorCore disabled for now. + cudnn_tensor_core_ = false; + // When fp16 RNN tests are introduced, we can enable TensorCore as follows: +// cudnn_tensor_core = +// mshadow::DataType::kFlag == mshadow::kFloat16 && GetEnvAllowTensorCore(); // Defaults input_mode_ = CUDNN_LINEAR_INPUT; // Don't support this yet // RNN Mode @@ -432,14 +456,36 @@ class CuDNNRNNOp : public Operator { seed_)); // RNN descriptors CUDNN_CALL(cudnnCreateRNNDescriptor(&rnn_desc_)); - CUDNN_CALL(cudnnSetRNNDescriptor(rnn_desc_, - param_.state_size, - param_.num_layers, - dropout_desc_, - input_mode_, - direction_, - mode_, - dtype_)); + + #if CUDNN_MAJOR >= 6 + cudnnRNNAlgo_t rnn_algo = CUDNN_RNN_ALGO_STANDARD; + CUDNN_CALL(cudnnSetRNNDescriptor_v6(s->dnn_handle_, + rnn_desc_, + param_.state_size, + param_.num_layers, + dropout_desc_, + input_mode_, + direction_, + mode_, + rnn_algo, + dtype_)); + #else + CUDNN_CALL(cudnnSetRNNDescriptor(rnn_desc_, + param_.state_size, + param_.num_layers, + dropout_desc_, + input_mode_, + direction_, + mode_, + dtype_)); + #endif + #if CUDNN_MAJOR >= 7 + cudnnMathType_t math_type = CUDNN_DEFAULT_MATH; + if (cudnn_tensor_core_ && rnn_algo == CUDNN_RNN_ALGO_STANDARD) { + math_type = CUDNN_TENSOR_OP_MATH; + } + CUDNN_CALL(cudnnSetRNNMatrixMathType(rnn_desc_, math_type)); + #endif // Get temp space sizes CUDNN_CALL(cudnnGetRNNWorkspaceSize(s->dnn_handle_, rnn_desc_, @@ -526,7 +572,7 @@ class CuDNNRNNOp : public Operator { cudnnRNNInputMode_t input_mode_; cudnnDropoutDescriptor_t dropout_desc_; Storage::Handle dropout_states_, reserve_space_; - uint64_t seed_ = 1337ull; + uint64_t seed_ = 17 + rand() % 4096; // NOLINT(runtime/threadsafe_fn) size_t workspace_byte_, reserve_space_byte_, dropout_byte_; int workspace_size_, dropout_size_; std::vector x_desc_vec_, y_desc_vec_, dx_desc_vec_, dy_desc_vec_; @@ -536,6 +582,8 @@ class CuDNNRNNOp : public Operator { cudnnTensorDescriptor_t dhy_desc_, dcy_desc_; cudnnFilterDescriptor_t w_desc_, dw_desc_; + // Allow TensorCore algo policy + bool cudnn_tensor_core_; #if CUDNN_MAJOR >= 5 cudnnTensorFormat_t format_; diff --git a/src/operator/cudnn_softmax_activation-inl.h b/src/operator/cudnn_softmax_activation-inl.h index 86c27317f923..c604a8f3f4c1 100644 --- a/src/operator/cudnn_softmax_activation-inl.h +++ b/src/operator/cudnn_softmax_activation-inl.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file cudnn_activation-inl.h * \brief * \author Bing Xu diff --git a/src/operator/cudnn_spatial_transformer-inl.h b/src/operator/cudnn_spatial_transformer-inl.h index b25e8cebc077..fc767841447b 100644 --- a/src/operator/cudnn_spatial_transformer-inl.h +++ b/src/operator/cudnn_spatial_transformer-inl.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2016 by Contributors * \file cudnn_spatial_transformer-inl.h * \brief * \author Wei Wu diff --git a/src/operator/custom/custom-inl.h b/src/operator/custom/custom-inl.h index b9224cd30f48..4b2d620be1d6 100644 --- a/src/operator/custom/custom-inl.h +++ b/src/operator/custom/custom-inl.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file native_op-inl.h * \brief * \author Junyuan Xie @@ -25,266 +43,33 @@ namespace mxnet { namespace op { +namespace custom { -struct CustomOpParam { - std::string op_type; - std::vector > kwargs; -}; - -template -class CustomOp : public Operator { - public: - explicit CustomOp(MXCallbackList* op_info) { - op_info_.reset(op_info, [](MXCallbackList *ptr){ - reinterpret_cast(ptr->callbacks[kCustomOpDelete])( - ptr->contexts[kCustomOpDelete]); - delete ptr; - }); - if (std::string("NaiveEngine") == dmlc::GetEnv("MXNET_ENGINE_TYPE", std::string())) { - sync_mode_ = true; - } else { - sync_mode_ = false; - destructing_ = false; - worker_ = std::thread([&]() { - std::unique_lock lock(mtx_); - while (!q_.empty() || !destructing_) { - cv_.wait(lock, [&] {return !q_.empty() || destructing_;}); - while (!q_.empty()) { - q_.front()(); - q_.pop(); - } - } - }); - } - } - - ~CustomOp() { - if (!sync_mode_) { - { - std::unique_lock lock(mtx_); - destructing_ = true; - cv_.notify_all(); - } - worker_.join(); - } - } - - virtual void Forward(const OpContext &ctx, - const std::vector &in_data, - const std::vector &req, - const std::vector &out_data, - const std::vector &aux_args); - - virtual void Backward(const OpContext &ctx, - const std::vector &out_grad, - const std::vector &in_data, - const std::vector &out_data, - const std::vector &req, - const std::vector &in_grad, - const std::vector &aux_args); - - virtual ExecType exec_type() const { - return kAsync; - } - - private: - Context get_ctx(); - std::shared_ptr op_info_; - std::mutex mtx_; - std::condition_variable cv_; - std::thread worker_; - std::queue > q_; - bool destructing_; - bool sync_mode_; -}; // CustomOp - -template -Operator* CreateOp(MXCallbackList *op_info); - -class CustomOpProp : public OperatorProperty { +class Registry { public: - static void Register(const std::string &op_type, CustomOpPropCreator creator) { + void Register(const std::string &op_type, CustomOpPropCreator creator) { + std::lock_guard lock(mutex_); if (registry_.find(op_type) != registry_.end()) { LOG(WARNING) << "New registration is overriding existing custom operator " << op_type; } registry_[op_type] = creator; } - void Init(const std::vector >& kwargs) override { - kwargs_ = kwargs; - param_.op_type = ""; - param_.kwargs.clear(); - std::vector keys, vals; - for (auto &p : kwargs) { - if (p.first == "op_type") { - param_.op_type = p.second; - } else { - param_.kwargs.push_back(p); - keys.push_back(p.first.c_str()); - vals.push_back(p.second.c_str()); - } - } - CHECK_NE(param_.op_type, "") << "Custom operator type missing"; - CHECK(registry_.find(param_.op_type) != registry_.end()) - << "Cannot find custom operator type " << param_.op_type; - CustomOpPropCreator creator = registry_[param_.op_type]; - info_.reset(new MXCallbackList, [](MXCallbackList* ptr){ - reinterpret_cast(ptr->callbacks[kCustomOpPropDelete])( - ptr->contexts[kCustomOpPropDelete]); - delete ptr; - }); - CHECK(creator(param_.op_type.c_str(), keys.size(), keys.data(), vals.data(), info_.get())); - num_inputs_ = ListArguments().size(); - num_outputs_ = ListOutputs().size(); - num_auxs_ = ListAuxiliaryStates().size(); - } - - std::vector ListArguments() const override { - char ** args = NULL; - CHECK(reinterpret_cast(info_->callbacks[kCustomOpPropListArguments])( - &args, info_->contexts[kCustomOpPropListArguments])); - std::vector ret; - for (int i = 0; args[i] != NULL; ++i) { - ret.push_back(args[i]); - } - return ret; - } - - std::vector ListOutputs() const override { - char ** args = NULL; - CHECK(reinterpret_cast(info_->callbacks[kCustomOpPropListOutputs])( - &args, info_->contexts[kCustomOpPropListOutputs])); - std::vector ret; - for (int i = 0; args[i] != NULL; ++i) { - ret.push_back(args[i]); - } - return ret; - } - - std::vector ListAuxiliaryStates() const override { - char ** args = NULL; - CHECK(reinterpret_cast(info_->callbacks[kCustomOpPropListAuxiliaryStates])( - &args, info_->contexts[kCustomOpPropListAuxiliaryStates])); - std::vector ret; - for (int i = 0; args[i] != NULL; ++i) { - ret.push_back(args[i]); - } - return ret; - } - - int NumOutputs() const override { - return ListOutputs().size(); - } - - std::map GetParams() const override { - return std::map(kwargs_.begin(), kwargs_.end()); - } - - - bool InferShape(std::vector *in_shape, - std::vector *out_shape, - std::vector *aux_shape) const override { - std::vector shapes; - std::vector ndims; - for (auto iter = in_shape->begin(); iter != in_shape->end(); ++iter) { - shapes.push_back(iter->data()); - ndims.push_back(iter->ndim()); - } - shapes.resize(num_inputs_+num_outputs_+num_auxs_); - ndims.resize(num_inputs_+num_outputs_+num_auxs_); - - CHECK(reinterpret_cast(info_->callbacks[kCustomOpPropInferShape])( - shapes.size(), ndims.data(), shapes.data(), info_->contexts[kCustomOpPropInferShape])); - for (unsigned i = 0; i < in_shape->size(); ++i) { - SHAPE_ASSIGN_CHECK(*in_shape, i, TShape(shapes[i], shapes[i]+ndims[i])); - } - out_shape->clear(); - for (unsigned i = num_inputs_; i < num_inputs_+num_outputs_; ++i) { - out_shape->push_back(TShape(shapes[i], shapes[i]+ndims[i])); - } - aux_shape->clear(); - for (unsigned i = num_inputs_+num_outputs_; i < shapes.size(); ++i) { - aux_shape->push_back(TShape(shapes[i], shapes[i]+ndims[i])); - } - return true; - } - - bool InferType(std::vector *in_type, - std::vector *out_type, - std::vector *aux_type) const override { - if (info_->num_callbacks <= kCustomOpPropInferType) { - return OperatorProperty::InferType(in_type, out_type, aux_type); - } - - std::vector types; - for (const auto &i : *in_type) types.push_back(i); - for (const auto &i : *out_type) types.push_back(i); - for (const auto &i : *aux_type) types.push_back(i); - - CHECK(reinterpret_cast(info_->callbacks[kCustomOpPropInferType])( - types.size(), types.data(), info_->contexts[kCustomOpPropInferType])); - for (unsigned i = 0; i < num_inputs_; ++i) { - TYPE_ASSIGN_CHECK(*in_type, i, types[i]); - } - for (unsigned i = 0; i < num_outputs_; ++i) { - TYPE_ASSIGN_CHECK(*out_type, i, types[i+num_inputs_]); - } - for (unsigned i = 0; i < num_auxs_; ++i) { - TYPE_ASSIGN_CHECK(*aux_type, i, types[i+num_inputs_+num_outputs_]); - } - return true; - } - - - OperatorProperty* Copy() const override { - CustomOpProp *prop_sym = new CustomOpProp(); - prop_sym->Init(kwargs_); - return prop_sym; - } - - std::string TypeString() const override { - return "Custom"; + CustomOpPropCreator Find(const std::string &op_type) { + std::lock_guard lock(mutex_); + auto it = registry_.find(op_type); + if (it != registry_.end()) return it->second; + return nullptr; } - std::vector DeclareBackwardDependency( - const std::vector &out_grad, - const std::vector &in_data, - const std::vector &out_data) const override { - int num_dep; - int *rdeps; - CHECK(reinterpret_cast( - info_->callbacks[kCustomOpPropDeclareBackwardDependency])( - out_grad.data(), in_data.data(), out_data.data(), &num_dep, - &rdeps, info_->contexts[kCustomOpPropDeclareBackwardDependency])); - std::vector deps; - deps.insert(deps.end(), rdeps, rdeps+num_dep); - return deps; - } - - std::vector > BackwardInplaceOption( - const std::vector &out_grad, - const std::vector &in_data, - const std::vector &out_data, - const std::vector &in_grad) const override { - return {}; - } - - Operator* CreateOperator(Context ctx) const override { - LOG(FATAL) << "Not Implemented."; - return NULL; - } - - Operator* CreateOperatorEx(Context ctx, std::vector *in_shape, - std::vector *in_type) const override; - + static Registry* Get(); private: - static std::map registry_; + Registry() {} + std::mutex mutex_; + std::map registry_; +}; - CustomOpParam param_; - std::shared_ptr info_; - std::vector > kwargs_; - unsigned num_inputs_, num_outputs_, num_auxs_; -}; // class CustomOpProp +} // namespace custom } // namespace op } // namespace mxnet #endif // MXNET_OPERATOR_CUSTOM_CUSTOM_INL_H_ diff --git a/src/operator/custom/custom.cc b/src/operator/custom/custom.cc index 06330a4a062e..59414d30ddc3 100644 --- a/src/operator/custom/custom.cc +++ b/src/operator/custom/custom.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file custom.cc * \brief * \author Junyuan Xie @@ -8,191 +26,387 @@ #include #include +#include "../../ndarray/autograd.h" +#include "../elemwise_op_common.h" + namespace mxnet { namespace op { -std::map CustomOpProp::registry_; +namespace custom { + +Registry* Registry::Get() { + static Registry inst; + return &inst; +} + +struct CustomParam { + std::string op_type; + size_t num_args, num_outs, num_auxs; + std::vector bwd_idx; + std::shared_ptr info; +}; + + +template +std::vector List(const NodeAttrs& attrs) { + const CustomParam& params = nnvm::get(attrs.parsed); + char ** args = NULL; + CHECK(reinterpret_cast( + params.info->callbacks[Type])( + &args, params.info->contexts[Type])); + std::vector ret; + for (int i = 0; args[i] != NULL; ++i) { + ret.push_back(args[i]); + } + return ret; +} + +void AttrParser(NodeAttrs* attrs) { + attrs->parsed = CustomParam(); + CustomParam& params = nnvm::get(attrs->parsed); + + std::vector keys, vals; + for (auto &p : attrs->dict) { + if (p.first == "op_type") { + params.op_type = p.second; + } else { + keys.push_back(p.first.c_str()); + vals.push_back(p.second.c_str()); + } + } + CHECK(!params.op_type.empty()) << "Required argument `op_type` is missing."; + CustomOpPropCreator creator = Registry::Get()->Find(params.op_type); + CHECK(Registry::Get()->Find(params.op_type) != nullptr) + << "Cannot find custom operator " << params.op_type; + params.info.reset(new MXCallbackList, [](MXCallbackList* ptr){ + reinterpret_cast(ptr->callbacks[kCustomOpPropDelete])( + ptr->contexts[kCustomOpPropDelete]); + delete ptr; + }); + CHECK(creator(params.op_type.c_str(), keys.size(), keys.data(), + vals.data(), params.info.get())); + + params.num_args = List(*attrs).size(); + params.num_outs = List(*attrs).size(); + params.num_auxs = List(*attrs).size(); + + int num_dep, *rdeps, counter = 0; + std::vector out_grad, in_data, out_data; + for (size_t i = 0; i < params.num_outs; ++i) out_grad.push_back(counter++); + for (size_t i = 0; i < params.num_args; ++i) in_data.push_back(counter++); + for (size_t i = 0; i < params.num_outs; ++i) out_data.push_back(counter++); + CHECK(reinterpret_cast( + params.info->callbacks[kCustomOpPropDeclareBackwardDependency])( + out_grad.data(), in_data.data(), out_data.data(), &num_dep, + &rdeps, params.info->contexts[kCustomOpPropDeclareBackwardDependency])); + params.bwd_idx.insert(params.bwd_idx.end(), rdeps, rdeps+num_dep); +} + +bool InferShape(const NodeAttrs& attrs, + std::vector *in_shape, + std::vector *out_shape) { + const CustomParam& params = nnvm::get(attrs.parsed); + + size_t total = params.num_args + params.num_outs + params.num_auxs; + std::vector shapes(total); + std::vector ndims(total); + size_t buff_size = 0; + for (const auto& i : *in_shape) buff_size += i.ndim(); + std::vector buff(buff_size); + uint32_t *ptr = buff.data(); + for (size_t i = 0; i < in_shape->size(); ++i) { + shapes[i] = ptr; + ndims[i] = (*in_shape)[i].ndim(); + for (size_t j = 0; j < (*in_shape)[i].ndim(); ++j, ++ptr) { + *ptr = static_cast((*in_shape)[i][j]); + } + } + + CHECK(reinterpret_cast( + params.info->callbacks[kCustomOpPropInferShape])( + shapes.size(), ndims.data(), shapes.data(), + params.info->contexts[kCustomOpPropInferShape])); -template<> -Context CustomOp::get_ctx() { - return Context::CPU(); + for (size_t i = 0; i < params.num_args; ++i) { + SHAPE_ASSIGN_CHECK(*in_shape, i, TShape(shapes[i], shapes[i]+ndims[i])); + } + + size_t base = params.num_args; + for (size_t i = 0; i < params.num_outs; ++i) { + SHAPE_ASSIGN_CHECK(*out_shape, i, + TShape(shapes[base+i], shapes[base+i]+ndims[base+i])); + } + + base = params.num_args + params.num_outs; + for (size_t i = 0; i < params.num_auxs; ++i) { + SHAPE_ASSIGN_CHECK(*in_shape, params.num_args+i, + TShape(shapes[base+i], shapes[base+i]+ndims[base+i])); + } + return true; } -template<> -Operator *CreateOp(MXCallbackList *op_info) { - return new CustomOp(op_info); +bool InferType(const NodeAttrs& attrs, + std::vector *in_type, + std::vector *out_type) { + const CustomParam& params = nnvm::get(attrs.parsed); + + if (params.info->num_callbacks <= kCustomOpPropInferType) { + return ElemwiseAttr( + attrs, in_type, out_type, -1); + } + + std::vector types; + types.reserve(params.num_args + params.num_outs + params.num_auxs); + for (size_t i = 0; i < params.num_args; ++i) { + types.push_back((*in_type)[i]); + } + for (const auto &i : *out_type) { + types.push_back(i); + } + for (size_t i = 0; i < params.num_auxs; ++i) { + types.push_back((*in_type)[params.num_args+i]); + } + + CHECK(reinterpret_cast( + params.info->callbacks[kCustomOpPropInferType])( + types.size(), types.data(), params.info->contexts[kCustomOpPropInferType])); + + for (size_t i = 0; i < params.num_args; ++i) { + TYPE_ASSIGN_CHECK(*in_type, i, types[i]); + } + for (size_t i = 0; i < params.num_outs; ++i) { + TYPE_ASSIGN_CHECK(*out_type, i, types[params.num_args+i]); + } + for (size_t i = 0; i < params.num_auxs; ++i) { + TYPE_ASSIGN_CHECK(*in_type, params.num_args+i, + types[params.num_args+params.num_outs+i]); + } + return true; } -#if MXNET_USE_CUDA -template<> -Context CustomOp::get_ctx() { - int dev_id; - CHECK_EQ(cudaGetDevice(&dev_id), cudaSuccess); - return Context::GPU(dev_id); +std::vector Gradient( + const nnvm::NodePtr& n, + const std::vector& out_grads) { + const CustomParam& params = nnvm::get(n->attrs.parsed); + + nnvm::NodePtr g = nnvm::Node::Create(); + g->attrs.op = nnvm::Op::Get("_backward_Custom"); + g->attrs.name = n->attrs.name; + g->attrs.parsed = params; + g->control_deps.emplace_back(n); + + g->inputs.reserve(params.bwd_idx.size()); + for (const int& t : params.bwd_idx) { + size_t i = static_cast(t); + if (i >= params.num_outs + params.num_args) { + uint32_t idx = static_cast(i-params.num_outs-params.num_args); + g->inputs.push_back(nnvm::NodeEntry{n, idx, 0}); + } else if (i >= params.num_outs) { + g->inputs.push_back(n->inputs[i-params.num_outs]); + } else { + g->inputs.push_back(out_grads[i]); + } + } + + for (size_t i = 0; i < params.num_auxs; ++i) { + g->inputs.push_back(n->inputs[i+params.num_args]); + } + + std::vector ret; + for (index_t i = 0; i < g->num_outputs(); ++i) { + ret.emplace_back(nnvm::NodeEntry{g, i, 0}); + } + + return ret; } -template<> -Operator* CreateOp(MXCallbackList *op_info) { - return new CustomOp(op_info); + +OpStatePtr CreateState(const NodeAttrs& attrs, Context ctx, + const std::vector& in_shape, + const std::vector& in_type) { + const CustomParam& params = nnvm::get(attrs.parsed); + + size_t total = params.num_args + params.num_outs + params.num_auxs; + std::vector shapes(total); + std::vector ndims(total); + size_t buff_size = 0; + for (const auto& i : in_shape) buff_size += i.ndim(); + std::vector buff(buff_size); + uint32_t *ptr = buff.data(); + for (size_t i = 0; i < in_shape.size(); ++i) { + shapes[i] = ptr; + ndims[i] = in_shape[i].ndim(); + for (size_t j = 0; j < in_shape[i].ndim(); ++j, ++ptr) { + *ptr = static_cast(in_shape[i][j]); + } + } + + std::ostringstream os; + os << ctx; + + MXCallbackList *op_info = new MXCallbackList; + CHECK(reinterpret_cast( + params.info->callbacks[kCustomOpPropCreateOperator])( + os.str().c_str(), shapes.size(), shapes.data(), ndims.data(), in_type.data(), + op_info, params.info->contexts[kCustomOpPropCreateOperator])); + + CustomParam state = params; + state.info.reset(op_info, [](MXCallbackList *ptr){ + reinterpret_cast(ptr->callbacks[kCustomOpDelete])( + ptr->contexts[kCustomOpDelete]); + delete ptr; + }); + + return OpStatePtr::Create(state); } -#endif // MXNET_USE_CUDA - -template -void CustomOp::Forward(const OpContext &ctx, - const std::vector &in_data, - const std::vector &req, - const std::vector &out_data, - const std::vector &aux_args) { - using namespace mshadow; - Context ndctx = get_ctx(); + +void Forward(const OpStatePtr& state, + const OpContext& ctx, + const std::vector& inputs, + const std::vector& req, + const std::vector& outputs) { + const CustomParam& params = state.get_state(); std::vector ptrs; - std::vector ndcpy; - std::vector ndvar; std::vector tags; - std::vector reqs(req.begin(), req.end()); - for (auto& blob : in_data) { - ptrs.push_back(reinterpret_cast(new NDArray(blob, ndctx.dev_id))); + for (size_t i = 0; i < params.num_args; ++i) { + NDArray *nd = new NDArray(inputs[i].Detach()); + ptrs.push_back(reinterpret_cast(nd)); tags.push_back(0); } - for (auto& blob : out_data) { - NDArray* nd = new NDArray(blob, ndctx.dev_id); + + for (size_t i = 0; i < params.num_outs; ++i) { + NDArray *nd = new NDArray(outputs[i].Detach()); ptrs.push_back(reinterpret_cast(nd)); - ndcpy.push_back(*nd); - ndvar.push_back(nd->var()); tags.push_back(1); } - for (auto& blob : aux_args) { - NDArray* nd = new NDArray(blob, ndctx.dev_id); + + for (size_t i = 0; i < params.num_auxs; ++i) { + NDArray *nd = new NDArray(inputs[i+params.num_args].Detach()); ptrs.push_back(reinterpret_cast(nd)); - ndcpy.push_back(*nd); - ndvar.push_back(nd->var()); tags.push_back(4); } - std::sort(ndvar.begin(), ndvar.end()); - ndvar.resize(std::unique(ndvar.begin(), ndvar.end()) - ndvar.begin()); - auto compute = [=]() mutable { - CHECK(reinterpret_cast(op_info_->callbacks[kCustomOpForward])( - ptrs.size(), ptrs.data(), tags.data(), reqs.data(), - static_cast(ctx.is_train), op_info_->contexts[kCustomOpForward])); + bool prev_recording = autograd::AutogradRuntime::Get()->SetIsRecording(false); + bool prev_training = autograd::AutogradRuntime::Get()->SetIsTraining(ctx.is_train); - // NDArray* in ptrs is freed by frontend side. We keep a copy in ndcpy to keep ndvar alive - Engine::Get()->PushSync([ndcpy, ctx](RunContext rctx) { - ctx.async_on_complete(); - }, ndctx, ndvar, {}, - FnProperty::kNormal, 0, PROFILER_MESSAGE("CustomOpForward")); - }; + CHECK(reinterpret_cast(params.info->callbacks[kCustomOpForward])( + ptrs.size(), ptrs.data(), tags.data(), reinterpret_cast(req.data()), + static_cast(ctx.is_train), params.info->contexts[kCustomOpForward])); - if (sync_mode_) { - compute(); - } else { - std::unique_lock lock(mtx_); - q_.push(compute); - cv_.notify_all(); - } + autograd::AutogradRuntime::Get()->SetIsTraining(prev_training); + autograd::AutogradRuntime::Get()->SetIsRecording(prev_recording); } -template -void CustomOp::Backward(const OpContext &ctx, - const std::vector &out_grad, - const std::vector &in_data, - const std::vector &out_data, - const std::vector &req, - const std::vector &in_grad, - const std::vector &aux_args) { - using namespace mshadow; - Context ndctx = get_ctx(); - std::vector ptrs; - std::vector ndcpy; - std::vector ndvar; + +void Backward(const OpStatePtr& state, + const OpContext& ctx, + const std::vector& inputs, + const std::vector& req, + const std::vector& outputs) { + const CustomParam& params = state.get_state(); + + size_t total = 2*params.num_args + 2*params.num_outs + params.num_auxs; + std::vector ptrs(params.num_args + 2*params.num_outs, nullptr); std::vector tags; - std::vector reqs(req.begin(), req.end()); + ptrs.reserve(total); + tags.reserve(total); + for (size_t i = 0; i < params.num_outs; ++i) tags.push_back(3); + for (size_t i = 0; i < params.num_args; ++i) tags.push_back(0); + for (size_t i = 0; i < params.num_outs; ++i) tags.push_back(1); - for (auto& blob : in_data) { - ptrs.push_back(reinterpret_cast(new NDArray(blob, ndctx.dev_id))); - tags.push_back(0); + for (size_t i = 0; i < params.bwd_idx.size(); ++i) { + NDArray *nd = new NDArray(inputs[i].Detach()); + ptrs[params.bwd_idx[i]] = reinterpret_cast(nd); } - for (auto& blob : out_data) { - ptrs.push_back(reinterpret_cast(new NDArray(blob, ndctx.dev_id))); - tags.push_back(1); + for (size_t i = 0; i < ptrs.size(); ++i) { + if (ptrs[i] == nullptr) ptrs[i] = reinterpret_cast(new NDArray()); } - for (auto& blob : in_grad) { - NDArray* nd = new NDArray(blob, ndctx.dev_id); + for (const auto& i : outputs) { + NDArray* nd = new NDArray(i.Detach()); ptrs.push_back(reinterpret_cast(nd)); - ndcpy.push_back(*nd); - ndvar.push_back(nd->var()); tags.push_back(2); } - for (auto& blob : aux_args) { - NDArray* nd = new NDArray(blob, ndctx.dev_id); + for (size_t i = 0; i < params.num_auxs; ++i) { + NDArray* nd = new NDArray(inputs[inputs.size()-params.num_auxs+i].Detach()); ptrs.push_back(reinterpret_cast(nd)); - ndcpy.push_back(*nd); - ndvar.push_back(nd->var()); tags.push_back(4); } - std::sort(ndvar.begin(), ndvar.end()); - ndvar.resize(std::unique(ndvar.begin(), ndvar.end()) - ndvar.begin()); - for (auto& blob : out_grad) { - ptrs.push_back(reinterpret_cast(new NDArray(blob, ndctx.dev_id))); - tags.push_back(3); - } - auto compute = [=]() mutable { - CHECK(reinterpret_cast(op_info_->callbacks[kCustomOpBackward])( - ptrs.size(), ptrs.data(), tags.data(), reqs.data(), 1, - op_info_->contexts[kCustomOpBackward])); + bool prev_recording = autograd::AutogradRuntime::Get()->SetIsRecording(false); + bool prev_training = autograd::AutogradRuntime::Get()->SetIsTraining(ctx.is_train); - // NDArray* in ptrs is freed by frontend side. We keep a copy in ndcpy to keep ndvar alive - Engine::Get()->PushSync([ndcpy, ctx](RunContext rctx){ - ctx.async_on_complete(); - }, ndctx, ndvar, {}, - FnProperty::kNormal, 0, PROFILER_MESSAGE("CustomOpBackward")); - }; + CHECK(reinterpret_cast(params.info->callbacks[kCustomOpBackward])( + ptrs.size(), ptrs.data(), tags.data(), reinterpret_cast(req.data()), + static_cast(ctx.is_train), params.info->contexts[kCustomOpBackward])); - if (sync_mode_) { - compute(); - } else { - std::unique_lock lock(mtx_); - q_.push(compute); - cv_.notify_all(); - } + autograd::AutogradRuntime::Get()->SetIsTraining(prev_training); + autograd::AutogradRuntime::Get()->SetIsRecording(prev_recording); } -Operator* CustomOpProp::CreateOperatorEx(Context ctx, std::vector *in_shape, - std::vector *in_type) const { - std::vector shapes; - std::vector ndims; - for (auto iter = in_shape->begin(); iter != in_shape->end(); ++iter) { - shapes.push_back(iter->data()); - ndims.push_back(iter->ndim()); - } - std::string str_ctx; - if (ctx.dev_mask() == cpu::kDevMask) { - str_ctx = "cpu"; - } else { - str_ctx = "gpu"; - } - MXCallbackList *op_info = new MXCallbackList; - CHECK(reinterpret_cast(info_->callbacks[kCustomOpPropCreateOperator])( - str_ctx.c_str(), shapes.size(), shapes.data(), ndims.data(), in_type->data(), op_info, - info_->contexts[kCustomOpPropCreateOperator])); - DO_BIND_DISPATCH(CreateOp, op_info); -} -MXNET_REGISTER_OP_PROPERTY(Custom, CustomOpProp) +NNVM_REGISTER_OP(Custom) .describe(R"code(Apply a custom operator implemented in a frontend language (like Python). Custom operators should override required methods like `forward` and `backward`. The custom operator must be registered before it can be used. Please check the tutorial here: http://mxnet.io/how_to/new_op.html. -)code") +)code" ADD_FILELINE) +.set_num_inputs([](const NodeAttrs& attrs){ + const CustomParam& params = nnvm::get(attrs.parsed); + return params.num_args + params.num_auxs; + }) +.set_num_outputs([](const NodeAttrs& attrs){ + const CustomParam& params = nnvm::get(attrs.parsed); + return params.num_outs; + }) +.set_attr_parser(AttrParser) +.set_attr("FInferShape", InferShape) +.set_attr("FInferType", InferType) +.set_attr("FListInputNames", [](const NodeAttrs& attrs) { + std::vector args = List(attrs); + std::vector auxs = List(attrs); + args.insert(args.end(), auxs.begin(), auxs.end()); + return args; + }) +.set_attr("FListOutputNames", List) +.set_attr("FMutateInputs", [](const NodeAttrs& attrs) { + const CustomParam& params = nnvm::get(attrs.parsed); + std::vector ret; + for (size_t i = 0; i < params.num_auxs; ++i) ret.push_back(i+params.num_args); + return ret; + }) +.set_attr("FExecType", [](const NodeAttrs& attrs) { + return ExecType::kLocal; + }) +.set_attr("FGradient", Gradient) +.set_attr("FCreateOpState", CreateState) +.set_attr("FStatefulComputeEx", Forward) +.set_attr("FStatefulComputeEx", Forward) +.add_argument("data", "NDArray-or-Symbol[]", "Input data for the custom operator.") .add_argument("op_type", "string", "Name of the custom operator. " "This is the name that is passed to `mx.operator.register` " - "to register the operator.") -.add_argument("data", "NDArray-or-Symbol", "Input data for the custom operator."); + "to register the operator."); + +NNVM_REGISTER_OP(_backward_Custom) +.set_num_inputs([](const NodeAttrs& attrs){ + const CustomParam& params = nnvm::get(attrs.parsed); + return params.bwd_idx.size(); + }) +.set_num_outputs([](const NodeAttrs& attrs){ + const CustomParam& params = nnvm::get(attrs.parsed); + return params.num_args; + }) +.set_attr("TIsLayerOpBackward", true) +.set_attr("TIsBackward", true) +.set_attr("FExecType", [](const NodeAttrs& attrs) { + return ExecType::kLocal; + }) +.set_attr("FStatefulComputeEx", Backward) +.set_attr("FStatefulComputeEx", Backward); +} // namespace custom } // namespace op } // namespace mxnet diff --git a/src/operator/custom/native_op-inl.h b/src/operator/custom/native_op-inl.h index b5706205c82b..ebce18611b56 100644 --- a/src/operator/custom/native_op-inl.h +++ b/src/operator/custom/native_op-inl.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file native_op-inl.h * \brief * \author Junyuan Xie @@ -108,7 +126,8 @@ class NativeOp : public Operator { NativeOpParam param_; std::vector ptrs; std::vector ndims; - std::vector shapes; + std::vector shapes; + std::vector shapes_buffer_; std::vector tags; std::map > > buffer_map; @@ -137,13 +156,18 @@ class NativeOp : public Operator { const std::string &prefix, mshadow::Stream *stream, int tag) { + size_t size = 0; + for (const auto& tblob : vec) size += tblob.shape_.ndim(); + shapes_buffer_.resize(size); + uint32_t *ptr = shapes_buffer_.data(); for (size_t i = 0; i < vec.size(); ++i) { std::stringstream name; name << prefix << i; SyncBuffer(vec[i], name.str(), stream); ptrs.push_back(buffer_map[name.str()].second.dptr_); ndims.push_back(vec[i].ndim()); - shapes.push_back(const_cast(vec[i].shape_.data())); + shapes.push_back(ptr); + ptr = nnvm::ShapeTypeCast(vec[i].shape_.begin(), vec[i].shape_.end(), ptr); tags.push_back(tag); } } @@ -198,11 +222,16 @@ class NativeOpProp : public OperatorProperty { bool InferShape(std::vector *in_shape, std::vector *out_shape, std::vector *aux_shape) const override { - std::vector shapes; + std::vector shapes; std::vector ndims; + size_t size = 0; + for (const auto& s : *in_shape) size += s.ndim(); + std::vector shapes_buffer(size); + uint32_t *ptr = shapes_buffer.data(); for (auto iter = in_shape->begin(); iter != in_shape->end(); ++iter) { - shapes.push_back(iter->data()); + shapes.push_back(ptr); ndims.push_back(iter->ndim()); + ptr = nnvm::ShapeTypeCast(iter->begin(), iter->end(), ptr); } shapes.resize(param_.num_inputs_+param_.num_outputs_); ndims.resize(param_.num_inputs_+param_.num_outputs_); diff --git a/src/operator/custom/native_op.cc b/src/operator/custom/native_op.cc index 7ab0614a041c..5dd35049d5bd 100644 --- a/src/operator/custom/native_op.cc +++ b/src/operator/custom/native_op.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file native_op.cc * \brief * \author Junyuan Xie @@ -21,6 +39,7 @@ DMLC_REGISTER_PARAMETER(NativeOpParam); MXNET_REGISTER_OP_PROPERTY(_Native, NativeOpProp) .describe("Stub for implementing an operator implemented in native frontend language.") +.add_argument("data", "NDArray-or-Symbol[]", "Input data for the custom operator.") .add_arguments(NativeOpParam::__FIELDS__()); } // namespace op diff --git a/src/operator/custom/native_op.cu b/src/operator/custom/native_op.cu index 807592626e8b..ad8d65e3c2eb 100644 --- a/src/operator/custom/native_op.cu +++ b/src/operator/custom/native_op.cu @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file native_op.cu * \brief * \author Junyuan Xie diff --git a/src/operator/custom/ndarray_op-inl.h b/src/operator/custom/ndarray_op-inl.h index a07a7f781d2d..b3a4662b669e 100644 --- a/src/operator/custom/ndarray_op-inl.h +++ b/src/operator/custom/ndarray_op-inl.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file native_op-inl.h * \brief * \author Junyuan Xie @@ -52,10 +70,6 @@ class NDArrayOp : public Operator { const std::vector &in_grad, const std::vector &aux_args); - virtual ExecType exec_type() const { - return kAsync; - } - private: NDArrayOpParam param_; Context get_ctx(); @@ -110,11 +124,16 @@ class NDArrayOpProp : public OperatorProperty { bool InferShape(std::vector *in_shape, std::vector *out_shape, std::vector *aux_shape) const override { - std::vector shapes; + std::vector shapes; std::vector ndims; + size_t size = 0; + for (const auto& s : *in_shape) size += s.ndim(); + std::vector shapes_buffer(size); + uint32_t *ptr = shapes_buffer.data(); for (auto iter = in_shape->begin(); iter != in_shape->end(); ++iter) { - shapes.push_back(iter->data()); + shapes.push_back(ptr); ndims.push_back(iter->ndim()); + ptr = nnvm::ShapeTypeCast(iter->begin(), iter->end(), ptr); } shapes.resize(param_.num_inputs_+param_.num_outputs_); ndims.resize(param_.num_inputs_+param_.num_outputs_); @@ -164,6 +183,10 @@ class NDArrayOpProp : public OperatorProperty { Operator* CreateOperator(Context ctx) const override; + ExecType exec_type() const override { + return ExecType::kAsync; + } + private: NDArrayOpParam param_; }; // class PythonProp diff --git a/src/operator/custom/ndarray_op.cc b/src/operator/custom/ndarray_op.cc index 773fe7753930..48426baea866 100644 --- a/src/operator/custom/ndarray_op.cc +++ b/src/operator/custom/ndarray_op.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file ndarray_op.cc * \brief * \author Junyuan Xie @@ -126,6 +144,7 @@ DMLC_REGISTER_PARAMETER(NDArrayOpParam); MXNET_REGISTER_OP_PROPERTY(_NDArray, NDArrayOpProp) .describe("Stub for implementing an operator implemented in native frontend language with ndarray.") +.add_argument("data", "NDArray-or-Symbol[]", "Input data for the custom operator.") .add_arguments(NDArrayOpParam::__FIELDS__()); } // namespace op diff --git a/src/operator/deconvolution-inl.h b/src/operator/deconvolution-inl.h index bac6f1dae308..43530138b8ea 100644 --- a/src/operator/deconvolution-inl.h +++ b/src/operator/deconvolution-inl.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file deconvolution-inl.h * \brief * \author Wei Wu @@ -43,26 +61,30 @@ struct DeconvolutionParam : public dmlc::Parameter { bool cudnn_off; dmlc::optional layout; DMLC_DECLARE_PARAMETER(DeconvolutionParam) { - DMLC_DECLARE_FIELD(kernel).describe("deconvolution kernel size: (h, w) or (d, h, w)"); + DMLC_DECLARE_FIELD(kernel).describe("Deconvolution kernel size: (h, w) or (d, h, w). " + "This is same as the kernel size used for the corresponding convolution"); DMLC_DECLARE_FIELD(stride).set_default(TShape()) - .describe("deconvolution stride: (h, w) or (d, h, w)"); + .describe("The stride used for the corresponding convolution: (h, w) or (d, h, w)."); DMLC_DECLARE_FIELD(dilate).set_default(TShape()) - .describe("deconvolution dilate: (h, w) or (d, h, w)"); + .describe("Dilation factor for each dimension of the input: (h, w) or (d, h, w)."); DMLC_DECLARE_FIELD(pad).set_default(TShape()) - .describe("pad for deconvolution: (h, w) or (d, h, w). " - "A good number is : (kernel-1)/2. " - "If target_shape is set, " - "pad will be ignored and computed accordingly"); + .describe("The amount of implicit zero padding added during convolution for each " + "dimension of the input: " + "(h, w) or (d, h, w). " + "``(kernel-1)/2`` is usually a good choice. " + "If `target_shape` is set, " + "`pad` will be ignored and a padding that will generate the target shape " + "will be used."); DMLC_DECLARE_FIELD(adj).set_default(TShape()) - .describe("adjustment for output shape: (h, w) or (d, h, w). " - "If target_shape is set, " - "ad will be ignored and computed accordingly"); + .describe("Adjustment for output shape: (h, w) or (d, h, w). " + "If `target_shape` is set, " + "`adj` will be ignored and computed accordingly."); DMLC_DECLARE_FIELD(target_shape).set_default(TShape()) - .describe("output shape with target shape : (h, w) or (d, h, w)"); + .describe("Shape of the output tensor: (h, w) or (d, h, w)."); DMLC_DECLARE_FIELD(num_filter).set_range(1, 100000) - .describe("deconvolution filter(channel) number"); + .describe("Number of output filters."); DMLC_DECLARE_FIELD(num_group).set_default(1) - .describe("number of groups partition"); + .describe("Number of groups partition."); DMLC_DECLARE_FIELD(workspace).set_default(512).set_range(0, 8192) .describe("Maximum temporal workspace allowed for deconvolution (MB)."); DMLC_DECLARE_FIELD(no_bias).set_default(true) @@ -72,7 +94,7 @@ struct DeconvolutionParam : public dmlc::Parameter { .add_enum("limited_workspace", deconv::kLimited) .add_enum("fastest", deconv::kFastest) .set_default(dmlc::optional()) - .describe("Whether to pick convolution algo by running performance test."); + .describe("Whether to pick convolution algorithm by running performance test."); DMLC_DECLARE_FIELD(cudnn_off).set_default(false) .describe("Turn off cudnn for this layer."); DMLC_DECLARE_FIELD(layout) @@ -82,29 +104,35 @@ struct DeconvolutionParam : public dmlc::Parameter { .add_enum("NHWC", mshadow::kNHWC) .add_enum("NDHWC", mshadow::kNDHWC) .set_default(dmlc::optional()) - .describe("Set layout for input, output and weight. Empty for\n " - "default layout: NCW for 1d, NCHW for 2d and NCDHW for 3d."); + .describe("Set layout for input, output and weight. Empty for " + "default layout, NCW for 1d, NCHW for 2d and NCDHW for 3d."); } template void InferPad(TShape input, index_t (&o_pad)[ndim], index_t (&o_adj)[ndim] ) const { + // Modified by Li.bs + // Use tag to control the calculation of pad + bool bCal = false; if (target_shape.ndim() != 0) { + for (index_t i = 0; i < target_shape.ndim(); i++) { + if (target_shape[i] != 0) bCal = true; + } + } + + if (bCal) { size_t input_ndim = input.ndim(); - for (unsigned int i = 0; i < ndim; i++) { + for (index_t i = 0; i < ndim; i++) { // input.ndim() can be larger than ndim, in case that the complete input // shape was passed and not only the ndim last ones o_pad[i] = stride[i] * (input[(input_ndim - ndim) + i] - 1) + DilatedKernelSize(i); - - CHECK_GE(o_pad[i], target_shape[i]) - << "too big target shape"; - + CHECK_GE(o_pad[i], target_shape[i]) << "too big target shape"; o_pad[i] -= target_shape[i]; o_adj[i] = o_pad[i] % 2; o_pad[i] = (o_pad[i] + 1) / 2; } } else { - for (unsigned int i = 0; i < ndim; i++) { + for (index_t i = 0; i < ndim; i++) { o_pad[i] = pad[i]; o_adj[i] = adj[i]; } @@ -147,7 +175,8 @@ class DeconvolutionOp : public Operator { Tensor out = out_data[deconv::kOut].get(s); index_t o_pad[2], o_adj[2]; - TShape dshape = {data.size(2), data.size(3)}; + TShape dshape = {static_cast(data.size(2)), + static_cast(data.size(3))}; param_.InferPad(dshape, o_pad, o_adj); Shape<3> wmat_shape = @@ -264,7 +293,8 @@ class DeconvolutionOp : public Operator { << "Must init CuBLAS handle in stream"; #endif index_t o_pad[2], o_adj[2]; - TShape dshape = {data.size(2), data.size(3)}; + TShape dshape = {static_cast(data.size(2)), + static_cast(data.size(3))}; param_.InferPad(dshape, o_pad, o_adj); const index_t nbatch = data.size(0); diff --git a/src/operator/deconvolution.cc b/src/operator/deconvolution.cc index e1c726ac0a6b..6a59ff6588ff 100644 --- a/src/operator/deconvolution.cc +++ b/src/operator/deconvolution.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file deconvolution.cc * \brief * \author Wei Wu @@ -24,8 +42,6 @@ Operator* CreateOp(DeconvolutionParam param, int dtype, Operator* DeconvolutionProp::CreateOperatorEx(Context ctx, std::vector *in_shape, std::vector *in_type) const { std::vector out_shape, aux_shape; - std::vector out_type, aux_type; - CHECK(InferType(in_type, &out_type, &aux_type)); CHECK(InferShape(in_shape, &out_shape, &aux_shape)); DO_BIND_DISPATCH(CreateOp, param_, in_type->at(0), in_shape, &out_shape, ctx); } @@ -33,11 +49,16 @@ Operator* DeconvolutionProp::CreateOperatorEx(Context ctx, std::vector * DMLC_REGISTER_PARAMETER(DeconvolutionParam); MXNET_REGISTER_OP_PROPERTY(Deconvolution, DeconvolutionProp) -.add_argument("data", "NDArray-or-Symbol", "Input data to the DeconvolutionOp.") -.add_argument("weight", "NDArray-or-Symbol", "Weight matrix.") -.add_argument("bias", "NDArray-or-Symbol", "Bias parameter.") +.add_argument("data", "NDArray-or-Symbol", "Input tensor to the deconvolution operation.") +.add_argument("weight", "NDArray-or-Symbol", "Weights representing the kernel.") +.add_argument("bias", "NDArray-or-Symbol", "Bias added to the result after the deconvolution " + "operation.") .add_arguments(DeconvolutionParam::__FIELDS__()) -.describe("Applies deconvolution to input and adds a bias."); +.describe("Computes 2D transposed convolution (aka fractionally strided convolution) of the " + "input tensor. This operation can be seen as the gradient of Convolution operation with " + "respect to its input. Convolution usually reduces the size of the input. Transposed " + "convolution works the other way, going from a smaller input to a larger output while " + "preserving the connectivity pattern."); } // namespace op } // namespace mxnet diff --git a/src/operator/deconvolution.cu b/src/operator/deconvolution.cu index 60cf0ad5a21a..e9b5cb8e3c7f 100644 --- a/src/operator/deconvolution.cu +++ b/src/operator/deconvolution.cu @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file deconvolution.cu * \brief * \author Wei Wu @@ -52,14 +70,14 @@ Operator* CreateOp(DeconvolutionParam param, int dtype, int backward_compute_type = desired_backward_compute_type; bool deconvolutionIsSupported = CuDNNDeconvolutionOp::Supports(param, forward_compute_type, - backward_compute_type); + backward_compute_type, ctx); // If cuDNN can't handle this case with fp16 backprop kernels, try fp32 backprop. if (!deconvolutionIsSupported && backward_compute_type == mshadow::kFloat16) { backward_compute_type = mshadow::kFloat32; deconvolutionIsSupported = CuDNNDeconvolutionOp::Supports(param, forward_compute_type, - backward_compute_type); + backward_compute_type, ctx); } // If cuDNN can't handle this case with fp16 forward kernels, try fp32 @@ -67,7 +85,7 @@ Operator* CreateOp(DeconvolutionParam param, int dtype, forward_compute_type = mshadow::kFloat32; deconvolutionIsSupported = CuDNNDeconvolutionOp::Supports(param, forward_compute_type, - backward_compute_type); + backward_compute_type, ctx); } if (!deconvolutionIsSupported) { LOG(WARNING) << diff --git a/src/operator/depthwise_convolution-inl.h b/src/operator/depthwise_convolution-inl.h new file mode 100644 index 000000000000..5beea4595f7a --- /dev/null +++ b/src/operator/depthwise_convolution-inl.h @@ -0,0 +1,349 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +/*! + * \file depthwise_convolution-inl.h + * \brief CUDA depthwise convolution code + * \author shuqian.qu@hobot.cc +*/ +#ifndef MXNET_OPERATOR_DEPTHWISE_CONVOLUTION_INL_H_ +#define MXNET_OPERATOR_DEPTHWISE_CONVOLUTION_INL_H_ +#include +#include +#include "./convolution-inl.h" +#include "../common/cuda_utils.h" + +#if MXNET_USE_CUDA +#include +#include "./depthwise_convolution_tf.cuh" + +namespace mxnet { +namespace op { +using namespace tf::depthwise_conv; +template +class DepthwiseConvolutionOp : public Operator { + public: + explicit DepthwiseConvolutionOp(const ConvolutionParam& param, + const std::vector& in_shape, + const std::vector& out_shape) { + args_.batch = in_shape[conv::kData][0]; + args_.in_channel = in_shape[conv::kData][1]; + args_.in_height = in_shape[conv::kData][2]; + args_.in_width = in_shape[conv::kData][3]; + args_.filter_height = in_shape[conv::kWeight][2]; + args_.filter_width = in_shape[conv::kWeight][3]; + args_.stride_height = param.stride[0]; + args_.stride_width = param.stride[1]; + args_.pad_height = param.pad[0]; + args_.pad_width = param.pad[1]; + args_.out_channel = out_shape[conv::kOut][1]; + args_.out_height = out_shape[conv::kOut][2]; + args_.out_width = out_shape[conv::kOut][3]; + bias_term_ = !param.no_bias; + } + + ~DepthwiseConvolutionOp() {} + + virtual void Forward(const OpContext &ctx, + const std::vector &in_data, + const std::vector &req, + const std::vector &out_data, + const std::vector &aux_args); + + virtual void Backward(const OpContext &ctx, + const std::vector &out_grad, + const std::vector &in_data, + const std::vector &out_data, + const std::vector &req, + const std::vector &in_grad, + const std::vector &aux_args); + + private: + DepthwiseArgs args_; + bool bias_term_; +}; // class DepthwiseConvolutionOp + +namespace depthwise_conv { +namespace cuda { +template +__global__ void __launch_bounds__(1024, 2) +DepthwiseConv2dBackwardFilterKernel(const DepthwiseArgs args, + const DType* out_grad, + const DType* input, + DType* filter_grad) { + const int in_height = args.in_height; + const int in_width = args.in_width; + const int channel = args.in_channel; + const int filter_height = kFilterHeight > 0 ? kFilterHeight : args.filter_height; + const int filter_width = kFilterWidth > 0 ? kFilterWidth : args.filter_width; + const int stride_height = args.stride_height; + const int stride_width = args.stride_width; + const int pad_height = args.pad_height; + const int pad_width = args.pad_width; + const int out_height = args.out_height; + const int out_width = args.out_width; + + const int filter_pixels = filter_width * filter_height; + const int out_pixels = out_height * out_width; + const int in_pixels = in_height * in_width; + const int batch_channel_num = channel * args.batch; + const int candidate_reduce_thread_num = out_pixels % blockDim.x; + + for (int b = blockIdx.x; b < batch_channel_num; b += gridDim.x) { + const int local_batch = b / channel; + const int local_channel = b % channel; + const int filter_offset_temp = local_channel * filter_pixels; + const int out_grad_offset_temp = (local_batch * channel * out_pixels) + + (local_channel * out_pixels); + + for (int out_id = threadIdx.x; out_id < out_pixels; out_id += blockDim.x) { + const int reduce_thread_num = ((out_pixels - out_id) > candidate_reduce_thread_num) ? + blockDim.x : candidate_reduce_thread_num; + + const int out_w = out_id % out_width; + const int out_h = (out_id / out_width) % out_height; + const int out_grad_offset = out_grad_offset_temp + (out_h * out_width) + (out_w); + const DType out_g = ldg(out_grad + out_grad_offset); + + const int in_h_start = out_h * stride_height - pad_height; + const int in_w_start = out_w * stride_width - pad_width; + CUDA_UNROLL for (int f_h = 0; f_h < filter_height; ++f_h) { + const int in_h = in_h_start + f_h; + const int input_offset_temp = (local_batch * channel * in_pixels) + + (local_channel * in_pixels) + (in_h * in_width); + const int filter_offset_h = filter_width * f_h; + + CUDA_UNROLL for (int f_w = 0; f_w < filter_width; ++f_w) { + const int in_w = in_w_start + f_w; + DType partial_grad = DType(0.0f); + if (in_h >= 0 && in_h < in_height && in_w >= 0 && in_w < in_width) { + const int input_offset = input_offset_temp + in_w; + partial_grad = ldg(input + input_offset) * out_g; + } + // reduce all valid partial grad in a block + typedef cub::BlockReduce BlockReduceT; + __shared__ typename BlockReduceT::TempStorage temp_storage_reduce; + DType aggregate = BlockReduceT(temp_storage_reduce).Sum(partial_grad, reduce_thread_num); + if (threadIdx.x == 0) { + DType* addr = filter_grad + f_w + filter_offset_h + filter_offset_temp; + atomicAdd(addr, aggregate); + } + __syncthreads(); + } // for filter_width + } // for filter_height + } // for out_pixels + __syncthreads(); + } // for batch_channel_num +} +} // namespace cuda + +template +void DepthwiseConv2dForwardGpu(mshadow::Stream *stream, + const DepthwiseArgs& args, + const std::vector &in_data, + const std::vector &out_data) { + using namespace mshadow; + using namespace mshadow::expr; + using namespace tf::depthwise_conv; + using namespace tf::depthwise_conv::cuda; + Tensor data = in_data[conv::kData].get(stream); + Tensor weight = in_data[conv::kWeight].get(stream); + Tensor out = out_data[conv::kOut].get(stream); + + // select kernel + if (CanLaunchDepthwiseConv2dGPUSmall(args)) { + LaunchDepthwiseConv2dGPUSmall( + stream, + args, + data.dptr_, + weight.dptr_, + out.dptr_); + } else { + int num_output = out_data[conv::kOut].shape_.Size(); + int block_num = std::min(num_output/mshadow::cuda::kBaseThreadNum + 1, + mshadow::cuda::kMaxGridNum); + auto s = mshadow::Stream::GetStream(stream); + if (args.filter_height == 3 && args.filter_width == 3) { + DepthwiseConv2dForwardKernel + <<>>(data.dptr_, + weight.dptr_, + args, + num_output, + out.dptr_); + } else { + DepthwiseConv2dForwardKernel + <<>>(data.dptr_, + weight.dptr_, + args, + num_output, + out.dptr_); + } + MSHADOW_CUDA_POST_KERNEL_CHECK(DepthwiseConv2dForwardKernel); + } +} + +template +void DepthwiseConv2dBackwardDataGpu(mshadow::Stream *stream, + const DepthwiseArgs& args, + const std::vector &out_grad, + const std::vector &in_data, + const std::vector &in_grad) { + using namespace mshadow; + using namespace mshadow::expr; + using namespace tf::depthwise_conv; + using namespace tf::depthwise_conv::cuda; + Tensor out_g = out_grad[conv::kOut].get(stream); + Tensor weight = in_data[conv::kWeight].get(stream); + Tensor in_data_g = in_grad[conv::kData].get(stream); + // select kernel + if (CanLaunchDepthwiseConv2dGPUSmall(args)) { + LaunchDepthwiseConv2dGPUSmall( + stream, + args, + out_g.dptr_, + weight.dptr_, + in_data_g.dptr_); + } else { + int num_in_grad = in_grad[conv::kData].shape_.Size(); + auto s = mshadow::Stream::GetStream(stream); + int block_num = std::min(num_in_grad/mshadow::cuda::kBaseThreadNum + 1, + mshadow::cuda::kMaxGridNum); + DepthwiseConv2dBackwardDataKernel + <<>>(args, + out_g.dptr_, + weight.dptr_, + in_data_g.dptr_, + num_in_grad); + MSHADOW_CUDA_POST_KERNEL_CHECK(DepthwiseConv2dBackwardDataKernel); + } +} + +template +void DepthwiseConv2dBackwardFilterGpu(mshadow::Stream *stream, + const DepthwiseArgs& args, + const std::vector &out_grad, + const std::vector &in_data, + const std::vector &in_grad) { + using namespace mshadow; + using namespace mshadow::expr; + using namespace tf::depthwise_conv; + Tensor out_g = out_grad[conv::kOut].get(stream); + Tensor in_d = in_data[conv::kData].get(stream); + Tensor weight_grad = in_grad[conv::kWeight].get(stream); + // select kernel + if (TryLaunchDepthwiseConv2dBackwardFilterGPUSmall(stream, args, + out_g.dptr_, + in_d.dptr_, + weight_grad.dptr_)) { + return; + } else { + int num_out_grad = out_grad[conv::kOut].shape_.Size(); + auto s = mshadow::Stream::GetStream(stream); + int block_num = std::min(args.out_channel * args.batch, mshadow::cuda::kMaxGridNum); + if (args.filter_width == 3 && args.filter_height == 3) { + cuda::DepthwiseConv2dBackwardFilterKernel + <<>>(args, + out_g.dptr_, + in_d.dptr_, + weight_grad.dptr_); + } else { + cuda::DepthwiseConv2dBackwardFilterKernel + <<>>(args, + out_g.dptr_, + in_d.dptr_, + weight_grad.dptr_); + } + MSHADOW_CUDA_POST_KERNEL_CHECK(DepthwiseConv2dBackwardFilterKernel); + } +} +} // namespace depthwise_conv + +template +void DepthwiseConvolutionOp::Forward(const OpContext &ctx, + const std::vector &in_data, + const std::vector &req, + const std::vector &out_data, + const std::vector &aux_states) { + using namespace mshadow; + using namespace mshadow::expr; + auto stream = ctx.get_stream(); + CHECK_EQ(req[conv::kOut], kWriteTo); + // output forward + depthwise_conv::DepthwiseConv2dForwardGpu(stream, args_, in_data, out_data); + + // bias forward + if (bias_term_) { + Tensor bias = in_data[conv::kBias].get(stream); + Tensor output_3d = out_data[conv::kOut].get_with_shape( + Shape3(args_.batch, args_.out_channel, args_.out_height * args_.out_width), stream); + // has bias term, broadcast it to the same shape of output_3d in channel dim + output_3d += mshadow::expr::broadcast<1>(bias, output_3d.shape_); + } +} + +template +void DepthwiseConvolutionOp::Backward(const OpContext &ctx, + const std::vector &out_grad, + const std::vector &in_data, + const std::vector &out_data, + const std::vector &req, + const std::vector &in_grad, + const std::vector &aux_states) { + using namespace mshadow; + using namespace mshadow::expr; + auto stream = ctx.get_stream(); + // backward data + if (req[conv::kData] != kNullOp) { + if (req[conv::kData] != kAddTo) { + mshadow::Tensor igrad = in_grad[conv::kData].get(stream); + igrad = 0.0f; + } + depthwise_conv::DepthwiseConv2dBackwardDataGpu(stream, + args_, + out_grad, + in_data, + in_grad); + } + + // backward filter + if (req[conv::kWeight] != kNullOp) { + if (req[conv::kWeight] != kAddTo) { + mshadow::Tensor wgrad = in_grad[conv::kWeight].get(stream); + wgrad = 0.0f; + } + depthwise_conv::DepthwiseConv2dBackwardFilterGpu(stream, + args_, + out_grad, + in_data, + in_grad); + } + + // backward bias + if (bias_term_) { + Tensor dbias = in_grad[conv::kBias].get(stream); + Tensor dout = out_grad[conv::kOut].get_with_shape( + Shape3(args_.batch, args_.out_channel, args_.out_height * args_.out_width), stream); + ASSIGN_DISPATCH(dbias, req[conv::kBias], sumall_except_dim<1>(dout)); + } +} +} // namespace op +} // namespace mxnet +#endif + +#endif // MXNET_OPERATOR_DEPTHWISE_CONVOLUTION_INL_H_ diff --git a/src/operator/depthwise_convolution_tf.cuh b/src/operator/depthwise_convolution_tf.cuh new file mode 100644 index 000000000000..a1538b68a7d0 --- /dev/null +++ b/src/operator/depthwise_convolution_tf.cuh @@ -0,0 +1,703 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +/*! + * \file depthwise_convolution_tf.cuh + * \brief some depthwise convolution CUDA kernel code. The main logic comes + * from tensorflow, but the filter's layerout and many argument names + * are different with origin version. + * \author shuqian.qu@hobot.cc +*/ +#ifndef MXNET_OPERATOR_DEPTHWISE_CONVOLUTION_TF_CUH_ +#define MXNET_OPERATOR_DEPTHWISE_CONVOLUTION_TF_CUH_ +#include "../common/cuda_utils.h" +#include "./mxnet_op.h" + +namespace tf { +namespace depthwise_conv { +struct DepthwiseArgs { + // Input layer dimensions + int batch; + int in_height; + int in_width; + int in_channel; + int filter_height; + int filter_width; + int stride_height; + int stride_width; + int pad_height; + int pad_width; + + // Output layer dimensions + int out_height; + int out_width; + int out_channel; +}; + +namespace cuda { +template +__global__ void __launch_bounds__(1024, 2) +DepthwiseConv2dForwardKernel(const DType* input, + const DType* filter, + const DepthwiseArgs args, + int num_outputs, + DType* output) { + const int in_channel = args.in_channel; + const int in_height = args.in_height; + const int in_width = args.in_width; + const int filter_height = kFilterHeight > 0 ? kFilterHeight : args.filter_height; + const int filter_width = kFilterWidth > 0 ? kFilterWidth : args.filter_width; + const int stride_height = args.stride_height; + const int stride_width = args.stride_width; + const int pad_height = args.pad_height; + const int pad_width = args.pad_width; + const int out_channel = args.out_channel; + const int out_height = args.out_height; + const int out_width = args.out_width; + + CUDA_KERNEL_LOOP(thread_id, num_outputs) { + // Compute the indexes of this thread in the output. + // + // We want coalesced reads so we make sure that each warp reads + // a contiguous chunk of memory. + // + // THIS IS PROBABLY WRONG, we are not doing coalesced reads + // into the input, because of the depth multiplier division... + const int out_w = thread_id % out_width; + const int out_h = (thread_id / out_width) % out_height; + const int out_c = (thread_id / out_width / out_height) % out_channel; + const int out_b = thread_id / out_width / out_height / out_channel; + const int in_c = out_c; + + // Data is stored in the following format (let's assume we + // flatten the height and width into one contiguous dimension + // called "P". + // + // B1C1P1 B1C1P2 ..... B1C2P1 B1C2P2 .... + // B2C1P1 B2C1P2 ..... B2C2P1 B2C2P2 .... + // + // Each row contains in_channel * in_height * in_width values + // for each sample in the batch. + // + // We can further flatten it into: + // + // B1C1P1 B1C1P2 ..... + // B1C2P1 B1C2P2 .... + // B2C1P1 B2C1P2 ..... + // B2C2P1 B2C2P2 .... + // + // where each row is a contiguous array of all of the spatial + // pixels for a given batch and input depth. The following + // loop unrolls across the filter dimensions for a given thread, + // indexing into the filter value and the corresponding input + // patch. + // + // We can compute the index into the patch once right here. + const int input_offset_temp = (out_b * in_channel + in_c) * (in_height * in_width); + const int filter_offset_temp = in_c * filter_height * filter_width; + + // Finally, we can iterate over the spatial dimensions and perform the + // convolution, writing into the output at the end. + // + // We perform an additional optimization, where we can determine + // whether the patch fits within the image indices statically, and + // avoid boundary checking within the loop. + const int input_h_start = out_h * stride_height - pad_height; + const int input_w_start = out_w * stride_width - pad_width; + const int input_h_end = input_h_start + filter_height; + const int input_w_end = input_w_start + filter_width; + + DType sum = 0; + if (input_h_start >= 0 && input_w_start >= 0 && + input_h_end < in_height && input_w_end < in_width) { + // Loop that doesn't need to check for boundary conditions. + CUDA_UNROLL for (int f_h = 0; f_h < filter_height; ++f_h) { + const int in_h = input_h_start + f_h; + const int filter_offset_h = filter_width * f_h; + CUDA_UNROLL for (int f_w = 0; f_w < filter_width; ++f_w) { + const int in_w = input_w_start + f_w; + const int input_offset = (input_offset_temp) + (in_h * in_width) + in_w; + const int filter_offset = filter_offset_temp + filter_offset_h + f_w; + sum += ldg(input + input_offset) * ldg(filter + filter_offset); + } + } + } else { + // Loop that needs to check for boundary conditions. + CUDA_UNROLL for (int f_h = 0; f_h < filter_height; ++f_h) { + const int in_h = input_h_start + f_h; + const int filter_offset_h = filter_width * f_h; + CUDA_UNROLL for (int f_w = 0; f_w < filter_width; ++f_w) { + const int in_w = input_w_start + f_w; + // TODO(vrv): the in_h check can be done outside of this loop; + // benchmark both methods to determine the better decision. + if (in_h >= 0 && in_h < in_height && in_w >= 0 && in_w < in_width) { + const int in_w = input_w_start + f_w; + const int input_offset = input_offset_temp + (in_h * in_width) + in_w; + const int filter_offset = filter_offset_temp + filter_offset_h + f_w; + sum += ldg(input + input_offset) * ldg(filter + filter_offset); + } + } + } + } + output[thread_id] = sum; + } +} + +// The DepthwiseConv2dKernelSmall perform either forward or backward input +// convolution depending on a template argument of this enum. +enum DepthwiseConv2dDirection { DIRECTION_FORWARD, DIRECTION_BACKWARD }; + +// CUDA kernel to compute the depthwise convolution forward pass in NCHW format, +// tailored for small images up to 32x32. Only use this kernel if +// CanLaunchDepthwiseConv2dGPUSmall(args) returns true. +// Tiles of the input and filter tensors are loaded into shared memory before +// performing the convolution. Each thread handles two elements per iteration, +// one each in the lower and upper half of a tile. +// Backward input direction is the same as forward direction with the filter +// rotated by 180°. +template +__global__ __launch_bounds__(1024, 2) void DepthwiseConv2dKernelSmall( + const DepthwiseArgs args, const DType* input, const DType* filter, DType* output) { + extern __shared__ __align__(sizeof(DType)) unsigned char shared_memory[]; + DType* const shared_data = reinterpret_cast(shared_memory); + + const int in_height = args.in_height; + const int in_width = args.in_width; + const int in_channel = args.in_channel; + const int filter_height = kFilterHeight > 0 ? kFilterHeight : args.filter_height; + const int filter_width = kFilterWidth > 0 ? kFilterWidth : args.filter_width; + const int pad_height = args.pad_height; + const int pad_width = args.pad_width; + + // Fixed blockDim.z, tailored for maximum grid size for images of size 16x16. + const int block_height = blockDim.y; + + // These values are the same for all threads and could + // be precomputed on the CPU. + const int block_pixels = in_width * block_height; + const int block_size = block_pixels * kBlockSlices; + const int in_pixels = in_width * in_height; + const int in_increment = in_width - 1; + const int filter_pixels = filter_height * filter_width; + const int tile_width = in_width + filter_width - 1; + const int even_height = kEvenHeight || (1 & ~in_height); + const int tile_height = in_height + filter_height - even_height; + const int tile_pixels = tile_width * tile_height; + const int tile_size = tile_pixels * kBlockSlices; + const int tile_offset = block_height * tile_width; + const int pad_offset = pad_height * tile_width + pad_width; + const int in_slices = in_channel * args.batch; + const int in_blocks = (in_slices + kBlockSlices - 1) / kBlockSlices; + + const int thread_width = threadIdx.x; + const int thread_height = threadIdx.y; + const int thread_channel = threadIdx.z; + + // Position in block. + const int thread_pix = thread_height * in_width + thread_width; + const int thread_idx = thread_channel * block_pixels + thread_pix; + + // Initialize tile, in particular the padding. + for (int i = thread_idx; i < tile_size; i += block_size) { + shared_data[i] = DType(0); + } + __syncthreads(); + + // Position in tensors. + const int tensor_idx = thread_channel * in_pixels + thread_pix; + + // Position in (padded) shared memory. + const int data_pix = thread_height * tile_width + thread_width; + const int data_idx = thread_channel * tile_pixels + data_pix; + + // Position in shared memory, offset by pad_height / pad_width. + const int tile_idx = data_idx + pad_offset; + + const int filter_pix = thread_pix; + const int filter_channel = thread_channel; + const int filter_idx = filter_pixels * filter_channel + filter_pix; + + const int max_slice = in_slices - thread_channel; + const int filter_write_offset = filter_pix < filter_pixels ? tile_size + filter_idx : 0; + const int filter_read_offset = tile_size + + (kDirection == DIRECTION_FORWARD ? + filter_pixels * filter_channel : filter_pixels * (filter_channel + 1)); + const bool skip_second = !kEvenHeight && thread_height + (in_height & 1) == block_height; + + for (int b = blockIdx.x; b < in_blocks; b += gridDim.x) { + const int slice = b * kBlockSlices; + + const int inout_offset = slice * in_pixels + tensor_idx; + const bool slice_in_range = slice < max_slice; + + if (slice_in_range) { + const DType* const in_ptr = inout_offset + input; + DType* const tile_ptr = tile_idx + shared_data; + tile_ptr[0] = ldg(in_ptr); + if (!skip_second) { + tile_ptr[tile_offset] = ldg(block_pixels + in_ptr); + } + } + + if (filter_write_offset != 0) { + const int filter_offset = ((slice + filter_channel) % in_channel)* filter_pixels + filter_pix; + shared_data[filter_write_offset] = ldg(filter_offset + filter); + } + + // Note: the condition to reach this is uniform across the entire block. + __syncthreads(); + + if (slice_in_range) { + DType sum1 = 0; + DType sum2 = 0; + int shared_offset = data_idx; + const DType* filter_ptr = filter_read_offset + shared_data; + CUDA_UNROLL for (int r = 0; r < filter_height; ++r) { + CUDA_UNROLL for (int c = 0; c < filter_width; ++c) { + if (kDirection == DIRECTION_BACKWARD) { + filter_ptr--; + } + const DType filter_value = *filter_ptr; + const DType* const tile_ptr = shared_offset + shared_data; + sum1 += filter_value * tile_ptr[0]; + sum2 += filter_value * tile_ptr[tile_offset]; + ++shared_offset; + if (kDirection == DIRECTION_FORWARD) { + filter_ptr++; + } + } + shared_offset += in_increment; + } + DType* const out_ptr = inout_offset + output; + if (kDirection == DIRECTION_FORWARD) { + out_ptr[0] = sum1; + if (!skip_second) { + out_ptr[block_pixels] = sum2; + } + } else { + out_ptr[0] += sum1; + if (!skip_second) { + out_ptr[block_pixels] += sum2; + } + } + } + + // Note: the condition to reach this is uniform across the entire block. + __syncthreads(); + } +} + +template +__global__ void __launch_bounds__(640, 2) +DepthwiseConv2dBackwardDataKernel(const DepthwiseArgs args, + const DType* out_grad, + const DType* filter, DType* in_grad, + int num_in_grad) { + const int channel = args.in_channel; + const int in_height = args.in_height; + const int in_width = args.in_width; + const int filter_height = args.filter_height; + const int filter_width = args.filter_width; + const int stride_height = args.stride_height; + const int stride_width = args.stride_width; + const int pad_height = args.pad_height; + const int pad_width = args.pad_width; + const int out_height = args.out_height; + const int out_width = args.out_width; + + const int in_pixels = in_height * in_width; + const int out_pixels = out_height * out_width; + + CUDA_KERNEL_LOOP(thread_id, num_in_grad) { + // Compute the indexes of this thread in the input. + const int in_w = thread_id % in_width; + const int in_h = (thread_id / in_width) % in_height; + const int channel_idx = (thread_id / in_width / in_height) % channel; + const int batch_idx = thread_id / channel / in_width / in_height; + DType sum = 0.0f; + + const int out_h_start = mxnet::common::cuda::CudaMax( + 0, (in_h - filter_height + pad_height + stride_height) / stride_height); + const int out_h_end = mxnet::common::cuda::CudaMin( + out_height - 1, (in_h + pad_height) / stride_height); + const int out_w_start = mxnet::common::cuda::CudaMax( + 0, (in_w - filter_width + pad_width + stride_width) / stride_width); + const int out_w_end = mxnet::common::cuda::CudaMin( + out_width - 1, (in_w + pad_width) / stride_width); + + const int filter_offset_temp = channel_idx * filter_height * filter_width; + const int out_grad_offset_temp = (batch_idx * channel * out_pixels) + + (channel_idx * out_pixels); + + for (int out_h = out_h_start; out_h <= out_h_end; ++out_h) { + const int f_h = in_h + pad_height - out_h * stride_height; + const int filter_offset_h = filter_offset_temp + f_h * filter_width; + const int out_grad_offset_h = out_grad_offset_temp + out_h * out_width; + for (int out_w = out_w_start; out_w <= out_w_end; ++out_w) { + const int f_w = in_w + pad_width - out_w * stride_width; + const int filter_offset = filter_offset_h + f_w; + const int out_grad_offset = out_grad_offset_h + out_w; + sum += ldg(out_grad + out_grad_offset) * ldg(filter + filter_offset); + } + } + const int in_grad_offset = (batch_idx * channel * in_pixels) + + (channel_idx * in_pixels) + (in_h * in_width) + (in_w); + in_grad[in_grad_offset] += sum; + } +} + +// CUDA kernel to compute the depthwise convolution backward w.r.t. filter in +// NCHW format, tailored for small images up to 32x32. Only use this kernel if +// CanLaunchDepthwiseConv2dGPUSmall(args) returns true. +// Tiles of the input tensor are loaded into shared memory before performing the +// convolution. Per iteration and filter element, each thread first performs +// a partial convolution for two elements, one each in the lower and upper half +// of a tile. The intermediate result of all pixels of a warp are then +// accumulated and written to shared memory. Finally, the values in shared +// memory are warp-accumulated (in chunks of kAccumPixels elements) and summed +// up in global memory using atomics. +// Requirements: threads per block must be multiple of 32 and <= launch_bounds, +// kAccumPixels * 64 >= args.in_height * args.in_width * kBlockSlices. +template +__global__ +__launch_bounds__(1024, 2) void DepthwiseConv2dBackwardFilterKernelSmall( + const DepthwiseArgs args, const DType* output, const DType* input, DType* filter) { + extern __shared__ __align__(sizeof(DType)) unsigned char shared_memory[]; + DType* const shared_data = reinterpret_cast(shared_memory); + + const int in_height = args.in_height; + const int in_width = blockDim.x; // slower (see b/62280718): args.in_width; + const int in_channel = args.in_channel; + const int filter_height = kFilterHeight > 0 ? kFilterHeight : args.filter_height; + const int filter_width = kFilterWidth > 0 ? kFilterWidth : args.filter_width; + const int pad_height = args.pad_height; + const int pad_width = args.pad_width; + + const int block_height = blockDim.y; + + // These values are the same for all threads and could + // be precomputed on the CPU. + const int block_pixels = in_width * block_height; + const int block_size = block_pixels * kBlockSlices; + assert((block_size & 31) == 0); + const int in_pixels = in_width * in_height; + const int in_increment = in_width - 1; + const int filter_pixels = filter_height * filter_width; + const int tile_width = in_width + filter_width - 1; + const int tile_height = 2 * block_height + filter_height - 1; + const int tile_pixels = tile_width * tile_height; + const int tile_size = tile_pixels * kBlockSlices; + const int tile_offset = block_height * tile_width; + const int pad_offset = pad_height * tile_width + pad_width; + const int in_slices = in_channel * args.batch; + const int in_blocks = (in_slices + kBlockSlices - 1) / kBlockSlices; + // The accumulator has a fixed number of pixels that can be reduced by one + // warp. Pixels beyond ceil(in_pixels * kBlockSlices / 64) are never written. + assert(kAccumPixels * 64 >= in_height * in_width * kBlockSlices); + const int accum_increment = kAccumPixels * kBlockSlices; + const int accum_size = filter_pixels * accum_increment; + + const int thread_width = threadIdx.x; + const int thread_height = threadIdx.y; + const int thread_channel = threadIdx.z; + + // Position in block. + const int thread_pix = thread_height * in_width + thread_width; + const int thread_idx = thread_channel * block_pixels + thread_pix; + + // Initialize tile, in particular the padding and accumulator. + for (int i = thread_idx; i < tile_size + accum_size; i += block_size) { + shared_data[i] = DType(0); + } + __syncthreads(); + + // Position in tensors. + const int tensor_idx = thread_channel * in_pixels + thread_pix; + + // Position in (padded) shared memory. + const int data_pix = thread_height * tile_width + thread_width; + const int data_idx = thread_channel * tile_pixels + data_pix; + + // Position in shared memory, offset by pad_height / pad_width. + const int tile_idx = data_idx + pad_offset; + + // Position in accumulator (kBlockSlices per warp, depth major). + const int accum_pix = thread_pix / (32 / kBlockSlices); + const int accum_idx = thread_channel * kAccumPixels + accum_pix; + + const int max_slice = in_slices - thread_channel; + const int accum_offset = tile_size + accum_idx; + const bool skip_second = block_height + thread_height >= in_height; + + for (int b = blockIdx.x; b < in_blocks; b += gridDim.x) { + const int slice = b * kBlockSlices; + + const int inout_offset = slice * in_pixels + tensor_idx; + const bool slice_in_range = slice < max_slice; + + if (slice_in_range) { + const DType* const in_ptr = inout_offset + input; + DType* const tile_ptr = tile_idx + shared_data; + tile_ptr[0] = ldg(in_ptr); + if (!skip_second) { + tile_ptr[tile_offset] = ldg(block_pixels + in_ptr); + } + } + + // Note: the condition to reach this is uniform across the entire block. + __syncthreads(); + + if (slice_in_range) { + const DType* const out_ptr = inout_offset + output; + const DType out1 = ldg(out_ptr); + const DType out2 = skip_second ? DType(0) : ldg(block_pixels + out_ptr); + int shared_offset = data_idx; + DType* accum_ptr = accum_offset + shared_data; + CUDA_UNROLL for (int r = 0; r < filter_height; ++r) { + CUDA_UNROLL for (int c = 0; c < filter_width; ++c) { + const DType* const tile_ptr = shared_offset + shared_data; + DType val = out1 * tile_ptr[0] + out2 * tile_ptr[tile_offset]; + // Warp-accumulate pixels of the same depth and write to accumulator. + for (int delta = 16 / kBlockSlices; delta > 0; delta /= 2) { + val += __shfl_down(val, delta); + } + if (!(thread_idx & 32 / kBlockSlices - 1)) { + *accum_ptr = val; + } + ++shared_offset; + accum_ptr += accum_increment; + } + shared_offset += in_increment; + } + } + + // Note: the condition to reach this is uniform across the entire block. + __syncthreads(); + + const DType* const accum_data = tile_size + shared_data; + for (int i = thread_idx; i < accum_size; i += block_size) { + const int filter_idx = i / kAccumPixels; + const int filter_pix = filter_idx / kBlockSlices; + const int filter_channel = (slice + filter_idx % kBlockSlices) % in_channel; + // convert to CHW + const int filter_offset = filter_channel * filter_pixels + + (filter_pix/filter_width) * filter_height + filter_pix % filter_width; + + if (filter_channel < in_channel) { + DType val = accum_data[i]; + // Warp-accumulate pixels of the same depth from the accumulator. + for (int delta = kAccumPixels / 2; delta > 0; delta /= 2) { + val += __shfl_down(val, delta); + } + if (!(thread_idx & kAccumPixels - 1)) { + atomicAdd(filter_offset + filter, val); + } + } + } + } +} + + +} // namespace cuda + +// Returns whether depthwise convolution forward or backward input pass can be +// performed using the faster ('Small') variant of the kernel. +bool CanLaunchDepthwiseConv2dGPUSmall(const DepthwiseArgs& args) { + return args.stride_height == 1 && args.stride_width == 1 && args.in_height <= 32 && + args.in_width <= 32 && args.in_height == args.out_height && + args.in_width == args.out_width && args.pad_height >= 0 && + args.pad_height < args.filter_height && args.pad_width >= 0 && + args.pad_width < args.filter_width && + args.filter_height * args.filter_width <= (args.in_height + 1) / 2 * args.in_width; +} + +// Returns whether depthwise convolution backward filter pass can be performed +// using the faster ('Small') variant of the kernel. +bool CanLaunchDepthwiseConv2dBackwardFilterGPUSmall(const DepthwiseArgs args, + const int block_height) { + return args.stride_height == 1 && args.stride_width == 1 && args.in_height <= 32 && + args.in_width <= 32 && args.in_height == args.out_height && + args.in_width == args.out_width && args.pad_height >= 0 && + args.pad_height < args.filter_height && args.pad_width >= 0 && + args.pad_width < args.filter_width && block_height <= args.in_height && + args.filter_height * args.filter_width <= block_height * args.in_width; +} + +template +void LaunchDepthwiseConv2dGPUSmall(mshadow::Stream *stream, + const DepthwiseArgs args, + const DType* input, const DType* filter, DType* output) { + const int block_height = (args.in_height + 1) / 2; + dim3 block_dim = dim3(args.in_width, block_height, kBlockSlices); + + const int tile_width = args.in_width + args.filter_width - 1; + const int tile_height = block_height * 2 + args.filter_height - 1; + const int tile_pixels = tile_height * tile_width; + const int filter_pixels = args.filter_height * args.filter_width; + const int shared_memory_size = + kBlockSlices * (tile_pixels + filter_pixels) * sizeof(DType); + const int num_outputs = + args.batch * args.out_height * args.out_width * args.out_channel; + int block_count = std::min(num_outputs/(block_dim.x * block_dim.y * block_dim.z) + 1, + (unsigned)mshadow::cuda::kMaxGridNum); + auto s = mshadow::Stream::GetStream(stream); + if (args.filter_height == 3 && args.filter_width == 3) { + cuda::DepthwiseConv2dKernelSmall + <<>>(args, input, filter, output); + } else { + cuda::DepthwiseConv2dKernelSmall + <<>>(args, input, filter, output); + } + MSHADOW_CUDA_POST_KERNEL_CHECK(DepthwiseConv2dKernelSmall); +} + +template +void LaunchDepthwiseConv2dGPUSmall(mshadow::Stream *stream, + const DepthwiseArgs args, + const DType* input, const DType* filter, DType* output) { + if (args.in_height & 1) { + LaunchDepthwiseConv2dGPUSmall( + stream, args, input, filter, output); + } else { + LaunchDepthwiseConv2dGPUSmall( + stream, args, input, filter, output); + } +} + +template +void LaunchDepthwiseConv2dGPUSmall(mshadow::Stream *stream, + const DepthwiseArgs args, + const DType* input, const DType* filter, DType* output) { + // Maximize (power of two) kBlockSlices while keeping a block within 1024 + // threads (2 pixels per thread). + const int block_pixels = (args.in_height + 1) / 2 * args.in_width; + if (block_pixels > 256) { + LaunchDepthwiseConv2dGPUSmall(stream, args, input, filter, output); + } else if (block_pixels > 128) { + LaunchDepthwiseConv2dGPUSmall(stream, args, input, filter, output); + } else { + LaunchDepthwiseConv2dGPUSmall(stream, args, input, filter, output); + } +} + +template +bool TryLaunchDepthwiseConv2dBackwardFilterGPUSmall(mshadow::Stream *stream, + const DepthwiseArgs args, + const int block_height, + const DType* out_grad, + const DType* input, + DType* filter_grad) { + const int tile_width = args.in_width + args.filter_width - 1; + const int tile_height = block_height * 2 + args.filter_height - 1; + const int tile_pixels = tile_height * tile_width; + const int filter_pixels = args.filter_height * args.filter_width; + const int shared_memory_size = + kBlockSlices * (tile_pixels + filter_pixels * kAccumPixels) * sizeof(DType); + if (shared_memory_size > 46 * 1024) { + return false; + } + + dim3 block_dim = dim3(args.in_width, block_height, kBlockSlices); + const int num_out_grad = + args.batch * args.out_height * args.out_width * args.out_channel; + int block_count = num_out_grad/(block_dim.x * block_dim.y * block_dim.z) + 1; + auto s = mshadow::Stream::GetStream(stream); + if (args.filter_height == 3 && args.filter_width == 3) { + cuda::DepthwiseConv2dBackwardFilterKernelSmall + <<>>( + args, out_grad, input, filter_grad); + } else { + cuda::DepthwiseConv2dBackwardFilterKernelSmall + <<>>( + args, out_grad, input, filter_grad); + } + MSHADOW_CUDA_POST_KERNEL_CHECK(DepthwiseConv2dBackwardFilterKernelSmall); + return true; +} + +template +bool TryLaunchDepthwiseConv2dBackwardFilterGPUSmall(mshadow::Stream *stream, + const DepthwiseArgs args, + const int block_height, + const DType* out_grad, + const DType* input, + DType* filter_grad) { + // Minimize (power of two) kAccumPixels, while satisfying + // kAccumPixels * 32 >= block_height * in_width * kBlockSlices. + const int block_pixels = block_height * args.in_width * kBlockSlices; + if (block_pixels > 512) { + return TryLaunchDepthwiseConv2dBackwardFilterGPUSmall( + stream, args, block_height, out_grad, input, filter_grad); + } else if (block_pixels > 256) { + return TryLaunchDepthwiseConv2dBackwardFilterGPUSmall( + stream, args, block_height, out_grad, input, filter_grad); + } else { + return TryLaunchDepthwiseConv2dBackwardFilterGPUSmall( + stream, args, block_height, out_grad, input, filter_grad); + } +} + +template +bool TryLaunchDepthwiseConv2dBackwardFilterGPUSmall(mshadow::Stream *stream, + const DepthwiseArgs args, + const DType* out_grad, + const DType* input, + DType* filter_grad) { + // Maximize (power of two) kBlockSlices while keeping a block within 1024 + // threads (2 pixels per thread). + int block_slices = 8; + int block_height = (args.in_height + 1) / 2; + int round_mask = 1; + for (; block_slices > 1; block_slices /= 2) { + // args.in_width * block_height * kBlockSlices must be multiple of 32. + for (; block_height * args.in_width * block_slices & 31; + round_mask = round_mask * 2 + 1) { + block_height = block_height + round_mask & ~round_mask; + } + int block_size = block_height * args.in_width * block_slices; + if (block_size <= 1024) { + break; + } + } + + if (!CanLaunchDepthwiseConv2dBackwardFilterGPUSmall(args, block_height)) { + return false; + } + + switch (block_slices) { + case 8: + return TryLaunchDepthwiseConv2dBackwardFilterGPUSmall( + stream, args, block_height, out_grad, input, filter_grad); + case 4: + return TryLaunchDepthwiseConv2dBackwardFilterGPUSmall( + stream, args, block_height, out_grad, input, filter_grad); + case 2: + return TryLaunchDepthwiseConv2dBackwardFilterGPUSmall( + stream, args, block_height, out_grad, input, filter_grad); + default: + return false; + } +} + +} // namespace depthwise_conv +} // namespace tf + +#endif // MXNET_OPERATOR_DEPTHWISE_CONVOLUTION_TF_CUH_ diff --git a/src/operator/dropout-inl.h b/src/operator/dropout-inl.h index 47bb0a3dffd3..b2fb7823bedc 100644 --- a/src/operator/dropout-inl.h +++ b/src/operator/dropout-inl.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file dropout-inl.h * \brief * \author Bing Xu @@ -29,6 +47,7 @@ namespace dropout { enum DropoutOpInputs {kData}; enum DropoutOpOutputs {kOut, kMask}; enum DropoutOpForwardResource {kRandom}; +enum DropoutOpMode {kTraining, kAlways}; } // namespace dropout namespace mxnet { @@ -58,10 +77,16 @@ static void bernoulli_generate(int n, double p, int* r) { struct DropoutParam : public dmlc::Parameter { float p; + int mode; DMLC_DECLARE_PARAMETER(DropoutParam) { DMLC_DECLARE_FIELD(p).set_default(0.5) .set_range(0, 1) .describe("Fraction of the input that gets dropped out during training time."); + DMLC_DECLARE_FIELD(mode) + .add_enum("training", dropout::kTraining) + .add_enum("always", dropout::kAlways) + .set_default(dropout::kTraining) + .describe("Whether to only turn on dropout during training or to also turn on for inference."); } }; // struct DropoutParam @@ -70,6 +95,7 @@ class DropoutOp : public Operator { public: explicit DropoutOp(DropoutParam param) { this->pkeep_ = 1.0f - param.p; + this->mode_ = param.mode; } virtual void Forward(const OpContext &ctx, @@ -86,9 +112,9 @@ class DropoutOp : public Operator { Stream *s = ctx.get_stream(); Tensor data = in_data[dropout::kData].FlatTo2D(s); Tensor out = out_data[dropout::kOut].FlatTo2D(s); - if (ctx.is_train) { + if (ctx.is_train || mode_ == dropout::kAlways) { Tensor mask = out_data[dropout::kMask].FlatTo2D(s); -#if defined(USE_MKL) && defined(_OPENMP) +#if !defined(__CUDACC__) && defined(USE_MKL) && defined(_OPENMP) DType* outptr = out.dptr_; DType* dataptr = data.dptr_; int* maskptr = reinterpret_cast(mask.dptr_); @@ -96,7 +122,7 @@ class DropoutOp : public Operator { bernoulli_generate(count, this->pkeep_, maskptr); #pragma omp parallel for for (int i = 0; i < count; ++i) { - outptr[i] = dataptr[i] * maskptr[i]; + outptr[i] = dataptr[i] * maskptr[i] * (1.0f / pkeep_); } #else Random *prnd = ctx.requested[dropout::kRandom].get_random(s); @@ -124,24 +150,29 @@ class DropoutOp : public Operator { Tensor grad = out_grad[dropout::kOut].FlatTo2D(s); Tensor mask = out_data[dropout::kMask].FlatTo2D(s); Tensor gdata = in_grad[dropout::kData].FlatTo2D(s); -#if defined(USE_MKL) && defined(_OPENMP) + if (ctx.is_train || mode_ == dropout::kAlways) { +#if !defined(__CUDACC__) && defined(USE_MKL) && defined(_OPENMP) DType* ingradptr = gdata.dptr_; DType* outgradptr = grad.dptr_; int* maskptr = reinterpret_cast(mask.dptr_); int count = mask.shape_[0]*mask.shape_[1]; - #pragma omp parallel for + #pragma omp parallel for for (int i = 0; i < count; ++i) { - ingradptr[i] = outgradptr[i] * maskptr[i]; + ingradptr[i] = outgradptr[i] * maskptr[i] * (1.0f / pkeep_); } #else // USE_MKL && _OPENMP Assign(gdata, req[dropout::kData], grad * mask); #endif // USE_MKL && _OPENMP + } else { + Assign(gdata, req[dropout::kData], F(grad)); + } } private: real_t pkeep_; + int mode_; }; // class DropoutOp diff --git a/src/operator/dropout.cc b/src/operator/dropout.cc index 20afef2c63c8..af65578ec6f8 100644 --- a/src/operator/dropout.cc +++ b/src/operator/dropout.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file dropout.cc * \brief * \author Bing Xu @@ -21,10 +39,6 @@ Operator *CreateOp(DropoutParam param, int dtype) { // DO_BIND_DISPATCH comes from operator_common.h Operator *DropoutProp::CreateOperatorEx(Context ctx, std::vector *in_shape, std::vector *in_type) const { - std::vector out_shape, aux_shape; - std::vector out_type, aux_type; - CHECK(InferType(in_type, &out_type, &aux_type)); - CHECK(InferShape(in_shape, &out_shape, &aux_shape)); DO_BIND_DISPATCH(CreateOp, param_, in_type->at(0)); } @@ -37,7 +51,8 @@ MXNET_REGISTER_OP_PROPERTY(Dropout, DropoutProp) The whole array is rescaled by :math:`1/(1-p)` to keep the expected sum of the input unchanged. -- During testing, this operator does not change the input. +- During testing, this operator does not change the input if mode is 'training'. + If mode is 'always', the same computaion as during training will be applied. Example:: diff --git a/src/operator/dropout.cu b/src/operator/dropout.cu index ea9eb7dfa200..5265d8013ff7 100644 --- a/src/operator/dropout.cu +++ b/src/operator/dropout.cu @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file dropout.cc * \brief * \author Bing Xu diff --git a/src/operator/elemwise_op_common.h b/src/operator/elemwise_op_common.h index def38126d08c..9b398f947e30 100644 --- a/src/operator/elemwise_op_common.h +++ b/src/operator/elemwise_op_common.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! -* Copyright (c) 2016 by Contributors * \file elemwise_op_common.h * \brief common function used for broadcasting and reducing * \author Xingjian Shi @@ -22,33 +40,42 @@ namespace mxnet { namespace op { template + std::string (*attr_string)(const AttrType&), + int n_in = -1, int n_out = -1> inline bool ElemwiseAttr(const nnvm::NodeAttrs& attrs, std::vector *in_attrs, std::vector *out_attrs, const AttrType& none) { AttrType dattr = none; - auto deduce = [&](std::vector *vec, const char *name) { - for (size_t i = 0; i < vec->size(); ++i) { + size_t in_size = in_attrs->size(); + size_t out_size = out_attrs->size(); + if (n_in != -1) + in_size = static_cast(n_in); + if (n_out != -1) + out_size = static_cast(n_out); + + auto deduce = [&](std::vector *vec, size_t size, const char *name) { + for (size_t i = 0; i < size; ++i) { CHECK(assign(&dattr, (*vec)[i])) << "Incompatible attr in node " << attrs.name << " at " << i << "-th " << name << ": " << "expected " << attr_string(dattr) << ", got " << attr_string((*vec)[i]); } }; - deduce(in_attrs, "input"); - if (reverse_infer) deduce(out_attrs, "output"); + deduce(in_attrs, in_size, "input"); + if (reverse_infer) deduce(out_attrs, out_size, "output"); - auto write = [&](std::vector *vec, const char *name) { - for (size_t i = 0; i < vec->size(); ++i) { + auto write = [&](std::vector *vec, size_t size, const char *name) { + for (size_t i = 0; i < size; ++i) { CHECK(assign(&(*vec)[i], dattr)) << "Incompatible attr in node " << attrs.name << " at " << i << "-th " << name << ": " << "expected " << attr_string(dattr) << ", got " << attr_string((*vec)[i]); } }; - write(in_attrs, "input"); - write(out_attrs, "output"); + write(in_attrs, in_size, "input"); + write(out_attrs, out_size, "output"); + if (is_none(dattr)) return false; return true; } @@ -57,8 +84,12 @@ template inline bool ElemwiseShape(const nnvm::NodeAttrs& attrs, std::vector *in_attrs, std::vector *out_attrs) { - CHECK_EQ(in_attrs->size(), static_cast(n_in)) << " in operator " << attrs.name; - CHECK_EQ(out_attrs->size(), static_cast(n_out)) << " in operator " << attrs.name; + if (n_in != -1) { + CHECK_EQ(in_attrs->size(), static_cast(n_in)) << " in operator " << attrs.name; + } + if (n_out != -1) { + CHECK_EQ(out_attrs->size(), static_cast(n_out)) << " in operator " << attrs.name; + } return ElemwiseAttr( attrs, in_attrs, out_attrs, TShape()); } @@ -67,8 +98,12 @@ template inline bool ElemwiseType(const nnvm::NodeAttrs& attrs, std::vector *in_attrs, std::vector *out_attrs) { - CHECK_EQ(in_attrs->size(), static_cast(n_in)) << " in operator " << attrs.name; - CHECK_EQ(out_attrs->size(), static_cast(n_out)) << " in operator " << attrs.name; + if (n_in != -1) { + CHECK_EQ(in_attrs->size(), static_cast(n_in)) << " in operator " << attrs.name; + } + if (n_out != -1) { + CHECK_EQ(out_attrs->size(), static_cast(n_out)) << " in operator " << attrs.name; + } return ElemwiseAttr( attrs, in_attrs, out_attrs, -1); } @@ -96,6 +131,23 @@ struct ElemwiseGradUseOut { } }; +// Transfer gradient and input and output to FGradient function +struct ElemwiseGradUseInOut { + const char *op_name; + std::vector operator()(const nnvm::NodePtr& n, + const std::vector& ograds) { + std::vector heads(ograds.begin(), ograds.end()); + for (auto& h : n->inputs) { + heads.push_back(h); + } + index_t n_out = n->num_outputs(); + for (index_t i = 0; i < n_out; ++i) { + heads.emplace_back(nnvm::NodeEntry{n, i, 0}); + } + return MakeGradNode(op_name, n, heads, n->attrs.dict); + } +}; + // Transfer only gradient to FGradient function struct ElemwiseGradUseNone { const char *op_name; @@ -105,6 +157,16 @@ struct ElemwiseGradUseNone { } }; +struct CloneGradient { + const char *op_name; + std::vector operator()(const nnvm::NodePtr& n, + const std::vector& ograds) { + std::vector ret; + for (size_t i = 0; i < n->inputs.size(); ++i) + ret.emplace_back(ograds[0]); + return ret; + } +}; } // namespace op } // namespace mxnet diff --git a/src/operator/fully_connected-inl.h b/src/operator/fully_connected-inl.h index 94616bc2e7d7..e2fab9f1f7dd 100644 --- a/src/operator/fully_connected-inl.h +++ b/src/operator/fully_connected-inl.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file fully_connect_op-inl.h * \brief fully connect operator and symbol */ diff --git a/src/operator/fully_connected.cc b/src/operator/fully_connected.cc index cec2015425c6..5dbaf8c82005 100644 --- a/src/operator/fully_connected.cc +++ b/src/operator/fully_connected.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file fully_connected.cc * \brief fully connect operator */ @@ -49,8 +67,6 @@ Operator* CreateOp(FullyConnectedParam param, int dtype, Operator *FullyConnectedProp::CreateOperatorEx(Context ctx, std::vector *in_shape, std::vector *in_type) const { std::vector out_shape(1, TShape()), aux_shape; - std::vector out_type(1, -1), aux_type; - CHECK(InferType(in_type, &out_type, &aux_type)); CHECK(InferShape(in_shape, &out_shape, &aux_shape)); DO_BIND_DISPATCH(CreateOp, param_, (*in_type)[0], in_shape, &out_shape, ctx); } diff --git a/src/operator/fully_connected.cu b/src/operator/fully_connected.cu index 7b834a3b08ef..28a0307b70bd 100644 --- a/src/operator/fully_connected.cu +++ b/src/operator/fully_connected.cu @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file fully_connected.cu * \brief fully connect operator */ diff --git a/src/operator/grid_generator-inl.h b/src/operator/grid_generator-inl.h index 1f88cf4935da..65fb8ccf2e07 100644 --- a/src/operator/grid_generator-inl.h +++ b/src/operator/grid_generator-inl.h @@ -1,318 +1,336 @@ -/*! - * Copyright (c) 2017 by Contributors - * \file grid_generator-inl.h - * \brief - * The operator generate sampling grid - * \author Xu Dong -*/ -#ifndef MXNET_OPERATOR_GRID_GENERATOR_INL_H_ -#define MXNET_OPERATOR_GRID_GENERATOR_INL_H_ - -#include -#include -#include -#include -#include -#include -#include -#include "./mshadow_op.h" -#include "./operator_common.h" - -namespace mxnet { -namespace op { - -namespace grid { -enum GridGeneratorOpInputs {kData}; -enum GridGeneratorOpOutputs {kOut, kGridDst}; -enum GridGeneratorOpResource {kTempSpace}; -enum GridGeneratorTransformType {kAffine, kWarp}; -} - -struct GridGeneratorParam : public dmlc::Parameter { - int transform_type; - TShape target_shape; - DMLC_DECLARE_PARAMETER(GridGeneratorParam) { - int shape[] = {0, 0}; - DMLC_DECLARE_FIELD(transform_type) - .add_enum("affine", grid::kAffine) - .add_enum("warp", grid::kWarp) - .describe("transformation type\n " - "if transformation type is affine, data is affine matrix : (batch, 6)\n " - "if transformation type is warp, data is optical flow : (batch, 2, h, w)"); - DMLC_DECLARE_FIELD(target_shape).set_default(TShape(shape, shape + 2)) - .describe("if transformation type is affine, the operator need a target_shape : (H, W)\n " - "if transofrmation type is warp, the operator will ignore target_shape"); - } -}; - -template -class GridGeneratorOp : public Operator { - public: - explicit GridGeneratorOp(GridGeneratorParam p) { - this->param_ = p; - } - - virtual void Forward(const OpContext &ctx, - const std::vector &in_data, - const std::vector &req, - const std::vector &out_data, - const std::vector &aux_args) { - using namespace mshadow; - using namespace mshadow::expr; - CHECK_EQ(req[grid::kOut], kWriteTo); - CHECK_EQ(in_data.size(), 1U); - CHECK_EQ(out_data.size(), 2U); - Stream *s = ctx.get_stream(); - switch (param_.transform_type) { - case grid::kAffine: { - // if transform_type is affine, data is affine matrix, input shape : (batch, 2, 3) - Tensor out = out_data[grid::kOut]. - get_with_shape(Shape2(out_data[grid::kOut].shape_[0] * 2, - out_data[grid::kOut].shape_[2] * out_data[grid::kOut].shape_[3]), s); - Tensor grid_dst = out_data[grid::kGridDst].get(s); - Shape<2> data_shape = Shape2(out_data[grid::kOut].shape_[0] * 2, 3); - Tensor data = in_data[grid::kData] - .get_with_shape(data_shape, s); - // x, y, 1 - grid_dst[0] = range(0, grid_dst.shape_[1]); - grid_dst[0] = grid_dst[0] - tcast(tcast(grid_dst[0] / - scalar(param_.target_shape[1]))) * scalar(param_.target_shape[1]); - grid_dst[0] = scalar(-1.0) + grid_dst[0] * - scalar(2.0 / (param_.target_shape[1] - 1)); - grid_dst[1] = range(0, grid_dst.shape_[1]); - grid_dst[1] = scalar(-1.0) + tcast(tcast(grid_dst[1] / - scalar(param_.target_shape[1]))) * scalar(2.0/(param_.target_shape[0] - 1)); - grid_dst[2] = scalar(1.0); - Assign(out, req[grid::kOut], dot(data, grid_dst)); - break; - } - // Warping transformation - case grid::kWarp: { - // if transform_type is warp, data is optical flow, input shape : (batch, 2, height, width) - // grid_src = grid_dst + optical flow - Tensor data = in_data[grid::kData].get(s); - Tensor out = out_data[grid::kOut].get(s); - // grid_dst : (2, H, W) - Tensor grid_dst = out_data[grid::kGridDst].get(s); - Tensor workspace = ctx.requested[grid::kTempSpace] - .get_space_typed(Shape2(2, 1), s); - grid_dst[0] = repmat(range(0, data.size(3)), data.size(2)); - grid_dst[1] = reshape(range(0, data.size(2), 1, data.size(3)), - Shape2(data.size(2), data.size(3))); - workspace[0] = scalar((DType(data.size(3)) - 1.0) / 2.0); - workspace[1] = scalar((DType(data.size(2)) - 1.0) / 2.0); - Assign(out, req[grid::kOut], - (data + broadcast_with_axis(grid_dst, -1, data.shape_[0])) / - broadcast_to(reshape(workspace, Shape4(1, 2, 1, 1)), - TShape(data.shape_)) - scalar(1)); - break; - } - } - } - - virtual void Backward(const OpContext &ctx, - const std::vector &out_grad, - const std::vector &in_data, - const std::vector &out_data, - const std::vector &req, - const std::vector &in_grad, - const std::vector &aux_args) { - using namespace mshadow; - using namespace mshadow::expr; - CHECK_EQ(in_data.size(), 1U); - CHECK_EQ(out_data.size(), 2U); - Stream *s = ctx.get_stream(); - switch (param_.transform_type) { - case grid::kAffine: { - Tensor grid_dst = out_data[grid::kGridDst].get(s); - Shape<2> data_shape = Shape2(in_grad[grid::kData].shape_[0] * 2, 3); - Tensor gdata = in_grad[grid::kData] - .get_with_shape(data_shape, s); - Shape<2> grad_shape = Shape2(out_grad[grid::kOut].shape_[0] * 2, - param_.target_shape[0] * param_.target_shape[1]); - Tensor grad = out_grad[grid::kOut] - .get_with_shape(grad_shape, s); - // grad : (batch * 2, H * W) grid_dst.T : (H * W, 3) - Assign(gdata, req[grid::kData] , dot(grad, grid_dst.T())); - break; - } - case grid::kWarp: { - Tensor grad = out_grad[grid::kOut].get(s); - Tensor gdata = in_grad[grid::kData].get(s); - Tensor workspace = ctx.requested[grid::kTempSpace] - .get_space_typed(Shape2(2, 1), s); - workspace[0] = scalar((DType(gdata.size(3)) - 1.0) / 2.0); - workspace[1] = scalar((DType(gdata.size(2)) - 1.0) / 2.0); - Assign(gdata, req[grid::kData], - grad / broadcast_to(reshape(workspace, Shape4(1, 2, 1, 1)), - TShape(gdata.shape_))); - break; - } - } - } - - private: - GridGeneratorParam param_; -}; // class GridGeneratorOp - -template -Operator* CreateOp(GridGeneratorParam param, int dtype); - -#if DMLC_USE_CXX11 -class GridGeneratorProp : public OperatorProperty { - public: - int NumVisibleOutputs() const override { - return 1; - } - - int NumOutputs() const override { - return 2; - } - - std::vector ListArguments() const override { - return {"data"}; - } - - std::vector ListOutputs() const override { - return {"output", "grid_dst"}; - } - - void Init(const std::vector >& kwargs) override { - param_.Init(kwargs); - } - - std::map GetParams() const override { - return param_.__DICT__(); - } - - bool InferShape(std::vector *in_shape, - std::vector *out_shape, - std::vector *aux_shape) const override { - using namespace mshadow; - CHECK_EQ(in_shape->size(), 1U) << "Input:[data]"; - const TShape &lshape = (*in_shape)[grid::kData]; - if (lshape.ndim() == 0) return false; - out_shape->clear(); - switch (param_.transform_type) { - case grid::kAffine: { - CHECK_EQ(lshape.ndim(), 2U) \ - << "if transform_type is affine, data is affine matrix" - "affine matrix should be 2D in batch-num_hidden"; - CHECK_EQ(lshape[1], 6U) << "incorrect data shape[1], should be 6"; - CHECK_GT(param_.target_shape[0], 0U) \ - << "incorrect target_shape: " << param_.target_shape[0]; - CHECK_GT(param_.target_shape[1], 0U) \ - << "incorrect target_shape: " << param_.target_shape[1]; - out_shape->push_back(Shape4(lshape[0], 2, param_.target_shape[0], param_.target_shape[1])); - out_shape->push_back(Shape2(3, param_.target_shape[0] * param_.target_shape[1])); - break; - } - case grid::kWarp: { - CHECK_EQ(lshape.ndim(), 4U) \ - << "if transform_type is warp, data is optical flow" - "optical flow should be 4D in batch-num_hidden-y-x"; - CHECK_EQ(lshape[1], 2U) << "incorrect data shape[1], should be 2"; - out_shape->push_back(lshape); - out_shape->push_back(Shape3(2, lshape[2], lshape[3])); - break; - } - } - return true; - } - - bool InferType(std::vector *in_type, - std::vector *out_type, - std::vector *aux_type) const override { - int dtype = -1; - for (size_t i = 0; i < in_type->size(); ++i) { - if (dtype == -1) { - dtype = in_type->at(i); - } else { - CHECK(in_type->at(i) == dtype || - in_type->at(i) == -1) << - "Non-uniform data type in GridGenerator"; - } - } - if (dtype == -1) { - LOG(FATAL) << "Not enough information to infer type in GridGenerator."; - return false; - } - size_t nin = this->ListArguments().size(); - in_type->clear(); - for (size_t i = 0; i < nin; ++i) in_type->push_back(dtype); - size_t naux = this->ListAuxiliaryStates().size(); - aux_type->clear(); - for (size_t i = 0; i < naux; ++i) aux_type->push_back(dtype); - size_t nout = this->ListOutputs().size(); - out_type->clear(); - for (size_t i = 0; i < nout; ++i) out_type->push_back(dtype); - return true; - } - - OperatorProperty* Copy() const override { - auto ptr = new GridGeneratorProp(); - ptr->param_ = param_; - return ptr; - } - - std::string TypeString() const override { - return "GridGenerator"; - } - - std::vector DeclareBackwardDependency( - const std::vector &out_grad, - const std::vector &in_data, - const std::vector &out_data) const override { - switch (param_.transform_type) { - case grid::kAffine: { - return {out_grad[grid::kOut], - out_data[grid::kGridDst]}; - } - case grid::kWarp: { - return {out_grad[grid::kOut]}; - } - } - return {}; - } - - std::vector ForwardResource( - const std::vector &in_shape) const override { - switch (param_.transform_type) { - case grid::kAffine: { - return{}; - } - case grid::kWarp: { - return{ ResourceRequest::kTempSpace }; - } - } - return{}; - } - - std::vector BackwardResource( - const std::vector &in_shape) const override { - switch (param_.transform_type) { - case grid::kAffine: { - return {}; - } - case grid::kWarp: { - return {ResourceRequest::kTempSpace}; - } - } - return {}; - } - - Operator* CreateOperator(Context ctx) const override { - LOG(FATAL) << "Not Implemented."; - return NULL; - } - - Operator* CreateOperatorEx(Context ctx, std::vector *in_shape, - std::vector *in_type) const override; - - private: - GridGeneratorParam param_; -}; // class GridGeneratorProp -#endif // DMLC_USE_CXX11 -} // namespace op -} // namespace mxnet -#endif // MXNET_OPERATOR_GRID_GENERATOR_INL_H_ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +/*! + * \file grid_generator-inl.h + * \brief + * The operator generate sampling grid + * \author Xu Dong +*/ +#ifndef MXNET_OPERATOR_GRID_GENERATOR_INL_H_ +#define MXNET_OPERATOR_GRID_GENERATOR_INL_H_ + +#include +#include +#include +#include +#include +#include +#include +#include "./mshadow_op.h" +#include "./operator_common.h" + +namespace mxnet { +namespace op { + +namespace grid { +enum GridGeneratorOpInputs {kData}; +enum GridGeneratorOpOutputs {kOut, kGridDst}; +enum GridGeneratorOpResource {kTempSpace}; +enum GridGeneratorTransformType {kAffine, kWarp}; +} + +struct GridGeneratorParam : public dmlc::Parameter { + int transform_type; + TShape target_shape; + DMLC_DECLARE_PARAMETER(GridGeneratorParam) { + int shape[] = {0, 0}; + DMLC_DECLARE_FIELD(transform_type) + .add_enum("affine", grid::kAffine) + .add_enum("warp", grid::kWarp) + .describe("The type of transformation. For `affine`, input data should be an affine matrix " + "of size (batch, 6). For `warp`, input data should be an optical flow of size " + "(batch, 2, h, w)."); + DMLC_DECLARE_FIELD(target_shape).set_default(TShape(shape, shape + 2)) + .describe("Specifies the output shape (H, W). This is required if transformation type is " + "`affine`. If transformation type is `warp`, this parameter is ignored."); + } +}; + +template +class GridGeneratorOp : public Operator { + public: + explicit GridGeneratorOp(GridGeneratorParam p) { + this->param_ = p; + } + + virtual void Forward(const OpContext &ctx, + const std::vector &in_data, + const std::vector &req, + const std::vector &out_data, + const std::vector &aux_args) { + using namespace mshadow; + using namespace mshadow::expr; + CHECK_EQ(req[grid::kOut], kWriteTo); + CHECK_EQ(in_data.size(), 1U); + CHECK_EQ(out_data.size(), 2U); + Stream *s = ctx.get_stream(); + switch (param_.transform_type) { + case grid::kAffine: { + // if transform_type is affine, data is affine matrix, input shape : (batch, 2, 3) + Tensor out = out_data[grid::kOut]. + get_with_shape(Shape2(out_data[grid::kOut].shape_[0] * 2, + out_data[grid::kOut].shape_[2] * out_data[grid::kOut].shape_[3]), s); + Tensor grid_dst = out_data[grid::kGridDst].get(s); + Shape<2> data_shape = Shape2(out_data[grid::kOut].shape_[0] * 2, 3); + Tensor data = in_data[grid::kData] + .get_with_shape(data_shape, s); + // x, y, 1 + grid_dst[0] = range(0, grid_dst.shape_[1]); + grid_dst[0] = grid_dst[0] - tcast(tcast(grid_dst[0] / + scalar(param_.target_shape[1]))) * scalar(param_.target_shape[1]); + grid_dst[0] = scalar(-1.0) + grid_dst[0] * + scalar(2.0 / (param_.target_shape[1] - 1)); + grid_dst[1] = range(0, grid_dst.shape_[1]); + grid_dst[1] = scalar(-1.0) + tcast(tcast(grid_dst[1] / + scalar(param_.target_shape[1]))) * scalar(2.0/(param_.target_shape[0] - 1)); + grid_dst[2] = scalar(1.0); + Assign(out, req[grid::kOut], dot(data, grid_dst)); + break; + } + // Warping transformation + case grid::kWarp: { + // if transform_type is warp, data is optical flow, input shape : (batch, 2, height, width) + // grid_src = grid_dst + optical flow + Tensor data = in_data[grid::kData].get(s); + Tensor out = out_data[grid::kOut].get(s); + // grid_dst : (2, H, W) + Tensor grid_dst = out_data[grid::kGridDst].get(s); + Tensor workspace = ctx.requested[grid::kTempSpace] + .get_space_typed(Shape2(2, 1), s); + grid_dst[0] = repmat(range(0, data.size(3)), data.size(2)); + grid_dst[1] = reshape(range(0, data.size(2), 1, data.size(3)), + Shape2(data.size(2), data.size(3))); + workspace[0] = scalar((DType(data.size(3)) - 1.0) / 2.0); + workspace[1] = scalar((DType(data.size(2)) - 1.0) / 2.0); + Assign(out, req[grid::kOut], + (data + broadcast_with_axis(grid_dst, -1, data.shape_[0])) / + broadcast_to(reshape(workspace, Shape4(1, 2, 1, 1)), + TShape(data.shape_)) - scalar(1)); + break; + } + } + } + + virtual void Backward(const OpContext &ctx, + const std::vector &out_grad, + const std::vector &in_data, + const std::vector &out_data, + const std::vector &req, + const std::vector &in_grad, + const std::vector &aux_args) { + using namespace mshadow; + using namespace mshadow::expr; + CHECK_EQ(in_data.size(), 1U); + CHECK_EQ(out_data.size(), 2U); + Stream *s = ctx.get_stream(); + switch (param_.transform_type) { + case grid::kAffine: { + Tensor grid_dst = out_data[grid::kGridDst].get(s); + Shape<2> data_shape = Shape2(in_grad[grid::kData].shape_[0] * 2, 3); + Tensor gdata = in_grad[grid::kData] + .get_with_shape(data_shape, s); + Shape<2> grad_shape = Shape2(out_grad[grid::kOut].shape_[0] * 2, + param_.target_shape[0] * param_.target_shape[1]); + Tensor grad = out_grad[grid::kOut] + .get_with_shape(grad_shape, s); + // grad : (batch * 2, H * W) grid_dst.T : (H * W, 3) + Assign(gdata, req[grid::kData] , dot(grad, grid_dst.T())); + break; + } + case grid::kWarp: { + Tensor grad = out_grad[grid::kOut].get(s); + Tensor gdata = in_grad[grid::kData].get(s); + Tensor workspace = ctx.requested[grid::kTempSpace] + .get_space_typed(Shape2(2, 1), s); + workspace[0] = scalar((DType(gdata.size(3)) - 1.0) / 2.0); + workspace[1] = scalar((DType(gdata.size(2)) - 1.0) / 2.0); + Assign(gdata, req[grid::kData], + grad / broadcast_to(reshape(workspace, Shape4(1, 2, 1, 1)), + TShape(gdata.shape_))); + break; + } + } + } + + private: + GridGeneratorParam param_; +}; // class GridGeneratorOp + +template +Operator* CreateOp(GridGeneratorParam param, int dtype); + +#if DMLC_USE_CXX11 +class GridGeneratorProp : public OperatorProperty { + public: + int NumVisibleOutputs() const override { + return 1; + } + + int NumOutputs() const override { + return 2; + } + + std::vector ListArguments() const override { + return {"data"}; + } + + std::vector ListOutputs() const override { + return {"output", "grid_dst"}; + } + + void Init(const std::vector >& kwargs) override { + param_.Init(kwargs); + } + + std::map GetParams() const override { + return param_.__DICT__(); + } + + bool InferShape(std::vector *in_shape, + std::vector *out_shape, + std::vector *aux_shape) const override { + using namespace mshadow; + CHECK_EQ(in_shape->size(), 1U) << "Input:[data]"; + const TShape &lshape = (*in_shape)[grid::kData]; + if (lshape.ndim() == 0) return false; + out_shape->clear(); + switch (param_.transform_type) { + case grid::kAffine: { + CHECK_EQ(lshape.ndim(), 2U) \ + << "if transform_type is affine, data is affine matrix" + "affine matrix should be 2D in batch-num_hidden"; + CHECK_EQ(lshape[1], 6U) << "incorrect data shape[1], should be 6"; + CHECK_GT(param_.target_shape[0], 0U) \ + << "incorrect target_shape: " << param_.target_shape[0]; + CHECK_GT(param_.target_shape[1], 0U) \ + << "incorrect target_shape: " << param_.target_shape[1]; + out_shape->push_back(Shape4(lshape[0], 2, param_.target_shape[0], param_.target_shape[1])); + out_shape->push_back(Shape2(3, param_.target_shape[0] * param_.target_shape[1])); + break; + } + case grid::kWarp: { + CHECK_EQ(lshape.ndim(), 4U) \ + << "if transform_type is warp, data is optical flow" + "optical flow should be 4D in batch-num_hidden-y-x"; + CHECK_EQ(lshape[1], 2U) << "incorrect data shape[1], should be 2"; + out_shape->push_back(lshape); + out_shape->push_back(Shape3(2, lshape[2], lshape[3])); + break; + } + } + return true; + } + + bool InferType(std::vector *in_type, + std::vector *out_type, + std::vector *aux_type) const override { + int dtype = -1; + for (size_t i = 0; i < in_type->size(); ++i) { + if (dtype == -1) { + dtype = in_type->at(i); + } else { + CHECK(in_type->at(i) == dtype || + in_type->at(i) == -1) << + "Non-uniform data type in GridGenerator"; + } + } + if (dtype == -1) { + LOG(FATAL) << "Not enough information to infer type in GridGenerator."; + return false; + } + size_t nin = this->ListArguments().size(); + in_type->clear(); + for (size_t i = 0; i < nin; ++i) in_type->push_back(dtype); + size_t naux = this->ListAuxiliaryStates().size(); + aux_type->clear(); + for (size_t i = 0; i < naux; ++i) aux_type->push_back(dtype); + size_t nout = this->ListOutputs().size(); + out_type->clear(); + for (size_t i = 0; i < nout; ++i) out_type->push_back(dtype); + return true; + } + + OperatorProperty* Copy() const override { + auto ptr = new GridGeneratorProp(); + ptr->param_ = param_; + return ptr; + } + + std::string TypeString() const override { + return "GridGenerator"; + } + + std::vector DeclareBackwardDependency( + const std::vector &out_grad, + const std::vector &in_data, + const std::vector &out_data) const override { + switch (param_.transform_type) { + case grid::kAffine: { + return {out_grad[grid::kOut], + out_data[grid::kGridDst]}; + } + case grid::kWarp: { + return {out_grad[grid::kOut]}; + } + } + return {}; + } + + std::vector ForwardResource( + const std::vector &in_shape) const override { + switch (param_.transform_type) { + case grid::kAffine: { + return{}; + } + case grid::kWarp: { + return{ ResourceRequest::kTempSpace }; + } + } + return{}; + } + + std::vector BackwardResource( + const std::vector &in_shape) const override { + switch (param_.transform_type) { + case grid::kAffine: { + return {}; + } + case grid::kWarp: { + return {ResourceRequest::kTempSpace}; + } + } + return {}; + } + + Operator* CreateOperator(Context ctx) const override { + LOG(FATAL) << "Not Implemented."; + return NULL; + } + + Operator* CreateOperatorEx(Context ctx, std::vector *in_shape, + std::vector *in_type) const override; + + private: + GridGeneratorParam param_; +}; // class GridGeneratorProp +#endif // DMLC_USE_CXX11 +} // namespace op +} // namespace mxnet +#endif // MXNET_OPERATOR_GRID_GENERATOR_INL_H_ diff --git a/src/operator/grid_generator.cc b/src/operator/grid_generator.cc index 831e8a359493..411f856be08b 100644 --- a/src/operator/grid_generator.cc +++ b/src/operator/grid_generator.cc @@ -1,42 +1,54 @@ -/*! - * Copyright (c) 2017 by Contributors - * \file grid_generator.cc - * \brief - * \author Xu Dong -*/ - -#include "./grid_generator-inl.h" - -namespace mxnet { -namespace op { -template<> -Operator* CreateOp(GridGeneratorParam param, int dtype) { - Operator *op = NULL; - if (dtype == mshadow::kFloat32) { - op = new GridGeneratorOp(param); - } else { - LOG(FATAL) << "Other DTypes are not supported!"; - } - return op; -} - -Operator *GridGeneratorProp::CreateOperatorEx(Context ctx, std::vector *in_shape, - std::vector *in_type) const { - std::vector out_shape, aux_shape; - std::vector out_type, aux_type; - CHECK(InferType(in_type, &out_type, &aux_type)); - CHECK(InferShape(in_shape, &out_shape, &aux_shape)); - DO_BIND_DISPATCH(CreateOp, param_, (*in_type)[0]); -} - -DMLC_REGISTER_PARAMETER(GridGeneratorParam); - -MXNET_REGISTER_OP_PROPERTY(GridGenerator, GridGeneratorProp) -.add_argument("data", "NDArray-or-Symbol", "Input data to the GridGeneratorOp.") -.describe("if transformation type is affine, data is affine matrix : (batch, 6)") -.describe("if transformation type is warp, data is optical flow : (batch, 2, h, w)") -.add_arguments(GridGeneratorParam::__FIELDS__()) -.describe("Generates sampling grid for bilinear sampling."); - -} // namespace op -} // namespace mxnet +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +/*! + * \file grid_generator.cc + * \brief + * \author Xu Dong +*/ + +#include "./grid_generator-inl.h" + +namespace mxnet { +namespace op { +template<> +Operator* CreateOp(GridGeneratorParam param, int dtype) { + Operator *op = NULL; + if (dtype == mshadow::kFloat32) { + op = new GridGeneratorOp(param); + } else { + LOG(FATAL) << "Other DTypes are not supported!"; + } + return op; +} + +Operator *GridGeneratorProp::CreateOperatorEx(Context ctx, std::vector *in_shape, + std::vector *in_type) const { + DO_BIND_DISPATCH(CreateOp, param_, (*in_type)[0]); +} + +DMLC_REGISTER_PARAMETER(GridGeneratorParam); + +MXNET_REGISTER_OP_PROPERTY(GridGenerator, GridGeneratorProp) +.add_argument("data", "NDArray-or-Symbol", "Input data to the function.") +.add_arguments(GridGeneratorParam::__FIELDS__()) +.describe("Generates 2D sampling grid for bilinear sampling."); + +} // namespace op +} // namespace mxnet diff --git a/src/operator/grid_generator.cu b/src/operator/grid_generator.cu index 991948cd1581..7c0a80258d36 100644 --- a/src/operator/grid_generator.cu +++ b/src/operator/grid_generator.cu @@ -1,21 +1,39 @@ -/*! - * Copyright (c) 2017 by Contributors - * \file grid_generator.cu - * \brief - * \author Xu Dong -*/ - -#include "./grid_generator-inl.h" - -namespace mxnet { -namespace op { -template<> -Operator* CreateOp(GridGeneratorParam param, int dtype) { - Operator *op = NULL; - MSHADOW_REAL_TYPE_SWITCH(dtype, DType, { - op = new GridGeneratorOp(param); - }) - return op; -} -} // namespace op -} // namespace mxnet +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +/*! + * \file grid_generator.cu + * \brief + * \author Xu Dong +*/ + +#include "./grid_generator-inl.h" + +namespace mxnet { +namespace op { +template<> +Operator* CreateOp(GridGeneratorParam param, int dtype) { + Operator *op = NULL; + MSHADOW_REAL_TYPE_SWITCH(dtype, DType, { + op = new GridGeneratorOp(param); + }) + return op; +} +} // namespace op +} // namespace mxnet diff --git a/src/operator/identity_attach_KL_sparse_reg-inl.h b/src/operator/identity_attach_KL_sparse_reg-inl.h index ca7eab0f399e..2307914f62a5 100644 --- a/src/operator/identity_attach_KL_sparse_reg-inl.h +++ b/src/operator/identity_attach_KL_sparse_reg-inl.h @@ -1,177 +1,195 @@ -/*! - * Copyright (c) 2015 by Contributors - * \file sparse_reg-inl.h - * \brief -*/ -#ifndef MXNET_OPERATOR_IDENTITY_ATTACH_KL_SPARSE_REG_INL_H_ -#define MXNET_OPERATOR_IDENTITY_ATTACH_KL_SPARSE_REG_INL_H_ -#include -#include -#include -#include -#include -#include -#include -#include "./mshadow_op.h" -#include "./operator_common.h" - -namespace mxnet { -namespace op { - -namespace sparsereg { -enum IdentityAttachKLSparseRegOpInputs {kData}; -enum IdentityAttachKLSparseRegOpOutputs {kOut}; -enum IdentityAttachKLSparseRegOpAuxiliary {kMovingAvg}; -enum IdentityAttachKLSparseRegBackResource {kTempSpace}; -} // namespace sparsereg - -struct IdentityAttachKLSparseRegParam : public dmlc::Parameter { - float penalty; - float sparseness_target; - float momentum; - DMLC_DECLARE_PARAMETER(IdentityAttachKLSparseRegParam) { - DMLC_DECLARE_FIELD(sparseness_target).set_default(0.1) - .set_range(0, 1) - .describe("The sparseness target"); - DMLC_DECLARE_FIELD(penalty).set_default(0.001) - .describe("The tradeoff parameter for the sparseness penalty"); - DMLC_DECLARE_FIELD(momentum).set_default(0.9) - .set_range(0, 1) - .describe("The momentum for running average"); - } -}; // struct IdentityAttachKLSparseRegParam - -// This op regularizes the output of a sigmoid activation function. -// In forward, it simply copies the input. -// In backward, it attaches sparseness penalty to the gradient. -// The regularization is based on the KL divergence of mean activation and target. -// More details: P11 of https://www.cs.toronto.edu/~hinton/absps/guideTR.pdf -// Please make sure that it is only paired with sigmoid activation, otherwise NaN may occur. -template -class IdentityAttachKLSparseRegOp : public Operator { - public: - explicit IdentityAttachKLSparseRegOp(IdentityAttachKLSparseRegParam param) { - this->param_ = param; - } - virtual void Forward(const OpContext &ctx, - const std::vector &in_data, - const std::vector &req, - const std::vector &out_data, - const std::vector &aux_args) { - using namespace mshadow; - using namespace mshadow::expr; - CHECK_EQ(in_data.size(), 1U); - CHECK_EQ(out_data.size(), 1U); - Stream *s = ctx.get_stream(); - Tensor data = in_data[sparsereg::kData].FlatTo2D(s); - Tensor out = out_data[sparsereg::kOut].FlatTo2D(s); - Assign(out, req[sparsereg::kData], F(data)); - } - - virtual void Backward(const OpContext &ctx, - const std::vector &out_grad, - const std::vector &in_data, - const std::vector &out_data, - const std::vector &req, - const std::vector &in_grad, - const std::vector &aux_args) { - using namespace mshadow; - using namespace mshadow::expr; - Stream *s = ctx.get_stream(); - Tensor grad_in = in_grad[sparsereg::kData].FlatTo2D(s); - Tensor data_in = in_data[sparsereg::kData].FlatTo2D(s); - Tensor grad_out = out_grad[sparsereg::kOut].FlatTo2D(s); - Tensor moving_avg = aux_args[sparsereg::kMovingAvg].get(s); - Tensor avg = ctx.requested[sparsereg::kTempSpace].get_space( - mshadow::Shape1(moving_avg.shape_[0]), s); - avg = sumall_except_dim<1>(data_in); - avg /= data_in.shape_[0]; - moving_avg = param_.momentum * moving_avg + (1 - param_.momentum) * avg; - Assign(grad_in, req[sparsereg::kData], grad_out + param_.penalty * - (-param_.sparseness_target / broadcast<1>(moving_avg, data_in.shape_) + - ((1 - param_.sparseness_target) / (1 - broadcast<1>(moving_avg, data_in.shape_))))); - } - - private: - IdentityAttachKLSparseRegParam param_; -}; // class IdentityAttachKLSparseRegOp - -template -Operator *CreateOp(IdentityAttachKLSparseRegParam param); - -#if DMLC_USE_CXX11 -class IdentityAttachKLSparseRegProp : public OperatorProperty { - public: - void Init(const std::vector >& kwargs) override { - param_.Init(kwargs); - } - - std::map GetParams() const override { - return param_.__DICT__(); - } - - bool InferShape(std::vector *in_shape, - std::vector *out_shape, - std::vector *aux_shape) const override { - using namespace mshadow; - CHECK_EQ(in_shape->size(), 1U); - const TShape &dshape = in_shape->at(sparsereg::kData); - if (dshape.ndim() == 0) return false; - out_shape->clear(); - out_shape->push_back(dshape); - aux_shape->clear(); - aux_shape->push_back(Shape1(dshape[1])); - return true; - } - - OperatorProperty* Copy() const override { - auto ptr = new IdentityAttachKLSparseRegProp(); - ptr->param_ = param_; - return ptr; - } - - std::string TypeString() const override { - return "IdentityAttachKLSparseReg"; - } - - std::vector DeclareBackwardDependency( - const std::vector &out_grad, - const std::vector &in_data, - const std::vector &out_data) const override { - return {out_grad[sparsereg::kOut], in_data[sparsereg::kData]}; - } - - std::vector > ForwardInplaceOption( - const std::vector &in_data, - const std::vector &out_data) const override { - return {{in_data[sparsereg::kData], out_data[sparsereg::kOut]}}; - } - - std::vector > BackwardInplaceOption( - const std::vector &out_grad, - const std::vector &in_data, - const std::vector &out_data, - const std::vector &in_grad) const { - return { {out_grad[sparsereg::kOut], in_grad[sparsereg::kData]} }; - } - - std::vector ListAuxiliaryStates() const override { - return {"moving_avg"}; - } - - std::vector BackwardResource( - const std::vector &in_shape) const override { - return {ResourceRequest::kTempSpace}; - } - - Operator* CreateOperator(Context ctx) const override; - - private: - IdentityAttachKLSparseRegParam param_; -}; // class IdentityAttachKLSparseRegProperty - -#endif // DMLC_USE_CXX11 -} // namespace op -} // namespace mxnet - -#endif // MXNET_OPERATOR_IDENTITY_ATTACH_KL_SPARSE_REG_INL_H_ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +/*! + * \file sparse_reg-inl.h + * \brief +*/ +#ifndef MXNET_OPERATOR_IDENTITY_ATTACH_KL_SPARSE_REG_INL_H_ +#define MXNET_OPERATOR_IDENTITY_ATTACH_KL_SPARSE_REG_INL_H_ +#include +#include +#include +#include +#include +#include +#include +#include "./mshadow_op.h" +#include "./operator_common.h" + +namespace mxnet { +namespace op { + +namespace sparsereg { +enum IdentityAttachKLSparseRegOpInputs {kData}; +enum IdentityAttachKLSparseRegOpOutputs {kOut}; +enum IdentityAttachKLSparseRegOpAuxiliary {kMovingAvg}; +enum IdentityAttachKLSparseRegBackResource {kTempSpace}; +} // namespace sparsereg + +struct IdentityAttachKLSparseRegParam : public dmlc::Parameter { + float penalty; + float sparseness_target; + float momentum; + DMLC_DECLARE_PARAMETER(IdentityAttachKLSparseRegParam) { + DMLC_DECLARE_FIELD(sparseness_target).set_default(0.1) + .set_range(0, 1) + .describe("The sparseness target"); + DMLC_DECLARE_FIELD(penalty).set_default(0.001) + .describe("The tradeoff parameter for the sparseness penalty"); + DMLC_DECLARE_FIELD(momentum).set_default(0.9) + .set_range(0, 1) + .describe("The momentum for running average"); + } +}; // struct IdentityAttachKLSparseRegParam + +// This op regularizes the output of a sigmoid activation function. +// In forward, it simply copies the input. +// In backward, it attaches sparseness penalty to the gradient. +// The regularization is based on the KL divergence of mean activation and target. +// More details: P11 of https://www.cs.toronto.edu/~hinton/absps/guideTR.pdf +// Please make sure that it is only paired with sigmoid activation, otherwise NaN may occur. +template +class IdentityAttachKLSparseRegOp : public Operator { + public: + explicit IdentityAttachKLSparseRegOp(IdentityAttachKLSparseRegParam param) { + this->param_ = param; + } + virtual void Forward(const OpContext &ctx, + const std::vector &in_data, + const std::vector &req, + const std::vector &out_data, + const std::vector &aux_args) { + using namespace mshadow; + using namespace mshadow::expr; + CHECK_EQ(in_data.size(), 1U); + CHECK_EQ(out_data.size(), 1U); + Stream *s = ctx.get_stream(); + Tensor data = in_data[sparsereg::kData].FlatTo2D(s); + Tensor out = out_data[sparsereg::kOut].FlatTo2D(s); + Assign(out, req[sparsereg::kData], F(data)); + } + + virtual void Backward(const OpContext &ctx, + const std::vector &out_grad, + const std::vector &in_data, + const std::vector &out_data, + const std::vector &req, + const std::vector &in_grad, + const std::vector &aux_args) { + using namespace mshadow; + using namespace mshadow::expr; + Stream *s = ctx.get_stream(); + Tensor grad_in = in_grad[sparsereg::kData].FlatTo2D(s); + Tensor data_in = in_data[sparsereg::kData].FlatTo2D(s); + Tensor grad_out = out_grad[sparsereg::kOut].FlatTo2D(s); + Tensor moving_avg = aux_args[sparsereg::kMovingAvg].get(s); + Tensor avg = ctx.requested[sparsereg::kTempSpace].get_space( + mshadow::Shape1(moving_avg.shape_[0]), s); + avg = sumall_except_dim<1>(data_in); + avg /= data_in.shape_[0]; + moving_avg = param_.momentum * moving_avg + (1 - param_.momentum) * avg; + Assign(grad_in, req[sparsereg::kData], grad_out + param_.penalty * + (-param_.sparseness_target / broadcast<1>(moving_avg, data_in.shape_) + + ((1 - param_.sparseness_target) / (1 - broadcast<1>(moving_avg, data_in.shape_))))); + } + + private: + IdentityAttachKLSparseRegParam param_; +}; // class IdentityAttachKLSparseRegOp + +template +Operator *CreateOp(IdentityAttachKLSparseRegParam param); + +#if DMLC_USE_CXX11 +class IdentityAttachKLSparseRegProp : public OperatorProperty { + public: + void Init(const std::vector >& kwargs) override { + param_.Init(kwargs); + } + + std::map GetParams() const override { + return param_.__DICT__(); + } + + bool InferShape(std::vector *in_shape, + std::vector *out_shape, + std::vector *aux_shape) const override { + using namespace mshadow; + CHECK_EQ(in_shape->size(), 1U); + const TShape &dshape = in_shape->at(sparsereg::kData); + if (dshape.ndim() == 0) return false; + out_shape->clear(); + out_shape->push_back(dshape); + aux_shape->clear(); + aux_shape->push_back(Shape1(dshape[1])); + return true; + } + + OperatorProperty* Copy() const override { + auto ptr = new IdentityAttachKLSparseRegProp(); + ptr->param_ = param_; + return ptr; + } + + std::string TypeString() const override { + return "IdentityAttachKLSparseReg"; + } + + std::vector DeclareBackwardDependency( + const std::vector &out_grad, + const std::vector &in_data, + const std::vector &out_data) const override { + return {out_grad[sparsereg::kOut], in_data[sparsereg::kData]}; + } + + std::vector > ForwardInplaceOption( + const std::vector &in_data, + const std::vector &out_data) const override { + return {{in_data[sparsereg::kData], out_data[sparsereg::kOut]}}; + } + + std::vector > BackwardInplaceOption( + const std::vector &out_grad, + const std::vector &in_data, + const std::vector &out_data, + const std::vector &in_grad) const override { + return { {out_grad[sparsereg::kOut], in_grad[sparsereg::kData]} }; + } + + std::vector ListAuxiliaryStates() const override { + return {"moving_avg"}; + } + + std::vector BackwardResource( + const std::vector &in_shape) const override { + return {ResourceRequest::kTempSpace}; + } + + Operator* CreateOperator(Context ctx) const override; + + private: + IdentityAttachKLSparseRegParam param_; +}; // class IdentityAttachKLSparseRegProperty + +#endif // DMLC_USE_CXX11 +} // namespace op +} // namespace mxnet + +#endif // MXNET_OPERATOR_IDENTITY_ATTACH_KL_SPARSE_REG_INL_H_ diff --git a/src/operator/identity_attach_KL_sparse_reg.cc b/src/operator/identity_attach_KL_sparse_reg.cc index 51e67721032c..5e776774e00b 100644 --- a/src/operator/identity_attach_KL_sparse_reg.cc +++ b/src/operator/identity_attach_KL_sparse_reg.cc @@ -1,37 +1,55 @@ -/*! - * Copyright (c) 2015 by Contributors - * \file identity_attach_KL_sparse_reg.cc - * \brief\ -*/ -#include "./identity_attach_KL_sparse_reg-inl.h" -#include - -namespace mxnet { -namespace op { -template<> -Operator *CreateOp(IdentityAttachKLSparseRegParam param) { - return new IdentityAttachKLSparseRegOp(param); -} - -Operator *IdentityAttachKLSparseRegProp::CreateOperator(Context ctx) const { - DO_BIND_DISPATCH(CreateOp, param_); -} - -DMLC_REGISTER_PARAMETER(IdentityAttachKLSparseRegParam); - -MXNET_REGISTER_OP_PROPERTY(IdentityAttachKLSparseReg, IdentityAttachKLSparseRegProp) -.describe("Apply a sparse regularization to the output a sigmoid activation function.") -.add_argument("data", "NDArray-or-Symbol", "Input data.") -.add_arguments(IdentityAttachKLSparseRegParam::__FIELDS__()); - -NNVM_REGISTER_OP(IdentityAttachKLSparseReg) -.set_attr("FSetInputVarAttrOnCompose", - [](const nnvm::NodeAttrs& attrs, nnvm::NodePtr var, const int index) { - if (var->attrs.dict.find("__init__") != var->attrs.dict.end()) return; - if (index == 1) { - var->attrs.dict["__init__"] = "[\"zero\", {}]"; - } - }); -} // namespace op -} // namespace mxnet - +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +/*! + * \file identity_attach_KL_sparse_reg.cc + * \brief\ +*/ +#include "./identity_attach_KL_sparse_reg-inl.h" +#include + +namespace mxnet { +namespace op { +template<> +Operator *CreateOp(IdentityAttachKLSparseRegParam param) { + return new IdentityAttachKLSparseRegOp(param); +} + +Operator *IdentityAttachKLSparseRegProp::CreateOperator(Context ctx) const { + DO_BIND_DISPATCH(CreateOp, param_); +} + +DMLC_REGISTER_PARAMETER(IdentityAttachKLSparseRegParam); + +MXNET_REGISTER_OP_PROPERTY(IdentityAttachKLSparseReg, IdentityAttachKLSparseRegProp) +.describe("Apply a sparse regularization to the output a sigmoid activation function.") +.add_argument("data", "NDArray-or-Symbol", "Input data.") +.add_arguments(IdentityAttachKLSparseRegParam::__FIELDS__()); + +NNVM_REGISTER_OP(IdentityAttachKLSparseReg) +.set_attr("FSetInputVarAttrOnCompose", + [](const nnvm::NodeAttrs& attrs, nnvm::NodePtr var, const int index) { + if (var->attrs.dict.find("__init__") != var->attrs.dict.end()) return; + if (index == 1) { + var->attrs.dict["__init__"] = "[\"zero\", {}]"; + } + }); +} // namespace op +} // namespace mxnet + diff --git a/src/operator/identity_attach_KL_sparse_reg.cu b/src/operator/identity_attach_KL_sparse_reg.cu index 6188fb9d954f..0a11fb167399 100644 --- a/src/operator/identity_attach_KL_sparse_reg.cu +++ b/src/operator/identity_attach_KL_sparse_reg.cu @@ -1,16 +1,34 @@ -/*! - * Copyright (c) 2015 by Contributors - * \file identity_attach_KL_sparse_reg.cu - * \brief -*/ -#include "./identity_attach_KL_sparse_reg-inl.h" - -namespace mxnet { -namespace op { -template<> -Operator *CreateOp(IdentityAttachKLSparseRegParam param) { - return new IdentityAttachKLSparseRegOp(param); -} - -} // namespace op -} // namespace mxnet +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +/*! + * \file identity_attach_KL_sparse_reg.cu + * \brief +*/ +#include "./identity_attach_KL_sparse_reg-inl.h" + +namespace mxnet { +namespace op { +template<> +Operator *CreateOp(IdentityAttachKLSparseRegParam param) { + return new IdentityAttachKLSparseRegOp(param); +} + +} // namespace op +} // namespace mxnet diff --git a/src/operator/instance_norm-inl.h b/src/operator/instance_norm-inl.h index 4a4f515ed601..6e78f7628a11 100644 --- a/src/operator/instance_norm-inl.h +++ b/src/operator/instance_norm-inl.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file instance_norm-inl.h * \brief Reproducing paper Instance Normalization: The Missing Ingredient for * Fast Stylization, D. Ulyanov, A. Vedaldi, V. Lempitsky, 2016 diff --git a/src/operator/instance_norm.cc b/src/operator/instance_norm.cc index bf3285a7a9d0..0666b4bd0303 100644 --- a/src/operator/instance_norm.cc +++ b/src/operator/instance_norm.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file instance_norm.cc * \brief * \author Sebastian Bodenstein @@ -18,10 +36,6 @@ Operator* CreateOp(InstanceNormParam param, int dtype) { Operator* InstanceNormProp::CreateOperatorEx(Context ctx, std::vector* in_shape, std::vector* in_type) const { - std::vector out_shape, aux_shape; - std::vector out_type, aux_type; - CHECK(InferType(in_type, &out_type, &aux_type)); - CHECK(InferShape(in_shape, &out_shape, &aux_shape)); DO_BIND_DISPATCH(CreateOp, param_, (*in_type)[0]); } diff --git a/src/operator/instance_norm.cu b/src/operator/instance_norm.cu index 096008463c4a..9f8cbea797ed 100644 --- a/src/operator/instance_norm.cu +++ b/src/operator/instance_norm.cu @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file instance_norm.cu * \brief * \author Sebastian Bodenstein diff --git a/src/operator/l2_normalization-inl.h b/src/operator/l2_normalization-inl.h index a49c8362645d..c1f17acbbce1 100644 --- a/src/operator/l2_normalization-inl.h +++ b/src/operator/l2_normalization-inl.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2016 by Contributors * \file l2_normalization_op-inl.h * \brief instance l2 Normalization op */ diff --git a/src/operator/l2_normalization.cc b/src/operator/l2_normalization.cc index 8a4112d8db35..6995a0d1e440 100644 --- a/src/operator/l2_normalization.cc +++ b/src/operator/l2_normalization.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file l2_normalization.cc * \brief l2 normalization operator */ diff --git a/src/operator/l2_normalization.cu b/src/operator/l2_normalization.cu index d7bab3586a27..ae76278559a8 100644 --- a/src/operator/l2_normalization.cu +++ b/src/operator/l2_normalization.cu @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file l2_normalization.cu * \brief l2 normalization operator */ diff --git a/src/operator/leaky_relu-inl.h b/src/operator/leaky_relu-inl.h index b0a5c0e53300..828930a0e405 100644 --- a/src/operator/leaky_relu-inl.h +++ b/src/operator/leaky_relu-inl.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file leaky_relu-inl.h * \brief leaky relu family operator * \author Bing Xu diff --git a/src/operator/leaky_relu.cc b/src/operator/leaky_relu.cc index 25fe17edb5ee..da58bd94bd57 100644 --- a/src/operator/leaky_relu.cc +++ b/src/operator/leaky_relu.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file leaky_relu.cc * \brief * \author Bing Xu @@ -24,7 +42,7 @@ DMLC_REGISTER_PARAMETER(LeakyReLUParam); MXNET_REGISTER_OP_PROPERTY(LeakyReLU, LeakyReLUProp) .describe(R"code(Applies Leaky rectified linear unit activation element-wise to the input. -Leaky ReLUs attempt to fix the "dying ReLU" problem by allowing a small `slope` +Leaky ReLUs attempt to fix the "dying ReLU" problem by allowing a small `slope` when the input is negative and has a slope of one when input is positive. The following modified ReLU Activation functions are supported: diff --git a/src/operator/leaky_relu.cu b/src/operator/leaky_relu.cu index c9af119a96ed..b9b3a7b73f9c 100644 --- a/src/operator/leaky_relu.cu +++ b/src/operator/leaky_relu.cu @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file leaky_relu.cc * \brief * \author Bing Xu diff --git a/src/operator/linalg.h b/src/operator/linalg.h new file mode 100644 index 000000000000..9284a5825d2c --- /dev/null +++ b/src/operator/linalg.h @@ -0,0 +1,118 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +/*! + * \file linalg.h + * \brief Unified tensor interface for advanced linear algebra functions + * (specifically BLAS3/LAPACK) from within mxnet. + */ +#ifndef MXNET_OPERATOR_LINALG_H_ +#define MXNET_OPERATOR_LINALG_H_ + +#include +#include "./c_lapack_api.h" +using namespace mshadow; + +// The purpose of this header is to expose the interfaces of the advanced +// linear algebra functions without clutter by the implementations. In contrast +// to the implementations in linalg_inline.h, no macros are used to generate +// similar functions that just differ by name/type in order to improve readability. +// +// Guidelines for extensions: +// For any type of computation the following should be provided at minimum: +// - 1 templated function supporting cpu/gpu float/double in non-batch mode +// - 1 templated function supporting cpu/gpu float/double in batch mode +// Naming conventions: +// - linalg_() +// - linalg_batch_() +// Signatures of CPU/GPU versions should be equivalent whenever possible including +// that a stream is supplied to the cpu-versions as (optional) last argument. +// The batched versions all work on tensors with one more dimension as the +// non-batched ones and the first/highest dimension iterates over the elements +// within the batch. + +//////////////////////////////// GEMM //////////////////////////////////////////// + +// CPU/GPU-versions of BLAS3 function "gemm". Please refer to the BLAS3-documentation +// for further information about the function and its parameters. +// Note that this is C = gemm(A,B,C), so C is input and output parameter. +template +void linalg_gemm(const Tensor& A, const Tensor& B, + const Tensor& C, DType alpha, DType beta, + bool tA, bool tB, Stream *s = 0); + +template +void linalg_batch_gemm(const Tensor& A, const Tensor& B, + const Tensor& C, DType alpha, DType beta, + bool tA, bool tB, Stream *s = 0); + +//////////////////////////////// TRSM //////////////////////////////////////////// + +// CPU/GPU-versions of BLAS3 function "trsm". Please refer to the BLAS3-documentation +// for further information about the function and its parameters. +// Note that this is B = trsm(A,B), so B is input and output parameter. +template +void linalg_trsm(const Tensor& A, const Tensor& B, + DType alpha, bool rightside, bool lower, bool transpose, Stream *s = 0); + +template +inline void linalg_batch_trsm(const Tensor& A, const Tensor& B, + DType alpha, bool rightside, bool lower, bool transpose, Stream *s = 0); + +//////////////////////////////// TRMM //////////////////////////////////////////// + +// CPU/GPU-versions of BLAS3 function "trmm". Please refer to the BLAS3-documentation +// for further information about the function and its parameters. +// Note that this is B = trmm(A,B), so B is input and output parameter. + +template +void linalg_trmm(const Tensor& A, const Tensor& B, + DType alpha, bool rightside, bool lower, bool transpose, Stream *s = 0); + +template +void linalg_batch_trmm(const Tensor& A, const Tensor& B, + DType alpha, bool rightside, bool lower, bool transpose, Stream *s = 0); + +//////////////////////////////// POTRF //////////////////////////////////////////// + +// CPU/GPU-versions of LAPACK function "potrf". Please refer to the LAPACK-documentation +// for further information about the function and its parameters. +// Note that this is A = potrf(A), so A is input and output parameter. + +template +void linalg_potrf(const Tensor& A, bool lower, Stream *s = 0); + +template +void linalg_batch_potrf(const Tensor& A, bool lower, Stream *s = 0); + +//////////////////////////////// POTRI //////////////////////////////////////////// + +// CPU/GPU-versions of LAPACK function "potri". Please refer to the LAPACK-documentation +// for further information about the function and its parameters. +// Note that this is A = potri(A), so A is input and output parameter. + +template +void linalg_potri(const Tensor& A, bool lower, Stream *s = 0); + +template +void linalg_batch_potri(const Tensor& A, bool lower, Stream *s = 0); + +#include "linalg_impl.h" + +#endif // MXNET_OPERATOR_LINALG_H_ diff --git a/src/operator/linalg_impl.h b/src/operator/linalg_impl.h new file mode 100644 index 000000000000..affa7941640b --- /dev/null +++ b/src/operator/linalg_impl.h @@ -0,0 +1,508 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +/*! + * \file linalg.h + * \brief Implementation of unified tensor interface for advanced linear algebra functions + * (specifically BLAS3/LAPACK) from within mxnet. + */ +#ifndef MXNET_OPERATOR_LINALG_IMPL_H_ +#define MXNET_OPERATOR_LINALG_IMPL_H_ + +#include + +// Convenience functions. +inline void linalg_check_batch_size(int A, int B, int C) { + CHECK_EQ(A, B) << "Inconsistent batch size between arguments to linear algebra operator"; + CHECK_EQ(A, C) << "Inconsistent batch size between arguments to linear algebra operator"; + CHECK_GT(A, 0) << "Zero batch size for arguments to linear algebra operator"; +} + +//////////////////////////////// GEMM //////////////////////////////////////////// + +// CPU/GPU-versions of BLAS3 function "gemm". Please refer to the BLAS3-documentation +// for further information about the function and its parameters. +// Note that this is C = gemm(A,B,C), so C is input and output parameter. + +template +inline void check_gemm(const Tensor& A, const Tensor& B, + const Tensor& C, DType alpha, DType beta, bool tA, bool tB) { + // Any checking that helps user debug potential problems. + CHECK_EQ((tA ? A.size(1) : A.size(0)), C.size(0)) + << "Non compatible matrix dimensions between inputs A and C for gemm"; + CHECK_EQ((tB ? B.size(0) : B.size(1)), C.size(1)) + << "Non compatible matrix dimensions between inputs B and C for gemm"; + CHECK_EQ((tA ? A.size(0) : A.size(1)), (tB ? B.size(1) : B.size(0))) + << "Non compatible matrix dimensions between inputs A and B for gemm"; +} + +#define LINALG_CPU_GEMM(fname, DType) \ +template<> inline \ +void linalg_gemm(const Tensor& A, const Tensor& B, \ + const Tensor& C, DType alpha, DType beta, \ + bool tA, bool tB, Stream *s) { \ + check_gemm(A, B, C, alpha, beta, tA, tB); \ + cblas_##fname(CblasRowMajor, (tA ? CblasTrans : CblasNoTrans), (tB ? CblasTrans : CblasNoTrans), \ + C.size(0), C.size(1), (tA ? A.size(0) : A.size(1)), alpha, \ + A.dptr_, A.stride_, B.dptr_, B.stride_, beta, C.dptr_, C.stride_); \ +} +LINALG_CPU_GEMM(sgemm, float) +LINALG_CPU_GEMM(dgemm, double) + +#define LINALG_CPU_BATCH_GEMM(DType) \ +template<> inline \ +void linalg_batch_gemm(const Tensor& A, const Tensor& B, \ + const Tensor& C, DType alpha, DType beta, \ + bool tA, bool tB, Stream *s) { \ + linalg_check_batch_size(A.size(0), B.size(0), C.size(0)); \ + for (index_t i = 0; i < A.size(0); ++i) { \ + linalg_gemm(A[i], B[i], C[i], alpha, beta, tA, tB); \ + } \ +} +LINALG_CPU_BATCH_GEMM(float) +LINALG_CPU_BATCH_GEMM(double) + +#ifdef __CUDACC__ + +template +__global__ void linalgCollectBatchOffsetsGPU(DType *a[], DType* b, int stride, int N) { + for (int i = blockIdx.x * blockDim.x + threadIdx.x; i < N; i += blockDim.x * gridDim.x) { + a[i] = b + i * stride; + } +} + +// cublas col-major processing accounted for by switching first two operands + +#define LINALG_GPU_GEMM(fname, DType) \ +template<> inline \ +void linalg_gemm(const Tensor& A, const Tensor& B, \ + const Tensor& C, DType alpha, DType beta, \ + bool tA, bool tB, Stream *s) { \ + using namespace mxnet; \ + using mshadow::gpu; \ + CHECK_NOTNULL(s); \ + check_gemm(A, B, C, alpha, beta, tA, tB); \ + CUBLAS_CALL(cublas##fname(Stream::GetBlasHandle(s), \ + (tB ? CUBLAS_OP_T : CUBLAS_OP_N), \ + (tA ? CUBLAS_OP_T : CUBLAS_OP_N), \ + C.size(1), C.size(0), (tB ? B.size(1) : B.size(0)), \ + &alpha, B.dptr_, B.stride_, A.dptr_, A.stride_, \ + &beta, C.dptr_, C.stride_)) \ +} +LINALG_GPU_GEMM(Sgemm, float) +LINALG_GPU_GEMM(Dgemm, double) + +#define LINALG_GPU_BATCH_GEMM(fname, DType) \ +template<> inline \ +void linalg_batch_gemm(const Tensor& A, const Tensor& B, \ + const Tensor& C, DType alpha, DType beta, \ + bool tA, bool tB, Stream *s) { \ + using namespace mxnet; \ + using mshadow::gpu; \ + CHECK_NOTNULL(s); \ + linalg_check_batch_size(A.size(0), B.size(0), C.size(0)); \ + check_gemm(A[0], B[0], C[0], alpha, beta, tA, tB); \ + Storage::Handle offsetsA, offsetsB, offsetsC; \ + offsetsA = Storage::Get()->Alloc(sizeof(DType*)*A.size(0), Context::GPU()); \ + offsetsB = Storage::Get()->Alloc(sizeof(DType*)*B.size(0), Context::GPU()); \ + offsetsC = Storage::Get()->Alloc(sizeof(DType*)*C.size(0), Context::GPU()); \ + using namespace mshadow::cuda; \ + int ngrid = std::min(kMaxGridNum, \ + static_cast((A.size(0) + kBaseThreadNum - 1) / kBaseThreadNum)); \ + linalgCollectBatchOffsetsGPU<<::GetStream(s)>>> \ + (static_cast(offsetsA.dptr), A.dptr_, A.size(1)*A.stride_, A.size(0)); \ + linalgCollectBatchOffsetsGPU<<::GetStream(s)>>> \ + (static_cast(offsetsB.dptr), B.dptr_, B.size(1)*B.stride_, B.size(0)); \ + linalgCollectBatchOffsetsGPU<<::GetStream(s)>>> \ + (static_cast(offsetsC.dptr), C.dptr_, C.size(1)*C.stride_, C.size(0)); \ + CUBLAS_CALL(cublas##fname(Stream::GetBlasHandle(s), \ + (tB ? CUBLAS_OP_T : CUBLAS_OP_N), \ + (tA ? CUBLAS_OP_T : CUBLAS_OP_N), \ + C.size(2), C.size(1), (tB ? B.size(2) : B.size(1)), \ + &alpha, static_cast(offsetsB.dptr), B.stride_, \ + static_cast(offsetsA.dptr), A.stride_, \ + &beta, static_cast(offsetsC.dptr), C.stride_, A.size(0))) \ + Storage::Get()->Free(offsetsA); \ + Storage::Get()->Free(offsetsB); \ + Storage::Get()->Free(offsetsC); \ +} +LINALG_GPU_BATCH_GEMM(SgemmBatched, float) +LINALG_GPU_BATCH_GEMM(DgemmBatched, double) + +#endif + +//////////////////////////////// TRSM //////////////////////////////////////////// + +// CPU/GPU-versions of BLAS3 function "trsm". Please refer to the BLAS3-documentation +// for further information about the function and its parameters. +// Note that this is B = trsm(A,B), so B is input and output parameter. + +template +inline void check_trsm(const Tensor& A, const Tensor& B, + DType alpha, bool rightside, bool lower, bool transpose) { + // Any checking that helps user debug potential problems. + CHECK_EQ(A.size(0), A.size(1)) + << "First input of trsm is not a square matrix."; + CHECK(!rightside || (B.size(1) == A.size(0))) + << "Non compatible matrix dimensions between inputs A and B for trsm"; + CHECK(rightside || (B.size(0) == A.size(1))) + << "Non compatible matrix dimensions between inputs A and B for trsm"; +} + +#define LINALG_CPU_TRSM(fname, DType) \ +template<> inline \ +void linalg_trsm(const Tensor& A, const Tensor& B, \ + DType alpha, bool rightside, bool lower, bool transpose, Stream *s) { \ + check_trsm(A, B, alpha, rightside, lower, transpose); \ + cblas_##fname(CblasRowMajor, (rightside ? CblasRight : CblasLeft), \ + (lower ? CblasLower : CblasUpper), (transpose ? CblasTrans : CblasNoTrans), \ + CblasNonUnit, B.size(0), B.size(1), alpha, A.dptr_, \ + A.stride_, B.dptr_, B.stride_); \ +} +LINALG_CPU_TRSM(strsm, float) +LINALG_CPU_TRSM(dtrsm, double) + +#define LINALG_CPU_BATCH_TRSM(DType) \ +template<> inline \ +void linalg_batch_trsm(const Tensor& A, const Tensor& B, \ + DType alpha, bool rightside, bool lower, bool transpose, Stream *s) { \ + linalg_check_batch_size(A.size(0), B.size(0), B.size(0)); \ + for (index_t i = 0; i < A.size(0); ++i) { \ + linalg_trsm(A[i], B[i], alpha, rightside, lower, transpose); \ + } \ +} +LINALG_CPU_BATCH_TRSM(float) +LINALG_CPU_BATCH_TRSM(double) + +#ifdef __CUDACC__ + +// cublas col-major processing accounted for by switching sides and fill mode + +#define LINALG_GPU_TRSM(fname, DType) \ +template<> inline \ +void linalg_trsm(const Tensor& A, const Tensor& B, \ + DType alpha, bool rightside, bool lower, bool transpose, Stream *s) { \ + using namespace mxnet; \ + using mshadow::gpu; \ + CHECK_NOTNULL(s); \ + check_trsm(A, B, alpha, rightside, lower, transpose); \ + CUBLAS_CALL(cublas##fname(Stream::GetBlasHandle(s), \ + (rightside ? CUBLAS_SIDE_LEFT : CUBLAS_SIDE_RIGHT), \ + (lower ? CUBLAS_FILL_MODE_UPPER : CUBLAS_FILL_MODE_LOWER), \ + (transpose ? CUBLAS_OP_T : CUBLAS_OP_N), \ + CUBLAS_DIAG_NON_UNIT, B.size(1), B.size(0), &alpha, \ + A.dptr_, A.stride_, B.dptr_, B.stride_)); \ +} +LINALG_GPU_TRSM(Strsm, float) +LINALG_GPU_TRSM(Dtrsm, double) + +#define LINALG_GPU_BATCH_TRSM(fname, DType) \ +template<> inline \ +void linalg_batch_trsm(const Tensor& A, const Tensor& B, \ + DType alpha, bool rightside, bool lower, bool transpose, Stream *s) { \ + using namespace mxnet; \ + using mshadow::gpu; \ + CHECK_NOTNULL(s); \ + linalg_check_batch_size(A.size(0), B.size(0), B.size(0)); \ + check_trsm(A[0], B[0], alpha, rightside, lower, transpose); \ + Storage::Handle offsetsA, offsetsB; \ + offsetsA = Storage::Get()->Alloc(sizeof(DType*)*A.size(0), Context::GPU()); \ + offsetsB = Storage::Get()->Alloc(sizeof(DType*)*B.size(0), Context::GPU()); \ + using namespace mshadow::cuda; \ + int ngrid = std::min(kMaxGridNum, \ + static_cast((A.size(0) + kBaseThreadNum - 1) / kBaseThreadNum)); \ + linalgCollectBatchOffsetsGPU<<::GetStream(s)>>> \ + (static_cast(offsetsA.dptr), A.dptr_, A.size(1)*A.stride_, A.size(0)); \ + linalgCollectBatchOffsetsGPU<<::GetStream(s)>>> \ + (static_cast(offsetsB.dptr), B.dptr_, B.size(1)*B.stride_, A.size(0)); \ + CUBLAS_CALL(cublas##fname(Stream::GetBlasHandle(s), \ + (rightside ? CUBLAS_SIDE_LEFT : CUBLAS_SIDE_RIGHT), \ + (lower ? CUBLAS_FILL_MODE_UPPER : CUBLAS_FILL_MODE_LOWER), \ + (transpose ? CUBLAS_OP_T : CUBLAS_OP_N), \ + CUBLAS_DIAG_NON_UNIT, B.size(2), B.size(1), &alpha, \ + static_cast(offsetsA.dptr), A.stride_, \ + static_cast(offsetsB.dptr), B.stride_, A.size(0))); \ + Storage::Get()->Free(offsetsA); \ + Storage::Get()->Free(offsetsB); \ +} +LINALG_GPU_BATCH_TRSM(StrsmBatched, float) +LINALG_GPU_BATCH_TRSM(DtrsmBatched, double) + +#endif + +//////////////////////////////// TRMM //////////////////////////////////////////// + +// CPU/GPU-versions of BLAS3 function "trmm". Please refer to the BLAS3-documentation +// for further information about the function and its parameters. +// Note that this is B = trmm(A,B), so B is input and output parameter. + +template +inline void check_trmm(const Tensor& A, const Tensor& B, + DType alpha, bool rightside, bool lower, bool transpose) { + // Any checking that helps user debug potential problems. + CHECK_EQ(A.size(0), A.size(1)) + << "First input of trmm is not a square matrix."; + CHECK(!rightside || (B.size(1) == A.size(0))) + << "Non compatible matrix dimensions between inputs A and B for trmm"; + CHECK(rightside || (B.size(0) == A.size(1))) + << "Non compatible matrix dimensions between inputs A and B for trmm"; +} + +#define LINALG_CPU_TRMM(fname, DType) \ +template<> inline \ +void linalg_trmm(const Tensor& A, const Tensor& B, \ + DType alpha, bool rightside, bool lower, bool transpose, Stream *s) { \ + check_trmm(A, B, alpha, rightside, lower, transpose); \ + cblas_##fname(CblasRowMajor, (rightside ? CblasRight : CblasLeft), \ + (lower ? CblasLower : CblasUpper), (transpose ? CblasTrans : CblasNoTrans), \ + CblasNonUnit, B.size(0), B.size(1), alpha, A.dptr_, \ + A.stride_, B.dptr_, B.stride_); \ +} +LINALG_CPU_TRMM(strmm, float) +LINALG_CPU_TRMM(dtrmm, double) + +#define LINALG_XPU_BATCH_TRMM(xpu, DType) \ +template<> inline \ +void linalg_batch_trmm(const Tensor& A, const Tensor& B, \ + DType alpha, bool rightside, bool lower, bool transpose, Stream *s) { \ + linalg_check_batch_size(A.size(0), B.size(0), B.size(0)); \ + for (index_t i = 0; i < A.size(0); ++i) { \ + linalg_trmm(A[i], B[i], alpha, rightside, lower, transpose, s); \ + } \ +} +LINALG_XPU_BATCH_TRMM(cpu, float) +LINALG_XPU_BATCH_TRMM(cpu, double) + +#ifdef __CUDACC__ + +// cublas col-major processing accounted for by switching sides and fill mode +// doing in-place computation by supplying B as second and third matrix +#define LINALG_GPU_TRMM(fname, DType) \ +template<> inline \ +void linalg_trmm(const Tensor& A, const Tensor& B, \ + DType alpha, bool rightside, bool lower, bool transpose, Stream *s) { \ + using namespace mxnet; \ + using mshadow::gpu; \ + CHECK_NOTNULL(s); \ + check_trmm(A, B, alpha, rightside, lower, transpose); \ + CUBLAS_CALL(cublas##fname(Stream::GetBlasHandle(s), \ + (rightside ? CUBLAS_SIDE_LEFT : CUBLAS_SIDE_RIGHT), \ + (lower ? CUBLAS_FILL_MODE_UPPER : CUBLAS_FILL_MODE_LOWER), \ + (transpose ? CUBLAS_OP_T : CUBLAS_OP_N), \ + CUBLAS_DIAG_NON_UNIT, B.size(0), B.size(1), &alpha, \ + A.dptr_, A.stride_, B.dptr_, B.stride_, \ + B.dptr_, B.stride_)); \ +} +LINALG_GPU_TRMM(Strmm, float) +LINALG_GPU_TRMM(Dtrmm, double) + +LINALG_XPU_BATCH_TRMM(gpu, float) +LINALG_XPU_BATCH_TRMM(gpu, double) + +#endif + +//////////////////////////////// POTRF //////////////////////////////////////////// + +// CPU/GPU-versions of LAPACK function "potrf". Please refer to the LAPACK-documentation +// for further information about the function and its parameters. +// Note that this is A = potrf(A), so A is input and output parameter. + +template +inline void check_potrf(const Tensor& A, bool lower) { + // Any checking that helps user debug potential problems. + CHECK_EQ(A.size(0), A.size(1)) + << "No square matrix as input to potrf."; +} + +#define LINALG_CPU_POTRF(fname, DType) \ +template<> inline \ +void linalg_potrf(const Tensor& A, bool lower, Stream *s) { \ + check_potrf(A, lower); \ + int ret(MXNET_LAPACK_##fname(MXNET_LAPACK_ROW_MAJOR, (lower ? 'L' : 'U'), A.size(0), \ + A.dptr_ , A.stride_)); \ + CHECK_EQ(ret, 0) << #fname << " failed in lapack on cpu."; \ +} +LINALG_CPU_POTRF(spotrf, float) +LINALG_CPU_POTRF(dpotrf, double) + +#define LINALG_CPU_BATCH_POTRF(DType) \ +template<> inline \ +void linalg_batch_potrf(const Tensor& A, bool lower, Stream *s) { \ + for (index_t i = 0; i < A.size(0); ++i) { \ + linalg_potrf(A[i], lower); \ + } \ +} +LINALG_CPU_BATCH_POTRF(float) +LINALG_CPU_BATCH_POTRF(double) + +#if MXNET_USE_CUSOLVER == 1 + +#define LINALG_GPU_BUFFSIZE_POTRF(fname, DType) \ +inline int linalg_potrf_buffsize(const Tensor& A, bool lower, Stream *s) { \ + using namespace mxnet; \ + using mshadow::gpu; \ + CHECK_NOTNULL(s); \ + int buffsize(0); \ + CUSOLVER_CALL(cusolver##fname(Stream::GetSolverHandle(s), \ + (lower ? CUBLAS_FILL_MODE_UPPER : CUBLAS_FILL_MODE_LOWER), \ + A.size(0), A.dptr_, A.stride_, &buffsize)); \ + return buffsize; \ +} +LINALG_GPU_BUFFSIZE_POTRF(DnSpotrf_bufferSize, float) +LINALG_GPU_BUFFSIZE_POTRF(DnDpotrf_bufferSize, double) + +#define LINALG_GPU_POTRF(fname, DType) \ +template<> inline \ +void linalg_potrf(const Tensor& A, bool lower, Stream *s) { \ + using namespace mxnet; \ + using mshadow::gpu; \ + CHECK_NOTNULL(s); \ + check_potrf(A, lower); \ + int buffsize(linalg_potrf_buffsize(A, lower, s)); \ + Storage::Handle buffer = Storage::Get()->Alloc(sizeof(DType)*buffsize, Context::GPU()); \ + Storage::Handle info = Storage::Get()->Alloc(sizeof(int), Context::GPU()); \ + CUSOLVER_CALL(cusolver##fname(Stream::GetSolverHandle(s), \ + (lower ? CUBLAS_FILL_MODE_UPPER : CUBLAS_FILL_MODE_LOWER), \ + A.size(0), A.dptr_, A.stride_, static_cast(buffer.dptr), buffsize, \ + static_cast(info.dptr))); \ + Storage::Get()->Free(buffer); \ + Storage::Get()->Free(info); \ +} +LINALG_GPU_POTRF(DnSpotrf, float) +LINALG_GPU_POTRF(DnDpotrf, double) + +#define LINALG_GPU_BATCH_POTRF(fname, DType) \ +template<> inline \ +void linalg_batch_potrf(const Tensor& A, bool lower, Stream *s) { \ + using namespace mxnet; \ + using mshadow::gpu; \ + CHECK_NOTNULL(s); \ + CHECK_GT(A.size(0), 0); \ + check_potrf(A[0], lower); \ + int buffsize(linalg_potrf_buffsize(A[0], lower, s)); \ + Storage::Handle buffer = Storage::Get()->Alloc(sizeof(DType)*buffsize, Context::GPU()); \ + Storage::Handle info = Storage::Get()->Alloc(sizeof(int), Context::GPU()); \ + for (mshadow::index_t i = 0; i < A.size(0); ++i) { \ + CUSOLVER_CALL(cusolver##fname(Stream::GetSolverHandle(s), \ + (lower ? CUBLAS_FILL_MODE_UPPER : CUBLAS_FILL_MODE_LOWER), \ + A[i].size(0), A[i].dptr_, A[i].stride_, \ + static_cast(buffer.dptr), buffsize, static_cast(info.dptr))); \ + } \ + Storage::Get()->Free(buffer); \ + Storage::Get()->Free(info); \ +} +LINALG_GPU_BATCH_POTRF(DnSpotrf, float) +LINALG_GPU_BATCH_POTRF(DnDpotrf, double) + +#endif + +//////////////////////////////// POTRI //////////////////////////////////////////// + +// CPU/GPU-versions of LAPACK function "potri". Please refer to the LAPACK-documentation +// for further information about the function and its parameters. +// Note that this is A = potri(A), so A is input and output parameter. + +template +inline void check_potri(const Tensor& A, bool lower) { + // Any checking that helps user debug potential problems. + CHECK_EQ(A.size(0), A.size(1)) << "No square matrix as input to potri."; +} + +#define LINALG_CPU_POTRI(fname, DType) \ +template<> inline \ +void linalg_potri(const Tensor& A, bool lower, Stream *s) { \ + check_potri(A, lower); \ + int ret(MXNET_LAPACK_##fname(MXNET_LAPACK_ROW_MAJOR, (lower ? 'L' : 'U'), A.size(0), \ + A.dptr_ , A.stride_)); \ + CHECK_EQ(ret, 0) << #fname << " failed in lapack on cpu."; \ +} +LINALG_CPU_POTRI(spotri, float) +LINALG_CPU_POTRI(dpotri, double) + +#define LINALG_CPU_BATCH_POTRI(DType) \ +template<> inline \ +void linalg_batch_potri(const Tensor& A, bool lower, Stream *s) { \ + for (index_t i = 0; i < A.size(0); ++i) { \ + linalg_potri(A[i], lower); \ + } \ +} +LINALG_CPU_BATCH_POTRI(float) +LINALG_CPU_BATCH_POTRI(double) + +#ifdef __CUDACC__ + +// Initializes multiple identity matrices on the same vector. +template +__global__ void linalgInitIdentityGPU(DType *a, int stride, int lda, int N) { + for (int i = blockIdx.x * blockDim.x + threadIdx.x; i < N; i += blockDim.x * gridDim.x) { + // index relative to the matrix. + int index(i % stride); + a[i] = (index / lda == index % lda ? DType(1.0) : DType(0)); + } +} + +// There is no direct support for potri in cuda. We emulate the function by two calls to trsm. +#define LINALG_GPU_POTRI(DType) \ +template<> inline \ +void linalg_potri(const Tensor& A, bool lower, Stream *s) { \ + using namespace mxnet; \ + CHECK_NOTNULL(s); \ + check_potri(A, lower); \ + Storage::Handle buffer = Storage::Get()->Alloc(sizeof(DType)*A.MSize(), Context::GPU()); \ + using namespace mshadow::cuda; \ + int ngrid = std::min(kMaxGridNum, \ + static_cast((A.MSize() + kBaseThreadNum - 1) / kBaseThreadNum)); \ + linalgInitIdentityGPU<<::GetStream(s)>>> \ + (static_cast(buffer.dptr), A.MSize(), A.stride_, A.MSize()); \ + Tensor B((DType *)buffer.dptr, A.shape_, A.stride_, s); \ + linalg_trsm(A, B, DType(1.0), false, lower, !lower, s); \ + linalg_trsm(A, B, DType(1.0), false, lower, lower, s); \ + Copy(A, B, s); \ + B.dptr_ = 0; \ + Storage::Get()->Free(buffer); \ +} +LINALG_GPU_POTRI(float) +LINALG_GPU_POTRI(double) + +#define LINALG_GPU_BATCH_POTRI(DType) \ +template<> inline \ +void linalg_batch_potri(const Tensor& A, bool lower, Stream *s) { \ + using namespace mxnet; \ + CHECK_NOTNULL(s); \ + CHECK_GT(A.size(0), 0); \ + check_potri(A[0], lower); \ + Storage::Handle buffer = Storage::Get()->Alloc(sizeof(DType)*A.MSize(), Context::GPU()); \ + using namespace mshadow::cuda; \ + int ngrid = std::min(kMaxGridNum, \ + static_cast((A.MSize() + kBaseThreadNum - 1) / kBaseThreadNum)); \ + linalgInitIdentityGPU<<::GetStream(s)>>> \ + (static_cast(buffer.dptr), A.size(1)*A.stride_, A.stride_, A.MSize()); \ + Tensor B((DType *)buffer.dptr, A.shape_, A.stride_, s); \ + linalg_batch_trsm(A, B, DType(1.0), false, lower, !lower, s); \ + linalg_batch_trsm(A, B, DType(1.0), false, lower, lower, s); \ + Copy(A, B, s); \ + B.dptr_ = 0; \ + Storage::Get()->Free(buffer); \ +} +LINALG_GPU_BATCH_POTRI(float) +LINALG_GPU_BATCH_POTRI(double) + +#endif + +#endif // MXNET_OPERATOR_LINALG_IMPL_H_ diff --git a/src/operator/loss_binary_op-inl.h b/src/operator/loss_binary_op-inl.h index a61cee7e3d3c..8add82725292 100644 --- a/src/operator/loss_binary_op-inl.h +++ b/src/operator/loss_binary_op-inl.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file loss_binary_op-inl.h * \brief Loss functions */ diff --git a/src/operator/loss_binary_op.cc b/src/operator/loss_binary_op.cc index 31f23fd1a234..d0a77946ffba 100644 --- a/src/operator/loss_binary_op.cc +++ b/src/operator/loss_binary_op.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file loss_binary_op.cc * \brief loss function that takes a data and label */ @@ -25,7 +43,7 @@ NNVM_REGISTER_OP(softmax_cross_entropy) .. math:: \text{CE(label, output)} = - \sum_i \text{label}_i \log(\text{output}_i) - Example:: +Example:: x = [[1, 2, 3], [11, 7, 5]] diff --git a/src/operator/loss_binary_op.cu b/src/operator/loss_binary_op.cu index 66700e7918b8..8694b9f2844f 100644 --- a/src/operator/loss_binary_op.cu +++ b/src/operator/loss_binary_op.cu @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file loss_binary_op.cu * \brief loss function that takes a data and label */ diff --git a/src/operator/lrn-inl.h b/src/operator/lrn-inl.h old mode 100755 new mode 100644 index 66be9ac7f4ed..a320a26bed30 --- a/src/operator/lrn-inl.h +++ b/src/operator/lrn-inl.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file lrn-inl.h * \brief * \author Bing Xu diff --git a/src/operator/lrn.cc b/src/operator/lrn.cc index e896e16b443a..46f4fca486b5 100644 --- a/src/operator/lrn.cc +++ b/src/operator/lrn.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file lrn.cc * \brief * \author Bing Xu @@ -28,10 +46,6 @@ Operator* CreateOp(LRNParam param, int dtype) { // DO_BIND_DISPATCH comes from operator_common.h Operator* LocalResponseNormProp::CreateOperatorEx(Context ctx, std::vector *in_shape, std::vector *in_type) const { - std::vector out_shape, aux_shape; - std::vector out_type, aux_type; - CHECK(InferType(in_type, &out_type, &aux_type)); - CHECK(InferShape(in_shape, &out_shape, &aux_shape)); DO_BIND_DISPATCH(CreateOp, param_, (*in_type)[0]); } @@ -42,14 +56,14 @@ MXNET_REGISTER_OP_PROPERTY(LRN, LocalResponseNormProp) .add_arguments(LRNParam::__FIELDS__()) .describe(R"code(Applies local response normalization to the input. -The local response normalization layer performs "lateral inhibition" by normalizing -over local input regions. +The local response normalization layer performs "lateral inhibition" by normalizing +over local input regions. If :math:`a_{x,y}^{i}` is the activity of a neuron computed by applying kernel :math:`i` at position -:math:`(x, y)` and then applying the ReLU nonlinearity, the response-normalized -activity :math:`b_{x,y}^{i}` is given by the expression: +:math:`(x, y)` and then applying the ReLU nonlinearity, the response-normalized +activity :math:`b_{x,y}^{i}` is given by the expression: -.. math:: +.. math:: b_{x,y}^{i} = \frac{a_{x,y}^{i}}{\Bigg({k + \alpha \sum_{j=max(0, i-\frac{n}{2})}^{min(N-1, i+\frac{n}{2})} (a_{x,y}^{j})^{2}}\Bigg)^{\beta}} where the sum runs over :math:`n` "adjacent" kernel maps at the same spatial position, and :math:`N` is the total diff --git a/src/operator/lrn.cu b/src/operator/lrn.cu old mode 100755 new mode 100644 index 681de80508c7..702f4b2fa92a --- a/src/operator/lrn.cu +++ b/src/operator/lrn.cu @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file lrn.cu * \brief * \author Bing Xu diff --git a/src/operator/make_loss-inl.h b/src/operator/make_loss-inl.h index 65af62732373..3f4a99373ca3 100644 --- a/src/operator/make_loss-inl.h +++ b/src/operator/make_loss-inl.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file make_loss-inl.h * \brief special layer for propagating loss */ diff --git a/src/operator/make_loss.cc b/src/operator/make_loss.cc index 1be93def5e87..748357d243f5 100644 --- a/src/operator/make_loss.cc +++ b/src/operator/make_loss.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file make_loss.cc * \brief special layer for propagating loss */ diff --git a/src/operator/make_loss.cu b/src/operator/make_loss.cu index 5f5fad6955fe..7f508500f58e 100644 --- a/src/operator/make_loss.cu +++ b/src/operator/make_loss.cu @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file make_loss.cu * \brief special layer for propagating loss */ diff --git a/src/operator/mkl/mkl_relu-inl.h b/src/operator/mkl/mkl_relu-inl.h index b77d18d0042c..8d7ab5e1e2db 100644 --- a/src/operator/mkl/mkl_relu-inl.h +++ b/src/operator/mkl/mkl_relu-inl.h @@ -100,7 +100,13 @@ class MKLReluOp : public Operator { Stream *s = ctx.get_stream(); Tensor data; Tensor out; - if (in_data[activation::kData].ndim() == 2) { + if (in_data[activation::kData].ndim() == 1) { + Shape<4> dshape = Shape4(in_data[activation::kData].shape_[0], 1, 1, 1); + data = mkl_experimental_direct_get_with_shape( + in_data[activation::kData], dshape, s); + out = mkl_experimental_direct_get_with_shape( + out_data[activation::kOut], dshape, s); + } else if (in_data[activation::kData].ndim() == 2) { Shape<4> dshape = Shape4(in_data[activation::kData].shape_[0], in_data[activation::kData].shape_[1], 1, 1); data = mkl_experimental_direct_get_with_shape( @@ -197,7 +203,15 @@ class MKLReluOp : public Operator { Tensor m_out_data; Tensor m_in_grad; - if (out_grad[activation::kOut].ndim() == 2) { + if (out_grad[activation::kOut].ndim() == 1) { + Shape<4> dshape = Shape4(out_grad[activation::kOut].shape_[0], 1, 1, 1); + m_out_grad = mkl_experimental_direct_get_with_shape( + out_grad[activation::kOut], dshape, s); + m_out_data = mkl_experimental_direct_get_with_shape( + out_data[activation::kOut], dshape, s); + m_in_grad = mkl_experimental_direct_get_with_shape( + in_grad[activation::kData], dshape, s); + } else if (out_grad[activation::kOut].ndim() == 2) { Shape<4> dshape = Shape4(out_grad[activation::kOut].shape_[0], out_grad[activation::kOut].shape_[1], 1, 1); m_out_grad = mkl_experimental_direct_get_with_shape( diff --git a/src/operator/mshadow_op.h b/src/operator/mshadow_op.h index 0a47db1a9b2b..f7815d2f8d4c 100644 --- a/src/operator/mshadow_op.h +++ b/src/operator/mshadow_op.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file mshadow_op.h * \brief * \author Bing Xu @@ -8,8 +26,13 @@ #define MXNET_OPERATOR_MSHADOW_OP_H_ #include +#include #include "special_functions-inl.h" +#ifdef __CUDACC__ +#include +#endif + namespace mxnet { namespace op { namespace mshadow_op { @@ -19,19 +42,21 @@ __constant__ const float PI = 3.14159265358979323846; const float PI = 3.14159265358979323846; using std::isnan; #endif +using std::enable_if; +using std::is_unsigned; /*! \brief identity Operation */ struct identity { template MSHADOW_XINLINE static DType Map(DType a) { - return DType(a); + return a; } }; struct identity_grad { template MSHADOW_XINLINE static DType Map(DType a) { - return DType(DType(1.0f)); + return DType(1.0f); } }; @@ -56,6 +81,20 @@ struct negation { } }; +struct reciprocal { + template + MSHADOW_XINLINE static DType Map(DType a) { + return DType(1.0f/a); + } +}; + +struct reciprocal_grad { + template + MSHADOW_XINLINE static DType Map(DType a) { + return DType(-(DType(1.0f) / (a * a))); + } +}; + /*! \brief sigmoid unit */ struct sigmoid { template @@ -131,13 +170,20 @@ struct tanh_grad { struct softrelu { template MSHADOW_XINLINE static DType Map(DType a) { - return DType(log1pf(expf(a))); + // Avoid overflow of exp for large inputs. + // Thresholds 20.0 is chosen such that softrelu(a) = a + // for a > 20 using floating precision. + if (a > DType(20.0)) { + return a; + } else { + return DType(log1pf(expf(a))); + } } }; struct softrelu_grad { template MSHADOW_XINLINE static DType Map(DType a) { - return DType(DType(1.0f) - expf(-a)); + return -DType(expm1f(-a)); } }; @@ -433,16 +479,23 @@ struct abs { /*! \brief used for generate element of sign */ struct sign { template - MSHADOW_XINLINE static DType Map(DType a) { + MSHADOW_XINLINE static typename enable_if::value, DType>::type + Map(DType a) { if (a < 0.0f) return DType(-DType(1.0f)); - if (a > 0.0f) return DType(DType(1.0f)); - return DType(DType(0.0f)); + if (a > 0.0f) return DType(1.0f); + return DType(0.0f); + } + template + MSHADOW_XINLINE static typename enable_if::value, DType>::type + Map(DType a) { + if (a > 0.0f) return DType(1.0f); + return DType(0.0f); } }; struct sign_grad { template MSHADOW_XINLINE static DType Map(DType a) { - return DType(DType(0.0f)); + return DType(0.0f); } }; /*! \brief used for generate element of power */ @@ -593,6 +646,14 @@ struct floor { } }; +/*! \brief used to round towards zero */ +struct trunc { + template + MSHADOW_XINLINE static DType Map(DType a) { + return DType(truncf(a)); + } +}; + /*! \brief used to round number to nearest integer */ struct rint { template @@ -656,6 +717,200 @@ struct rdiv_grad { } }; +struct mod { + template + MSHADOW_XINLINE static typename enable_if::value, DType>::type + Map(DType a, DType b) { + if (b == DType(0)) { + return DType(0); + } else if (b < DType(0)) { + if (a < DType(0)) { + return DType(-::fmod(-static_cast(a), -static_cast(b))); + } else { + return DType(::fmod(static_cast(a), -static_cast(b)) + + (::fmod(static_cast(a), -static_cast(b)) != DType(0) + ? b : DType(0))); + } + } else { + if (a < DType(0)) { + return DType(-::fmod(-static_cast(a), static_cast(b)) + + (::fmod(-static_cast(a), static_cast(b)) != DType(0) + ? b : DType(0))); + } else { + return DType(::fmod(static_cast(a), static_cast(b))); + } + } + } + template + MSHADOW_XINLINE static typename enable_if::value, DType>::type + Map(DType a, DType b) { + if (b == DType(0)) { + return DType(0); + } else { + return DType(::fmod(static_cast(a), static_cast(b))); + } + } +}; +#ifdef __CUDACC__ +template<> +MSHADOW_XINLINE mshadow::half::half2_t mod::Map + (mshadow::half::half2_t a, + mshadow::half::half2_t b) { + return a%b; +} +#endif + +struct mod_grad { + template + MSHADOW_XINLINE static DType Map(DType a, DType b) { + return DType(0); + } +}; +template<> +MSHADOW_XINLINE double mod_grad::Map(double a, double b) { + return 1.0f; +} +template<> +MSHADOW_XINLINE float mod_grad::Map(float a, float b) { + return 1.0f; +} +#ifdef __CUDACC__ +template<> +MSHADOW_XINLINE mshadow::half::half_t mod_grad::Map + (mshadow::half::half_t a, + mshadow::half::half_t b) { + return mshadow::half::half_t(1.0f); +} +template<> +MSHADOW_XINLINE mshadow::half::half2_t mod_grad::Map + (mshadow::half::half2_t a, + mshadow::half::half2_t b) { + mshadow::half::half2_t result = mshadow::half::half2_t(); +#if MSHADOW_CUDA_HALF2 + result.half2_ = ::__float2half2_rn(1.0f); +#else + result.half_t2[0] = mshadow::half::half_t(0.0f); + result.half_t2[1] = mshadow::half::half_t(1.0f); +#endif + return result; +} +#endif + +struct mod_rgrad { + template + MSHADOW_XINLINE static DType Map(DType a, DType b) { + return DType(0); + } +}; +template<> +MSHADOW_XINLINE double mod_rgrad::Map(double a, double b) { + return -::floor(a/b); +} +template<> +MSHADOW_XINLINE float mod_rgrad::Map(float a, float b) { + return -::floorf(a/b); +} +#ifdef __CUDACC__ +template<> +MSHADOW_XINLINE mshadow::half::half_t mod_rgrad::Map + (mshadow::half::half_t a, + mshadow::half::half_t b) { + return mshadow::half::half_t(-::floorf(static_cast(a/b))); +} +template<> +MSHADOW_XINLINE mshadow::half::half2_t mod_rgrad::Map + (mshadow::half::half2_t a, + mshadow::half::half2_t b) { +#if MSHADOW_CUDA_HALF2 + return mshadow::half::half2_t(__hneg2(::h2floor((a/b).half2_))); +#else + return mshadow::half::half2_t(mshadow::half::half_t(-::floorf( + static_cast(a.half_t2[0]/b.half_t2[0]))), + mshadow::half::half_t(-::floorf( + static_cast(a.half_t2[1]/b.half_t2[1])))); +#endif +} +#endif + +struct rmod { + template + MSHADOW_XINLINE static typename enable_if::value, DType>::type + Map(DType a, DType b) { + if (a == DType(0)) { + return DType(0); + } else if (a < DType(0)) { + if (b < DType(0)) { + return DType(-::fmod(-static_cast(b), -static_cast(a))); + } else { + return DType(::fmod(static_cast(b), -static_cast(a)) + + (::fmod(static_cast(b), -static_cast(a)) != DType(0) + ? a : DType(0))); + } + } else { + if (b < DType(0)) { + return DType(-::fmod(-static_cast(b), static_cast(a)) + + (::fmod(-static_cast(b), static_cast(a)) != DType(0) + ? a : DType(0))); + } else { + return DType(::fmod(static_cast(b), static_cast(a))); + } + } + } + template + MSHADOW_XINLINE static typename enable_if::value, DType>::type + Map(DType a, DType b) { + if (a == DType(0)) { + return DType(0); + } else { + return DType(::fmod(static_cast(b), static_cast(a))); + } + } +}; +#ifdef __CUDACC__ +template<> +MSHADOW_XINLINE mshadow::half::half2_t rmod::Map + (mshadow::half::half2_t a, + mshadow::half::half2_t b) { + return b%a; +} +#endif + +struct rmod_grad { + template + MSHADOW_XINLINE static DType Map(DType a, DType b) { + return DType(0); + } +}; +template<> +MSHADOW_XINLINE double rmod_grad::Map(double a, double b) { + return -::floor(b/a); +} +template<> +MSHADOW_XINLINE float rmod_grad::Map(float a, float b) { + return -::floorf(b/a); +} +#ifdef __CUDACC__ +template<> +MSHADOW_XINLINE mshadow::half::half_t rmod_grad::Map + (mshadow::half::half_t a, + mshadow::half::half_t b) { + return mshadow::half::half_t(-::floorf(static_cast(b/a))); +} +template<> +MSHADOW_XINLINE mshadow::half::half2_t rmod_grad::Map + (mshadow::half::half2_t a, + mshadow::half::half2_t b) { +#if MSHADOW_CUDA_HALF2 + return mshadow::half::half2_t(::__hneg2(::h2floor((b/a).half2_))); +#else + return mshadow::half::half2_t(mshadow::half::half_t(-::floorf( + static_cast(b.half_t2[0]/a.half_t2[0]))), + mshadow::half::half_t(-::floorf( + static_cast(b.half_t2[1]/a.half_t2[1])))); +#endif +} +#endif + struct clip { template MSHADOW_XINLINE static DType Map(DType x, DType bound) { @@ -727,7 +982,7 @@ MSHADOW_XINLINE double gammaln_grad::Map(double a) { /* Smooth L1 Loss is a loss specific for R-CNN franchise training * Smooth L1 Loss function - * f(x) = 0.5 * (sigma * x) ^ 2, x < 1 / sigma^2 + * f(x) = 0.5 * (sigma * x) ^ 2, |x| < 1 / sigma^2 * = |x| - 0.5 / sigma / sigma, otherwise * When sigma = 1, it is equivalent to Huber Loss evaluated at * delta = 1. @@ -750,7 +1005,7 @@ struct smooth_l1_loss { }; // struct smooth_l1_loss /* The derivative of smooth l1 loss is - * f'(x) = sigma^2 * x, x < 1 / sigma^2 + * f'(x) = sigma^2 * x, |x| < 1 / sigma^2 * = sign(x), otherwise */ struct smooth_l1_gradient { diff --git a/src/operator/mxnet_op.h b/src/operator/mxnet_op.h index 9b5dcfe3d3b1..0af7d026d9d5 100644 --- a/src/operator/mxnet_op.h +++ b/src/operator/mxnet_op.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2017 by Contributors * \file mxnet_op.h * \brief * \author Junyuan Xie diff --git a/src/operator/nn/im2col.cuh b/src/operator/nn/im2col.cuh index 786fd22f8c9b..edd5b0dcfb2f 100644 --- a/src/operator/nn/im2col.cuh +++ b/src/operator/nn/im2col.cuh @@ -1,34 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! ******************* BEGIN Caffe Copyright Notice and Disclaimer **************** * * COPYRIGHT - * + * * All contributions by the University of California: * Copyright (c) 2014-2017 The Regents of the University of California (Regents) * All rights reserved. - * + * * All other contributions: * Copyright (c) 2014-2017, the respective contributors * All rights reserved. - * + * * Caffe uses a shared copyright model: each contributor holds copyright over * their contributions to Caffe. The project versioning records all such * contribution and copyright details. If a contributor wants to further mark * their specific copyright on a particular contribution, they should indicate * their copyright solely in the commit message of the change when it is * committed. - * + * * LICENSE - * + * * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * + * modification, are permitted provided that the following conditions are met: + * * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. + * list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * + * and/or other materials provided with the distribution. + * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE @@ -39,9 +58,9 @@ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * + * * CONTRIBUTION AGREEMENT - * + * * By contributing to the BVLC/caffe repository through pull-request, comment, * or otherwise, the contributor releases their content to the * license and copyright terms herein. @@ -304,7 +323,7 @@ inline void im2col(mshadow::Stream* s, <<::GetStream(s)>>>( num_kernels, data_im, im_shape[2], im_shape[3], kernel_shape[0], kernel_shape[1], - pad[0], pad[1], stride[0], stride[1], dilation[0], dilation[1], + pad[0], pad[1], stride[0], stride[1], dilation[0], dilation[1], col_shape[1], col_shape[2], data_col); break; case 3: diff --git a/src/operator/nn/im2col.h b/src/operator/nn/im2col.h index 435d502da77e..621b2451a19e 100644 --- a/src/operator/nn/im2col.h +++ b/src/operator/nn/im2col.h @@ -1,3 +1,22 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! ******************* BEGIN Caffe Copyright Notice and Disclaimer **************** * @@ -48,7 +67,6 @@ * ***************** END Caffe Copyright Notice and Disclaimer ******************** * - * Copyright (c) 2017 by Contributors * \file im2col.h * \brief Function definitions of converting an image to * column matrix based on kernel, padding, and dilation. @@ -241,7 +259,7 @@ inline void im2col(mshadow::Stream* s, if (2 == kernel_shape.ndim()) { im2col_cpu(data_im, im_shape[1], im_shape[2], im_shape[3], kernel_shape[0], kernel_shape[1], pad[0], pad[1], - stride[0], stride[1], dilation[1], dilation[1], data_col); + stride[0], stride[1], dilation[0], dilation[1], data_col); } else { im2col_nd_core_cpu(data_im, true, im_shape, col_shape, kernel_shape, pad, stride, dilation, data_col); diff --git a/src/operator/nn/pool.cuh b/src/operator/nn/pool.cuh index 54fd3461d80f..0e9cff0c51e4 100644 --- a/src/operator/nn/pool.cuh +++ b/src/operator/nn/pool.cuh @@ -1,3 +1,22 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! ******************* BEGIN Caffe Copyright Notice and Disclaimer **************** * diff --git a/src/operator/nn/pool.h b/src/operator/nn/pool.h index 79accb5d521f..3bac86560407 100644 --- a/src/operator/nn/pool.h +++ b/src/operator/nn/pool.h @@ -1,3 +1,22 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! ******************* BEGIN Caffe Copyright Notice and Disclaimer **************** * @@ -48,7 +67,6 @@ * ***************** END Caffe Copyright Notice and Disclaimer ******************** * - * Copyright (c) 2017 by Contributors * \file pool.h * \brief Function definitions of pooling 1/2/3-D images. * We adopted looping 2-D image pixels from Caffe and extended it to 1-D and 3-D cases. diff --git a/src/operator/nn/softmax-inl.h b/src/operator/nn/softmax-inl.h index 11eeb5d23624..e1150b14f69d 100644 --- a/src/operator/nn/softmax-inl.h +++ b/src/operator/nn/softmax-inl.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2017 by Contributors * \file softmax-inl.h * \brief */ @@ -43,7 +61,7 @@ inline void Softmax(Stream *s, DType *in, DType *out, index_t sa = stride[axis]; #pragma omp parallel for - for (int i = 0; i < N; ++i) { + for (int i = 0; i < static_cast(N); ++i) { index_t base = unravel_dot(i, sshape, stride); DType mmax = in[base]; @@ -90,7 +108,7 @@ inline void SoftmaxGrad(Stream *s, DType *out, DType *ograd, index_t sa = stride[axis]; #pragma omp parallel for - for (int i = 0; i < N; ++i) { + for (int i = 0; i < static_cast(N); ++i) { index_t base = unravel_dot(i, sshape, stride); DType sum = DType(0); diff --git a/src/operator/nn/softmax.cc b/src/operator/nn/softmax.cc index f1f2078ddce1..58c1a051248e 100644 --- a/src/operator/nn/softmax.cc +++ b/src/operator/nn/softmax.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2017 by Contributors * \file softmax.cc * \brief CPU Implementation of softmax */ diff --git a/src/operator/nn/softmax.cu b/src/operator/nn/softmax.cu index 570f5bf15c88..d5a843ddc07b 100644 --- a/src/operator/nn/softmax.cu +++ b/src/operator/nn/softmax.cu @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2017 by Contributors * \file softmax.cc * \brief CPU Implementation of softmax */ diff --git a/src/operator/nnpack/nnpack_convolution-inl.h b/src/operator/nnpack/nnpack_convolution-inl.h index 03f4a0bcfdb4..4a1342688969 100644 --- a/src/operator/nnpack/nnpack_convolution-inl.h +++ b/src/operator/nnpack/nnpack_convolution-inl.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2016 by Contributors * \file nnpack_convolution-inl.h * \brief * \author Carwin diff --git a/src/operator/nnpack/nnpack_fully_connected-inl.h b/src/operator/nnpack/nnpack_fully_connected-inl.h index 2d87db1e1aec..f85ddd89c702 100644 --- a/src/operator/nnpack/nnpack_fully_connected-inl.h +++ b/src/operator/nnpack/nnpack_fully_connected-inl.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2016 by Contributors * \file nnpack_fully_connected-inl.h * \brief * \author Wei Wu diff --git a/src/operator/nnpack/nnpack_pooling-inl.h b/src/operator/nnpack/nnpack_pooling-inl.h index 0df070de812c..968ead16204d 100644 --- a/src/operator/nnpack/nnpack_pooling-inl.h +++ b/src/operator/nnpack/nnpack_pooling-inl.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2016 by Contributors * \file nnpack_pooling-inl.h * \brief * \author Wei Wu diff --git a/src/operator/nnpack/nnpack_util.cc b/src/operator/nnpack/nnpack_util.cc index 8004bb1063dc..b873b591fa57 100644 --- a/src/operator/nnpack/nnpack_util.cc +++ b/src/operator/nnpack/nnpack_util.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2016 by Contributors * \file nnpack_util.cc * \brief * \author Wei Wu diff --git a/src/operator/nnpack/nnpack_util.h b/src/operator/nnpack/nnpack_util.h index 280c6ffce875..cde1880257a3 100644 --- a/src/operator/nnpack/nnpack_util.h +++ b/src/operator/nnpack/nnpack_util.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2016 by Contributors * \file nnpack_util.h * \brief * \author Carwin diff --git a/src/operator/operator.cc b/src/operator/operator.cc index e476c583e668..9117c1c1288a 100644 --- a/src/operator/operator.cc +++ b/src/operator/operator.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file operator.cc * \brief operator module of mxnet */ diff --git a/src/operator/operator_common.h b/src/operator/operator_common.h old mode 100755 new mode 100644 index a43d092bceb6..2d46bd3230ce --- a/src/operator/operator_common.h +++ b/src/operator/operator_common.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file operator_common.h * \brief common internal header of most operators * this header includes utility functions operator can use diff --git a/src/operator/operator_util.cc b/src/operator/operator_util.cc index 84a19d6b4b15..25fa209a026c 100644 --- a/src/operator/operator_util.cc +++ b/src/operator/operator_util.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file operator_util.cc * Implementation of operator util. */ diff --git a/src/operator/optimizer_op-inl.h b/src/operator/optimizer_op-inl.h old mode 100755 new mode 100644 index 96f480bf8bc7..70759b15251a --- a/src/operator/optimizer_op-inl.h +++ b/src/operator/optimizer_op-inl.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2016 by Contributors * \file optimizer_op-inl.h * \brief Optimizer operators * \author Junyuan Xie @@ -153,6 +171,110 @@ inline void SGDMomUpdate(const nnvm::NodeAttrs& attrs, }); } +template +inline bool MP_SGD_InferType(const nnvm::NodeAttrs& attrs, + std::vector *in_attrs, + std::vector *out_attrs) { + CHECK_EQ(in_attrs->size(), static_cast(total_in)) << " in operator " << attrs.name; + CHECK_EQ(out_attrs->size(), static_cast(n_out)) << " in operator " << attrs.name; + for (int i = n_in; i < total_in; ++i) { + TYPE_ASSIGN_CHECK(*in_attrs, i, mshadow::kFloat32); + } + return ElemwiseAttr( + attrs, in_attrs, out_attrs, -1); +} + +struct MP_SGDKernel { + template + MSHADOW_XINLINE static void Map(int i, DType* out_data, const DType* weight_data, + const DType* grad_data, float* weight32, const float param_clip_gradient, + const float param_lr, const float param_wd, const float param_rescale_grad, + const OpReqType req) { + if (param_clip_gradient >= 0.0f) { + float w = weight32[i]; + w = (1.f - param_lr*param_wd)*w - + (param_lr) * mshadow_op::clip::Map(param_rescale_grad*static_cast(grad_data[i]), + param_clip_gradient); + weight32[i] = w; + KERNEL_ASSIGN(out_data[i], req, (DType)w); + } else { + float w = weight32[i]; + w = (1.f-param_lr*param_wd)*w + - (param_lr*param_rescale_grad)*static_cast(grad_data[i]); + weight32[i] = w; + KERNEL_ASSIGN(out_data[i], req, (DType)w); + } + } +}; + +template +inline void MP_SGDUpdate(const nnvm::NodeAttrs& attrs, + const OpContext &ctx, + const std::vector &inputs, + const std::vector &req, + const std::vector &outputs) { + using namespace mxnet_op; + const SGDParam& param = nnvm::get(attrs.parsed); + Stream* s = ctx.get_stream(); + MSHADOW_REAL_TYPE_SWITCH(inputs[0].type_flag_, DType, { + Tensor weight = inputs[0].FlatTo2D(s); + Tensor grad = inputs[1].FlatTo2D(s); + Tensor weight32 = inputs[2].FlatTo2D(s); + Tensor out = outputs[0].FlatTo2D(s); + Kernel::Launch(s, weight.shape_.Size(), out.dptr_, weight.dptr_, + grad.dptr_, weight32.dptr_, param.clip_gradient, + param.lr, param.wd, + param.rescale_grad, req[0]); + }); +} + +struct MP_SGDMomKernel { + template + MSHADOW_XINLINE static void Map(int i, DType* out_data, float* mom_data, + const DType* weight_data, const DType* grad_data, float* weight32, + const float param_clip_gradient, const float param_momentum, const float param_lr, + const float param_wd, const float param_rescale_grad, const OpReqType req) { + float w = weight32[i]; + float mom = mom_data[i]; + if (param_clip_gradient >= 0.0f) { + mom = param_momentum*mom + - param_lr*param_wd*w + - param_lr + *mshadow_op::clip::Map(param_rescale_grad*static_cast(grad_data[i]), + param_clip_gradient); + } else { + mom = param_momentum*mom + - param_lr*param_wd*w + - param_lr*param_rescale_grad*static_cast(grad_data[i]); + } + mom_data[i] = mom; + w = w + mom; + weight32[i] = w; + KERNEL_ASSIGN(out_data[i], req, w); + } +}; + +template +inline void MP_SGDMomUpdate(const nnvm::NodeAttrs& attrs, + const OpContext &ctx, + const std::vector &inputs, + const std::vector &req, + const std::vector &outputs) { + using namespace mxnet_op; + SGDMomParam param = nnvm::get(attrs.parsed); + Stream* s = ctx.get_stream(); + MSHADOW_REAL_TYPE_SWITCH(inputs[0].type_flag_, DType, { + Tensor weight = inputs[0].FlatTo2D(s); + Tensor grad = inputs[1].FlatTo2D(s); + Tensor mom = inputs[2].FlatTo2D(s); + Tensor weight32 = inputs[3].FlatTo2D(s); + Tensor out = outputs[0].FlatTo2D(s); + Kernel::Launch(s, weight.shape_.Size(), out.dptr_, mom.dptr_, + weight.dptr_, grad.dptr_, weight32.dptr_, param.clip_gradient, param.momentum, + param.lr, param.wd, param.rescale_grad, req[0]); + }); +} + struct AdamParam : public dmlc::Parameter { float lr; float beta1; @@ -336,7 +458,7 @@ struct RMSPropParam : public dmlc::Parameter { DMLC_DECLARE_FIELD(lr) .describe("Learning rate"); DMLC_DECLARE_FIELD(gamma1).set_default(0.95f) - .describe("The dacay rate of momentum estimates."); + .describe("The decay rate of momentum estimates."); DMLC_DECLARE_FIELD(epsilon).set_default(1e-8f) .describe("A small constant for numerical stability."); DMLC_DECLARE_FIELD(wd).set_default(0.0f) diff --git a/src/operator/optimizer_op.cc b/src/operator/optimizer_op.cc index 9ec6aacaafac..b26c333edaef 100644 --- a/src/operator/optimizer_op.cc +++ b/src/operator/optimizer_op.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2016 by Contributors * \file optimizer_op.cc * \brief Optimizer operators * \author Junyuan Xie @@ -68,6 +86,40 @@ Where the parameter ``momentum`` is the decay rate of momentum estimates at each .add_argument("mom", "NDArray-or-Symbol", "Momentum") .add_arguments(SGDMomParam::__FIELDS__()); +NNVM_REGISTER_OP(mp_sgd_update) +.describe("Updater function for multi-precision sgd optimizer") +.set_num_inputs(3) +.set_num_outputs(1) +.set_attr_parser(ParamParser) +.set_attr("FInferShape", ElemwiseShape<3, 1>) +.set_attr("FInferType", MP_SGD_InferType<2, 1, 3>) +.set_attr("FCompute", MP_SGDUpdate) +.set_attr("FMutateInputs", + [](const nnvm::NodeAttrs& attrs) { + return std::vector{2}; + }) +.add_argument("weight", "NDArray-or-Symbol", "Weight") +.add_argument("grad", "NDArray-or-Symbol", "gradient") +.add_argument("weight32", "NDArray-or-Symbol", "Weight32") +.add_arguments(SGDParam::__FIELDS__()); + +NNVM_REGISTER_OP(mp_sgd_mom_update) +.describe("Updater function for multi-precision sgd optimizer") +.set_num_inputs(4) +.set_num_outputs(1) +.set_attr_parser(ParamParser) +.set_attr("FInferShape", ElemwiseShape<4, 1>) +.set_attr("FInferType", MP_SGD_InferType<2, 1, 4>) +.set_attr("FMutateInputs", + [](const nnvm::NodeAttrs& attrs) { + return std::vector{2, 3}; + }) +.set_attr("FCompute", MP_SGDMomUpdate) +.add_argument("weight", "NDArray-or-Symbol", "Weight") +.add_argument("grad", "NDArray-or-Symbol", "Gradient") +.add_argument("mom", "NDArray-or-Symbol", "Momentum") +.add_argument("weight32", "NDArray-or-Symbol", "Weight32") +.add_arguments(SGDMomParam::__FIELDS__()); NNVM_REGISTER_OP(adam_update) .describe(R"code(Update function for Adam optimizer. Adam is seen as a generalization diff --git a/src/operator/optimizer_op.cu b/src/operator/optimizer_op.cu index 2b2667ec317b..0e74e303dbc9 100644 --- a/src/operator/optimizer_op.cu +++ b/src/operator/optimizer_op.cu @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2016 by Contributors * \file optimizer_op.cu * \brief Optimizer operators * \author Junyuan Xie @@ -15,6 +33,12 @@ NNVM_REGISTER_OP(sgd_update) NNVM_REGISTER_OP(sgd_mom_update) .set_attr("FCompute", SGDMomUpdate); +NNVM_REGISTER_OP(mp_sgd_update) +.set_attr("FCompute", MP_SGDUpdate); + +NNVM_REGISTER_OP(mp_sgd_mom_update) +.set_attr("FCompute", MP_SGDMomUpdate); + NNVM_REGISTER_OP(adam_update) .set_attr("FCompute", AdamUpdate); diff --git a/src/operator/pad-inl.h b/src/operator/pad-inl.h index e6e6b7b30327..80f9e0bf92ac 100644 --- a/src/operator/pad-inl.h +++ b/src/operator/pad-inl.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2016 by Contributors * \file pad-inl.h * \brief * \author Sebastian Bodenstien diff --git a/src/operator/pad.cc b/src/operator/pad.cc index febd5207ff81..468629a43672 100644 --- a/src/operator/pad.cc +++ b/src/operator/pad.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file pad.cc * \brief * \author Sebastian Bodenstein @@ -121,12 +139,18 @@ void single_image_constant(const Tensor &dst, const int pad_t = pad[4]; const int pad_l = pad[6]; int c, w, h; + // using these vars to avoid casting overhead each loop iteration + const int dst0 = dst.size(0); + const int dst1 = dst.size(1); + const int dst2 = dst.size(2); + const int src1 = src.size(1); + const int src2 = src.size(2); #pragma omp parallel for private(c, w, h) - for (c = 0; c < dst.size(0); ++c) { - for (h = 0; h < dst.size(1); ++h) { - for (w = 0; w < dst.size(2); ++w) { - if ((w < pad_l) || (h < pad_t) || (h >= (src.size(1) + pad_t)) || - (w >= (src.size(2) + pad_l))) { + for (c = 0; c < dst0; ++c) { + for (h = 0; h < dst1; ++h) { + for (w = 0; w < dst2; ++w) { + if ((w < pad_l) || (h < pad_t) || (h >= (src1 + pad_t)) || + (w >= (src2 + pad_l))) { dst[c][h][w] = constant_value; } else { dst[c][h][w] = src[c][h - pad_t][w - pad_l]; @@ -142,11 +166,15 @@ void single_image_constant_grad(const Tensor &in_grad, mxnet::TShape pad) { const int pad_t = pad[4]; const int pad_l = pad[6]; + + const int in_grad0 = in_grad.size(0); + const int in_grad1 = in_grad.size(1); + const int in_grad2 = in_grad.size(2); int c, h, w; #pragma omp parallel for private(c, w, h) - for (c = 0; c < in_grad.size(0); ++c) { - for (h = 0; h < in_grad.size(1); ++h) { - for (w = 0; w < in_grad.size(2); ++w) { + for (c = 0; c < in_grad0; ++c) { + for (h = 0; h < in_grad1; ++h) { + for (w = 0; w < in_grad2; ++w) { in_grad[c][h][w] += out_grad[c][h + pad_t][w + pad_l]; } } @@ -404,15 +432,24 @@ void single_image_constant(const Tensor &dst, const int pad_f = pad[4]; const int pad_t = pad[6]; const int pad_l = pad[8]; + + const int dst0 = dst.size(0); + const int dst1 = dst.size(1); + const int dst2 = dst.size(2); + const int dst3 = dst.size(3); + const int src1 = src.size(1); + const int src2 = src.size(2); + const int src3 = src.size(3); + int c, d, w, h; #pragma omp parallel for private(c, d, w, h) - for (c = 0; c < dst.size(0); ++c) { - for (d = 0; d < dst.size(1); ++d) { - for (h = 0; h < dst.size(2); ++h) { - for (w = 0; w < dst.size(3); ++w) { + for (c = 0; c < dst0; ++c) { + for (d = 0; d < dst1; ++d) { + for (h = 0; h < dst2; ++h) { + for (w = 0; w < dst3; ++w) { if ((w < pad_l) || (h < pad_t) || (d < pad_f) || - (d >= (src.size(1) + pad_f)) || (h >= (src.size(2) + pad_t)) || - (w >= (src.size(3) + pad_l))) { + (d >= (src1 + pad_f)) || (h >= (src2 + pad_t)) || + (w >= (src3 + pad_l))) { dst[c][d][h][w] = constant_value; } else { dst[c][d][h][w] = src[c][d - pad_f][h - pad_t][w - pad_l]; @@ -430,12 +467,16 @@ void single_image_constant_grad(const Tensor &in_grad, const int pad_f = pad[4]; const int pad_t = pad[6]; const int pad_l = pad[8]; + const int in_grad0 = in_grad.size(0); + const int in_grad1 = in_grad.size(1); + const int in_grad2 = in_grad.size(2); + const int in_grad3 = in_grad.size(3); int c, d, w, h; #pragma omp parallel for private(c, d, w, h) - for (c = 0; c < in_grad.size(0); ++c) { - for (d = 0; d < in_grad.size(1); ++d) { - for (h = 0; h < in_grad.size(2); ++h) { - for (w = 0; w < in_grad.size(3); ++w) { + for (c = 0; c < in_grad0; ++c) { + for (d = 0; d < in_grad1; ++d) { + for (h = 0; h < in_grad2; ++h) { + for (w = 0; w < in_grad3; ++w) { in_grad[c][d][h][w] += out_grad[c][d + pad_f][h + pad_t][w + pad_l]; } } @@ -634,10 +675,6 @@ Operator *CreateOp(PadParam param, int dtype) { // DO_BIND_DISPATCH comes from operator_common.h Operator *PadProp::CreateOperatorEx(Context ctx, std::vector *in_shape, std::vector *in_type) const { - std::vector out_shape, aux_shape; - std::vector out_type, aux_type; - CHECK(InferType(in_type, &out_type, &aux_type)); - CHECK(InferShape(in_shape, &out_shape, &aux_shape)); DO_BIND_DISPATCH(CreateOp, param_, (*in_type)[0]); } @@ -701,7 +738,7 @@ Example:: [ 20. 20. 21. 22. 22.] [ 20. 20. 21. 22. 22.]]]] - pad(x, mode="constant", constant_value=0, pad_width=(0,0,0,0,2,2,1,1)) = + pad(x, mode="constant", constant_value=0, pad_width=(0,0,0,0,1,1,1,1)) = [[[[ 0. 0. 0. 0. 0.] [ 0. 1. 2. 3. 0.] diff --git a/src/operator/pad.cu b/src/operator/pad.cu index bf7265cfa342..98220b6c39ef 100644 --- a/src/operator/pad.cu +++ b/src/operator/pad.cu @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file pad.cu * \brief * \author Sebastian Bodenstein diff --git a/src/operator/pooling-inl.h b/src/operator/pooling-inl.h index 8156c3796539..fbc6981a7591 100644 --- a/src/operator/pooling-inl.h +++ b/src/operator/pooling-inl.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2017 by Contributors * \file pooling-inl.h * \brief * \author Bing Xu, Jun Wu diff --git a/src/operator/pooling.cc b/src/operator/pooling.cc index 7997f7ff39af..51dce873cd04 100644 --- a/src/operator/pooling.cc +++ b/src/operator/pooling.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2017 by Contributors * \file pooling.cc * \brief * \author Bing Xu, Jun Wu @@ -35,7 +53,6 @@ Operator *CreateOp(PoolingParam param, int dtype) { break; } } - LOG(INFO) << MKLPoolingOp::getName() << " Skip MKL optimization"; #endif #if MXNET_USE_NNPACK == 1 // NNPACK only support max-pooling with kernel = 2, stride = 2, pooling_convention @@ -70,10 +87,6 @@ Operator *CreateOp(PoolingParam param, int dtype) { // DO_BIND_DISPATCH comes from operator_common.h Operator* PoolingProp::CreateOperatorEx(Context ctx, std::vector *in_shape, std::vector *in_type) const { - std::vector out_shape, aux_shape; - std::vector out_type, aux_type; - CHECK(InferType(in_type, &out_type, &aux_type)); - CHECK(InferShape(in_shape, &out_shape, &aux_shape)); DO_BIND_DISPATCH(CreateOp, param_, (*in_type)[0]); } @@ -95,7 +108,7 @@ The shapes for 2-D pooling are out_height = f(height, kernel[0], pad[0], stride[0]) out_width = f(width, kernel[1], pad[1], stride[1]) -The defintion of *f* depends on ``pooling_convention``, which has two options: +The definition of *f* depends on ``pooling_convention``, which has two options: - **valid** (default):: diff --git a/src/operator/pooling.cu b/src/operator/pooling.cu index bc7716b946af..950f09956258 100644 --- a/src/operator/pooling.cu +++ b/src/operator/pooling.cu @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2017 by Contributors * \file pooling.cu * \brief * \author Bing Xu, Jun Wu diff --git a/src/operator/pooling_v1-inl.h b/src/operator/pooling_v1-inl.h index 0b9f7adce62f..e541298ed2ab 100644 --- a/src/operator/pooling_v1-inl.h +++ b/src/operator/pooling_v1-inl.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file pooling_v1-inl.h * \brief * \author Bing Xu diff --git a/src/operator/pooling_v1.cc b/src/operator/pooling_v1.cc index 31be2acece9b..40de7457520f 100644 --- a/src/operator/pooling_v1.cc +++ b/src/operator/pooling_v1.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file pooling_v1.cc * \brief * \author Bing Xu @@ -56,7 +74,7 @@ The shapes for 2-D pooling is out_height = f(height, kernel[0], pad[0], stride[0]) out_width = f(width, kernel[1], pad[1], stride[1]) -The defintion of *f* depends on ``pooling_convention``, which has two options: +The definition of *f* depends on ``pooling_convention``, which has two options: - **valid** (default):: diff --git a/src/operator/pooling_v1.cu b/src/operator/pooling_v1.cu index 99aebbc6446c..4db22c18420d 100644 --- a/src/operator/pooling_v1.cu +++ b/src/operator/pooling_v1.cu @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file pooling_v1.cu * \brief * \author Bing Xu diff --git a/src/operator/tensor/multisample_op.cc b/src/operator/random/multisample_op.cc similarity index 80% rename from src/operator/tensor/multisample_op.cc rename to src/operator/random/multisample_op.cc index 52db07f09081..f1264e5dc3cc 100644 --- a/src/operator/tensor/multisample_op.cc +++ b/src/operator/random/multisample_op.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2017 by Contributors * \file multisample_op.cc * \brief CPU-implementation of multi-sampling operators */ @@ -138,8 +156,8 @@ DMLC_REGISTER_PARAMETER(MultiSampleParam); }) \ .set_attr("FCompute", MultiSampleOpForward) \ .set_attr("FGradient", MakeZeroGradNodes) \ - .add_arguments(MultiSampleParam::__FIELDS__()) \ - .add_argument(input_name_1, "NDArray-or-Symbol", input_desc_1) + .add_argument(input_name_1, "NDArray-or-Symbol", input_desc_1) \ + .add_arguments(MultiSampleParam::__FIELDS__()) #define MXNET_OPERATOR_REGISTER_SAMPLING1(distr, sampler, input_name, input_desc, \ description) \ @@ -153,24 +171,26 @@ DMLC_REGISTER_PARAMETER(MultiSampleParam); .add_argument(input_name_2, "NDArray-or-Symbol", input_desc_2); inline std::string uniform_desc() { - return std::string(R"code(Concurrent sampling from multiple + return std::string(R"code(Concurrent sampling from multiple uniform distributions on the intervals given by *[low,high)*. + The parameters of the distributions are provided as input arrays. Let *[s]* be the shape of the input arrays, *n* be the dimension of *[s]*, *[t]* be the shape specified as the parameter of the operator, and *m* be the dimension -of *[t]*. Then the output will be a *(n+m)*-dimensional array with shape *[s]x[t]*. For any -valid *n*-dimensional index *i* with respect to the input arrays, *output[i]* will be -an *m*-dimensional array that holds randomly drawn samples from the distribution which -is parameterized by the input values at index *i*. If the shape parameter of the +of *[t]*. Then the output will be a *(n+m)*-dimensional array with shape *[s]x[t]*. + +For any valid *n*-dimensional index *i* with respect to the input arrays, *output[i]* +will be an *m*-dimensional array that holds randomly drawn samples from the distribution +which is parameterized by the input values at index *i*. If the shape parameter of the operator is not set, then one sample will be drawn per distribution and the output array has the same shape as the input arrays. Examples:: low = [ 0.0, 2.5 ] - high = [ 1.0, 3.7 ] + high = [ 1.0, 3.7 ] - // Draw a single sample for each distribution + // Draw a single sample for each distribution sample_uniform(low, high) = [ 0.40451524, 3.18687344] // Draw a vector containing two samples for each distribution @@ -180,15 +200,17 @@ Examples:: } inline std::string normal_desc() { - return std::string(R"code(Concurrent sampling from multiple + return std::string(R"code(Concurrent sampling from multiple normal distributions with parameters *mu* (mean) and *sigma* (standard deviation). + The parameters of the distributions are provided as input arrays. Let *[s]* be the shape of the input arrays, *n* be the dimension of *[s]*, *[t]* be the shape specified as the parameter of the operator, and *m* be the dimension -of *[t]*. Then the output will be a *(n+m)*-dimensional array with shape *[s]x[t]*. For any -valid *n*-dimensional index *i* with respect to the input arrays, *output[i]* will be -an *m*-dimensional array that holds randomly drawn samples from the distribution which -is parameterized by the input values at index *i*. If the shape parameter of the +of *[t]*. Then the output will be a *(n+m)*-dimensional array with shape *[s]x[t]*. + +For any valid *n*-dimensional index *i* with respect to the input arrays, *output[i]* +will be an *m*-dimensional array that holds randomly drawn samples from the distribution +which is parameterized by the input values at index *i*. If the shape parameter of the operator is not set, then one sample will be drawn per distribution and the output array has the same shape as the input arrays. @@ -207,15 +229,17 @@ Examples:: } inline std::string gamma_desc() { - return std::string(R"code(Concurrent sampling from multiple + return std::string(R"code(Concurrent sampling from multiple gamma distributions with parameters *alpha* (shape) and *beta* (scale). + The parameters of the distributions are provided as input arrays. Let *[s]* be the shape of the input arrays, *n* be the dimension of *[s]*, *[t]* be the shape specified as the parameter of the operator, and *m* be the dimension -of *[t]*. Then the output will be a *(n+m)*-dimensional array with shape *[s]x[t]*. For any -valid *n*-dimensional index *i* with respect to the input arrays, *output[i]* will be -an *m*-dimensional array that holds randomly drawn samples from the distribution which -is parameterized by the input values at index *i*. If the shape parameter of the +of *[t]*. Then the output will be a *(n+m)*-dimensional array with shape *[s]x[t]*. + +For any valid *n*-dimensional index *i* with respect to the input arrays, *output[i]* +will be an *m*-dimensional array that holds randomly drawn samples from the distribution +which is parameterized by the input values at index *i*. If the shape parameter of the operator is not set, then one sample will be drawn per distribution and the output array has the same shape as the input arrays. @@ -234,15 +258,17 @@ Examples:: } inline std::string exponential_desc() { - return std::string(R"code(Concurrent sampling from multiple + return std::string(R"code(Concurrent sampling from multiple exponential distributions with parameters lambda (rate). + The parameters of the distributions are provided as an input array. Let *[s]* be the shape of the input array, *n* be the dimension of *[s]*, *[t]* be the shape specified as the parameter of the operator, and *m* be the dimension -of *[t]*. Then the output will be a *(n+m)*-dimensional array with shape *[s]x[t]*. For any -valid *n*-dimensional index *i* with respect to the input array, *output[i]* will be -an *m*-dimensional array that holds randomly drawn samples from the distribution which -is parameterized by the input value at index *i*. If the shape parameter of the +of *[t]*. Then the output will be a *(n+m)*-dimensional array with shape *[s]x[t]*. + +For any valid *n*-dimensional index *i* with respect to the input array, *output[i]* +will be an *m*-dimensional array that holds randomly drawn samples from the distribution +which is parameterized by the input value at index *i*. If the shape parameter of the operator is not set, then one sample will be drawn per distribution and the output array has the same shape as the input array. @@ -260,17 +286,20 @@ Examples:: } inline std::string poisson_desc() { - return std::string(R"code(Concurrent sampling from multiple + return std::string(R"code(Concurrent sampling from multiple Poisson distributions with parameters lambda (rate). + The parameters of the distributions are provided as an input array. Let *[s]* be the shape of the input array, *n* be the dimension of *[s]*, *[t]* be the shape specified as the parameter of the operator, and *m* be the dimension -of *[t]*. Then the output will be a *(n+m)*-dimensional array with shape *[s]x[t]*. For any -valid *n*-dimensional index *i* with respect to the input array, *output[i]* will be -an *m*-dimensional array that holds randomly drawn samples from the distribution which -is parameterized by the input value at index *i*. If the shape parameter of the +of *[t]*. Then the output will be a *(n+m)*-dimensional array with shape *[s]x[t]*. + +For any valid *n*-dimensional index *i* with respect to the input array, *output[i]* +will be an *m*-dimensional array that holds randomly drawn samples from the distribution +which is parameterized by the input value at index *i*. If the shape parameter of the operator is not set, then one sample will be drawn per distribution and the output array has the same shape as the input array. + Samples will always be returned as a floating point data type. Examples:: @@ -287,17 +316,20 @@ Examples:: } inline std::string negative_binomial_desc() { - return std::string(R"code(Concurrent sampling from multiple + return std::string(R"code(Concurrent sampling from multiple negative binomial distributions with parameters *k* (failure limit) and *p* (failure probability). + The parameters of the distributions are provided as input arrays. Let *[s]* be the shape of the input arrays, *n* be the dimension of *[s]*, *[t]* be the shape specified as the parameter of the operator, and *m* be the dimension -of *[t]*. Then the output will be a *(n+m)*-dimensional array with shape *[s]x[t]*. For any -valid *n*-dimensional index *i* with respect to the input arrays, *output[i]* will be -an *m*-dimensional array that holds randomly drawn samples from the distribution which -is parameterized by the input values at index *i*. If the shape parameter of the +of *[t]*. Then the output will be a *(n+m)*-dimensional array with shape *[s]x[t]*. + +For any valid *n*-dimensional index *i* with respect to the input arrays, *output[i]* +will be an *m*-dimensional array that holds randomly drawn samples from the distribution +which is parameterized by the input values at index *i*. If the shape parameter of the operator is not set, then one sample will be drawn per distribution and the output array has the same shape as the input arrays. + Samples will always be returned as a floating point data type. Examples:: @@ -315,17 +347,20 @@ Examples:: } inline std::string generalized_negative_binomial_desc() { - return std::string(R"code(Concurrent sampling from multiple + return std::string(R"code(Concurrent sampling from multiple generalized negative binomial distributions with parameters *mu* (mean) and *alpha* (dispersion). + The parameters of the distributions are provided as input arrays. Let *[s]* be the shape of the input arrays, *n* be the dimension of *[s]*, *[t]* be the shape specified as the parameter of the operator, and *m* be the dimension -of *[t]*. Then the output will be a *(n+m)*-dimensional array with shape *[s]x[t]*. For any -valid *n*-dimensional index *i* with respect to the input arrays, *output[i]* will be -an *m*-dimensional array that holds randomly drawn samples from the distribution which -is parameterized by the input values at index *i*. If the shape parameter of the +of *[t]*. Then the output will be a *(n+m)*-dimensional array with shape *[s]x[t]*. + +For any valid *n*-dimensional index *i* with respect to the input arrays, *output[i]* +will be an *m*-dimensional array that holds randomly drawn samples from the distribution +which is parameterized by the input values at index *i*. If the shape parameter of the operator is not set, then one sample will be drawn per distribution and the output array has the same shape as the input arrays. + Samples will always be returned as a floating point data type. Examples:: diff --git a/src/operator/tensor/multisample_op.h b/src/operator/random/multisample_op.h similarity index 87% rename from src/operator/tensor/multisample_op.h rename to src/operator/random/multisample_op.h index 6e84d1dab368..748b3ba0ccad 100644 --- a/src/operator/tensor/multisample_op.h +++ b/src/operator/random/multisample_op.h @@ -1,10 +1,28 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2017 by Contributors * \file sampling_op.h * \brief Function definitions of operators for sampling from multiple distributions */ -#ifndef MXNET_OPERATOR_TENSOR_MULTISAMPLE_OP_H_ -#define MXNET_OPERATOR_TENSOR_MULTISAMPLE_OP_H_ +#ifndef MXNET_OPERATOR_RANDOM_MULTISAMPLE_OP_H_ +#define MXNET_OPERATOR_RANDOM_MULTISAMPLE_OP_H_ #include #include @@ -174,4 +192,4 @@ void MultiSampleOpForward(const nnvm::NodeAttrs& attrs, } // namespace op } // namespace mxnet -#endif // MXNET_OPERATOR_TENSOR_MULTISAMPLE_OP_H_ +#endif // MXNET_OPERATOR_RANDOM_MULTISAMPLE_OP_H_ diff --git a/src/operator/random/sample_multinomial_op.cc b/src/operator/random/sample_multinomial_op.cc new file mode 100644 index 000000000000..b358b3b2b4f2 --- /dev/null +++ b/src/operator/random/sample_multinomial_op.cc @@ -0,0 +1,111 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +/*! + * \file sample_multinomial_op.h + * \brief Operator for sampling from multinomial distributions + */ +#include "./sample_multinomial_op.h" + +namespace mxnet { +namespace op { + +DMLC_REGISTER_PARAMETER(SampleMultinomialParam); + + +NNVM_REGISTER_OP(sample_multinomial) +.describe(R"code(Concurrent sampling from multiple multinomial distributions. + +*data* is an *n* dimensional array whose last dimension has length *k*, where +*k* is the number of possible outcomes of each multinomial distribution. This +operator will draw *shape* samples from each distribution. If shape is empty +one sample will be drawn from each distribution. + +If *get_prob* is true, a second array containing log likelihood of the drawn +samples will also be returned. This is usually used for reinforcement learning +where you can provide reward as head gradient for this array to estimate +gradient. + +Note that the input distribution must be normalized, i.e. *data* must sum to +1 along its last axis. + +Examples:: + + probs = [[0, 0.1, 0.2, 0.3, 0.4], [0.4, 0.3, 0.2, 0.1, 0]] + + // Draw a single sample for each distribution + sample_multinomial(probs) = [3, 0] + + // Draw a vector containing two samples for each distribution + sample_multinomial(probs, shape=(2)) = [[4, 2], + [0, 0]] + + // requests log likelihood + sample_multinomial(probs, get_prob=True) = [2, 1], [0.2, 0.3] +)code") +.set_num_inputs(1) +.set_num_outputs([](const nnvm::NodeAttrs& attrs) { + const SampleMultinomialParam& param = nnvm::get(attrs.parsed); + return param.get_prob ? 2U : 1U; + }) +.set_attr_parser(ParamParser) +.set_attr("FInferShape", SampleMultinomialOpShape) +.set_attr("FInferType", SampleMultinomialOpType) +.set_attr("FResourceRequest", + [](const nnvm::NodeAttrs& attrs) { + return std::vector{ + ResourceRequest::kRandom, ResourceRequest::kTempSpace}; + }) +.set_attr("FGradient", + [](const nnvm::NodePtr& n, const std::vector& ograds) { + const SampleMultinomialParam& param = nnvm::get(n->attrs.parsed); + if (param.get_prob) { + return MakeGradNode("_backward_sample_multinomial", n, + {ograds[1], n->inputs[0], nnvm::NodeEntry{n, 0, 0}}, + std::unordered_map()); + } else { + return MakeZeroGradNodes(n, ograds); + } + }) +.set_attr("FCompute", SampleMultinomialForward) +.add_argument("data", "NDArray-or-Symbol", + "Distribution probabilities. Must sum to one on the last axis.") +.add_arguments(SampleMultinomialParam::__FIELDS__()); + + +struct SampleMultinomialBackwardCPUKernel { + template + MSHADOW_XINLINE static void Map(int i, index_t K, index_t M, + DType* ograd, DType* dist, IType* out, + DType* igrad) { + for (index_t j = 0; j < M; ++j) { + igrad[i*K + out[i*M + j]] += ograd[i*M + j] / dist[i*K + out[i*M + j]]; + } + } +}; + +NNVM_REGISTER_OP(_backward_sample_multinomial) +.set_num_inputs(3) +.set_num_outputs(1) +.set_attr("TIsBackward", true) +.set_attr("FCompute", + SampleMultinomialBackward); + +} // namespace op +} // namespace mxnet diff --git a/src/operator/random/sample_multinomial_op.cu b/src/operator/random/sample_multinomial_op.cu new file mode 100644 index 000000000000..c2bc99b7323e --- /dev/null +++ b/src/operator/random/sample_multinomial_op.cu @@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +/*! + * \file sample_multinomial_op.h + * \brief Operator for sampling from multinomial distributions + */ +#include "./sample_multinomial_op.h" + +namespace mxnet { +namespace op { + +NNVM_REGISTER_OP(sample_multinomial) +.set_attr("FCompute", SampleMultinomialForward); + + +struct SampleMultinomialBackwardGPUKernel { + template + MSHADOW_XINLINE static void Map(int i, index_t K, index_t M, + DType* ograd, DType* dist, IType* out, + DType* igrad) { + for (index_t j = 0; j < M; ++j) { + atomicAdd(&igrad[i*K + out[i*M + j]], ograd[i*M + j] / dist[i*K + out[i*M + j]]); + } + } +}; + + +NNVM_REGISTER_OP(_backward_sample_multinomial) +.set_attr("FCompute", + SampleMultinomialBackward); + + +} // namespace op +} // namespace mxnet diff --git a/src/operator/random/sample_multinomial_op.h b/src/operator/random/sample_multinomial_op.h new file mode 100644 index 000000000000..2b016329f390 --- /dev/null +++ b/src/operator/random/sample_multinomial_op.h @@ -0,0 +1,194 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +/*! + * \file sample_multinomial_op.h + * \brief Operator for sampling from multinomial distributions + */ +#ifndef MXNET_OPERATOR_RANDOM_SAMPLE_MULTINOMIAL_OP_H_ +#define MXNET_OPERATOR_RANDOM_SAMPLE_MULTINOMIAL_OP_H_ + +#include +#include +#include "../mshadow_op.h" +#include "../mxnet_op.h" +#include "../operator_common.h" +#include "../elemwise_op_common.h" + +namespace mxnet { +namespace op { + +struct SampleMultinomialParam : public dmlc::Parameter { + TShape shape; + bool get_prob; + int dtype; + DMLC_DECLARE_PARAMETER(SampleMultinomialParam) { + DMLC_DECLARE_FIELD(shape) + .set_default(TShape()) + .describe("Shape to be sampled from each random distribution."); + DMLC_DECLARE_FIELD(get_prob) + .set_default(false) + .describe("Whether to also return the log probability of sampled " + "result. This is usually used for differentiating through " + "stochastic variables, e.g. in reinforcement learning."); + DMLC_DECLARE_FIELD(dtype) + .add_enum("int32", mshadow::kInt32) + .set_default(mshadow::kInt32) + .describe("DType of the output in case this can't be inferred. " + "Only support int32 for now."); + } +}; + + +inline bool SampleMultinomialOpShape(const nnvm::NodeAttrs& attrs, + std::vector* in_attrs, + std::vector* out_attrs) { + const SampleMultinomialParam& param = nnvm::get(attrs.parsed); + + CHECK_EQ(in_attrs->size(), 1U); + CHECK_EQ(out_attrs->size(), param.get_prob ? 2U : 1U); + const TShape& ishape = (*in_attrs)[0]; + if (!ishape.ndim()) return false; + + if (ishape.ndim() == 1) { + if (param.shape.ndim()) { + SHAPE_ASSIGN_CHECK(*out_attrs, 0, param.shape); + if (param.get_prob) SHAPE_ASSIGN_CHECK(*out_attrs, 0, param.shape); + } else { + SHAPE_ASSIGN_CHECK(*out_attrs, 0, TShape(1)); + if (param.get_prob) SHAPE_ASSIGN_CHECK(*out_attrs, 0, TShape(1)); + } + return true; + } + + TShape oshape(ishape.ndim() - 1 + param.shape.ndim()); + for (size_t i = 0; i < ishape.ndim() - 1; ++i) { + oshape[i] = ishape[i]; + } + for (size_t i = 0; i < param.shape.ndim(); ++i) { + oshape[i + ishape.ndim() - 1] = param.shape[i]; + } + SHAPE_ASSIGN_CHECK(*out_attrs, 0, oshape); + if (param.get_prob) SHAPE_ASSIGN_CHECK(*out_attrs, 1, oshape); + return true; +} + + +inline bool SampleMultinomialOpType(const nnvm::NodeAttrs& attrs, + std::vector* in_attrs, + std::vector* out_attrs) { + const SampleMultinomialParam& param = nnvm::get(attrs.parsed); + + CHECK_EQ(in_attrs->size(), 1U); + CHECK_EQ(out_attrs->size(), param.get_prob ? 2U : 1U); + int itype = (*in_attrs)[0]; + if (itype == -1) return false; + + TYPE_ASSIGN_CHECK(*out_attrs, 0, param.dtype); + if (param.get_prob) { + TYPE_ASSIGN_CHECK(*out_attrs, 1, itype); + } + return true; +} + +struct SampleMultinomialKernel { + template + MSHADOW_XINLINE static void Map(int i, index_t K, index_t M, + DType* dist, float* uniform, IType* out, + DType* prob) { + for (index_t j = 0; j < M; ++j) { + DType loc = static_cast(uniform[i*M + j]); + DType acc = 0; + bool found = false; + for (index_t k = 0; k < K; ++k) { + acc += dist[i*K + k]; + if (acc > loc) { + found = true; + out[i*M + j] = static_cast(k); + if (prob != nullptr) prob[i*M + j] = logf(dist[i*K + k]); + break; + } + } + if (!found) { + out[i*M + j] = static_cast(K-1); + if (prob != nullptr) prob[i*M + j] = logf(dist[i*K + K - 1]); + } + } + } +}; + + +template +void SampleMultinomialForward(const nnvm::NodeAttrs& attrs, + const OpContext& ctx, + const std::vector& inputs, + const std::vector& req, + const std::vector& outputs) { + using namespace mshadow; + using namespace mxnet_op; + const SampleMultinomialParam& param = nnvm::get(attrs.parsed); + + index_t K = inputs[0].shape_[inputs[0].ndim()-1]; + index_t N = inputs[0].Size()/K; + index_t M = outputs[0].Size()/N; + + Stream *s = ctx.get_stream(); + MSHADOW_REAL_TYPE_SWITCH(inputs[0].type_flag_, DType, { + Random *prnd = ctx.requested[0].get_random(s); + Tensor uniform = + ctx.requested[1].get_space_typed(Shape1(N*M), s); + prnd->SampleUniform(&uniform, 0, 1); + Kernel::Launch( + s, N, K, M, inputs[0].dptr(), uniform.dptr_, outputs[0].dptr(), + param.get_prob ? outputs[1].dptr() : nullptr); + }); +} + + +template +void SampleMultinomialBackward(const nnvm::NodeAttrs& attrs, + const OpContext& ctx, + const std::vector& inputs, + const std::vector& req, + const std::vector& outputs) { + using namespace mshadow; + using namespace mxnet_op; + if (req[0] == kNullOp) return; + + index_t K = outputs[0].shape_[outputs[0].ndim()-1]; + index_t N = outputs[0].Size()/K; + index_t M = inputs[0].Size()/N; + + Stream *s = ctx.get_stream(); + MSHADOW_REAL_TYPE_SWITCH(inputs[0].type_flag_, DType, { + if (req[0] != kAddTo) { + Tensor out = outputs[0].FlatTo1D(s); + out = 0; + } + Kernel::Launch( + s, N, K, M, inputs[0].dptr(), inputs[1].dptr(), + inputs[2].dptr(), outputs[0].dptr()); + }); +} + + +} // namespace op +} // namespace mxnet + +#endif // MXNET_OPERATOR_RANDOM_SAMPLE_MULTINOMIAL_OP_H_ diff --git a/src/operator/tensor/sample_op.cc b/src/operator/random/sample_op.cc similarity index 85% rename from src/operator/tensor/sample_op.cc rename to src/operator/random/sample_op.cc index 1b3c293548e1..8d87d2b99d14 100644 --- a/src/operator/tensor/sample_op.cc +++ b/src/operator/random/sample_op.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2016 by Contributors * \file sample_op.cc * \brief CPU Implementation of sample op */ @@ -106,7 +124,7 @@ MXNET_OPERATOR_REGISTER_SAMPLE(random_negative_binomial, SampleNegBinomialParam) .add_alias("_sample_negbinomial") .describe(R"code(Draw random samples from a negative binomial distribution. -Samples are distributed according to a negative binomial distribution parametrized by +Samples are distributed according to a negative binomial distribution parametrized by *k* (limit of unsuccessful experiments) and *p* (failure probability in each experiment). Samples will always be returned as a floating point data type. @@ -121,8 +139,8 @@ MXNET_OPERATOR_REGISTER_SAMPLE(random_generalized_negative_binomial, SampleGenNe .add_alias("_sample_gennegbinomial") .describe(R"code(Draw random samples from a generalized negative binomial distribution. -Samples are distributed according to a generalized negative binomial distribution parametrized by -*mu* (mean) and *alpha* (dispersion). *alpha* is defined as *1/k* where *k* is the failure limit of the +Samples are distributed according to a generalized negative binomial distribution parametrized by +*mu* (mean) and *alpha* (dispersion). *alpha* is defined as *1/k* where *k* is the failure limit of the number of unsuccessful experiments (generalized to real numbers). Samples will always be returned as a floating point data type. diff --git a/src/operator/tensor/sample_op.cu b/src/operator/random/sample_op.cu similarity index 79% rename from src/operator/tensor/sample_op.cu rename to src/operator/random/sample_op.cu index 62c8a73249cb..0d4b2e5a8270 100644 --- a/src/operator/tensor/sample_op.cu +++ b/src/operator/random/sample_op.cu @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2016 by Contributors * \file sample_op.cu * \brief GPU Implementation of sample op */ diff --git a/src/operator/tensor/sample_op.h b/src/operator/random/sample_op.h similarity index 93% rename from src/operator/tensor/sample_op.h rename to src/operator/random/sample_op.h index b5f20bc57dd3..a1a6a2345b1b 100644 --- a/src/operator/tensor/sample_op.h +++ b/src/operator/random/sample_op.h @@ -1,10 +1,28 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2016 by Contributors * \file sample_op.h * \brief Elementary sampling operators */ -#ifndef MXNET_OPERATOR_TENSOR_SAMPLE_OP_H_ -#define MXNET_OPERATOR_TENSOR_SAMPLE_OP_H_ +#ifndef MXNET_OPERATOR_RANDOM_SAMPLE_OP_H_ +#define MXNET_OPERATOR_RANDOM_SAMPLE_OP_H_ #include #include @@ -12,7 +30,7 @@ #include #include "../mshadow_op.h" #include "../elemwise_op_common.h" -#include "./init_op.h" +#include "../tensor/init_op.h" namespace mxnet { namespace op { @@ -386,4 +404,4 @@ inline std::vector SampleResource(const NodeAttrs& attrs) { } // namespace op } // namespace mxnet -#endif // MXNET_OPERATOR_TENSOR_SAMPLE_OP_H_ +#endif // MXNET_OPERATOR_RANDOM_SAMPLE_OP_H_ diff --git a/src/operator/regression_output-inl.h b/src/operator/regression_output-inl.h index 7f8b2948ebfa..0de312cff8d6 100644 --- a/src/operator/regression_output-inl.h +++ b/src/operator/regression_output-inl.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file regression_ouput-inl.h * \brief Regression output operator. */ diff --git a/src/operator/regression_output.cc b/src/operator/regression_output.cc index fc71a993d43a..d19f336d2aa2 100644 --- a/src/operator/regression_output.cc +++ b/src/operator/regression_output.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file regression_output.cc * \brief regression output operator */ diff --git a/src/operator/regression_output.cu b/src/operator/regression_output.cu index 18e7a1f4184c..64dcef3df6f0 100644 --- a/src/operator/regression_output.cu +++ b/src/operator/regression_output.cu @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file regression_output.cu * \brief regression output operator */ diff --git a/src/operator/rnn-inl.h b/src/operator/rnn-inl.h index bc5f3d7da6ee..4f09ebe9c3ea 100644 --- a/src/operator/rnn-inl.h +++ b/src/operator/rnn-inl.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file rnn-inl.h * \brief * \author Sebastian Bodenstein diff --git a/src/operator/rnn.cc b/src/operator/rnn.cc index f43379fdd8dd..4c7954f3e5a6 100644 --- a/src/operator/rnn.cc +++ b/src/operator/rnn.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file rnn.cc * \brief * \author Sebastian Bodenstein @@ -22,10 +40,6 @@ Operator *CreateOp(RNNParam param, int dtype) { Operator *RNNProp::CreateOperatorEx(Context ctx, std::vector *in_shape, std::vector *in_type) const { - std::vector out_shape, aux_shape; - std::vector out_type, aux_type; - CHECK(InferType(in_type, &out_type, &aux_type)); - CHECK(InferShape(in_shape, &out_shape, &aux_shape)); DO_BIND_DISPATCH(CreateOp, param_, (*in_type)[0]); } diff --git a/src/operator/rnn.cu b/src/operator/rnn.cu index 4e3998eac269..0daee32abe5b 100644 --- a/src/operator/rnn.cu +++ b/src/operator/rnn.cu @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file rnn.cu * \brief * \author Sebastian Bodenstein diff --git a/src/operator/roi_pooling-inl.h b/src/operator/roi_pooling-inl.h index cc1555d8c330..f2f836408c5a 100644 --- a/src/operator/roi_pooling-inl.h +++ b/src/operator/roi_pooling-inl.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file roi_pooling-inl.h * \brief roi pooling operator and symbol * \author Kye-Hyeon Kim, Jian Guo diff --git a/src/operator/roi_pooling.cc b/src/operator/roi_pooling.cc index 9c5d7c1ca5d6..7518392e37eb 100644 --- a/src/operator/roi_pooling.cc +++ b/src/operator/roi_pooling.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file roi_pooling.cc * \brief roi pooling operator * \author Ross Girshick, Kye-Hyeon Kim, Jian Guo @@ -224,10 +242,6 @@ Operator *CreateOp(ROIPoolingParam param, int dtype) { Operator *ROIPoolingProp::CreateOperatorEx(Context ctx, std::vector *in_shape, std::vector *in_type) const { - std::vector out_shape, aux_shape; - std::vector out_type, aux_type; - CHECK(InferType(in_type, &out_type, &aux_type)); - CHECK(InferShape(in_shape, &out_shape, &aux_shape)); DO_BIND_DISPATCH(CreateOp, param_, in_type->at(0)); } diff --git a/src/operator/roi_pooling.cu b/src/operator/roi_pooling.cu index 677ab83efa61..28981c11ebd6 100644 --- a/src/operator/roi_pooling.cu +++ b/src/operator/roi_pooling.cu @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file roi_pooling.cu * \brief roi pooling operator * \author Ross Girshick, Kye-Hyeon Kim, Jian Guo diff --git a/src/operator/sequence_last-inl.h b/src/operator/sequence_last-inl.h index c2acbf164197..0551ee933f0a 100644 --- a/src/operator/sequence_last-inl.h +++ b/src/operator/sequence_last-inl.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2016 by Contributors * \file sequence_last-inl.h * \brief * \author Sebastian Bodenstien diff --git a/src/operator/sequence_last.cc b/src/operator/sequence_last.cc index 7c796613efa8..6c04bdd34d86 100644 --- a/src/operator/sequence_last.cc +++ b/src/operator/sequence_last.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file sequence_last.cc * \brief * \author Sebastian Bodenstein @@ -20,10 +38,6 @@ Operator *CreateOp(SequenceLastParam param, int dtype) { Operator *SequenceLastProp::CreateOperatorEx(Context ctx, std::vector *in_shape, std::vector *in_type) const { - std::vector out_shape, aux_shape; - std::vector out_type, aux_type; - CHECK(InferType(in_type, &out_type, &aux_type)); - CHECK(InferShape(in_shape, &out_shape, &aux_shape)); DO_BIND_DISPATCH(CreateOp, param_, (*in_type)[0]); } diff --git a/src/operator/sequence_last.cu b/src/operator/sequence_last.cu index 329c2c77f6b4..9215b2478c1d 100644 --- a/src/operator/sequence_last.cu +++ b/src/operator/sequence_last.cu @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file sequence_last.cu * \brief * \author Sebastian Bodenstein diff --git a/src/operator/sequence_mask-inl.h b/src/operator/sequence_mask-inl.h index 69c98746553b..dec1f2a2b7ed 100644 --- a/src/operator/sequence_mask-inl.h +++ b/src/operator/sequence_mask-inl.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2016 by Contributors * \file wl_sequence_mask-inl.h * \brief * \author Sebastian Bodenstien diff --git a/src/operator/sequence_mask.cc b/src/operator/sequence_mask.cc index 763bc17171ae..ed90f3ee4a4f 100644 --- a/src/operator/sequence_mask.cc +++ b/src/operator/sequence_mask.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file sequence_mask.cc * \brief * \author Sebastian Bodenstein @@ -33,10 +51,6 @@ Operator *CreateOp(SequenceMaskParam param, int dtype) { Operator *SequenceMaskProp::CreateOperatorEx(Context ctx, std::vector *in_shape, std::vector *in_type) const { - std::vector out_shape, aux_shape; - std::vector out_type, aux_type; - CHECK(InferType(in_type, &out_type, &aux_type)); - CHECK(InferShape(in_shape, &out_shape, &aux_shape)); DO_BIND_DISPATCH(CreateOp, param_, (*in_type)[0]); } diff --git a/src/operator/sequence_mask.cu b/src/operator/sequence_mask.cu index 41c08942cdff..d370ff3d13ec 100644 --- a/src/operator/sequence_mask.cu +++ b/src/operator/sequence_mask.cu @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file sequence_mask.cu * \brief * \author Sebastian Bodenstein diff --git a/src/operator/sequence_op_common.h b/src/operator/sequence_op_common.h index a2924921218f..9e5843161087 100644 --- a/src/operator/sequence_op_common.h +++ b/src/operator/sequence_op_common.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file sequence_op_common.h * \brief common function used for sequence layers * \author Sebastian Bodenstein diff --git a/src/operator/sequence_reverse-inl.h b/src/operator/sequence_reverse-inl.h index 048eb3e2eb78..0a43138a085c 100644 --- a/src/operator/sequence_reverse-inl.h +++ b/src/operator/sequence_reverse-inl.h @@ -1,8 +1,27 @@ -/*! - * Copyright (c) 2016 by Contributors +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +/* * \file sequence_reverse-inl.h * \brief * \author Sebastian Bodenstien + * \author Marek Kolodziej */ #ifndef MXNET_OPERATOR_SEQUENCE_REVERSE_INL_H_ @@ -13,12 +32,13 @@ #include #include #include -#include #include #include +#include +#include "./mshadow_op.h" +#include "./mxnet_op.h" #include "./operator_common.h" #include "./sequence_op_common.h" -#include "./mshadow_op.h" namespace mxnet { namespace op { @@ -34,35 +54,68 @@ struct SequenceReverseParam : public dmlc::Parameter { DMLC_DECLARE_FIELD(use_sequence_length) .set_default(false) .describe( - "If set to true, this layer takes in an extra input parameter `sequence_length` " + "If set to true, this layer takes in an extra input parameter " + "`sequence_length` " "to specify variable length sequence"); } }; +struct ReverseKernel { + template + MSHADOW_XINLINE static void Map( + const int i, DType *const out_data, const DType *const in_data, + const OpReqType req, const index_t max_seq_len, const index_t batch_size, + const index_t other_dim, const index_t numel, const DType *const indices + ) { + for (index_t batch = 0; batch < batch_size; ++batch) { + const index_t num_seq = indices + ? static_cast(indices[batch]) + : max_seq_len; + const index_t padded_periods = max_seq_len - num_seq; + // padded part + if (padded_periods > 0 && i < static_cast(padded_periods)) { + const int padded_in_offset = + (i + num_seq) * batch_size * other_dim + batch * other_dim; + + for (index_t j = 0; j < other_dim; ++j) { + KERNEL_ASSIGN(out_data[padded_in_offset + j], req, + in_data[padded_in_offset + j]); + } + } + // unpadded part + if (i < static_cast(num_seq)) { + const int in_offset = i * batch_size * other_dim + batch * other_dim; + const int out_offset = + numel - (i + 1 + padded_periods) * batch_size * other_dim + + batch * other_dim; + + for (index_t j = 0; j < other_dim; ++j) { + KERNEL_ASSIGN(out_data[out_offset + j], req, in_data[in_offset + j]); + } + } + } + } +}; + template class SequenceReverseOp : public Operator { public: explicit SequenceReverseOp(SequenceReverseParam p) { this->param_ = p; } - void sequence_reverse(const mshadow::Tensor data, + void sequence_reverse(const mshadow::Tensor &data, const mshadow::Tensor &out, - std::vector indices, OpReqType req) { + const OpReqType req, const DType *const indices, + mshadow::Stream *const s) { using namespace mshadow; using namespace mshadow::expr; - index_t seq_length; - index_t max_seq_len = data.size(0); - index_t batch_size = data.size(1); - for (index_t b = 0; b < batch_size; ++b) { - seq_length = indices[b]; - for (index_t s = 0; s < max_seq_len; ++s) { - if (s < seq_length) - Assign( - out[s][b], req, - F( - data[seq_length - s - 1][b])) - else // preserve padding type - Assign(out[s][b], req, F(data[s][b])) - } - } + + const index_t max_seq_len = data.size(0); + const index_t batch_size = data.size(1); + const index_t other_dim = data.size(2); + const index_t tensor_numel = data.shape_.Size(); + + mxnet_op::Kernel::Launch( + s, max_seq_len, out.dptr_, data.dptr_, req, max_seq_len, batch_size, + other_dim, tensor_numel, indices); } virtual void Forward(const OpContext &ctx, const std::vector &in_data, @@ -73,7 +126,7 @@ class SequenceReverseOp : public Operator { using namespace mshadow::expr; CHECK_EQ(in_data.size(), param_.use_sequence_length ? 2U : 1U); CHECK_EQ(out_data.size(), 1U); - Stream *s = ctx.get_stream(); + Stream *const s = ctx.get_stream(); // Get any size input + output into required form int max_seq_len = in_data[seq_reverse::kData].size(0); @@ -87,14 +140,12 @@ class SequenceReverseOp : public Operator { Tensor out = out_data[seq_reverse::kOut].get_with_shape(s3, s); - // copy indices to vector - std::vector indices_vec(n, max_seq_len); - if (param_.use_sequence_length) - IndexTensorToVector( - in_data[seq_reverse::kSequenceLength].get(s), - &indices_vec); + const DType *const indices = + param_.use_sequence_length + ? in_data[seq_reverse::kSequenceLength].dptr() + : nullptr; - sequence_reverse(data, out, indices_vec, req[seq_reverse::kOut]); + sequence_reverse(data, out, req[seq_reverse::kOut], indices, s); } virtual void Backward(const OpContext &ctx, @@ -122,15 +173,13 @@ class SequenceReverseOp : public Operator { in_grad[seq_reverse::kData].get_with_shape(s3, s); Tensor output_grad = out_grad[seq_reverse::kOut].get_with_shape(s3, s); - // copy indices to vector - std::vector indices_vec(n, max_seq_len); - if (param_.use_sequence_length) - IndexTensorToVector( - in_data[seq_reverse::kSequenceLength].get(s), - &indices_vec); - sequence_reverse(output_grad, data_grad, indices_vec, - req[seq_reverse::kData]); + const DType *const indices = + param_.use_sequence_length + ? in_data[seq_reverse::kSequenceLength].dptr() + : nullptr; + + sequence_reverse(output_grad, data_grad, req[seq_reverse::kData], indices, s); } private: diff --git a/src/operator/sequence_reverse.cc b/src/operator/sequence_reverse.cc index 871db9b3d486..61821d3945f7 100644 --- a/src/operator/sequence_reverse.cc +++ b/src/operator/sequence_reverse.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file sequence_reverse.cc * \brief * \author Sebastian Bodenstein @@ -20,10 +38,6 @@ Operator *CreateOp(SequenceReverseParam param, int dtype) { Operator *SequenceReverseProp::CreateOperatorEx( Context ctx, std::vector *in_shape, std::vector *in_type) const { - std::vector out_shape, aux_shape; - std::vector out_type, aux_type; - CHECK(InferType(in_type, &out_type, &aux_type)); - CHECK(InferShape(in_shape, &out_shape, &aux_shape)); DO_BIND_DISPATCH(CreateOp, param_, (*in_type)[0]); } diff --git a/src/operator/sequence_reverse.cu b/src/operator/sequence_reverse.cu index cdd8f348950c..c6cc3f66d0fe 100644 --- a/src/operator/sequence_reverse.cu +++ b/src/operator/sequence_reverse.cu @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file sequence_reverse.cu * \brief * \author Sebastian Bodenstein diff --git a/src/operator/slice_channel-inl.h b/src/operator/slice_channel-inl.h index 69d1f2ad5449..a48c52f0b70e 100644 --- a/src/operator/slice_channel-inl.h +++ b/src/operator/slice_channel-inl.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file slice_channel-inl.h * \brief * \author Bing Xu diff --git a/src/operator/slice_channel.cc b/src/operator/slice_channel.cc index 85e9e447ee1d..7293ba6afcf3 100644 --- a/src/operator/slice_channel.cc +++ b/src/operator/slice_channel.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file slice_channel.cc * \brief * \author Bing Xu @@ -29,9 +47,9 @@ DMLC_REGISTER_PARAMETER(SliceChannelParam); MXNET_REGISTER_OP_PROPERTY(SliceChannel, SliceChannelProp) .describe(R"code(Splits an array along a particular axis into multiple sub-arrays. -.. note:: ``SliceChannel`` is depreacted. Use ``split`` instead. +.. note:: ``SliceChannel`` is deprecated. Use ``split`` instead. -**Note** that `num_outputs` should evenly divide the length of the axis +**Note** that `num_outputs` should evenly divide the length of the axis along which to split the array. Example:: @@ -72,6 +90,8 @@ Example:: along the `axis` which it is split. Also `squeeze_axis` can be set to true only if ``input.shape[axis] == num_outputs``. +Example:: + z = split(x, axis=0, num_outputs=3, squeeze_axis=1) // a list of 3 arrays with shape (2, 1) z = [[ 1.] [ 2.]] diff --git a/src/operator/slice_channel.cu b/src/operator/slice_channel.cu index 6afd45003ed3..eb1c9c8b6e93 100644 --- a/src/operator/slice_channel.cu +++ b/src/operator/slice_channel.cu @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file slice_channel.cc * \brief * \author Bing Xu diff --git a/src/operator/softmax_activation-inl.h b/src/operator/softmax_activation-inl.h index 0f4bf5f290af..b1b76930b483 100644 --- a/src/operator/softmax_activation-inl.h +++ b/src/operator/softmax_activation-inl.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file softmax_activation-inl.h * \brief SoftmaxActivation operator * \author Junyuan Xie diff --git a/src/operator/softmax_activation.cc b/src/operator/softmax_activation.cc index 37546bfa60b2..115b0a730cde 100644 --- a/src/operator/softmax_activation.cc +++ b/src/operator/softmax_activation.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file activation.cc * \brief softmax_activation op * \author Junyuan Xie @@ -26,7 +44,7 @@ MXNET_REGISTER_OP_PROPERTY(SoftmaxActivation, SoftmaxActivationProp) .. note:: - This operator has been depreated, please use `softmax`. + This operator has been deprecated, please use `softmax`. If `mode` = ``instance``, this operator will compute a softmax for each instance in the batch. This is the default mode. diff --git a/src/operator/softmax_activation.cu b/src/operator/softmax_activation.cu index b2d903a98ae6..5bebed2846b8 100644 --- a/src/operator/softmax_activation.cu +++ b/src/operator/softmax_activation.cu @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file softmax_activation.cu * \brief * \author Junyuan Xie diff --git a/src/operator/softmax_output-inl.h b/src/operator/softmax_output-inl.h index e47398c5d66d..fa158c120d1a 100644 --- a/src/operator/softmax_output-inl.h +++ b/src/operator/softmax_output-inl.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file softmax_output-inl.h * \brief * \author Bing Xu @@ -44,7 +62,8 @@ struct SoftmaxOutputParam : public dmlc::Parameter { "during backward, if `use_ignore` is set to ``true``)."); DMLC_DECLARE_FIELD(multi_output).set_default(false) .describe("If set to ``true``, the softmax function will be computed along " - "the second axis."); + "axis ``1``. This is applied when the shape " + "of input array differs from the shape of label array."); DMLC_DECLARE_FIELD(use_ignore).set_default(false) .describe("If set to ``true``, the `ignore_label` value will not contribute " "to the backward gradient."); diff --git a/src/operator/softmax_output.cc b/src/operator/softmax_output.cc index 85e1efa65664..52bb2a400755 100644 --- a/src/operator/softmax_output.cc +++ b/src/operator/softmax_output.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file softmax_output.cc * \brief * \author Bing Xu @@ -20,10 +38,6 @@ Operator *CreateOp(SoftmaxOutputParam param, int dtype) { // DO_BIND_DISPATCH comes from operator_common.h Operator *SoftmaxOutputProp::CreateOperatorEx(Context ctx, std::vector *in_shape, std::vector *in_type) const { - std::vector out_shape, aux_shape; - std::vector out_type, aux_type; - CHECK(InferType(in_type, &out_type, &aux_type)); - CHECK(InferShape(in_shape, &out_shape, &aux_shape)); DO_BIND_DISPATCH(CreateOp, param_, (*in_type)[0]); } @@ -32,13 +46,13 @@ DMLC_REGISTER_PARAMETER(SoftmaxOutputParam); MXNET_REGISTER_OP_PROPERTY(SoftmaxOutput, SoftmaxOutputProp) .describe(R"code(Computes the gradient of cross entropy loss with respect to softmax output. -- This operator computes the graident in two steps. +- This operator computes the gradient in two steps. The cross entropy loss does not actually need to be computed. - Applies softmax function on the input array. - Computes and returns the gradient of cross entropy loss w.r.t. the softmax output. -- The softmax function, cross entropy loss and graident is given by: +- The softmax function, cross entropy loss and gradient is given by: - Softmax Function: @@ -71,7 +85,28 @@ MXNET_REGISTER_OP_PROPERTY(SoftmaxOutput, SoftmaxOutputProp) The provided label can be a one-hot label array or a probability label array. - If the parameter `use_ignore` is ``true``, `ignore_label` can specify input instances - with a particular label to be ignored during backward propagation. + with a particular label to be ignored during backward propagation. **This has no effect when + softmax `output` has same shape as `label`**. + + Example:: + + data = [[1,2,3,4],[2,2,2,2],[3,3,3,3],[4,4,4,4]] + label = [1,0,2,3] + ignore_label = 1 + SoftmaxOutput(data=data, label = label,\ + multi_output=true, use_ignore=true,\ + ignore_label=ignore_label) + ## forward softmax output + [[ 0.0320586 0.08714432 0.23688284 0.64391428] + [ 0.25 0.25 0.25 0.25 ] + [ 0.25 0.25 0.25 0.25 ] + [ 0.25 0.25 0.25 0.25 ]] + ## backward gradient output + [[ 0. 0. 0. 0. ] + [-0.75 0.25 0.25 0.25] + [ 0.25 0.25 -0.75 0.25] + [ 0.25 0.25 0.25 -0.75]] + ## notice that the first row is all 0 because label[0] is 1, which is equal to ignore_label. - The parameter `grad_scale` can be used to rescale the gradient, which is often used to give each loss function different weights. diff --git a/src/operator/softmax_output.cu b/src/operator/softmax_output.cu index 7d9324bd2632..8de5df6655f7 100644 --- a/src/operator/softmax_output.cu +++ b/src/operator/softmax_output.cu @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file softmax_output.cu * \brief * \author Bing Xu diff --git a/src/operator/spatial_transformer-inl.h b/src/operator/spatial_transformer-inl.h index 8c02d35ebc44..77967579340f 100644 --- a/src/operator/spatial_transformer-inl.h +++ b/src/operator/spatial_transformer-inl.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2016 by Contributors * \file spatial_transformer-inl.h * \brief * Reproducing paper: aderberg M, Simonyan K, Zisserman A. "Spatial transformer networks" diff --git a/src/operator/spatial_transformer.cc b/src/operator/spatial_transformer.cc index fe91a143c23e..51b0ebfde1f0 100644 --- a/src/operator/spatial_transformer.cc +++ b/src/operator/spatial_transformer.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2016 by Contributors * \file spatial_transformer.cc * \brief * \author Wei Wu @@ -9,6 +27,10 @@ namespace mshadow { template +bool between(DType value, int lowerBound, int upperBound) { + return (value >= lowerBound && value <= upperBound); +} +template inline void BilinearSamplingForward(const Tensor &output, const Tensor &input, const Tensor grid_src) { @@ -20,24 +42,33 @@ inline void BilinearSamplingForward(const Tensor &output, for (index_t n = 0; n < static_cast(o_n); ++n) { for (index_t c = 0; c < static_cast(o_c); ++c) { for (index_t h = 0; h < static_cast(o_h); ++h) { - for (index_t w = 0; w < o_w; ++w) { + for (index_t w = 0; w < static_cast(o_w); ++w) { index_t out_index = n * o_c * o_h * o_w + c * o_h * o_w + h * o_w + w; index_t grid_index = n * o_h * o_w * 2 + h * o_w + w; DType y_real = (*(grid + grid_index + o_h * o_w) + 1) * (i_h - 1) / 2; DType x_real = (*(grid + grid_index) + 1) * (i_w - 1) / 2; - index_t top_left_y = std::min(i_h, std::max(0, static_cast(floor(y_real)))); - index_t top_left_x = std::min(i_w, std::max(0, static_cast(floor(x_real)))); + int top_left_y = static_cast(floor(y_real)); + int top_left_x = static_cast(floor(x_real)); DType top_left_y_w = 1.0 - (y_real - top_left_y); DType top_left_x_w = 1.0 - (x_real - top_left_x); - index_t data_index = n * i_c * i_h * i_w + c * i_h * i_w + top_left_y * i_w + top_left_x; - DType top_left_v = *(data + data_index); - DType top_right_v = *(data + data_index + 1); - DType bottom_left_v = *(data + data_index + i_w); - DType bottom_right_v = *(data + data_index + i_w + 1); + int data_index = n * i_c * i_h * i_w + c * i_h * i_w + + top_left_y * i_w + top_left_x; + DType top_left_v = 0; + DType top_right_v = 0; + DType bottom_left_v = 0; + DType bottom_right_v = 0; + if (between(top_left_x, 0, i_w-1) && between(top_left_y, 0, i_h-1)) + top_left_v = *(data + data_index); + if (between(top_left_x + 1, 0, i_w-1) && between(top_left_y, 0, i_h-1)) + top_right_v = *(data + data_index + 1); + if (between(top_left_x, 0, i_w-1) && between(top_left_y + 1, 0, i_h-1)) + bottom_left_v = *(data + data_index + i_w); + if (between(top_left_x+1, 0, i_w-1) && between(top_left_y + 1, 0, i_h-1)) + bottom_right_v = *(data + data_index + i_w + 1); *(out+out_index) = top_left_v * top_left_y_w * top_left_x_w + - top_right_v * top_left_y_w * (1.0 - top_left_x_w) + - bottom_left_v * (1.0 - top_left_y_w) * top_left_x_w + - bottom_right_v * (1.0 - top_left_y_w) * (1.0 - top_left_x_w); + top_right_v * top_left_y_w * (1.0 - top_left_x_w) + + bottom_left_v * (1.0 - top_left_y_w) * top_left_x_w + + bottom_right_v * (1.0 - top_left_y_w) * (1.0 - top_left_x_w); } } } @@ -64,27 +95,38 @@ inline void BilinearSamplingBackward(const Tensor &input_grad, index_t grid_src_index = n * o_h * o_w * 2 + h * o_w + w; DType y_real = (*(grid_src + grid_src_index + o_h * o_w) + 1) * (i_h - 1) / 2; DType x_real = (*(grid_src + grid_src_index) + 1) * (i_w - 1) / 2; - index_t top_left_y = std::min(i_h, std::max(0, static_cast(floor(y_real)))); - index_t top_left_x = std::min(i_w, std::max(0, static_cast(floor(x_real)))); + index_t top_left_y = static_cast(floor(y_real)); + index_t top_left_x = static_cast(floor(x_real)); DType top_left_y_w = 1.0 - (y_real - top_left_y); DType top_left_x_w = 1.0 - (x_real - top_left_x); - for (index_t c = 0; c < o_c; ++c) { + for (index_t c = 0; c < static_cast(o_c); ++c) { index_t grad_index = n * o_c * o_h * o_w + c * o_h * o_w + h * o_w + w; index_t data_index = n * i_c * i_h * i_w + c * i_h * i_w + top_left_y * i_w + top_left_x; // calc 4 vertex value in input data - DType top_left_v = *(data + data_index); - DType top_right_v = *(data + data_index + 1); - DType bottom_left_v = *(data + data_index + i_w); - DType bottom_right_v = *(data + data_index + i_w + 1); - // calc input grad - *(g_input + data_index) += *(grad + grad_index) * top_left_y_w * top_left_x_w; - *(g_input + data_index + 1) += *(grad + grad_index) * top_left_y_w - * (1.0 - top_left_x_w); - *(g_input + data_index+ i_w) += *(grad + grad_index) * (1.0 - top_left_y_w) - * top_left_x_w; - *(g_input + data_index+ i_w + 1) += *(grad + grad_index) * (1.0 - top_left_y_w) - * (1.0 - top_left_x_w); + DType top_left_v = 0; + DType top_right_v = 0; + DType bottom_left_v = 0; + DType bottom_right_v = 0; + if (between(top_left_x, 0, i_w-1) && between(top_left_y, 0, i_h-1)) { + *(g_input + data_index) += *(grad + grad_index) * top_left_y_w * top_left_x_w; + top_left_v = *(data + data_index); + } + if (between(top_left_x+1, 0, i_w-1) && between(top_left_y, 0, i_h-1)) { + *(g_input + data_index + 1) += *(grad + grad_index) * top_left_y_w + * (1.0 - top_left_x_w); + top_right_v = *(data + data_index + 1); + } + if (between(top_left_x, 0, i_w-1) && between(top_left_y+1, 0, i_h-1)) { + *(g_input + data_index+ i_w) += *(grad + grad_index) * (1.0 - top_left_y_w) + * top_left_x_w; + bottom_left_v = *(data + data_index + i_w); + } + if (between(top_left_x+1, 0, i_w-1) && between(top_left_y+1, 0, i_h-1)) { + *(g_input + data_index+ i_w + 1) += *(grad + grad_index) * (1.0 - top_left_y_w) + * (1.0 - top_left_x_w); + bottom_right_v = *(data + data_index + i_w + 1); + } // calc weight grad of top_left_w, then multiple -1 is the grad of grid_src top_left_y_gw -= *(grad + grad_index) * (top_right_v - bottom_right_v + (top_left_v - top_right_v - bottom_left_v + bottom_right_v) @@ -116,10 +158,6 @@ Operator* CreateOp(SpatialTransformerParam param, int dtype) { Operator *SpatialTransformerProp::CreateOperatorEx(Context ctx, std::vector *in_shape, std::vector *in_type) const { - std::vector out_shape, aux_shape; - std::vector out_type, aux_type; - CHECK(InferType(in_type, &out_type, &aux_type)); - CHECK(InferShape(in_shape, &out_shape, &aux_shape)); DO_BIND_DISPATCH(CreateOp, param_, (*in_type)[0]); } diff --git a/src/operator/spatial_transformer.cu b/src/operator/spatial_transformer.cu index 4942f6573dac..d5e4480dc187 100644 --- a/src/operator/spatial_transformer.cu +++ b/src/operator/spatial_transformer.cu @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2016 by Contributors * \file spatial_transformer.cu * \brief * \author Wei Wu @@ -13,6 +31,10 @@ namespace mshadow { template +__device__ bool between(DType value, int lowerBound, int upperBound) { + return (value >= lowerBound && value <= upperBound); +} +template __global__ void BilinearSamplingForwardKernel(const int i_c, const int i_h, const int i_w, const DType* data, const DType* grid, const int o_n, @@ -30,19 +52,27 @@ __global__ void BilinearSamplingForwardKernel(const int i_c, const int i_h, index_t grid_index = n * o_h * o_w * 2 + h * o_w + w; DType y_real = (*(grid + grid_index + o_h * o_w) + 1) * (i_h - 1) / 2; DType x_real = (*(grid + grid_index) + 1) * (i_w - 1) / 2; - index_t top_left_y = min(i_h, max(0, static_cast(floor(y_real)))); - index_t top_left_x = min(i_w, max(0, static_cast(floor(x_real)))); + int top_left_y = static_cast(floor(y_real)); + int top_left_x = static_cast(floor(x_real)); DType top_left_y_w = 1.0 - (y_real - top_left_y); DType top_left_x_w = 1.0 - (x_real - top_left_x); - index_t data_index = n * i_c * i_h * i_w + c * i_h * i_w + top_left_y * i_w + top_left_x; - DType top_left_v = *(data + data_index); - DType top_right_v = *(data + data_index + 1); - DType bottom_left_v = *(data + data_index + i_w); - DType bottom_right_v = *(data + data_index + i_w + 1); + int data_index = n * i_c * i_h * i_w + c * i_h * i_w + top_left_y * i_w + top_left_x; + DType top_left_v = 0; + DType top_right_v = 0; + DType bottom_left_v = 0; + DType bottom_right_v = 0; + if (between(top_left_x, 0, i_w-1) && between(top_left_y, 0, i_h-1)) + top_left_v = *(data + data_index); + if (between(top_left_x + 1, 0, i_w-1) && between(top_left_y, 0, i_h-1)) + top_right_v = *(data + data_index + 1); + if (between(top_left_x, 0, i_w-1) && between(top_left_y + 1, 0, i_h-1)) + bottom_left_v = *(data + data_index + i_w); + if (between(top_left_x+1, 0, i_w-1) && between(top_left_y + 1, 0, i_h-1)) + bottom_right_v = *(data + data_index + i_w + 1); *(out+out_index) = top_left_v * top_left_y_w * top_left_x_w + - top_right_v * top_left_y_w * (1.0 - top_left_x_w) + - bottom_left_v * (1.0 - top_left_y_w) * top_left_x_w + - bottom_right_v * (1.0 - top_left_y_w) * (1.0 - top_left_x_w); + top_right_v * top_left_y_w * (1.0 - top_left_x_w) + + bottom_left_v * (1.0 - top_left_y_w) * top_left_x_w + + bottom_right_v * (1.0 - top_left_y_w) * (1.0 - top_left_x_w); } } @@ -65,29 +95,43 @@ __global__ void BilinearSamplingBackwardKernel(const int i_c, const int i_h, index_t grid_src_index = n * o_h * o_w * 2 + h * o_w + w; DType y_real = (*(grid_src + grid_src_index + o_h * o_w) + 1) * (i_h - 1) / 2; DType x_real = (*(grid_src + grid_src_index) + 1) * (i_w - 1) / 2; - index_t top_left_y = min(i_h, max(0, static_cast(floor(y_real)))); - index_t top_left_x = min(i_w, max(0, static_cast(floor(x_real)))); + int top_left_y = static_cast(floor(y_real)); + int top_left_x = static_cast(floor(x_real)); DType top_left_y_w = 1.0 - (y_real - top_left_y); DType top_left_x_w = 1.0 - (x_real - top_left_x); for (index_t c = 0; c < o_c; ++c) { index_t grad_index = n * o_c * o_h * o_w + c * o_h * o_w + h * o_w + w; index_t data_index = n * i_c * i_h * i_w + c * i_h * i_w + top_left_y * i_w + top_left_x; // calc 4 vertex value in input data - DType top_left_v = *(data + data_index); - DType top_right_v = *(data + data_index + 1); - DType bottom_left_v = *(data + data_index + i_w); - DType bottom_right_v = *(data + data_index + i_w + 1); + DType top_left_v = 0; + DType top_right_v = 0; + DType bottom_left_v = 0; + DType bottom_right_v = 0; // calc input grad - *(g_input + data_index) += *(grad + grad_index) * top_left_y_w * top_left_x_w; - *(g_input + data_index + 1) += *(grad + grad_index) * top_left_y_w * (1.0 - top_left_x_w); - *(g_input + data_index+ i_w) += *(grad + grad_index) * (1.0 - top_left_y_w) * top_left_x_w; - *(g_input + data_index+ i_w + 1) += *(grad + grad_index) * (1.0 - top_left_y_w) * - (1.0 - top_left_x_w); + if (between(top_left_x, 0, i_w-1) && between(top_left_y, 0, i_h-1)) { + *(g_input + data_index) += *(grad + grad_index) * top_left_y_w * top_left_x_w; + top_left_v = *(data + data_index); + } + if (between(top_left_x+1, 0, i_w-1) && between(top_left_y, 0, i_h-1)) { + *(g_input + data_index + 1) += *(grad + grad_index) * top_left_y_w * (1.0 - top_left_x_w); + top_right_v = *(data + data_index + 1); + } + if (between(top_left_x, 0, i_w-1) && between(top_left_y+1, 0, i_h-1)) { + *(g_input + data_index+ i_w) += *(grad + grad_index) * (1.0 - top_left_y_w) * top_left_x_w; + bottom_left_v = *(data + data_index + i_w); + } + if (between(top_left_x+1, 0, i_w-1) && between(top_left_y+1, 0, i_h-1)) { + *(g_input + data_index+ i_w + 1) += *(grad + grad_index) * (1.0 - top_left_y_w) * + (1.0 - top_left_x_w); + bottom_right_v = *(data + data_index + i_w + 1); + } // calc weight grad of top_left_w, then multiple -1 is the grad of grid_src top_left_y_gw -= *(grad + grad_index) * (top_right_v - bottom_right_v + - (top_left_v - top_right_v - bottom_left_v + bottom_right_v) * top_left_x_w); - top_left_x_gw -= *(grad + grad_index) * (bottom_left_v - bottom_right_v + (top_left_v - - top_right_v - bottom_left_v + bottom_right_v) * top_left_y_w); + (top_left_v - top_right_v - bottom_left_v + bottom_right_v) + * top_left_x_w); + top_left_x_gw -= *(grad + grad_index) * (bottom_left_v - bottom_right_v + + (top_left_v - top_right_v - bottom_left_v + bottom_right_v) + * top_left_y_w); } // calc grid_src grad *(grid_src + grid_src_index + o_h * o_w) = top_left_y_gw * (i_h - 1) / 2; diff --git a/src/operator/special_functions-inl.h b/src/operator/special_functions-inl.h index 743391e0fce0..b9460a3e7f0f 100644 --- a/src/operator/special_functions-inl.h +++ b/src/operator/special_functions-inl.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file special_functions-inl.h * \brief * \author Valentin Flunkert diff --git a/src/operator/svm_output-inl.h b/src/operator/svm_output-inl.h index 7f460ace8967..f02546144107 100644 --- a/src/operator/svm_output-inl.h +++ b/src/operator/svm_output-inl.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file svm_output-inl.h * \brief * \author Jonas Amaro diff --git a/src/operator/svm_output.cc b/src/operator/svm_output.cc index ead853e214b8..766968dfaf0f 100644 --- a/src/operator/svm_output.cc +++ b/src/operator/svm_output.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file svm_output.cc * \brief * \author Jonas Amaro @@ -62,10 +80,6 @@ Operator *CreateOp(SVMOutputParam param, int dtype) { // DO_BIND_DISPATCH comes from operator_common.h Operator *SVMOutputProp::CreateOperatorEx(Context ctx, std::vector *in_shape, std::vector *in_type) const { - std::vector out_shape, aux_shape; - std::vector out_type, aux_type; - CHECK(InferType(in_type, &out_type, &aux_type)); - CHECK(InferShape(in_shape, &out_shape, &aux_shape)); DO_BIND_DISPATCH(CreateOp, param_, (*in_type)[0]); } @@ -84,4 +98,3 @@ This tutorial demonstrates using SVM as output layer for classification instead } // namespace op } // namespace mxnet - diff --git a/src/operator/svm_output.cu b/src/operator/svm_output.cu index d4b959683287..250df9147f87 100644 --- a/src/operator/svm_output.cu +++ b/src/operator/svm_output.cu @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file svm_output.cu * \brief * \author Jonas Amaro diff --git a/src/operator/swapaxis-inl.h b/src/operator/swapaxis-inl.h index 9595f6e93884..89c724556b8b 100644 --- a/src/operator/swapaxis-inl.h +++ b/src/operator/swapaxis-inl.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file swapaxis-inl.h * \brief * \author Ming Zhang diff --git a/src/operator/swapaxis.cc b/src/operator/swapaxis.cc index 24ea807ef9ce..a6c3e8bff0c7 100644 --- a/src/operator/swapaxis.cc +++ b/src/operator/swapaxis.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file swapaxis.cc * \brief * \author Ming Zhang @@ -21,10 +39,6 @@ Operator* CreateOp(SwapAxisParam param, int dtype) { Operator* SwapAxisProp::CreateOperatorEx(Context ctx, std::vector *in_shape, std::vector *in_type) const { - std::vector out_shape, aux_shape; - std::vector out_type, aux_type; - CHECK(InferShape(in_shape, &out_shape, &aux_shape)); - CHECK(InferType(in_type, &out_type, &aux_type)); DO_BIND_DISPATCH(CreateOp, param_, in_type->at(0)); } diff --git a/src/operator/swapaxis.cu b/src/operator/swapaxis.cu index 93f78c2e733d..e9b105d71ea4 100644 --- a/src/operator/swapaxis.cu +++ b/src/operator/swapaxis.cu @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file swapaxis.cu * \brief * \author Ming Zhang diff --git a/src/operator/tensor/broadcast_reduce-inl.cuh b/src/operator/tensor/broadcast_reduce-inl.cuh index 2ba0eb5cec17..b1a259f9b791 100644 --- a/src/operator/tensor/broadcast_reduce-inl.cuh +++ b/src/operator/tensor/broadcast_reduce-inl.cuh @@ -1,605 +1,624 @@ -/*! - * Copyright (c) 2015-2017 by Contributors - * \file broadcast_reduce-inl.cuh - * \brief CUDA implementations for binary broadcast and reduce - * \author Antti-Pekka Hynninen -*/ -#ifndef MXNET_OPERATOR_TENSOR_BROADCAST_REDUCE_INL_CUH_ -#define MXNET_OPERATOR_TENSOR_BROADCAST_REDUCE_INL_CUH_ - -using namespace mshadow::cuda; - -template -__launch_bounds__(kMaxThreadsPerBlock) -__global__ void binary_broadcast_kernel(const int N, const bool addto, - const DType* __restrict lhs, - const DType* __restrict rhs, DType *out, - const Shape lstride, const Shape rstride, - const Shape oshape) { - for (int idx = blockIdx.x * blockDim.x * unroll + threadIdx.x; idx < N; - idx += blockDim.x * gridDim.x * unroll) - { - int j[unroll]; - int k[unroll]; - DType val[unroll]; - #pragma unroll - for (int i=0;i < unroll;i++) { - unravel_dot(idx + i*blockDim.x, oshape, lstride, rstride, &j[i], &k[i]); - val[i] = OP::Map(lhs[j[i]], rhs[k[i]]); - } - #pragma unroll - for (int i=0;i < unroll;i++) { - if (idx + i*blockDim.x < N) assign(&out[idx + i*blockDim.x], addto, val[i]); - } - - } -} - -template -void BinaryBroadcastComputeImpl(Stream *s, const OpReqType req, - const TBlob& lhs, const TBlob& rhs, const TBlob& out) { - if (req == kNullOp) return; - cudaStream_t stream = Stream::GetStream(s); - int N = out.shape_.Size(); - const int warpSize = 32; - const int unroll = 2; - int nthread = std::min(kMaxThreadsPerBlock, ((N + warpSize - 1)/warpSize)*warpSize ); - int ngrid = std::min(kBaseGridNum, (N + nthread*unroll - 1) / (nthread*unroll)); - Shape lstride = calc_stride(lhs.shape_.get()); - Shape rstride = calc_stride(rhs.shape_.get()); - binary_broadcast_kernel<<>>( - N, req == kAddTo, lhs.dptr(), rhs.dptr(), out.dptr(), lstride, rstride, - out.shape_.get()); -} - -const int nthread_reduce = kMaxThreadsPerBlock; -template -__launch_bounds__(nthread_reduce) -__global__ void reduce_kernel(const int N, const int M, const bool addto, - const DType* __restrict big, DType *small, - const Shape big_shape0, const Shape small_shape, - const Shape big_shape, const Shape big_stride, - const int Mnext, const bool do_transpose) { - extern __shared__ char shTileChar[]; - DType* shTile = (DType*)(shTileChar); - const int tid = threadIdx.x + threadIdx.y*blockDim.x; - const int bx = (do_transpose) ? blockDim.y : blockDim.x; - const int by = (do_transpose) ? blockDim.x : blockDim.y; - const int tidx = (do_transpose) ? tid / by : threadIdx.x; - const int tidy = (do_transpose) ? tid % by : threadIdx.y; - for (int m0 = blockIdx.y; m0 < Mnext; m0 += gridDim.y) { - // This TB handles M range [Mstart, ...., Mend - 1] - const int Mstart = (int)((uint64_t)M*(uint64_t)m0/(uint64_t)Mnext); - const int Mend = (int)((uint64_t)M*(uint64_t)(m0 + 1)/(uint64_t)Mnext); - for (int idx0 = blockIdx.x*bx; idx0 < N; idx0 += bx*gridDim.x) { - int idx = idx0 + tidx; - Shape coord = unravel(idx, small_shape); - int idx_big0 = ravel(coord, big_shape0); - - DType val; - Reducer::SetInitValue(val); - if (idx < N) { - for (int k = tidy + Mstart; k < Mend; k += by*unroll) { - int idx_big[unroll]; - #pragma unroll - for (int u=0;u < unroll;u++) { - idx_big[u] = idx_big0 + unravel_dot(k + u*by, big_shape, big_stride); - } - DType tmp[unroll]; - #pragma unroll - for (int u=0;u < unroll;u++) { - if (k + u*by < Mend) { - tmp[u] = OP::Map(big[idx_big[u]]); - } - } - #pragma unroll - for (int u=0;u < unroll;u++) { - if (k + u*by < Mend) Reducer::Reduce(val, tmp[u]); - } - } - } - - // Shared memory block bx * by. Reduction is along by. Final result is in tidy=0 - if (by > 1) { - // Fix bx to avoid bank conflicts. Assumes warpSize number of banks - const int fbx = (do_transpose && ((bx & (warpSize - 1)) == 0)) ? (bx + 1) : bx; - const int it0 = tidx + tidy*fbx; - shTile[it0] = val; - __syncthreads(); - for (int t=1;t < by;t <<= 1) { - DType tmp; - Reducer::SetInitValue(tmp); - if (tidy + t < by) tmp = shTile[it0 + t*fbx]; - __syncthreads(); - Reducer::Reduce(shTile[it0], tmp); - __syncthreads(); - } - if (idx < N && tidy == 0) { - assign(&small[idx + m0*N], addto, shTile[tidx]); - } - } else { - if (idx < N) { - assign(&small[idx + m0*N], addto, val); - } - } - } - } - -} - -template -__launch_bounds__(nthread_reduce) -__global__ void reduce_kernel(const int N, const int M, const bool addto, - const DType* __restrict big, const DType* __restrict lhs, - const DType* __restrict rhs, DType *small, - const Shape big_shape0, const Shape lhs_shape0, - const Shape rhs_shape0, const Shape small_shape, - const Shape big_shape, const Shape lhs_shape, - const Shape rhs_shape, const Shape big_stride, - const Shape lhs_stride, const Shape rhs_stride, - const int Mnext, const bool do_transpose) { - extern __shared__ char shTileChar[]; - DType* shTile = (DType*)(shTileChar); - const int tid = threadIdx.x + threadIdx.y*blockDim.x; - const int bx = (do_transpose) ? blockDim.y : blockDim.x; - const int by = (do_transpose) ? blockDim.x : blockDim.y; - const int tidx = (do_transpose) ? tid / by : threadIdx.x; - const int tidy = (do_transpose) ? tid % by : threadIdx.y; - for (int m0 = blockIdx.y; m0 < Mnext; m0 += gridDim.y) { - // This TB handles M range [Mstart, ...., Mend - 1] - const int Mstart = (int)((uint64_t)M*(uint64_t)m0/(uint64_t)Mnext); - const int Mend = (int)((uint64_t)M*(uint64_t)(m0 + 1)/(uint64_t)Mnext); - for (int idx0 = blockIdx.x*bx; idx0 < N; idx0 += bx*gridDim.x) { - int idx = idx0 + tidx; - Shape coord = unravel(idx, small_shape); - int idx_big0 = ravel(coord, big_shape0); - int idx_lhs0 = ravel(coord, lhs_shape0); - int idx_rhs0 = ravel(coord, rhs_shape0); - - DType val; - Reducer::SetInitValue(val); - if (idx < N) { - for (int k = tidy + Mstart; k < Mend; k += by*unroll) { - int idx_big[unroll]; - int idx_lhs[unroll]; - int idx_rhs[unroll]; - #pragma unroll - for (int u=0;u < unroll;u++) { - idx_big[u] = idx_big0 + unravel_dot(k + u*by, big_shape, big_stride); - idx_lhs[u] = idx_lhs0 + unravel_dot(k + u*by, lhs_shape, lhs_stride); - idx_rhs[u] = idx_rhs0 + unravel_dot(k + u*by, rhs_shape, rhs_stride); - } - DType tmp[unroll]; - #pragma unroll - for (int u=0;u < unroll;u++) { - if (k + u*by < Mend) { - tmp[u] = OP1::Map(big[idx_big[u]], OP2::Map(lhs[idx_lhs[u]], rhs[idx_rhs[u]])); - } - } - #pragma unroll - for (int u=0;u < unroll;u++) { - if (k + u*by < Mend) Reducer::Reduce(val, tmp[u]); - } - } - } - - // Shared memory block bx * by. Reduction is along by. Final result is in tidy=0 - if (by > 1) { - // Fix bx to avoid bank conflicts. Assumes warpSize number of banks - const int fbx = (do_transpose && ((bx & (warpSize - 1)) == 0)) ? (bx + 1) : bx; - const int it0 = tidx + tidy*fbx; - shTile[it0] = val; - __syncthreads(); - for (int t=1;t < by;t <<= 1) { - DType tmp; - Reducer::SetInitValue(tmp); - if (tidy + t < by) tmp = shTile[it0 + t*fbx]; - __syncthreads(); - Reducer::Reduce(shTile[it0], tmp); - __syncthreads(); - } - if (idx < N && tidy == 0) { - assign(&small[idx + m0*N], addto, shTile[tidx]); - } - } else { - if (idx < N) { - assign(&small[idx + m0*N], addto, val); - } - } - } - } - -} - -// Simple reduction of lines when M is small -template -__launch_bounds__(kMaxThreadsPerBlock) -__global__ void reduce_lines_kernel(const int N, const int M, const bool addto, - const int small_in_stride, const DType* __restrict small_in, DType *small_out) { - for (int idx = threadIdx.x + blockIdx.x*blockDim.x; idx < N; idx += blockDim.x*gridDim.x) { - - DType val; - Reducer::SetInitValue(val); - for (int k = 0; k < M; k++) { - Reducer::Reduce(val, small_in[idx + k*small_in_stride]); - } - - if (idx < N) { - assign(&small_out[idx], addto, val); - } - - } -} - -template -__global__ void reduce_kernel_M1(const int N, const bool addto, - const DType* __restrict big, DType *small, const Shape bshape, - const Shape sshape) { - for (int idx = threadIdx.x + blockIdx.x*blockDim.x; idx < N; idx += blockDim.x*gridDim.x) { - Shape coord = unravel(idx, sshape); - int j = ravel(coord, bshape); - assign(&small[idx], addto, OP::Map(big[j])); - } -} - -template -__global__ void reduce_kernel_M1(const int N, const bool addto, - const DType* __restrict big, - const DType* __restrict lhs, - const DType* __restrict rhs, - DType *small, - const Shape big_shape, - const Shape lhs_shape, - const Shape rhs_shape, - const Shape small_shape) { - for (int idx = threadIdx.x + blockIdx.x*blockDim.x; idx < N; idx += blockDim.x*gridDim.x) { - Shape coord = unravel(idx, small_shape); - int idx_big = ravel(coord, big_shape); - int idx_lhs = ravel(coord, lhs_shape); - int idx_rhs = ravel(coord, rhs_shape); - DType val = OP1::Map(big[idx_big], OP2::Map(lhs[idx_lhs], rhs[idx_rhs])); - assign(&small[idx], addto, val); - } -} - -// Returns the stride with which the fastest dimension is moving. -// Used to detect memory access scatter. -template -MSHADOW_XINLINE int fastest_stride(const Shape& small, const Shape& big, - const Shape& big_stride) { - for (int i = ndim-1; i >= 0; --i) { - if (big[i] != 1) { - return (small[i] == big[i]) ? 1 : big_stride[i]; - } - } - return 1; -} - -// Returns a/b integer division rounded up -template -Type ceil_idiv(const Type a, const Type b) { - return (a + b - 1)/b; -} - -// Configuration for ReduceImpl() -template -struct ReduceImplConfig { - static const int warpSize = 32; - static const int unroll_reduce = 2; - static const int maxLoopPerTB = 64; - int N; - int M; - int Mnext; - struct { - dim3 blockDim; - dim3 gridDim; - int shMemSize; - bool do_transpose; - } kernel_1; - struct { - int blockSize; - int gridSize; - } kernel_2; - size_t workspace_size; - - Shape rshape, rstride; - Shape lhs_shape, lhs_stride; - Shape rhs_shape, rhs_stride; -}; - -static inline uint64_t calc_num_load(const int X, const int Y, const int* strides) { - const int warpSize = ReduceImplConfig<1>::warpSize; - // Number of full warps - uint64_t num_full_warp = X / warpSize; - // Length of the partial warp i.e. number of threads that are performing loads - uint64_t len_part_warp = X % warpSize; - - uint64_t num_load_full = (std::min(warpSize, strides[0]) + - std::min(warpSize, strides[1]) + - std::min(warpSize, strides[2]))*num_full_warp; - - uint64_t num_load_part = - (std::min(len_part_warp, ceil_idiv(len_part_warp*strides[0], warpSize)) + - std::min(len_part_warp, ceil_idiv(len_part_warp*strides[1], warpSize)) + - std::min(len_part_warp, ceil_idiv(len_part_warp*strides[2], warpSize)))* - (len_part_warp != 0); - - uint64_t num_load = (num_load_full + num_load_part)*(uint64_t)Y; - return num_load; -} - -template -ReduceImplConfig ConfigureReduceImpl(const TBlob& small, const TBlob& big, const TBlob* lhs, - const TBlob* rhs) { - - ReduceImplConfig config; - - diff(small.shape_.get(), big.shape_.get(), &config.rshape, &config.rstride); - config.N = small.shape_.Size(); - config.M = config.rshape.Size(); - - bool multiOp = false; - if (lhs != NULL) { - CHECK_NOTNULL(rhs); - diff(small.shape_.get(), lhs->shape_.get(), &config.lhs_shape, - &config.lhs_stride); - diff(small.shape_.get(), rhs->shape_.get(), &config.rhs_shape, - &config.rhs_stride); - multiOp = true; - } - - config.workspace_size = 0; - - if (config.M == 1) { - config.kernel_1.blockDim.x = kMaxThreadsPerBlock; - config.kernel_1.gridDim.x = std::min((unsigned int)kBaseGridNum, - (config.N + config.kernel_1.blockDim.x - 1)/config.kernel_1.blockDim.x); - } else { - - int reduce_strides[3]; - reduce_strides[0] = fastest_stride(small.shape_.get(), big.shape_.get(), - big.shape_.get()); - reduce_strides[1] = (multiOp) ? fastest_stride(small.shape_.get(), - lhs->shape_.get(), lhs->shape_.get()) : 1; - reduce_strides[2] = (multiOp) ? fastest_stride(small.shape_.get(), - rhs->shape_.get(), rhs->shape_.get()) : 1; - - int reduce_strides_transp[3]; - reduce_strides_transp[0] = fastest_stride(small.shape_.get(), config.rshape, - config.rstride); - reduce_strides_transp[1] = (multiOp) ? - fastest_stride(small.shape_.get(), config.lhs_shape, config.lhs_stride) : 1; - reduce_strides_transp[2] = (multiOp) ? - fastest_stride(small.shape_.get(), config.rhs_shape, config.rhs_stride) : 1; - - uint64_t num_load = calc_num_load(config.N, config.M, reduce_strides); - uint64_t num_load_transp = calc_num_load(config.M, config.N, reduce_strides_transp); - - config.Mnext = 1; - config.kernel_1.do_transpose = (num_load > num_load_transp); - - config.kernel_1.blockDim.x = 0; - config.kernel_1.blockDim.y = 0; - - if (config.kernel_1.do_transpose) { - // Fastest thread ID goes through M - // Loop over N has step size config.kernel_1.blockDim.y - if (config.N < 8) { - config.kernel_1.blockDim.y = 1; - } else if (config.N < 256) { - config.kernel_1.blockDim.y = 4; - } else { - if (config.M < 8) { - config.kernel_1.blockDim.x = 1; - } else if (config.M < 256) { - config.kernel_1.blockDim.x = 4; - } else { - config.kernel_1.blockDim.x = config.warpSize; - } - } - } else { - // Fastest thread ID goes through N - // Loop over M has step size config.kernel_1.blockDim.y - if (config.M < 8) { - config.kernel_1.blockDim.y = 1; - } else if (config.M < 256) { - config.kernel_1.blockDim.y = 4; - } else { - if (config.N < 8) { - config.kernel_1.blockDim.x = 1; - } else if (config.N < 256) { - config.kernel_1.blockDim.x = 4; - } else { - config.kernel_1.blockDim.x = config.warpSize; - } - } - } - - if (config.kernel_1.blockDim.x == 0 && config.kernel_1.blockDim.y == 0) { - LOG(FATAL) << "Unable to set blockDim"; - } else if (config.kernel_1.blockDim.x == 0) { - config.kernel_1.blockDim.x = nthread_reduce / config.kernel_1.blockDim.y; - } else if (config.kernel_1.blockDim.y == 0) { - config.kernel_1.blockDim.y = nthread_reduce / config.kernel_1.blockDim.x; - } - - if (config.kernel_1.do_transpose) { - // Fastest thread ID goes through M - config.kernel_1.gridDim.x = std::min((unsigned int)kBaseGridNum, - ceil_idiv(config.N, config.kernel_1.blockDim.y)); - config.kernel_1.gridDim.y = std::min(kBaseGridNum, config.Mnext); - int by = config.kernel_1.blockDim.y; - if (config.kernel_1.blockDim.y % config.warpSize == 0) { - // Fix shared memory bank conflict - by++; - } - config.kernel_1.shMemSize = (config.kernel_1.blockDim.x > 1) ? - config.kernel_1.blockDim.x*by*sizeof(DType) : 0; - // Maximum number of times we want TB to loop in M - // Max size of M-block each TB can handle - int maxMblock = config.kernel_1.blockDim.x*config.maxLoopPerTB; - config.Mnext = (config.M + maxMblock - 1) / maxMblock; - } else { - // Fastest thread ID goes through N - config.kernel_1.gridDim.x = std::min((unsigned int)kBaseGridNum, - ceil_idiv(config.N, config.kernel_1.blockDim.x)); - config.kernel_1.gridDim.y = std::min(kBaseGridNum, config.Mnext); - config.kernel_1.shMemSize = (config.kernel_1.blockDim.y > 1) ? - config.kernel_1.blockDim.x*config.kernel_1.blockDim.y*sizeof(DType) : 0; - // Maximum number of times we want TB to loop in M - // Max size of M-block each TB can handle - int maxMblock = config.kernel_1.blockDim.y*config.maxLoopPerTB; - config.Mnext = (config.M + maxMblock - 1) / maxMblock; - } - - if (config.Mnext > 1) { - // small_dptr[] is N*Mnext*sizeof(DType) bytes - config.workspace_size += config.N*config.Mnext*sizeof(DType); - // Set gridDim.y to Mnext - config.kernel_1.gridDim.y = std::min(kBaseGridNum, config.Mnext); - } - - if (config.Mnext > 1) { - config.kernel_2.blockSize = kMaxThreadsPerBlock; - config.kernel_2.gridSize = std::min((int)kBaseGridNum, - (config.N + config.kernel_2.blockSize - 1)/config.kernel_2.blockSize ); - } - - } - - return config; -} - -#define KERNEL_UNROLL_SWITCH(do_unroll, unrollAmount, unrollVar, ...) \ - if (do_unroll) { \ - const int unrollVar = unrollAmount; \ - {__VA_ARGS__} \ - } else { \ - const int unrollVar = 1; \ - {__VA_ARGS__} \ - } - -template -void ReduceImpl(cudaStream_t stream, const TBlob& small, const OpReqType req, - const TBlob& big, const Tensor& workspace, - const ReduceImplConfig& config) { - if (config.M == 1) { - reduce_kernel_M1 - <<< config.kernel_1.gridDim, config.kernel_1.blockDim, 0, stream >>>( - config.N, req == kAddTo, big.dptr(), small.dptr(), big.shape_.get(), - small.shape_.get()); - } else { - - DType* small_dptr = small.dptr(); - bool addto = (req == kAddTo); - if (config.Mnext > 1) { - // small_dptr[] is N*Mnext*sizeof(DType) bytes - small_dptr = reinterpret_cast(workspace.dptr_); - addto = false; - // Check that the workspace is contigiuous - CHECK_EQ(workspace.CheckContiguous(), true); - // Check that we have enough storage - CHECK_GE(workspace.size(0), config.workspace_size); - } - - const int by = (config.kernel_1.do_transpose) ? - config.kernel_1.blockDim.x : config.kernel_1.blockDim.y; - const bool do_unroll = ( config.M / (by*config.Mnext) >= config.unroll_reduce ); - KERNEL_UNROLL_SWITCH(do_unroll, ReduceImplConfig::unroll_reduce, UNROLL, { - reduce_kernel - <<< config.kernel_1.gridDim, config.kernel_1.blockDim, config.kernel_1.shMemSize, stream>>>( - config.N, config.M, addto, big.dptr(), small_dptr, big.shape_.get(), - small.shape_.get(), config.rshape, config.rstride, config.Mnext, - config.kernel_1.do_transpose); - }); - - if (config.Mnext > 1) { - reduce_lines_kernel - <<< config.kernel_2.gridSize, config.kernel_2.blockSize, 0, stream >>> - (config.N, config.Mnext, req == kAddTo, config.N, small_dptr, small.dptr()); - } - } -} - -template -void ReduceImpl(cudaStream_t stream, const TBlob& small, const TBlob& lhs, const TBlob& rhs, - const OpReqType req, const TBlob& big, const Tensor& workspace, - const ReduceImplConfig& config) { - if (config.M == 1) { - reduce_kernel_M1 - <<< config.kernel_1.gridDim, config.kernel_1.blockDim, 0, stream >>>( - config.N, req == kAddTo, big.dptr(), lhs.dptr(), rhs.dptr(), - small.dptr(), big.shape_.get(), lhs.shape_.get(), - rhs.shape_.get(), small.shape_.get()); - } else { - DType* small_dptr = small.dptr(); - bool addto = (req == kAddTo); - if (config.Mnext > 1) { - // small_dptr[] is N*Mnext*sizeof(DType) bytes - small_dptr = reinterpret_cast(workspace.dptr_); - addto = false; - // Check that the workspace is contigiuous - CHECK_EQ(workspace.CheckContiguous(), true); - // Check that we have enough storage - CHECK_GE(workspace.size(0), config.workspace_size); - } - - const int by = (config.kernel_1.do_transpose) ? - config.kernel_1.blockDim.x : config.kernel_1.blockDim.y; - const bool do_unroll = ( config.M / (by*config.Mnext) >= config.unroll_reduce ); - KERNEL_UNROLL_SWITCH(do_unroll, ReduceImplConfig::unroll_reduce, UNROLL, { - reduce_kernel - <<< config.kernel_1.gridDim, config.kernel_1.blockDim, config.kernel_1.shMemSize, stream>>>( - config.N, config.M, addto, big.dptr(), lhs.dptr(), rhs.dptr(), - small_dptr, big.shape_.get(), lhs.shape_.get(), - rhs.shape_.get(), small.shape_.get(), config.rshape, config.lhs_shape, - config.rhs_shape, config.rstride, config.lhs_stride, config.rhs_stride, config.Mnext, - config.kernel_1.do_transpose); - }); - - if (config.Mnext > 1) { - reduce_lines_kernel - <<< config.kernel_2.gridSize, config.kernel_2.blockSize, 0, stream >>> - (config.N, config.Mnext, req == kAddTo, config.N, small_dptr, small.dptr()); - } - } -} - -#undef KERNEL_UNROLL_SWITCH - -template -void Reduce(Stream *s, const TBlob& small, const OpReqType req, - const Tensor& workspace, const TBlob& big) { - if (req == kNullOp) return; - cudaStream_t stream = Stream::GetStream(s); - ReduceImplConfig config = ConfigureReduceImpl(small, big, NULL, NULL); - ReduceImpl(stream, small, req, big, workspace, config); -} - -template -void Reduce(Stream *s, const TBlob& small, const OpReqType req, - const Tensor& workspace, const TBlob& big, - const TBlob& lhs, const TBlob& rhs) { - if (req == kNullOp) return; - cudaStream_t stream = Stream::GetStream(s); - ReduceImplConfig config = ConfigureReduceImpl(small, big, &lhs, &rhs); - ReduceImpl(stream, small, lhs, rhs, req, big, workspace, config); -} - -template -size_t ReduceWorkspaceSize(Stream *s, const TBlob& small, const OpReqType req, - const TBlob& big) { - if (req == kNullOp) return 0; - ReduceImplConfig config = ConfigureReduceImpl(small, big, NULL, NULL); - return config.workspace_size; -} - -template -size_t ReduceWorkspaceSize(Stream *s, const TBlob& small, const OpReqType req, - const TBlob& big, const TBlob& lhs, const TBlob& rhs) { - if (req == kNullOp) return 0; - ReduceImplConfig config = ConfigureReduceImpl(small, big, &lhs, &rhs); - return config.workspace_size; -} - -#endif //MXNET_OPERATOR_TENSOR_BROADCAST_REDUCE_INL_CUH_ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +/*! + * Copyright (c) 2015-2017 by Contributors + * \file broadcast_reduce-inl.cuh + * \brief CUDA implementations for binary broadcast and reduce + * \author Antti-Pekka Hynninen +*/ +#ifndef MXNET_OPERATOR_TENSOR_BROADCAST_REDUCE_INL_CUH_ +#define MXNET_OPERATOR_TENSOR_BROADCAST_REDUCE_INL_CUH_ + +using namespace mshadow::cuda; + +template +__launch_bounds__(kMaxThreadsPerBlock) +__global__ void binary_broadcast_kernel(const int N, const bool addto, + const DType* __restrict lhs, + const DType* __restrict rhs, DType *out, + const Shape lstride, const Shape rstride, + const Shape oshape) { + for (int idx = blockIdx.x * blockDim.x * unroll + threadIdx.x; idx < N; + idx += blockDim.x * gridDim.x * unroll) + { + int j[unroll]; + int k[unroll]; + DType val[unroll]; + #pragma unroll + for (int i=0;i < unroll;i++) { + unravel_dot(idx + i*blockDim.x, oshape, lstride, rstride, &j[i], &k[i]); + val[i] = OP::Map(lhs[j[i]], rhs[k[i]]); + } + #pragma unroll + for (int i=0;i < unroll;i++) { + if (idx + i*blockDim.x < N) assign(&out[idx + i*blockDim.x], addto, val[i]); + } + + } +} + +template +void BinaryBroadcastComputeImpl(Stream *s, const OpReqType req, + const TBlob& lhs, const TBlob& rhs, const TBlob& out) { + if (req == kNullOp) return; + cudaStream_t stream = Stream::GetStream(s); + int N = out.shape_.Size(); + const int warpSize = 32; + const int unroll = 2; + int nthread = std::min(kMaxThreadsPerBlock, ((N + warpSize - 1)/warpSize)*warpSize ); + int ngrid = std::min(kBaseGridNum, (N + nthread*unroll - 1) / (nthread*unroll)); + Shape lstride = calc_stride(lhs.shape_.get()); + Shape rstride = calc_stride(rhs.shape_.get()); + binary_broadcast_kernel<<>>( + N, req == kAddTo, lhs.dptr(), rhs.dptr(), out.dptr(), lstride, rstride, + out.shape_.get()); +} + +const int nthread_reduce = kMaxThreadsPerBlock; +template +__launch_bounds__(nthread_reduce) +__global__ void reduce_kernel(const int N, const int M, const bool addto, + const DType* __restrict big, DType *small, + const Shape big_shape0, const Shape small_shape, + const Shape big_shape, const Shape big_stride, + const int Mnext, const bool do_transpose) { + extern __shared__ char shTileChar[]; + DType* shTile = (DType*)(shTileChar); + const int tid = threadIdx.x + threadIdx.y*blockDim.x; + const int bx = (do_transpose) ? blockDim.y : blockDim.x; + const int by = (do_transpose) ? blockDim.x : blockDim.y; + const int tidx = (do_transpose) ? tid / by : threadIdx.x; + const int tidy = (do_transpose) ? tid % by : threadIdx.y; + for (int m0 = blockIdx.y; m0 < Mnext; m0 += gridDim.y) { + // This TB handles M range [Mstart, ...., Mend - 1] + const int Mstart = (int)((uint64_t)M*(uint64_t)m0/(uint64_t)Mnext); + const int Mend = (int)((uint64_t)M*(uint64_t)(m0 + 1)/(uint64_t)Mnext); + for (int idx0 = blockIdx.x*bx; idx0 < N; idx0 += bx*gridDim.x) { + int idx = idx0 + tidx; + Shape coord = unravel(idx, small_shape); + int idx_big0 = ravel(coord, big_shape0); + + DType val; + Reducer::SetInitValue(val); + if (idx < N) { + for (int k = tidy + Mstart; k < Mend; k += by*unroll) { + int idx_big[unroll]; + #pragma unroll + for (int u=0;u < unroll;u++) { + idx_big[u] = idx_big0 + unravel_dot(k + u*by, big_shape, big_stride); + } + DType tmp[unroll]; + #pragma unroll + for (int u=0;u < unroll;u++) { + if (k + u*by < Mend) { + tmp[u] = OP::Map(big[idx_big[u]]); + } + } + #pragma unroll + for (int u=0;u < unroll;u++) { + if (k + u*by < Mend) Reducer::Reduce(val, tmp[u]); + } + } + } + + // Shared memory block bx * by. Reduction is along by. Final result is in tidy=0 + if (by > 1) { + // Fix bx to avoid bank conflicts. Assumes warpSize number of banks + const int fbx = (do_transpose && ((bx & (warpSize - 1)) == 0)) ? (bx + 1) : bx; + const int it0 = tidx + tidy*fbx; + shTile[it0] = val; + __syncthreads(); + for (int t=1;t < by;t <<= 1) { + DType tmp; + Reducer::SetInitValue(tmp); + if (tidy + t < by) tmp = shTile[it0 + t*fbx]; + __syncthreads(); + Reducer::Reduce(shTile[it0], tmp); + __syncthreads(); + } + if (idx < N && tidy == 0) { + assign(&small[idx + m0*N], addto, shTile[tidx]); + } + } else { + if (idx < N) { + assign(&small[idx + m0*N], addto, val); + } + } + } + } + +} + +template +__launch_bounds__(nthread_reduce) +__global__ void reduce_kernel(const int N, const int M, const bool addto, + const DType* __restrict big, const DType* __restrict lhs, + const DType* __restrict rhs, DType *small, + const Shape big_shape0, const Shape lhs_shape0, + const Shape rhs_shape0, const Shape small_shape, + const Shape big_shape, const Shape lhs_shape, + const Shape rhs_shape, const Shape big_stride, + const Shape lhs_stride, const Shape rhs_stride, + const int Mnext, const bool do_transpose) { + extern __shared__ char shTileChar[]; + DType* shTile = (DType*)(shTileChar); + const int tid = threadIdx.x + threadIdx.y*blockDim.x; + const int bx = (do_transpose) ? blockDim.y : blockDim.x; + const int by = (do_transpose) ? blockDim.x : blockDim.y; + const int tidx = (do_transpose) ? tid / by : threadIdx.x; + const int tidy = (do_transpose) ? tid % by : threadIdx.y; + for (int m0 = blockIdx.y; m0 < Mnext; m0 += gridDim.y) { + // This TB handles M range [Mstart, ...., Mend - 1] + const int Mstart = (int)((uint64_t)M*(uint64_t)m0/(uint64_t)Mnext); + const int Mend = (int)((uint64_t)M*(uint64_t)(m0 + 1)/(uint64_t)Mnext); + for (int idx0 = blockIdx.x*bx; idx0 < N; idx0 += bx*gridDim.x) { + int idx = idx0 + tidx; + Shape coord = unravel(idx, small_shape); + int idx_big0 = ravel(coord, big_shape0); + int idx_lhs0 = ravel(coord, lhs_shape0); + int idx_rhs0 = ravel(coord, rhs_shape0); + + DType val; + Reducer::SetInitValue(val); + if (idx < N) { + for (int k = tidy + Mstart; k < Mend; k += by*unroll) { + int idx_big[unroll]; + int idx_lhs[unroll]; + int idx_rhs[unroll]; + #pragma unroll + for (int u=0;u < unroll;u++) { + idx_big[u] = idx_big0 + unravel_dot(k + u*by, big_shape, big_stride); + idx_lhs[u] = idx_lhs0 + unravel_dot(k + u*by, lhs_shape, lhs_stride); + idx_rhs[u] = idx_rhs0 + unravel_dot(k + u*by, rhs_shape, rhs_stride); + } + DType tmp[unroll]; + #pragma unroll + for (int u=0;u < unroll;u++) { + if (k + u*by < Mend) { + tmp[u] = OP1::Map(big[idx_big[u]], OP2::Map(lhs[idx_lhs[u]], rhs[idx_rhs[u]])); + } + } + #pragma unroll + for (int u=0;u < unroll;u++) { + if (k + u*by < Mend) Reducer::Reduce(val, tmp[u]); + } + } + } + + // Shared memory block bx * by. Reduction is along by. Final result is in tidy=0 + if (by > 1) { + // Fix bx to avoid bank conflicts. Assumes warpSize number of banks + const int fbx = (do_transpose && ((bx & (warpSize - 1)) == 0)) ? (bx + 1) : bx; + const int it0 = tidx + tidy*fbx; + shTile[it0] = val; + __syncthreads(); + for (int t=1;t < by;t <<= 1) { + DType tmp; + Reducer::SetInitValue(tmp); + if (tidy + t < by) tmp = shTile[it0 + t*fbx]; + __syncthreads(); + Reducer::Reduce(shTile[it0], tmp); + __syncthreads(); + } + if (idx < N && tidy == 0) { + assign(&small[idx + m0*N], addto, shTile[tidx]); + } + } else { + if (idx < N) { + assign(&small[idx + m0*N], addto, val); + } + } + } + } + +} + +// Simple reduction of lines when M is small +template +__launch_bounds__(kMaxThreadsPerBlock) +__global__ void reduce_lines_kernel(const int N, const int M, const bool addto, + const int small_in_stride, const DType* __restrict small_in, DType *small_out) { + for (int idx = threadIdx.x + blockIdx.x*blockDim.x; idx < N; idx += blockDim.x*gridDim.x) { + + DType val; + Reducer::SetInitValue(val); + for (int k = 0; k < M; k++) { + Reducer::Reduce(val, small_in[idx + k*small_in_stride]); + } + + if (idx < N) { + assign(&small_out[idx], addto, val); + } + + } +} + +template +__global__ void reduce_kernel_M1(const int N, const bool addto, + const DType* __restrict big, DType *small, const Shape bshape, + const Shape sshape) { + for (int idx = threadIdx.x + blockIdx.x*blockDim.x; idx < N; idx += blockDim.x*gridDim.x) { + Shape coord = unravel(idx, sshape); + int j = ravel(coord, bshape); + assign(&small[idx], addto, OP::Map(big[j])); + } +} + +template +__global__ void reduce_kernel_M1(const int N, const bool addto, + const DType* __restrict big, + const DType* __restrict lhs, + const DType* __restrict rhs, + DType *small, + const Shape big_shape, + const Shape lhs_shape, + const Shape rhs_shape, + const Shape small_shape) { + for (int idx = threadIdx.x + blockIdx.x*blockDim.x; idx < N; idx += blockDim.x*gridDim.x) { + Shape coord = unravel(idx, small_shape); + int idx_big = ravel(coord, big_shape); + int idx_lhs = ravel(coord, lhs_shape); + int idx_rhs = ravel(coord, rhs_shape); + DType val = OP1::Map(big[idx_big], OP2::Map(lhs[idx_lhs], rhs[idx_rhs])); + assign(&small[idx], addto, val); + } +} + +// Returns the stride with which the fastest dimension is moving. +// Used to detect memory access scatter. +template +MSHADOW_XINLINE int fastest_stride(const Shape& small, const Shape& big, + const Shape& big_stride) { + for (int i = ndim-1; i >= 0; --i) { + if (big[i] != 1) { + return (small[i] == big[i]) ? 1 : big_stride[i]; + } + } + return 1; +} + +// Returns a/b integer division rounded up +template +Type ceil_idiv(const Type a, const Type b) { + return (a + b - 1)/b; +} + +// Configuration for ReduceImpl() +template +struct ReduceImplConfig { + static const int warpSize = 32; + static const int unroll_reduce = 2; + static const int maxLoopPerTB = 64; + int N; + int M; + int Mnext; + struct { + dim3 blockDim; + dim3 gridDim; + int shMemSize; + bool do_transpose; + } kernel_1; + struct { + int blockSize; + int gridSize; + } kernel_2; + size_t workspace_size; + + Shape rshape, rstride; + Shape lhs_shape, lhs_stride; + Shape rhs_shape, rhs_stride; +}; + +static inline uint64_t calc_num_load(const int X, const int Y, const int* strides) { + const int warpSize = ReduceImplConfig<1>::warpSize; + // Number of full warps + uint64_t num_full_warp = X / warpSize; + // Length of the partial warp i.e. number of threads that are performing loads + uint64_t len_part_warp = X % warpSize; + + uint64_t num_load_full = (std::min(warpSize, strides[0]) + + std::min(warpSize, strides[1]) + + std::min(warpSize, strides[2]))*num_full_warp; + + uint64_t num_load_part = + (std::min(len_part_warp, ceil_idiv(len_part_warp*strides[0], warpSize)) + + std::min(len_part_warp, ceil_idiv(len_part_warp*strides[1], warpSize)) + + std::min(len_part_warp, ceil_idiv(len_part_warp*strides[2], warpSize)))* + (len_part_warp != 0); + + uint64_t num_load = (num_load_full + num_load_part)*(uint64_t)Y; + return num_load; +} + +template +ReduceImplConfig ConfigureReduceImpl(const TBlob& small, const TBlob& big, const TBlob* lhs, + const TBlob* rhs) { + + ReduceImplConfig config; + + diff(small.shape_.get(), big.shape_.get(), &config.rshape, &config.rstride); + config.N = small.shape_.Size(); + config.M = config.rshape.Size(); + + bool multiOp = false; + if (lhs != NULL) { + CHECK_NOTNULL(rhs); + diff(small.shape_.get(), lhs->shape_.get(), &config.lhs_shape, + &config.lhs_stride); + diff(small.shape_.get(), rhs->shape_.get(), &config.rhs_shape, + &config.rhs_stride); + multiOp = true; + } + + config.workspace_size = 0; + + if (config.M == 1) { + config.kernel_1.blockDim.x = kMaxThreadsPerBlock; + config.kernel_1.gridDim.x = std::min((unsigned int)kBaseGridNum, + (config.N + config.kernel_1.blockDim.x - 1)/config.kernel_1.blockDim.x); + } else { + + int reduce_strides[3]; + reduce_strides[0] = fastest_stride(small.shape_.get(), big.shape_.get(), + big.shape_.get()); + reduce_strides[1] = (multiOp) ? fastest_stride(small.shape_.get(), + lhs->shape_.get(), lhs->shape_.get()) : 1; + reduce_strides[2] = (multiOp) ? fastest_stride(small.shape_.get(), + rhs->shape_.get(), rhs->shape_.get()) : 1; + + int reduce_strides_transp[3]; + reduce_strides_transp[0] = fastest_stride(small.shape_.get(), config.rshape, + config.rstride); + reduce_strides_transp[1] = (multiOp) ? + fastest_stride(small.shape_.get(), config.lhs_shape, config.lhs_stride) : 1; + reduce_strides_transp[2] = (multiOp) ? + fastest_stride(small.shape_.get(), config.rhs_shape, config.rhs_stride) : 1; + + uint64_t num_load = calc_num_load(config.N, config.M, reduce_strides); + uint64_t num_load_transp = calc_num_load(config.M, config.N, reduce_strides_transp); + + config.Mnext = 1; + config.kernel_1.do_transpose = (num_load > num_load_transp); + + config.kernel_1.blockDim.x = 0; + config.kernel_1.blockDim.y = 0; + + if (config.kernel_1.do_transpose) { + // Fastest thread ID goes through M + // Loop over N has step size config.kernel_1.blockDim.y + if (config.N < 8) { + config.kernel_1.blockDim.y = 1; + } else if (config.N < 256) { + config.kernel_1.blockDim.y = 4; + } else { + if (config.M < 8) { + config.kernel_1.blockDim.x = 1; + } else if (config.M < 256) { + config.kernel_1.blockDim.x = 4; + } else { + config.kernel_1.blockDim.x = config.warpSize; + } + } + } else { + // Fastest thread ID goes through N + // Loop over M has step size config.kernel_1.blockDim.y + if (config.M < 8) { + config.kernel_1.blockDim.y = 1; + } else if (config.M < 256) { + config.kernel_1.blockDim.y = 4; + } else { + if (config.N < 8) { + config.kernel_1.blockDim.x = 1; + } else if (config.N < 256) { + config.kernel_1.blockDim.x = 4; + } else { + config.kernel_1.blockDim.x = config.warpSize; + } + } + } + + if (config.kernel_1.blockDim.x == 0 && config.kernel_1.blockDim.y == 0) { + LOG(FATAL) << "Unable to set blockDim"; + } else if (config.kernel_1.blockDim.x == 0) { + config.kernel_1.blockDim.x = nthread_reduce / config.kernel_1.blockDim.y; + } else if (config.kernel_1.blockDim.y == 0) { + config.kernel_1.blockDim.y = nthread_reduce / config.kernel_1.blockDim.x; + } + + if (config.kernel_1.do_transpose) { + // Fastest thread ID goes through M + config.kernel_1.gridDim.x = std::min((unsigned int)kBaseGridNum, + ceil_idiv(config.N, config.kernel_1.blockDim.y)); + config.kernel_1.gridDim.y = std::min(kBaseGridNum, config.Mnext); + int by = config.kernel_1.blockDim.y; + if (config.kernel_1.blockDim.y % config.warpSize == 0) { + // Fix shared memory bank conflict + by++; + } + config.kernel_1.shMemSize = (config.kernel_1.blockDim.x > 1) ? + config.kernel_1.blockDim.x*by*sizeof(DType) : 0; + // Maximum number of times we want TB to loop in M + // Max size of M-block each TB can handle + int maxMblock = config.kernel_1.blockDim.x*config.maxLoopPerTB; + config.Mnext = (config.M + maxMblock - 1) / maxMblock; + } else { + // Fastest thread ID goes through N + config.kernel_1.gridDim.x = std::min((unsigned int)kBaseGridNum, + ceil_idiv(config.N, config.kernel_1.blockDim.x)); + config.kernel_1.gridDim.y = std::min(kBaseGridNum, config.Mnext); + config.kernel_1.shMemSize = (config.kernel_1.blockDim.y > 1) ? + config.kernel_1.blockDim.x*config.kernel_1.blockDim.y*sizeof(DType) : 0; + // Maximum number of times we want TB to loop in M + // Max size of M-block each TB can handle + int maxMblock = config.kernel_1.blockDim.y*config.maxLoopPerTB; + config.Mnext = (config.M + maxMblock - 1) / maxMblock; + } + + if (config.Mnext > 1) { + // small_dptr[] is N*Mnext*sizeof(DType) bytes + config.workspace_size += config.N*config.Mnext*sizeof(DType); + // Set gridDim.y to Mnext + config.kernel_1.gridDim.y = std::min(kBaseGridNum, config.Mnext); + } + + if (config.Mnext > 1) { + config.kernel_2.blockSize = kMaxThreadsPerBlock; + config.kernel_2.gridSize = std::min((int)kBaseGridNum, + (config.N + config.kernel_2.blockSize - 1)/config.kernel_2.blockSize ); + } + + } + + return config; +} + +#define KERNEL_UNROLL_SWITCH(do_unroll, unrollAmount, unrollVar, ...) \ + if (do_unroll) { \ + const int unrollVar = unrollAmount; \ + {__VA_ARGS__} \ + } else { \ + const int unrollVar = 1; \ + {__VA_ARGS__} \ + } + +template +void ReduceImpl(cudaStream_t stream, const TBlob& small, const OpReqType req, + const TBlob& big, const Tensor& workspace, + const ReduceImplConfig& config) { + if (config.M == 1) { + reduce_kernel_M1 + <<< config.kernel_1.gridDim, config.kernel_1.blockDim, 0, stream >>>( + config.N, req == kAddTo, big.dptr(), small.dptr(), big.shape_.get(), + small.shape_.get()); + } else { + + DType* small_dptr = small.dptr(); + bool addto = (req == kAddTo); + if (config.Mnext > 1) { + // small_dptr[] is N*Mnext*sizeof(DType) bytes + small_dptr = reinterpret_cast(workspace.dptr_); + addto = false; + // Check that the workspace is contigiuous + CHECK_EQ(workspace.CheckContiguous(), true); + // Check that we have enough storage + CHECK_GE(workspace.size(0), config.workspace_size); + } + + const int by = (config.kernel_1.do_transpose) ? + config.kernel_1.blockDim.x : config.kernel_1.blockDim.y; + const bool do_unroll = ( config.M / (by*config.Mnext) >= config.unroll_reduce ); + KERNEL_UNROLL_SWITCH(do_unroll, ReduceImplConfig::unroll_reduce, UNROLL, { + reduce_kernel + <<< config.kernel_1.gridDim, config.kernel_1.blockDim, config.kernel_1.shMemSize, stream>>>( + config.N, config.M, addto, big.dptr(), small_dptr, big.shape_.get(), + small.shape_.get(), config.rshape, config.rstride, config.Mnext, + config.kernel_1.do_transpose); + }); + + if (config.Mnext > 1) { + reduce_lines_kernel + <<< config.kernel_2.gridSize, config.kernel_2.blockSize, 0, stream >>> + (config.N, config.Mnext, req == kAddTo, config.N, small_dptr, small.dptr()); + } + } +} + +template +void ReduceImpl(cudaStream_t stream, const TBlob& small, const TBlob& lhs, const TBlob& rhs, + const OpReqType req, const TBlob& big, const Tensor& workspace, + const ReduceImplConfig& config) { + if (config.M == 1) { + reduce_kernel_M1 + <<< config.kernel_1.gridDim, config.kernel_1.blockDim, 0, stream >>>( + config.N, req == kAddTo, big.dptr(), lhs.dptr(), rhs.dptr(), + small.dptr(), big.shape_.get(), lhs.shape_.get(), + rhs.shape_.get(), small.shape_.get()); + } else { + DType* small_dptr = small.dptr(); + bool addto = (req == kAddTo); + if (config.Mnext > 1) { + // small_dptr[] is N*Mnext*sizeof(DType) bytes + small_dptr = reinterpret_cast(workspace.dptr_); + addto = false; + // Check that the workspace is contigiuous + CHECK_EQ(workspace.CheckContiguous(), true); + // Check that we have enough storage + CHECK_GE(workspace.size(0), config.workspace_size); + } + + const int by = (config.kernel_1.do_transpose) ? + config.kernel_1.blockDim.x : config.kernel_1.blockDim.y; + const bool do_unroll = ( config.M / (by*config.Mnext) >= config.unroll_reduce ); + KERNEL_UNROLL_SWITCH(do_unroll, ReduceImplConfig::unroll_reduce, UNROLL, { + reduce_kernel + <<< config.kernel_1.gridDim, config.kernel_1.blockDim, config.kernel_1.shMemSize, stream>>>( + config.N, config.M, addto, big.dptr(), lhs.dptr(), rhs.dptr(), + small_dptr, big.shape_.get(), lhs.shape_.get(), + rhs.shape_.get(), small.shape_.get(), config.rshape, config.lhs_shape, + config.rhs_shape, config.rstride, config.lhs_stride, config.rhs_stride, config.Mnext, + config.kernel_1.do_transpose); + }); + + if (config.Mnext > 1) { + reduce_lines_kernel + <<< config.kernel_2.gridSize, config.kernel_2.blockSize, 0, stream >>> + (config.N, config.Mnext, req == kAddTo, config.N, small_dptr, small.dptr()); + } + } +} + +#undef KERNEL_UNROLL_SWITCH + +template +void Reduce(Stream *s, const TBlob& small, const OpReqType req, + const Tensor& workspace, const TBlob& big) { + if (req == kNullOp) return; + cudaStream_t stream = Stream::GetStream(s); + ReduceImplConfig config = ConfigureReduceImpl(small, big, NULL, NULL); + ReduceImpl(stream, small, req, big, workspace, config); +} + +template +void Reduce(Stream *s, const TBlob& small, const OpReqType req, + const Tensor& workspace, const TBlob& big, + const TBlob& lhs, const TBlob& rhs) { + if (req == kNullOp) return; + cudaStream_t stream = Stream::GetStream(s); + ReduceImplConfig config = ConfigureReduceImpl(small, big, &lhs, &rhs); + ReduceImpl(stream, small, lhs, rhs, req, big, workspace, config); +} + +template +size_t ReduceWorkspaceSize(Stream *s, const TBlob& small, const OpReqType req, + const TBlob& big) { + if (req == kNullOp) return 0; + ReduceImplConfig config = ConfigureReduceImpl(small, big, NULL, NULL); + return config.workspace_size; +} + +template +size_t ReduceWorkspaceSize(Stream *s, const TBlob& small, const OpReqType req, + const TBlob& big, const TBlob& lhs, const TBlob& rhs) { + if (req == kNullOp) return 0; + ReduceImplConfig config = ConfigureReduceImpl(small, big, &lhs, &rhs); + return config.workspace_size; +} + +#endif //MXNET_OPERATOR_TENSOR_BROADCAST_REDUCE_INL_CUH_ diff --git a/src/operator/tensor/broadcast_reduce-inl.h b/src/operator/tensor/broadcast_reduce-inl.h index 84d420bc865f..744308d9a486 100644 --- a/src/operator/tensor/broadcast_reduce-inl.h +++ b/src/operator/tensor/broadcast_reduce-inl.h @@ -1,7 +1,25 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015-2017 by Contributors * \file broadcast_reduce_kernel.h - * \brief Function defintion of elementwise unary operators + * \brief Function definition of elementwise unary operators */ #ifndef MXNET_OPERATOR_TENSOR_BROADCAST_REDUCE_INL_H_ #define MXNET_OPERATOR_TENSOR_BROADCAST_REDUCE_INL_H_ diff --git a/src/operator/tensor/broadcast_reduce_op.h b/src/operator/tensor/broadcast_reduce_op.h index 186ee236c6ac..aa678fd7dd82 100644 --- a/src/operator/tensor/broadcast_reduce_op.h +++ b/src/operator/tensor/broadcast_reduce_op.h @@ -1,7 +1,25 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file elementwise_unary_op-inl.h - * \brief Function defintion of elementwise unary operators + * \brief Function definition of elementwise unary operators */ #ifndef MXNET_OPERATOR_TENSOR_BROADCAST_REDUCE_OP_H_ #define MXNET_OPERATOR_TENSOR_BROADCAST_REDUCE_OP_H_ @@ -20,6 +38,7 @@ namespace op { struct ReduceAxesParam : public dmlc::Parameter { TShape axis; bool keepdims; + bool exclude; DMLC_DECLARE_PARAMETER(ReduceAxesParam) { DMLC_DECLARE_FIELD(axis).set_default(TShape()) .describe(R"code(The axis or axes along which to perform the reduction. @@ -30,10 +49,17 @@ struct ReduceAxesParam : public dmlc::Parameter { If `axis` is int, a reduction is performed on a particular axis. If `axis` is a tuple of ints, a reduction is performed on all the axes - specified in the tuple.)code"); + specified in the tuple. + + If `exclude` is true, reduction will be performed on the axes that are + NOT in axis instead. + + Negative values means indexing from right to left.)code"); DMLC_DECLARE_FIELD(keepdims).set_default(false) .describe("If this is set to `True`, the reduced axes are left " "in the result as dimension with size one."); + DMLC_DECLARE_FIELD(exclude).set_default(false) + .describe("Whether to perform reduction on axis that are NOT in axis instead."); } }; @@ -113,28 +139,28 @@ inline TShape AxisShapeCompact(TShape shape, int *axis, bool allow_2d) { return mshadow::Shape3(leading, M, trailing); } -inline TShape ReduceAxisShapeImpl(const ReduceAxisParam& param, const TShape& ishape) { - if (!param.axis || ishape.ndim() == 1) { - if (param.keepdims) { +inline TShape ReduceAxisShapeImpl(const TShape& ishape, const dmlc::optional& axis, + bool keepdims) { + if (!axis || ishape.ndim() == 1) { + if (keepdims) { return TShape(ishape.ndim()); - } else { - return mshadow::Shape1(1); - } - } else { - int axis = CheckAxis(param.axis.value(), ishape.ndim()); - if (param.keepdims) { - TShape oshape = ishape; - oshape[axis] = 1; - return oshape; - } else { - TShape oshape(ishape.ndim() - 1); - for (int i = 0; i < axis; ++i) oshape[i] = ishape[i]; - for (int i = axis+1; i < static_cast(ishape.ndim()); ++i) { - oshape[i-1] = ishape[i]; - } - return oshape; } + return mshadow::Shape1(1); } + + int new_axis = CheckAxis(axis.value(), ishape.ndim()); + if (keepdims) { + TShape oshape = ishape; + oshape[new_axis] = 1; + return oshape; + } + + TShape oshape(ishape.ndim() - 1); + for (int i = 0; i < new_axis; ++i) oshape[i] = ishape[i]; + for (int i = new_axis+1; i < static_cast(ishape.ndim()); ++i) { + oshape[i-1] = ishape[i]; + } + return oshape; } inline bool ReduceAxisShape(const nnvm::NodeAttrs& attrs, @@ -146,46 +172,88 @@ inline bool ReduceAxisShape(const nnvm::NodeAttrs& attrs, if (ishape.ndim() == 0) return false; const ReduceAxisParam& param = nnvm::get(attrs.parsed); - SHAPE_ASSIGN_CHECK(*out_attrs, 0, ReduceAxisShapeImpl(param, ishape)); + SHAPE_ASSIGN_CHECK(*out_attrs, 0, + ReduceAxisShapeImpl(ishape, param.axis, param.keepdims)); return true; } -inline bool ReduceAxesShape(const nnvm::NodeAttrs& attrs, - std::vector *in_attrs, - std::vector *out_attrs) { - CHECK_EQ(in_attrs->size(), 1U); - CHECK_EQ(out_attrs->size(), 1U); - if ((*in_attrs)[0].ndim() == 0) return false; - const ReduceAxesParam& param = nnvm::get(attrs.parsed); - TShape &ishape = (*in_attrs)[0]; - TShape oshape; - if (param.axis.ndim() == 0) { - if (param.keepdims) { - oshape = TShape(ishape.ndim()); +inline TShape ReduceAxesShapeImpl(const TShape& ishape, const TShape& axis, + bool keepdims, bool exclude) { + if (axis.ndim() == 0) { + if (keepdims) { + return TShape(ishape.ndim()); } else { - oshape = TShape(1); + return TShape(1); } + } + + TShape axes(axis); + for (index_t i = 0; i < axes.ndim(); i++) { + if (axes[i] < 0) { + axes[i] += ishape.ndim(); + } + } + std::sort(axes.begin(), axes.end()); + + for (index_t i = 1; i < axes.ndim(); i++) { + CHECK_LT(axes[i-1], axes[i]) + << "Reduction axes have duplicates " + << axes; + } + CHECK_LT(axes[axes.ndim()-1], ishape.ndim()) + << "Reduction axis " << axes[axes.ndim()-1] + << " Exceeds input dimensions " << ishape; + CHECK_GE(axes[0], 0) + << "Reduction axis " << axis + << " Exceeds input dimensions " << ishape; + + TShape oshape; + if (keepdims) { + oshape = TShape(ishape); + } else if (exclude) { + oshape = TShape(axes.ndim()); } else { - if (param.keepdims) { - oshape = ishape; - for (index_t i = 0; i < param.axis.ndim(); ++i) { - oshape[param.axis[i]] = 1; + oshape = TShape(std::max(1, ishape.ndim() - axes.ndim())); + } + + if (keepdims && exclude) { + for (index_t i = 0, j = 0; i < ishape.ndim(); ++i) { + if (j < axes.ndim() && i == axes[j]) { + ++j; + continue; } - } else { - CHECK_LT(param.axis[param.axis.ndim()-1], ishape.ndim()) - << "Reduction axis " << param.axis[param.axis.ndim()-1] - << " Exceeds input dimensions " << ishape; - oshape = TShape(std::max(1, ishape.ndim() - param.axis.ndim())); - for (index_t i = 0, j = 0, k = 0; i < ishape.ndim(); ++i) { - if (j < param.axis.ndim() && i == param.axis[j]) { - ++j; - continue; - } - oshape[k++] = ishape[i]; + oshape[i] = 1; + } + } else if (keepdims) { + for (index_t i = 0; i < axes.ndim(); ++i) { + oshape[axes[i]] = 1; + } + } else if (exclude) { + for (index_t i = 0; i < axes.ndim(); ++i) { + oshape[i] = ishape[axes[i]]; + } + } else { + for (index_t i = 0, j = 0, k = 0; i < ishape.ndim(); ++i) { + if (j < axes.ndim() && i == axes[j]) { + ++j; + continue; } + oshape[k++] = ishape[i]; } } - SHAPE_ASSIGN_CHECK(*out_attrs, 0, oshape); + return oshape; +} + +inline bool ReduceAxesShape(const nnvm::NodeAttrs& attrs, + std::vector *in_attrs, + std::vector *out_attrs) { + CHECK_EQ(in_attrs->size(), 1U); + CHECK_EQ(out_attrs->size(), 1U); + if ((*in_attrs)[0].ndim() == 0) return false; + const ReduceAxesParam& param = nnvm::get(attrs.parsed); + SHAPE_ASSIGN_CHECK(*out_attrs, 0, + ReduceAxesShapeImpl((*in_attrs)[0], param.axis, + param.keepdims, param.exclude)); return true; } @@ -309,22 +377,20 @@ void ReduceAxesComputeImpl(const nnvm::NodeAttrs& attrs, BroadcastReduceShapeCompact(inputs[0].shape_, small, &src_shape, &dst_shape); Stream *s = ctx.get_stream(); MSHADOW_TYPE_SWITCH(outputs[0].type_flag_, DType, { - if (dst_shape.ndim() == 2) { - Tensor out = - outputs[0].get_with_shape(dst_shape.get<2>(), s); - Tensor data = - inputs[0].get_with_shape(src_shape.get<2>(), s); - ReduceToAssign(out, req[0], data); - if (normalize) out /= scalar(src_shape.Size()/dst_shape.Size()); - } else { - const int ndim = MXNET_SPECIAL_MAX_NDIM; - Tensor out = - outputs[0].get_with_shape(dst_shape.get(), s); - Tensor data = - inputs[0].get_with_shape(src_shape.get(), s); - ReduceToAssign(out, req[0], data); - if (normalize) out /= scalar(src_shape.Size()/dst_shape.Size()); - } + const TBlob in_data = inputs[0].reshape(src_shape); + const TBlob out_data = outputs[0].reshape(dst_shape); + BROADCAST_NDIM_SWITCH(dst_shape.ndim(), NDim, { + size_t workspace_size = broadcast::ReduceWorkspaceSize( + s, out_data, req[0], in_data); + Tensor workspace = + ctx.requested[0].get_space_typed(Shape1(workspace_size), s); + broadcast::Reduce( + s, out_data, req[0], workspace, in_data); + if (normalize) { + auto out = out_data.FlatTo2D(s); + out /= scalar(src_shape.Size()/dst_shape.Size()); + } + }); }); } @@ -334,20 +400,12 @@ void ReduceAxesCompute(const nnvm::NodeAttrs& attrs, const std::vector& inputs, const std::vector& req, const std::vector& outputs) { - // using namespace mshadow; - // using namespace mshadow::expr; const ReduceAxesParam& param = nnvm::get(attrs.parsed); TShape small; - if (!param.keepdims) { - if (param.axis.ndim() == 0) { - small = TShape(inputs[0].shape_.ndim()); - } else { - small = inputs[0].shape_; - for (index_t i = 0; i < param.axis.ndim(); ++i) - small[param.axis[i]] = 1; - } - } else { + if (param.keepdims) { small = outputs[0].shape_; + } else { + small = ReduceAxesShapeImpl(inputs[0].shape_, param.axis, true, param.exclude); } ReduceAxesComputeImpl(attrs, ctx, inputs, req, outputs, small); @@ -364,12 +422,10 @@ void ReduceAxesBackwardUseInOut(const nnvm::NodeAttrs& attrs, using namespace mshadow::expr; const ReduceAxesParam& param = nnvm::get(attrs.parsed); TShape small; - if (param.axis.ndim() == 0) { - small = TShape(outputs[0].shape_.ndim()); + if (param.keepdims) { + small = inputs[0].shape_; } else { - small = outputs[0].shape_; - for (index_t i = 0; i < param.axis.ndim(); ++i) - small[param.axis[i]] = 1; + small = ReduceAxesShapeImpl(outputs[0].shape_, param.axis, true, param.exclude); } TShape src_shape, dst_shape; @@ -454,13 +510,12 @@ inline void ReduceAxesBackwardUseNone(const nnvm::NodeAttrs& attrs, using namespace mshadow::expr; const ReduceAxesParam& param = nnvm::get(attrs.parsed); TShape small; - if (param.axis.ndim() == 0) { - small = TShape(outputs[0].shape_.ndim()); + if (param.keepdims) { + small = inputs[0].shape_; } else { - small = outputs[0].shape_; - for (index_t i = 0; i < param.axis.ndim(); ++i) - small[param.axis[i]] = 1; + small = ReduceAxesShapeImpl(outputs[0].shape_, param.axis, true, param.exclude); } + BroadcastComputeImpl(attrs, ctx, inputs, req, outputs, small); if (normalize) { Stream *s = ctx.get_stream(); @@ -475,7 +530,6 @@ template inline void AxesParamParser(nnvm::NodeAttrs* attrs) { PType param; param.Init(attrs->dict); - std::sort(¶m.axis[0], ¶m.axis[param.axis.ndim()]); attrs->parsed = std::move(param); } @@ -550,12 +604,16 @@ inline bool PickOpShape(const nnvm::NodeAttrs& attrs, const PickParam& param = nnvm::get(attrs.parsed); if (!param.axis) LOG(FATAL) << "axis=None is not supported by pick yet. Must specify an axis."; - ReduceAxisParam tmp_param; - tmp_param.axis = param.axis; - tmp_param.keepdims = param.keepdims; - TShape oshape = ReduceAxisShapeImpl(tmp_param, ishape); + TShape oshape = ReduceAxisShapeImpl(ishape, param.axis, param.keepdims); SHAPE_ASSIGN_CHECK(*out_attrs, 0, oshape); - SHAPE_ASSIGN_CHECK(*in_attrs, 1, oshape); + if (!(*in_attrs)[1].ndim()) return false; + if ((*in_attrs)[1].ndim() == ishape.ndim()) { + SHAPE_ASSIGN_CHECK(*in_attrs, 1, + ReduceAxisShapeImpl(ishape, param.axis, true)); + } else { + SHAPE_ASSIGN_CHECK(*in_attrs, 1, + ReduceAxisShapeImpl(ishape, param.axis, false)); + } return true; } @@ -583,7 +641,7 @@ void PickOpForward(const nnvm::NodeAttrs& attrs, const PickParam& param = nnvm::get(attrs.parsed); const TShape& ishape = inputs[0].shape_; - int axis = CheckAxis(param.axis.value(), ishape.ndim()); + index_t axis = CheckAxis(param.axis.value(), ishape.ndim()); int leading = 1, trailing = 1, M = ishape[axis]; for (index_t i = 0; i < axis; ++i) leading *= ishape[i]; for (index_t i = axis+1; i < ishape.ndim(); ++i) trailing *= ishape[i]; diff --git a/src/operator/tensor/broadcast_reduce_op_index.cc b/src/operator/tensor/broadcast_reduce_op_index.cc index 5944e2374ea3..6887955880bc 100644 --- a/src/operator/tensor/broadcast_reduce_op_index.cc +++ b/src/operator/tensor/broadcast_reduce_op_index.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2016 by Contributors * \file broadcast_reduce_op.cc * \brief CPU Implementation of broadcast and reduce functions. */ diff --git a/src/operator/tensor/broadcast_reduce_op_index.cu b/src/operator/tensor/broadcast_reduce_op_index.cu index e07b3a2b66d7..defa35ea6227 100644 --- a/src/operator/tensor/broadcast_reduce_op_index.cu +++ b/src/operator/tensor/broadcast_reduce_op_index.cu @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2016 by Contributors * \file broadcast_reduce_op.cu * \brief GPU Implementation of broadcast and reduce functions. */ diff --git a/src/operator/tensor/broadcast_reduce_op_value.cc b/src/operator/tensor/broadcast_reduce_op_value.cc index 5d05b84d4739..551ee8be89d5 100644 --- a/src/operator/tensor/broadcast_reduce_op_value.cc +++ b/src/operator/tensor/broadcast_reduce_op_value.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2016 by Contributors * \file broadcast_reduce_op.cc * \brief CPU Implementation of broadcast and reduce functions. */ @@ -50,6 +68,10 @@ Example:: )code" ADD_FILELINE) .set_attr("FCompute", ReduceAxesCompute) +.set_attr("FResourceRequest", + [](const NodeAttrs& attrs) { + return std::vector{ResourceRequest::kTempSpace}; + }) .set_attr("FGradient", ElemwiseGradUseNone{"_backward_sum"}); MXNET_OPERATOR_REGISTER_REDUCE_BACKWARD(_backward_sum) @@ -59,6 +81,10 @@ MXNET_OPERATOR_REGISTER_REDUCE_BACKWARD(_backward_sum) MXNET_OPERATOR_REGISTER_REDUCE(mean) .describe(get_reduce_axes_description("mean", __LINE__)) .set_attr("FCompute", ReduceAxesCompute) +.set_attr("FResourceRequest", + [](const NodeAttrs& attrs) { + return std::vector{ResourceRequest::kTempSpace}; + }) .set_attr("FGradient", ElemwiseGradUseNone{"_backward_mean"}); MXNET_OPERATOR_REGISTER_REDUCE_BACKWARD(_backward_mean) @@ -67,7 +93,11 @@ MXNET_OPERATOR_REGISTER_REDUCE_BACKWARD(_backward_mean) MXNET_OPERATOR_REGISTER_REDUCE(prod) .describe(get_reduce_axes_description("product", __LINE__)) -.set_attr("FCompute", ReduceAxesCompute< cpu, mshadow_op::product>) +.set_attr("FCompute", ReduceAxesCompute) +.set_attr("FResourceRequest", + [](const NodeAttrs& attrs) { + return std::vector{ResourceRequest::kTempSpace}; + }) .set_attr("FGradient", ReduceGrad{ "_backward_prod" }); MXNET_OPERATOR_REGISTER_REDUCE_BACKWARD(_backward_prod) @@ -79,6 +109,10 @@ MXNET_OPERATOR_REGISTER_REDUCE(nansum) )code" ADD_FILELINE) .set_attr("FCompute", ReduceAxesCompute) +.set_attr("FResourceRequest", + [](const NodeAttrs& attrs) { + return std::vector{ResourceRequest::kTempSpace}; + }) .set_attr("FGradient", ReduceGrad{ "_backward_nansum" }); MXNET_OPERATOR_REGISTER_REDUCE_BACKWARD(_backward_nansum) @@ -90,6 +124,10 @@ MXNET_OPERATOR_REGISTER_REDUCE(nanprod) )code" ADD_FILELINE) .set_attr("FCompute", ReduceAxesCompute) +.set_attr("FResourceRequest", + [](const NodeAttrs& attrs) { + return std::vector{ResourceRequest::kTempSpace}; + }) .set_attr("FGradient", ReduceGrad{ "_backward_nanprod" }); MXNET_OPERATOR_REGISTER_REDUCE_BACKWARD(_backward_nanprod) @@ -100,6 +138,10 @@ MXNET_OPERATOR_REGISTER_REDUCE(max) .add_alias("max_axis") .describe(get_reduce_axes_description("max", __LINE__)) .set_attr("FCompute", ReduceAxesCompute) +.set_attr("FResourceRequest", + [](const NodeAttrs& attrs) { + return std::vector{ResourceRequest::kTempSpace}; + }) .set_attr("FGradient", ReduceGrad{"_backward_max"}); MXNET_OPERATOR_REGISTER_REDUCE_BACKWARD(_backward_max) @@ -110,6 +152,10 @@ MXNET_OPERATOR_REGISTER_REDUCE(min) .add_alias("min_axis") .describe(get_reduce_axes_description("min", __LINE__)) .set_attr("FCompute", ReduceAxesCompute) +.set_attr("FResourceRequest", + [](const NodeAttrs& attrs) { + return std::vector{ResourceRequest::kTempSpace}; + }) .set_attr("FGradient", ReduceGrad{"_backward_min"}); MXNET_OPERATOR_REGISTER_REDUCE_BACKWARD(_backward_min) @@ -171,7 +217,11 @@ So with `shape=(2,0)`, we will obtain the same result as in the above example. NNVM_REGISTER_OP(_broadcast_backward) .set_attr_parser(ParamParser) .set_attr("TIsBackward", true) -.set_attr("FCompute", ReduceAxesCompute); +.set_attr("FCompute", ReduceAxesCompute) +.set_attr("FResourceRequest", + [](const NodeAttrs& attrs) { + return std::vector{ResourceRequest::kTempSpace}; + }); NNVM_REGISTER_OP(norm) .describe(R"code(Flattens the input array and then computes the l2 norm. diff --git a/src/operator/tensor/broadcast_reduce_op_value.cu b/src/operator/tensor/broadcast_reduce_op_value.cu index e4b90d580043..2c216e78982d 100644 --- a/src/operator/tensor/broadcast_reduce_op_value.cu +++ b/src/operator/tensor/broadcast_reduce_op_value.cu @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2016 by Contributors * \file broadcast_reduce_op.cu * \brief GPU Implementation of broadcast and reduce functions. */ diff --git a/src/operator/tensor/control_flow_op.cc b/src/operator/tensor/control_flow_op.cc index b26f44d76523..bf08fe7e9d94 100644 --- a/src/operator/tensor/control_flow_op.cc +++ b/src/operator/tensor/control_flow_op.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2017 by Contributors * \file control_flow_op.cc * \brief CPU Implementation of flow control */ @@ -14,7 +32,7 @@ NNVM_REGISTER_OP(where) " from condition are true or false. x and y must have the same" " shape. If condition has the same shape as x, each element" " in the output array is from x if the corresponding element" - " in the condition is true, and from y if false. If condtion" + " in the condition is true, and from y if false. If condition" " does not have the same shape as x, it must be a 1D array" " whose size is the same as x's first dimension size. Each" " row of the output array is from x's row if the corresponding" diff --git a/src/operator/tensor/control_flow_op.cu b/src/operator/tensor/control_flow_op.cu index dbea01c43d3d..da2c47247f28 100644 --- a/src/operator/tensor/control_flow_op.cu +++ b/src/operator/tensor/control_flow_op.cu @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2017 by Contributors * \file control_flow_op.cu * \brief */ diff --git a/src/operator/tensor/control_flow_op.h b/src/operator/tensor/control_flow_op.h index 0ab24899042d..c240247202e8 100644 --- a/src/operator/tensor/control_flow_op.h +++ b/src/operator/tensor/control_flow_op.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2017 by Contributors * \file control_flow.h * \brief Function definitions of operators for controlling flow */ @@ -108,7 +126,7 @@ inline bool WhereOpShape(const nnvm::NodeAttrs& attrs, SHAPE_ASSIGN_CHECK(*in_attrs, 0, tshape); return true; } else if ((*in_attrs)[0].ndim() == 1) { - return (*in_attrs)[0].Size() == tshape[0]; + return (*in_attrs)[0].Size() == static_cast(tshape[0]); } return false; } diff --git a/src/operator/tensor/elemwise_binary_broadcast_op.h b/src/operator/tensor/elemwise_binary_broadcast_op.h index 4a84e9a978f7..851a1c5cdf2f 100644 --- a/src/operator/tensor/elemwise_binary_broadcast_op.h +++ b/src/operator/tensor/elemwise_binary_broadcast_op.h @@ -1,7 +1,25 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file elementwise_binary_broadcast_op.h - * \brief Function defintion of elementwise unary operators + * \brief Function definition of elementwise unary operators */ #ifndef MXNET_OPERATOR_TENSOR_ELEMWISE_BINARY_BROADCAST_OP_H_ #define MXNET_OPERATOR_TENSOR_ELEMWISE_BINARY_BROADCAST_OP_H_ @@ -138,54 +156,6 @@ void BinaryBroadcastCompute(const nnvm::NodeAttrs& attrs, } } -template -void ReduceToAssign(mshadow::Tensor out, - const OpReqType req, const SrcExp &src_) { - using namespace mshadow; - using namespace mshadow::expr; - Shape src_shape = ShapeCheck::Check(src_); - Shape axes; - index_t reducing_size = 1, remaining_size = 1; - int i = 0; - for (int k = 0; k < ndim; ++k) - if (src_shape[k] != out.shape_[k]) - ++i; - for (int j = ndim-1, k = ndim-1; k >= 0; --k) { - if (src_shape[k] == out.shape_[k]) { - axes[j--] = k; - remaining_size *= src_shape[k]; - } else { - axes[--i] = k; - reducing_size *= src_shape[k]; - } - } - if (reducing_size == 1) { - ASSIGN_DISPATCH(out, req, F(src_)); - } else { - ASSIGN_DISPATCH(out.FlatTo1D(), req, - (reduce_except_dim<1, Reducer>(reshape(transpose(src_, axes), - Shape2(reducing_size, remaining_size))))); - } -} - -template -void ReduceToAssign(mshadow::Tensor out, const OpReqType req, const SrcExp &src_) { - using namespace mshadow; - using namespace mshadow::expr; - Shape<2> src_shape = ShapeCheck<2, SrcExp>::Check(src_); - if (src_shape == out.shape_) { - ASSIGN_DISPATCH(out, req, F(src_)); - } else if (src_shape[0] == out.shape_[0]) { - ASSIGN_DISPATCH(out.FlatTo1D(), req, (reduce_except_dim<0, Reducer>(src_))); - } else if (src_shape[1] == out.shape_[1]) { - ASSIGN_DISPATCH(out.FlatTo1D(), req, (reduce_except_dim<1, Reducer>(src_))); - } else { - ASSIGN_DISPATCH(out.FlatTo1D(), req, - (reduce_except_dim<1, Reducer>(reshape(src_, - Shape2(src_shape.Size(), 1))))); - } -} - template void BinaryBroadcastBackwardUseNone(const nnvm::NodeAttrs& attrs, const OpContext& ctx, @@ -195,7 +165,7 @@ void BinaryBroadcastBackwardUseNone(const nnvm::NodeAttrs& attrs, using namespace broadcast; TShape new_lshape, new_rshape, new_oshape; int ndim = BinaryBroadcastShapeCompact(outputs[0].shape_, outputs[1].shape_, inputs[0].shape_, - &new_lshape, &new_rshape, &new_oshape); + &new_lshape, &new_rshape, &new_oshape); if (!ndim) { BinaryBackwardUseNone(attrs, ctx, inputs, req, outputs); } else { @@ -267,8 +237,6 @@ void BinaryBroadcastBackwardUseIn(const nnvm::NodeAttrs& attrs, } } -#undef BROADCAST_NDIM_SWITCH - #define MXNET_OPERATOR_REGISTER_BINARY_BROADCAST(name) \ NNVM_REGISTER_OP(name) \ .set_num_inputs(2) \ diff --git a/src/operator/tensor/elemwise_binary_broadcast_op_basic.cc b/src/operator/tensor/elemwise_binary_broadcast_op_basic.cc index 0d0a1d8b5df0..c80d46a883ea 100644 --- a/src/operator/tensor/elemwise_binary_broadcast_op_basic.cc +++ b/src/operator/tensor/elemwise_binary_broadcast_op_basic.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2016 by Contributors * \file elemwise_binary_scalar_op.cc * \brief CPU Implementation of unary function. */ @@ -153,5 +171,38 @@ NNVM_REGISTER_OP(_backward_broadcast_div) .set_attr("FCompute", BinaryBroadcastBackwardUseIn); +MXNET_OPERATOR_REGISTER_BINARY_BROADCAST(broadcast_mod) +.describe(R"code(Returns element-wise modulo of the input arrays with broadcasting. + +Example:: + + x = [[ 8., 8., 8.], + [ 8., 8., 8.]] + + y = [[ 2.], + [ 3.]] + + broadcast_mod(x, y) = [[ 0., 0., 0.], + [ 2., 2., 2.]] + +)code" ADD_FILELINE) +.set_attr("FCompute", BinaryBroadcastCompute) +.set_attr("FGradient", ElemwiseGradUseIn{"_backward_broadcast_mod"}); + +NNVM_REGISTER_OP(_backward_broadcast_mod) +.set_num_inputs(3) +.set_num_outputs(2) +.set_attr("TIsBackward", true) +.set_attr("FInplaceOption", + [](const NodeAttrs& attrs){ + return std::vector >{{0, 1}}; + }) +.set_attr("FResourceRequest", + [](const NodeAttrs& attrs) { + return std::vector{ResourceRequest::kTempSpace}; + }) +.set_attr("FCompute", BinaryBroadcastBackwardUseIn); + } // namespace op } // namespace mxnet diff --git a/src/operator/tensor/elemwise_binary_broadcast_op_basic.cu b/src/operator/tensor/elemwise_binary_broadcast_op_basic.cu index f23d3d0cbad8..bf69132cff14 100644 --- a/src/operator/tensor/elemwise_binary_broadcast_op_basic.cu +++ b/src/operator/tensor/elemwise_binary_broadcast_op_basic.cu @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2016 by Contributors * \file elemwise_binary_scalar_op.cu * \brief GPU Implementation of unary function. */ @@ -37,5 +55,12 @@ NNVM_REGISTER_OP(_backward_broadcast_div) .set_attr("FCompute", BinaryBroadcastBackwardUseIn); +NNVM_REGISTER_OP(broadcast_mod) +.set_attr("FCompute", BinaryBroadcastCompute); + +NNVM_REGISTER_OP(_backward_broadcast_mod) +.set_attr("FCompute", BinaryBroadcastBackwardUseIn); + } // namespace op } // namespace mxnet diff --git a/src/operator/tensor/elemwise_binary_broadcast_op_extended.cc b/src/operator/tensor/elemwise_binary_broadcast_op_extended.cc index 127d8c0ec1c5..42da19155ef5 100644 --- a/src/operator/tensor/elemwise_binary_broadcast_op_extended.cc +++ b/src/operator/tensor/elemwise_binary_broadcast_op_extended.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2016 by Contributors * \file elemwise_binary_scalar_op.cc * \brief CPU Implementation of unary function. */ diff --git a/src/operator/tensor/elemwise_binary_broadcast_op_extended.cu b/src/operator/tensor/elemwise_binary_broadcast_op_extended.cu index 649e19ba092a..2b7cc70b59a7 100644 --- a/src/operator/tensor/elemwise_binary_broadcast_op_extended.cu +++ b/src/operator/tensor/elemwise_binary_broadcast_op_extended.cu @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2016 by Contributors * \file elemwise_binary_scalar_op.cu * \brief GPU Implementation of unary function. */ diff --git a/src/operator/tensor/elemwise_binary_broadcast_op_logic.cc b/src/operator/tensor/elemwise_binary_broadcast_op_logic.cc index 900f376fe421..957b00b5e774 100644 --- a/src/operator/tensor/elemwise_binary_broadcast_op_logic.cc +++ b/src/operator/tensor/elemwise_binary_broadcast_op_logic.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2016 by Contributors * \file elemwise_binary_scalar_op.cc * \brief CPU Implementation of unary function. */ diff --git a/src/operator/tensor/elemwise_binary_broadcast_op_logic.cu b/src/operator/tensor/elemwise_binary_broadcast_op_logic.cu index 167b5d34f00a..8673b4f1f1ed 100644 --- a/src/operator/tensor/elemwise_binary_broadcast_op_logic.cu +++ b/src/operator/tensor/elemwise_binary_broadcast_op_logic.cu @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2016 by Contributors * \file elemwise_binary_scalar_op.cu * \brief GPU Implementation of unary function. */ diff --git a/src/operator/tensor/elemwise_binary_op.h b/src/operator/tensor/elemwise_binary_op.h index f0dd152341c3..87b0d46a63c9 100644 --- a/src/operator/tensor/elemwise_binary_op.h +++ b/src/operator/tensor/elemwise_binary_op.h @@ -1,7 +1,25 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2016 by Contributors * \file elemwise_binary_op.h - * \brief Function defintion of elementwise binary operators + * \brief Function definition of elementwise binary operators */ #ifndef MXNET_OPERATOR_TENSOR_ELEMWISE_BINARY_OP_H_ #define MXNET_OPERATOR_TENSOR_ELEMWISE_BINARY_OP_H_ diff --git a/src/operator/tensor/elemwise_binary_op_basic.cc b/src/operator/tensor/elemwise_binary_op_basic.cc index be4c1d88e983..65d4ca9aadd6 100644 --- a/src/operator/tensor/elemwise_binary_op_basic.cc +++ b/src/operator/tensor/elemwise_binary_op_basic.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2016 by Contributors * \file elemwise_binary_scalar_op.cc * \brief CPU Implementation of unary function. */ @@ -12,7 +30,7 @@ MXNET_OPERATOR_REGISTER_BINARY(elemwise_add) .add_alias("_add").add_alias("_plus").add_alias("_Plus") .describe("Adds arguments element-wise.") .set_attr("FCompute", BinaryCompute) -.set_attr("FGradient", ElemwiseGradUseNone{"_backward_add"}); +.set_attr("FGradient", CloneGradient{"_backward_add"}); // specialized gradient add function to do add to optimization // this must differ from elemwise_add to prevent add to optimization in forward pass. @@ -78,5 +96,21 @@ NNVM_REGISTER_OP(_backward_div) .set_attr("FCompute", BinaryBackwardUseIn); +MXNET_OPERATOR_REGISTER_BINARY(_mod) +.add_alias("_Mod") +.set_attr("FCompute", BinaryCompute) +.set_attr("FGradient", ElemwiseGradUseIn{"_backward_mod"}); + +NNVM_REGISTER_OP(_backward_mod) +.set_num_inputs(3) +.set_num_outputs(2) +.set_attr("TIsBackward", true) +.set_attr("FInplaceOption", + [](const NodeAttrs& attrs){ + return std::vector >{{0, 1}}; + }) +.set_attr("FCompute", BinaryBackwardUseIn); + } // namespace op } // namespace mxnet diff --git a/src/operator/tensor/elemwise_binary_op_basic.cu b/src/operator/tensor/elemwise_binary_op_basic.cu index ff432380d6d1..429140a63bc5 100644 --- a/src/operator/tensor/elemwise_binary_op_basic.cu +++ b/src/operator/tensor/elemwise_binary_op_basic.cu @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2016 by Contributors * \file elemwise_binary_scalar_op.cu * \brief GPU Implementation of unary function. */ @@ -40,5 +58,12 @@ NNVM_REGISTER_OP(_backward_div) .set_attr("FCompute", BinaryBackwardUseInWithHalf2); +NNVM_REGISTER_OP(_mod) +.set_attr("FCompute", BinaryComputeWithHalf2); + +NNVM_REGISTER_OP(_backward_mod) +.set_attr("FCompute", BinaryBackwardUseInWithHalf2); + } // namespace op } // namespace mxnet diff --git a/src/operator/tensor/elemwise_binary_op_extended.cc b/src/operator/tensor/elemwise_binary_op_extended.cc index c1669c6daf6e..31d977c9fd48 100644 --- a/src/operator/tensor/elemwise_binary_op_extended.cc +++ b/src/operator/tensor/elemwise_binary_op_extended.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2016 by Contributors * \file elemwise_binary_scalar_op.cc * \brief CPU Implementation of unary function. */ diff --git a/src/operator/tensor/elemwise_binary_op_extended.cu b/src/operator/tensor/elemwise_binary_op_extended.cu index 7325ebfff819..9a10b05cf901 100644 --- a/src/operator/tensor/elemwise_binary_op_extended.cu +++ b/src/operator/tensor/elemwise_binary_op_extended.cu @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2016 by Contributors * \file elemwise_binary_scalar_op.cu * \brief GPU Implementation of unary function. */ diff --git a/src/operator/tensor/elemwise_binary_op_logic.cc b/src/operator/tensor/elemwise_binary_op_logic.cc index 0903b503e1c8..85f2bf11539d 100644 --- a/src/operator/tensor/elemwise_binary_op_logic.cc +++ b/src/operator/tensor/elemwise_binary_op_logic.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2016 by Contributors * \file elemwise_binary_scalar_op.cc * \brief CPU Implementation of unary function. */ diff --git a/src/operator/tensor/elemwise_binary_op_logic.cu b/src/operator/tensor/elemwise_binary_op_logic.cu index 9fab912227b1..1a703ed90ed0 100644 --- a/src/operator/tensor/elemwise_binary_op_logic.cu +++ b/src/operator/tensor/elemwise_binary_op_logic.cu @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2016 by Contributors * \file elemwise_binary_scalar_op.cu * \brief GPU Implementation of unary function. */ diff --git a/src/operator/tensor/elemwise_binary_scalar_op.h b/src/operator/tensor/elemwise_binary_scalar_op.h index e134d823be77..f27df274e5b2 100644 --- a/src/operator/tensor/elemwise_binary_scalar_op.h +++ b/src/operator/tensor/elemwise_binary_scalar_op.h @@ -1,7 +1,25 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2016 by Contributors * \file elemwise_binary_scalar_op.h - * \brief Function defintion of elementwise binary scalar operators + * \brief Function definition of elementwise binary scalar operators */ #ifndef MXNET_OPERATOR_TENSOR_ELEMWISE_BINARY_SCALAR_OP_H_ #define MXNET_OPERATOR_TENSOR_ELEMWISE_BINARY_SCALAR_OP_H_ diff --git a/src/operator/tensor/elemwise_binary_scalar_op_basic.cc b/src/operator/tensor/elemwise_binary_scalar_op_basic.cc index ddbba4d10f2c..3249bcbaa7ca 100644 --- a/src/operator/tensor/elemwise_binary_scalar_op_basic.cc +++ b/src/operator/tensor/elemwise_binary_scalar_op_basic.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2016 by Contributors * \file elemwise_binary_scalar_op.cc * \brief CPU Implementation of unary function. */ @@ -44,5 +62,25 @@ MXNET_OPERATOR_REGISTER_BINARY(_backward_rdiv_scalar) .set_attr_parser([](NodeAttrs* attrs) {attrs->parsed = std::stod(attrs->dict["scalar"]);}) .set_attr("FCompute", BinaryScalarBackward); +MXNET_OPERATOR_REGISTER_BINARY_SCALAR(_mod_scalar) +.set_attr("FCompute", BinaryScalarCompute) +.set_attr("FGradient", ElemwiseGradUseIn{"_backward_mod_scalar"}) +.add_alias("_ModScalar"); + +MXNET_OPERATOR_REGISTER_BINARY(_backward_mod_scalar) +.add_argument("scalar", "float", "scalar value") +.set_attr_parser([](NodeAttrs* attrs) {attrs->parsed = std::stod(attrs->dict["scalar"]);}) +.set_attr("FCompute", BinaryScalarBackward); + +MXNET_OPERATOR_REGISTER_BINARY_SCALAR(_rmod_scalar) +.set_attr("FCompute", BinaryScalarCompute) +.set_attr("FGradient", ElemwiseGradUseIn{"_backward_rmod_scalar"}) +.add_alias("_RModScalar"); + +MXNET_OPERATOR_REGISTER_BINARY(_backward_rmod_scalar) +.add_argument("scalar", "float", "scalar value") +.set_attr_parser([](NodeAttrs* attrs) {attrs->parsed = std::stod(attrs->dict["scalar"]);}) +.set_attr("FCompute", BinaryScalarBackward); + } // namespace op } // namespace mxnet diff --git a/src/operator/tensor/elemwise_binary_scalar_op_basic.cu b/src/operator/tensor/elemwise_binary_scalar_op_basic.cu index 356b34901670..a843f67c2723 100644 --- a/src/operator/tensor/elemwise_binary_scalar_op_basic.cu +++ b/src/operator/tensor/elemwise_binary_scalar_op_basic.cu @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2016 by Contributors * \file elemwise_binary_scalar_op.cu * \brief GPU Implementation of unary function. */ @@ -30,5 +48,17 @@ NNVM_REGISTER_OP(_rdiv_scalar) NNVM_REGISTER_OP(_backward_rdiv_scalar) .set_attr("FCompute", BinaryScalarBackward); +NNVM_REGISTER_OP(_mod_scalar) +.set_attr("FCompute", BinaryScalarCompute); + +NNVM_REGISTER_OP(_backward_mod_scalar) +.set_attr("FCompute", BinaryScalarBackward); + +NNVM_REGISTER_OP(_rmod_scalar) +.set_attr("FCompute", BinaryScalarCompute); + +NNVM_REGISTER_OP(_backward_rmod_scalar) +.set_attr("FCompute", BinaryScalarBackward); + } // namespace op } // namespace mxnet diff --git a/src/operator/tensor/elemwise_binary_scalar_op_extended.cc b/src/operator/tensor/elemwise_binary_scalar_op_extended.cc index 6b712fce2dcb..785fce2dcbb6 100644 --- a/src/operator/tensor/elemwise_binary_scalar_op_extended.cc +++ b/src/operator/tensor/elemwise_binary_scalar_op_extended.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2016 by Contributors * \file elemwise_binary_scalar_op.cc * \brief CPU Implementation of unary function. */ diff --git a/src/operator/tensor/elemwise_binary_scalar_op_extended.cu b/src/operator/tensor/elemwise_binary_scalar_op_extended.cu index 4623b0572938..74e6b7de478b 100644 --- a/src/operator/tensor/elemwise_binary_scalar_op_extended.cu +++ b/src/operator/tensor/elemwise_binary_scalar_op_extended.cu @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2016 by Contributors * \file elemwise_binary_scalar_op.cu * \brief GPU Implementation of unary function. */ diff --git a/src/operator/tensor/elemwise_binary_scalar_op_logic.cc b/src/operator/tensor/elemwise_binary_scalar_op_logic.cc index a0f5c2355f39..6771fff21387 100644 --- a/src/operator/tensor/elemwise_binary_scalar_op_logic.cc +++ b/src/operator/tensor/elemwise_binary_scalar_op_logic.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2016 by Contributors * \file elemwise_binary_scalar_op.cc * \brief CPU Implementation of unary function. */ diff --git a/src/operator/tensor/elemwise_binary_scalar_op_logic.cu b/src/operator/tensor/elemwise_binary_scalar_op_logic.cu index e400b3baa8d8..9fee4e9e4c87 100644 --- a/src/operator/tensor/elemwise_binary_scalar_op_logic.cu +++ b/src/operator/tensor/elemwise_binary_scalar_op_logic.cu @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2016 by Contributors * \file elemwise_binary_scalar_op.cu * \brief GPU Implementation of unary function. */ diff --git a/src/operator/tensor/elemwise_sum.cc b/src/operator/tensor/elemwise_sum.cc index 06ec01e8ebd0..652be72f3fab 100644 --- a/src/operator/tensor/elemwise_sum.cc +++ b/src/operator/tensor/elemwise_sum.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file elemwise_sum.cc * \brief elementwise sum operator */ @@ -36,22 +54,6 @@ std::vector ElementWiseSumGrad( return ret; } -bool ElementWiseSumShape(const nnvm::NodeAttrs& attrs, - std::vector *in_attrs, - std::vector *out_attrs) { - CHECK_EQ(out_attrs->size(), 1); - return ElemwiseAttr( - attrs, in_attrs, out_attrs, TShape()); -} - -bool ElementWiseSumType(const nnvm::NodeAttrs& attrs, - std::vector *in_attrs, - std::vector *out_attrs) { - CHECK_EQ(out_attrs->size(), 1); - return ElemwiseAttr( - attrs, in_attrs, out_attrs, -1); -} - NNVM_REGISTER_OP(add_n) .add_alias("ElementWiseSum") .describe(R"doc(Adds all input arguments element-wise. @@ -81,9 +83,9 @@ NNVM_REGISTER_OP(add_n) "FInplaceOption", [](const NodeAttrs& attrs) { return std::vector >{{0, 0}}; }) -.set_attr("FInferShape", ElementWiseSumShape) -.set_attr("FInferType", ElementWiseSumType) -.set_attr("FGradient", ElementWiseSumGrad) +.set_attr("FInferShape", ElemwiseShape<-1, 1>) +.set_attr("FInferType", ElemwiseType<-1, 1>) +.set_attr("FGradient", CloneGradient{"_backward_add_n"}) .add_argument("args", "NDArray-or-Symbol[]", "Positional input arguments"); diff --git a/src/operator/tensor/elemwise_sum.cu b/src/operator/tensor/elemwise_sum.cu index 63c3a96acb6a..ce734ad90c8a 100644 --- a/src/operator/tensor/elemwise_sum.cu +++ b/src/operator/tensor/elemwise_sum.cu @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file elemwise_sum.cu * \brief elementwise sum operator */ diff --git a/src/operator/tensor/elemwise_sum.h b/src/operator/tensor/elemwise_sum.h index 2dd6a133b407..3d6d72511156 100644 --- a/src/operator/tensor/elemwise_sum.h +++ b/src/operator/tensor/elemwise_sum.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file elemwise_sum.h * \brief elementwise sum * \author Bing Xu diff --git a/src/operator/tensor/elemwise_unary_op.cc b/src/operator/tensor/elemwise_unary_op.cc index ce29a2fdb308..defe72d3738c 100644 --- a/src/operator/tensor/elemwise_unary_op.cc +++ b/src/operator/tensor/elemwise_unary_op.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2016 by Contributors * \file elemwise_unary_op.cc * \brief CPU Implementation of unary function. */ @@ -48,6 +66,10 @@ MXNET_OPERATOR_REGISTER_BINARY(_backward_sigmoid) MXNET_OPERATOR_REGISTER_UNARY(_copy) .MXNET_DESCRIBE("Returns a copy of the input.") .add_alias("identity") +.set_attr("FInplaceIdentity", + [](const NodeAttrs& attrs){ + return std::vector{true}; + }) .set_attr("FCompute", IdentityCompute) .set_attr("FGradient", ElemwiseGradUseNone{"_copy"}); @@ -59,6 +81,10 @@ NNVM_REGISTER_OP(_backward_copy) [](const NodeAttrs& attrs){ return std::vector >{{0, 0}}; }) +.set_attr("FInplaceIdentity", + [](const NodeAttrs& attrs){ + return std::vector{true}; + }) .set_attr("FCompute", IdentityCompute); MXNET_OPERATOR_REGISTER_UNARY(BlockGrad) @@ -89,6 +115,10 @@ Example:: [ 1. 1.] )code" ADD_FILELINE) +.set_attr("FInplaceIdentity", + [](const NodeAttrs& attrs){ + return std::vector{true}; + }) .set_attr("FCompute", IdentityCompute) .set_attr("FGradient", MakeZeroGradNodes); @@ -100,6 +130,10 @@ MXNET_OPERATOR_REGISTER_UNARY(make_loss) [](const NodeAttrs& attrs) { return std::vector{"loss"}; }) +.set_attr("FInplaceIdentity", + [](const NodeAttrs& attrs){ + return std::vector{true}; + }) .set_attr("FCompute", IdentityCompute) .set_attr("FGradient", [](const nnvm::NodePtr& n, const std::vector& ograds) { @@ -113,10 +147,18 @@ MXNET_OPERATOR_REGISTER_UNARY(make_loss) // identity output as first input, but attributes are constrainted to be like rhs NNVM_REGISTER_OP(_identity_with_attr_like_rhs) .set_num_inputs(2) +.set_attr("FListInputNames", + [](const NodeAttrs& attrs) { + return std::vector{"lhs", "rhs"}; + }) .set_attr( "FInplaceOption", [](const NodeAttrs& attrs) { return std::vector >{{0, 0}}; }) +.set_attr("FInplaceIdentity", + [](const NodeAttrs& attrs){ + return std::vector{true}; + }) .set_attr("FIgnoreInputs", [](const NodeAttrs& attrs) { return std::vector(1, 1); }) .set_attr("FCompute", IdentityCompute) @@ -131,7 +173,9 @@ NNVM_REGISTER_OP(_identity_with_attr_like_rhs) {n->inputs[1]}, nullptr, &n); lhs.push_back(nnvm::NodeEntry{ng, 0, 0}); return lhs; - }); + }) +.add_argument("lhs", "NDArray-or-Symbol", "First input.") +.add_argument("rhs", "NDArray-or-Symbol", "Second input."); DMLC_REGISTER_PARAMETER(CastParam); NNVM_REGISTER_OP(Cast) @@ -154,6 +198,10 @@ Example:: [](const NodeAttrs& attrs){ return std::vector >{{0, 0}}; }) +.set_attr("FInplaceIdentity", + [](const NodeAttrs& attrs){ + return std::vector{true}; + }) .set_attr("FCompute", CastCompute) .set_attr("FGradient", ElemwiseGradUseNone{"_backward_cast"}) .add_argument("data", "NDArray-or-Symbol", "The input.") @@ -161,14 +209,40 @@ Example:: NNVM_REGISTER_OP(_backward_cast) .set_attr("TIsBackward", true) +.set_attr("FInplaceOption", + [](const NodeAttrs& attrs){ + return std::vector >{{0, 0}}; + }) +.set_attr("FInplaceIdentity", + [](const NodeAttrs& attrs){ + return std::vector{true}; + }) .set_attr("FCompute", CastCompute); // negative MXNET_OPERATOR_REGISTER_UNARY(negative) -.MXNET_DESCRIBE("Negate src") +.MXNET_DESCRIBE("Numerical negative of the argument, element-wise.") .set_attr("FCompute", UnaryCompute) .set_attr("FGradient", ElemwiseGradUseNone{"negative"}); +// reciprocal +MXNET_OPERATOR_REGISTER_UNARY(reciprocal) +.describe(R"code(Returns the reciprocal of the argument, element-wise. + +Calculates 1/x. + +Example:: + + reciprocal([-2, 1, 3, 1.6, 0.2]) = [-0.5, 1.0, 0.33333334, 0.625, 5.0] + +)code" ADD_FILELINE) +.set_attr("FCompute", UnaryCompute) +.set_attr("FGradient", ElemwiseGradUseIn{"_backward_reciprocal"}); + +MXNET_OPERATOR_REGISTER_BINARY(_backward_reciprocal) +.set_attr("FCompute", + BinaryCompute >); + // abs MXNET_OPERATOR_REGISTER_UNARY(abs) .describe(R"code(Returns element-wise absolute value of the input. @@ -230,6 +304,8 @@ Example:: MXNET_OPERATOR_REGISTER_UNARY(ceil) .describe(R"code(Returns element-wise ceiling of the input. +The ceil of the scalar x is the smallest integer i, such that i >= x. + Example:: ceil([-2.1, -1.9, 1.5, 1.9, 2.1]) = [-2., -1., 2., 2., 3.] @@ -241,6 +317,8 @@ Example:: MXNET_OPERATOR_REGISTER_UNARY(floor) .describe(R"code(Returns element-wise floor of the input. +The floor of the scalar x is the largest integer i, such that i <= x. + Example:: floor([-2.1, -1.9, 1.5, 1.9, 2.1]) = [-3., -2., 1., 1., 2.] @@ -248,6 +326,20 @@ Example:: )code" ADD_FILELINE) .set_attr("FCompute", UnaryCompute); +// trunc +MXNET_OPERATOR_REGISTER_UNARY(trunc) +.describe(R"code(Return the element-wise truncated value of the input. + +The truncated value of the scalar x is the nearest integer i which is closer to +zero than x is. In short, the fractional part of the signed number x is discarded. + +Example:: + + trunc([-2.1, -1.9, 1.5, 1.9, 2.1]) = [-2., -1., 1., 1., 2.] + +)code" ADD_FILELINE) +.set_attr("FCompute", UnaryCompute); + // fix MXNET_OPERATOR_REGISTER_UNARY(fix) .describe(R"code(Returns element-wise rounded value to the nearest integer towards zero of the input. @@ -268,7 +360,7 @@ MXNET_OPERATOR_REGISTER_UNARY(square) Example:: - square([2, 3, 4]) = [3, 9, 16] + square([2, 3, 4]) = [4, 9, 16] )code" ADD_FILELINE) .set_attr("FCompute", UnaryCompute) diff --git a/src/operator/tensor/elemwise_unary_op.cu b/src/operator/tensor/elemwise_unary_op.cu index 746b39fe4c8c..4211ea305b4e 100644 --- a/src/operator/tensor/elemwise_unary_op.cu +++ b/src/operator/tensor/elemwise_unary_op.cu @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2016 by Contributors * \file elemwise_unary_op.cu * \brief GPU Implementation of unary function. */ @@ -47,6 +65,14 @@ NNVM_REGISTER_OP(_backward_cast) NNVM_REGISTER_OP(negative) .set_attr("FCompute", UnaryCompute); +// reciprocal +NNVM_REGISTER_OP(reciprocal) +.set_attr("FCompute", UnaryCompute); + +NNVM_REGISTER_OP(_backward_reciprocal) +.set_attr("FCompute", + BinaryCompute >); + // abs NNVM_REGISTER_OP(abs) .set_attr("FCompute", UnaryCompute); @@ -73,6 +99,10 @@ NNVM_REGISTER_OP(ceil) NNVM_REGISTER_OP(floor) .set_attr("FCompute", UnaryCompute); +// trunc +NNVM_REGISTER_OP(trunc) +.set_attr("FCompute", UnaryCompute); + // rint NNVM_REGISTER_OP(rint) .set_attr("FCompute", UnaryCompute); diff --git a/src/operator/tensor/elemwise_unary_op.h b/src/operator/tensor/elemwise_unary_op.h index 00f4e42cba79..b6994844e0fe 100644 --- a/src/operator/tensor/elemwise_unary_op.h +++ b/src/operator/tensor/elemwise_unary_op.h @@ -1,7 +1,25 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file elementwise_unary_op-inl.h - * \brief Function defintion of elementwise unary operators + * \brief Function definition of elementwise unary operators */ #ifndef MXNET_OPERATOR_TENSOR_ELEMWISE_UNARY_OP_H_ #define MXNET_OPERATOR_TENSOR_ELEMWISE_UNARY_OP_H_ diff --git a/src/operator/tensor/indexing_op-inl.cuh b/src/operator/tensor/indexing_op-inl.cuh index 93a970a90ba6..4458151f1782 100644 --- a/src/operator/tensor/indexing_op-inl.cuh +++ b/src/operator/tensor/indexing_op-inl.cuh @@ -1,287 +1,312 @@ -/*! - * Copyright (c) 2017 by Contributors - * \file indexing_op-inl.cuh - * \brief CUDA implementations for indexing_op.h - * \author Antti-Pekka Hynninen -*/ -#ifndef MXNET_OPERATOR_TENSOR_INDEXING_OP_CUH_ -#define MXNET_OPERATOR_TENSOR_INDEXING_OP_CUH_ -#include -#include - -namespace mxnet { -namespace op { -const int kWarpSize = 32; - -template -__global__ void AddTakeGradLargeBatchKernel(DType* dst, - // If idx_start == NULL, then in-kernel edge - // detection is used - const IdxType *idx_start, - // idx_start_size_ptr ignored if idx_start == NULL - const int* idx_start_size_ptr, - const IdxType *sorted, const IdxType *index, - const DType *src, - int ymax, int xmax) { - // Size of the shared memory is [blockDim.x*SZ*blockDim.y]*sizeof(DType) - extern __shared__ char sh_grad_weight_char[]; - DType* sh_grad_weight = (DType*)sh_grad_weight_char; - - int iidx_end = (idx_start == NULL) ? ymax : *idx_start_size_ptr; - - for (int iidx = blockIdx.y;iidx < iidx_end;iidx += gridDim.y) { - - // Thread block sums up elements in the range [idx_begin, idx_end-1] - int idx_begin, idx_end; - int sorted_value; - if (idx_start == NULL) { - idx_begin = iidx; - sorted_value = static_cast(sorted[idx_begin]); - if (idx_begin > 0 && sorted_value == static_cast(sorted[idx_begin - 1])) continue; - // Algorithm is explained using an example: - // blockDim.x = 32 - // blockDim.y = 4 - // sorted[idx_begin:] = [4 4 4 9] - // (3,4) denotes threadIdx.x=3, threadIdx.y=4, ":" is used for ranges - // (0:31,0:3) sorted_value = 4 - idx_end = idx_begin + 1; - unsigned int* sh_ballot = (unsigned int*)sh_grad_weight_char; - int no_edge = 0; - do { - int idx = idx_end + threadIdx.x + threadIdx.y*blockDim.x; - // Example: - // (0:1,0) sorted_idx = 4 - // (rest) sorted_idx = -1 - int sorted_idx = (idx < ymax) ? static_cast(sorted[idx]) : -1; - // Example: - // (0:31,0) sh_ballot[0] = 0b100 - // (rest) sh_ballot[1...3] = 0 - // sh_ballot[] tells us which thread within the warp found the edge - sh_ballot[threadIdx.y] = __ballot(sorted_value != sorted_idx); - __syncthreads(); - // No edge if sh_ballot[threadIdx.x] == 0 - // NOTE: All warps have the same value for no_edge - // Example: - // (0,:) no_edge = 0 - // (rest) no_edge = 1 - no_edge = (threadIdx.x < blockDim.y) ? (sh_ballot[threadIdx.x] == 0) : 1; - idx_end += blockDim.x*blockDim.y; - // Example: - // __all(no_edge) = 0 since no_edge = 0 for threadIdx.x = 0, hence we leave the loop - } while (__all(no_edge)); - idx_end -= blockDim.x*blockDim.y; - // Find the first edge - // Example: - // (0,:) val = 1 - // (rest) val = 0 - unsigned int val = (threadIdx.x < blockDim.y && sh_ballot[threadIdx.x] != 0) ? - 1 : 0; - // NOTE: Set nth bit if thread n in the warp has val = 1 - // Example: - // (all) val = 1 - val = __ballot( val ); - // __ffs() returns the position of first set bit, 1...32. __ffs(1) = 1 - // j will be the warp index where edge was found - // Example: - // (all) j = 1 - 1 = 0 - int j = __ffs(val) - 1; - // j = warp index where the edge was found - // __ffs(sh_ballot[j]) - 1 = warp lane where the edge was found - // idx_end points to the one over the last value. - // Example: - // idx_end += 0*blockDim.x + _ffs(0b100) - 1 = 0 + 3 - 1 = 2 - // sorted[idx_end] = 9 - idx_end += j*blockDim.x + __ffs(sh_ballot[j]) - 1; - __syncthreads(); - } else { - idx_begin = idx_start[iidx]; - idx_end = ((iidx + 1) < iidx_end) ? idx_start[iidx + 1] : ymax; - sorted_value = static_cast(sorted[idx_begin]); - } - - const int start_feature = threadIdx.x + blockIdx.x * blockDim.x * SZ; - const int dst_row = sorted_value * xmax; - - int num_idx = idx_end - idx_begin; - int idx0 = idx_begin + threadIdx.y*num_idx/blockDim.y; - int idx1 = idx_begin + (threadIdx.y + 1)*num_idx/blockDim.y; - - // Read and sum data into grad_weight[] - DType grad_weight[SZ]; - #pragma unroll - for (int ii = 0; ii < SZ; ii++) { - grad_weight[ii] = (DType)0; - } - for (int idx=idx0; idx < idx1;idx++) { - const int src_row = static_cast(index[idx]) * xmax; - #pragma unroll - for (int ii = 0; ii < SZ; ii++) - { - int feature_dim = start_feature + ii * blockDim.x; - if (feature_dim < xmax) - { - grad_weight[ii] += src[src_row + feature_dim]; - } - } - } - #pragma unroll - for (int ii = 0; ii < SZ; ii++) { - sh_grad_weight[threadIdx.x + ii*blockDim.x + threadIdx.y*blockDim.x*SZ] = grad_weight[ii]; - } - __syncthreads(); - // We now have grad_weight[] values, reduce within thread block - for (int t=1;t < blockDim.y;t <<= 1) { - DType tmp[SZ]; - #pragma unroll - for (int ii = 0; ii < SZ; ii++) { - tmp[ii] = (threadIdx.y + t < blockDim.y) ? - sh_grad_weight[threadIdx.x + ii*blockDim.x + (threadIdx.y + t)*blockDim.x*SZ] : (DType)0; - } - __syncthreads(); - #pragma unroll - for (int ii = 0; ii < SZ; ii++) { - sh_grad_weight[threadIdx.x + ii*blockDim.x + threadIdx.y*blockDim.x*SZ] += tmp[ii]; - } - __syncthreads(); - } - // Result is in sh_grad_weight[threadIdx.x + ii*blockDim.x] - if (threadIdx.y == 0) { - #pragma unroll - for (int ii = 0; ii < SZ; ii++) { - int feature_dim = start_feature + ii * blockDim.x; - if (feature_dim < xmax) { - dst[dst_row + feature_dim] += sh_grad_weight[threadIdx.x + ii*blockDim.x]; - } - } - } - - } -} - -template -inline typename std::enable_if::value, size_t>::type -AddTakeGradLargeBatchWorkspaceSize(size_t num_keys) { - size_t encode_bytes = 0; - cub::DeviceRunLengthEncode::Encode - (NULL, encode_bytes, NULL, NULL, NULL, NULL, num_keys); - size_t exclusivesum_bytes = 0; - cub::DeviceScan::ExclusiveSum(NULL, exclusivesum_bytes, - NULL, NULL, num_keys); - size_t temporary_bytes = std::max(encode_bytes, exclusivesum_bytes); - size_t unique_bytes = num_keys*sizeof(IndexType); - size_t counts_bytes = num_keys*sizeof(IndexType); - size_t num_runs_bytes = 1*sizeof(int); - return (unique_bytes + counts_bytes + num_runs_bytes + temporary_bytes); -} - -template -inline void AddTakeGradLargeBatch(mshadow::Tensor dst, - const mshadow::Tensor& sorted, - const mshadow::Tensor& index, - const mshadow::Tensor &src, - mshadow::Tensor* workspace) { - CHECK_EQ(dst.CheckContiguous(), true); - CHECK_EQ(sorted.CheckContiguous(), true); - CHECK_EQ(index.CheckContiguous(), true); - CHECK_EQ(src.CheckContiguous(), true); - // const int kWarpBits = kMemUnitBits; - cudaStream_t stream = mshadow::Stream::GetStream(dst.stream_); - IndexType* sum_counts_ptr = NULL; - int* num_runs_ptr = NULL; - if (dst.size(0)*4 < src.size(0) && workspace != NULL) { - // Workspace given and potentially loops at least 4 times, use CUB to create sum_counts - CHECK_EQ(workspace->CheckContiguous(), true); - // workspace = [unique_out, counts_out, temporary_storage] - size_t unique_bytes = sorted.size(0)*sizeof(IndexType); - size_t counts_bytes = sorted.size(0)*sizeof(IndexType); - size_t num_runs_bytes = 1*sizeof(int); - - size_t encode_bytes = 0; - cub::DeviceRunLengthEncode::Encode - (NULL, encode_bytes, NULL, NULL, NULL, NULL, sorted.size(0), stream); - size_t exclusivesum_bytes = 0; - cub::DeviceScan::ExclusiveSum - (NULL, exclusivesum_bytes, NULL, NULL, sorted.size(0), stream); - size_t temporary_bytes = std::max(encode_bytes, exclusivesum_bytes); - - // Check that we have enough storage - CHECK_GE(workspace->size(0), unique_bytes + counts_bytes + - num_runs_bytes + temporary_bytes); - - IndexType* unique_out_ptr = reinterpret_cast(workspace->dptr_); - IndexType* counts_out_ptr = reinterpret_cast(workspace->dptr_ + unique_bytes); - num_runs_ptr = reinterpret_cast(workspace->dptr_ + unique_bytes + - counts_bytes); - void* temporary_storage = reinterpret_cast(workspace->dptr_ + unique_bytes + - counts_bytes + num_runs_bytes); - - cub::DeviceRunLengthEncode::Encode - (temporary_storage, temporary_bytes, sorted.dptr_, unique_out_ptr, counts_out_ptr, - num_runs_ptr, sorted.size(0), stream); - - sum_counts_ptr = unique_out_ptr; - cub::DeviceScan::ExclusiveSum - (temporary_storage, temporary_bytes, counts_out_ptr, sum_counts_ptr, - sorted.size(0), stream); - } - - const int num_unique_est = min(dst.size(0), src.size(0)); - const int max_nthread = 128; - const int num_y = max(src.size(0)/num_unique_est, 1); - const int block_dim_x = kWarpSize; - const int block_dim_y = min(num_y, max_nthread/block_dim_x); - const int SZ = min((src.size(1) + block_dim_x - 1) / block_dim_x, 4); - const int grid_dim_x = (src.size(1) + block_dim_x * SZ - 1) / (block_dim_x * SZ); - const int grid_dim_y = min(num_unique_est, mshadow::cuda::kBaseGridNum); - dim3 dimBlock(block_dim_x, block_dim_y); - dim3 dimGrid(grid_dim_x, grid_dim_y); - // Maximum shared memory usage: 128*4*sizeof(DType), which is 4K for 64bit DType elements - int shmem_size = dimBlock.x*SZ*dimBlock.y*sizeof(DType); - - CHECK_EQ(dst.size(1), src.size(1)) << "AddTakeGradLargeBatch: shape mismatch"; - CHECK_EQ(index.size(0), src.size(0)) << "AddTakeGradLargeBatch: shape mismatch"; - mshadow::cuda::CheckLaunchParam(dimGrid, dimBlock, "AddTakeGradLargeBatch"); - - switch (SZ) { - case 1: - AddTakeGradLargeBatchKernel<1, DType> - <<>> - (dst.dptr_, sum_counts_ptr, num_runs_ptr, - sorted.dptr_, index.dptr_, src.dptr_, - static_cast(src.size(0)), - static_cast(src.size(1))); - break; - case 2: - AddTakeGradLargeBatchKernel<2, DType> - <<>> - (dst.dptr_, sum_counts_ptr, num_runs_ptr, - sorted.dptr_, index.dptr_, src.dptr_, - static_cast(src.size(0)), - static_cast(src.size(1))); - break; - case 3: - AddTakeGradLargeBatchKernel<3, DType> - <<>> - (dst.dptr_, sum_counts_ptr, num_runs_ptr, - sorted.dptr_, index.dptr_, src.dptr_, - static_cast(src.size(0)), - static_cast(src.size(1))); - break; - case 4: - AddTakeGradLargeBatchKernel<4, DType> - <<>> - (dst.dptr_, sum_counts_ptr, num_runs_ptr, - sorted.dptr_, index.dptr_, src.dptr_, - static_cast(src.size(0)), - static_cast(src.size(1))); - break; - default: - LOG(FATAL) << "AddTakeGradLargeBatch, incorrect value SZ " << SZ; - break; - } - MSHADOW_CUDA_POST_KERNEL_CHECK(AddTakeGradLargeBatchKernel); -} - -} // namespace op -} // namespace mxnet -#endif // MXNET_OPERATOR_TENSOR_INDEXING_OP_CUH_ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +/*! + * Copyright (c) 2017 by Contributors + * \file indexing_op-inl.cuh + * \brief CUDA implementations for indexing_op.h + * \author Antti-Pekka Hynninen +*/ +#ifndef MXNET_OPERATOR_TENSOR_INDEXING_OP_CUH_ +#define MXNET_OPERATOR_TENSOR_INDEXING_OP_CUH_ +#include +#include + +#if CUDA_VERSION >= 9000 +#define FULLMASK 0xFFFFFFFF +#define __ballot(x) __ballot_sync(FULLMASK, (x)) +#define __all(x) __all_sync(FULLMASK, (x)) +#endif + +namespace mxnet { +namespace op { +const int kWarpSize = 32; + +template +__global__ void AddTakeGradLargeBatchKernel(DType* dst, + // If idx_start == NULL, then in-kernel edge + // detection is used + const IdxType *idx_start, + // idx_start_size_ptr ignored if idx_start == NULL + const int* idx_start_size_ptr, + const IdxType *sorted, const IdxType *index, + const DType *src, + int ymax, int xmax) { + // Size of the shared memory is [blockDim.x*SZ*blockDim.y]*sizeof(DType) + extern __shared__ char sh_grad_weight_char[]; + DType* sh_grad_weight = (DType*)sh_grad_weight_char; + + int iidx_end = (idx_start == NULL) ? ymax : *idx_start_size_ptr; + + for (int iidx = blockIdx.y;iidx < iidx_end;iidx += gridDim.y) { + + // Thread block sums up elements in the range [idx_begin, idx_end-1] + int idx_begin, idx_end; + int sorted_value; + if (idx_start == NULL) { + idx_begin = iidx; + sorted_value = static_cast(sorted[idx_begin]); + if (idx_begin > 0 && sorted_value == static_cast(sorted[idx_begin - 1])) continue; + // Algorithm is explained using an example: + // blockDim.x = 32 + // blockDim.y = 4 + // sorted[idx_begin:] = [4 4 4 9] + // (3,4) denotes threadIdx.x=3, threadIdx.y=4, ":" is used for ranges + // (0:31,0:3) sorted_value = 4 + idx_end = idx_begin + 1; + unsigned int* sh_ballot = (unsigned int*)sh_grad_weight_char; + int no_edge = 0; + do { + int idx = idx_end + threadIdx.x + threadIdx.y*blockDim.x; + // Example: + // (0:1,0) sorted_idx = 4 + // (rest) sorted_idx = -1 + int sorted_idx = (idx < ymax) ? static_cast(sorted[idx]) : -1; + // Example: + // (0:31,0) sh_ballot[0] = 0b100 + // (rest) sh_ballot[1...3] = 0 + // sh_ballot[] tells us which thread within the warp found the edge + sh_ballot[threadIdx.y] = __ballot(sorted_value != sorted_idx); + __syncthreads(); + // No edge if sh_ballot[threadIdx.x] == 0 + // NOTE: All warps have the same value for no_edge + // Example: + // (0,:) no_edge = 0 + // (rest) no_edge = 1 + no_edge = (threadIdx.x < blockDim.y) ? (sh_ballot[threadIdx.x] == 0) : 1; + idx_end += blockDim.x*blockDim.y; + // Example: + // __all(no_edge) = 0 since no_edge = 0 for threadIdx.x = 0, hence we leave the loop + } while (__all(no_edge)); + idx_end -= blockDim.x*blockDim.y; + // Find the first edge + // Example: + // (0,:) val = 1 + // (rest) val = 0 + unsigned int val = (threadIdx.x < blockDim.y && sh_ballot[threadIdx.x] != 0) ? + 1 : 0; + // NOTE: Set nth bit if thread n in the warp has val = 1 + // Example: + // (all) val = 1 + val = __ballot( val ); + // __ffs() returns the position of first set bit, 1...32. __ffs(1) = 1 + // j will be the warp index where edge was found + // Example: + // (all) j = 1 - 1 = 0 + int j = __ffs(val) - 1; + // j = warp index where the edge was found + // __ffs(sh_ballot[j]) - 1 = warp lane where the edge was found + // idx_end points to the one over the last value. + // Example: + // idx_end += 0*blockDim.x + _ffs(0b100) - 1 = 0 + 3 - 1 = 2 + // sorted[idx_end] = 9 + idx_end += j*blockDim.x + __ffs(sh_ballot[j]) - 1; + __syncthreads(); + } else { + idx_begin = idx_start[iidx]; + idx_end = ((iidx + 1) < iidx_end) ? idx_start[iidx + 1] : ymax; + sorted_value = static_cast(sorted[idx_begin]); + } + + const int start_feature = threadIdx.x + blockIdx.x * blockDim.x * SZ; + const int dst_row = sorted_value * xmax; + + int num_idx = idx_end - idx_begin; + int idx0 = idx_begin + threadIdx.y*num_idx/blockDim.y; + int idx1 = idx_begin + (threadIdx.y + 1)*num_idx/blockDim.y; + + // Read and sum data into grad_weight[] + DType grad_weight[SZ]; + #pragma unroll + for (int ii = 0; ii < SZ; ii++) { + grad_weight[ii] = (DType)0; + } + for (int idx=idx0; idx < idx1;idx++) { + const int src_row = static_cast(index[idx]) * xmax; + #pragma unroll + for (int ii = 0; ii < SZ; ii++) + { + int feature_dim = start_feature + ii * blockDim.x; + if (feature_dim < xmax) + { + grad_weight[ii] += src[src_row + feature_dim]; + } + } + } + #pragma unroll + for (int ii = 0; ii < SZ; ii++) { + sh_grad_weight[threadIdx.x + ii*blockDim.x + threadIdx.y*blockDim.x*SZ] = grad_weight[ii]; + } + __syncthreads(); + // We now have grad_weight[] values, reduce within thread block + for (int t=1;t < blockDim.y;t <<= 1) { + DType tmp[SZ]; + #pragma unroll + for (int ii = 0; ii < SZ; ii++) { + tmp[ii] = (threadIdx.y + t < blockDim.y) ? + sh_grad_weight[threadIdx.x + ii*blockDim.x + (threadIdx.y + t)*blockDim.x*SZ] : (DType)0; + } + __syncthreads(); + #pragma unroll + for (int ii = 0; ii < SZ; ii++) { + sh_grad_weight[threadIdx.x + ii*blockDim.x + threadIdx.y*blockDim.x*SZ] += tmp[ii]; + } + __syncthreads(); + } + // Result is in sh_grad_weight[threadIdx.x + ii*blockDim.x] + if (threadIdx.y == 0) { + #pragma unroll + for (int ii = 0; ii < SZ; ii++) { + int feature_dim = start_feature + ii * blockDim.x; + if (feature_dim < xmax) { + dst[dst_row + feature_dim] += sh_grad_weight[threadIdx.x + ii*blockDim.x]; + } + } + } + + } +} + +template +inline typename std::enable_if::value, size_t>::type +AddTakeGradLargeBatchWorkspaceSize(size_t num_keys) { + size_t encode_bytes = 0; + cub::DeviceRunLengthEncode::Encode + (NULL, encode_bytes, NULL, NULL, NULL, NULL, num_keys); + size_t exclusivesum_bytes = 0; + cub::DeviceScan::ExclusiveSum(NULL, exclusivesum_bytes, + NULL, NULL, num_keys); + size_t temporary_bytes = std::max(encode_bytes, exclusivesum_bytes); + size_t unique_bytes = num_keys*sizeof(IndexType); + size_t counts_bytes = num_keys*sizeof(IndexType); + size_t num_runs_bytes = 1*sizeof(int); + return (unique_bytes + counts_bytes + num_runs_bytes + temporary_bytes); +} + +template +inline void AddTakeGradLargeBatch(mshadow::Tensor dst, + const mshadow::Tensor& sorted, + const mshadow::Tensor& index, + const mshadow::Tensor &src, + mshadow::Tensor* workspace) { + CHECK_EQ(dst.CheckContiguous(), true); + CHECK_EQ(sorted.CheckContiguous(), true); + CHECK_EQ(index.CheckContiguous(), true); + CHECK_EQ(src.CheckContiguous(), true); + // const int kWarpBits = kMemUnitBits; + cudaStream_t stream = mshadow::Stream::GetStream(dst.stream_); + IndexType* sum_counts_ptr = NULL; + int* num_runs_ptr = NULL; + if (dst.size(0)*4 < src.size(0) && workspace != NULL) { + // Workspace given and potentially loops at least 4 times, use CUB to create sum_counts + CHECK_EQ(workspace->CheckContiguous(), true); + // workspace = [unique_out, counts_out, temporary_storage] + size_t unique_bytes = sorted.size(0)*sizeof(IndexType); + size_t counts_bytes = sorted.size(0)*sizeof(IndexType); + size_t num_runs_bytes = 1*sizeof(int); + + size_t encode_bytes = 0; + cub::DeviceRunLengthEncode::Encode + (NULL, encode_bytes, NULL, NULL, NULL, NULL, sorted.size(0), stream); + size_t exclusivesum_bytes = 0; + cub::DeviceScan::ExclusiveSum + (NULL, exclusivesum_bytes, NULL, NULL, sorted.size(0), stream); + size_t temporary_bytes = std::max(encode_bytes, exclusivesum_bytes); + + // Check that we have enough storage + CHECK_GE(workspace->size(0), unique_bytes + counts_bytes + + num_runs_bytes + temporary_bytes); + + IndexType* unique_out_ptr = reinterpret_cast(workspace->dptr_); + IndexType* counts_out_ptr = reinterpret_cast(workspace->dptr_ + unique_bytes); + num_runs_ptr = reinterpret_cast(workspace->dptr_ + unique_bytes + + counts_bytes); + void* temporary_storage = reinterpret_cast(workspace->dptr_ + unique_bytes + + counts_bytes + num_runs_bytes); + + cub::DeviceRunLengthEncode::Encode + (temporary_storage, temporary_bytes, sorted.dptr_, unique_out_ptr, counts_out_ptr, + num_runs_ptr, sorted.size(0), stream); + + sum_counts_ptr = unique_out_ptr; + cub::DeviceScan::ExclusiveSum + (temporary_storage, temporary_bytes, counts_out_ptr, sum_counts_ptr, + sorted.size(0), stream); + } + + const int num_unique_est = min(dst.size(0), src.size(0)); + const int max_nthread = 128; + const int num_y = max(src.size(0)/num_unique_est, 1); + const int block_dim_x = kWarpSize; + const int block_dim_y = min(num_y, max_nthread/block_dim_x); + const int SZ = min((src.size(1) + block_dim_x - 1) / block_dim_x, 4); + const int grid_dim_x = (src.size(1) + block_dim_x * SZ - 1) / (block_dim_x * SZ); + const int grid_dim_y = min(num_unique_est, mshadow::cuda::kBaseGridNum); + dim3 dimBlock(block_dim_x, block_dim_y); + dim3 dimGrid(grid_dim_x, grid_dim_y); + // Maximum shared memory usage: 128*4*sizeof(DType), which is 4K for 64bit DType elements + int shmem_size = dimBlock.x*SZ*dimBlock.y*sizeof(DType); + + CHECK_EQ(dst.size(1), src.size(1)) << "AddTakeGradLargeBatch: shape mismatch"; + CHECK_EQ(index.size(0), src.size(0)) << "AddTakeGradLargeBatch: shape mismatch"; + mshadow::cuda::CheckLaunchParam(dimGrid, dimBlock, "AddTakeGradLargeBatch"); + + switch (SZ) { + case 1: + AddTakeGradLargeBatchKernel<1, DType> + <<>> + (dst.dptr_, sum_counts_ptr, num_runs_ptr, + sorted.dptr_, index.dptr_, src.dptr_, + static_cast(src.size(0)), + static_cast(src.size(1))); + break; + case 2: + AddTakeGradLargeBatchKernel<2, DType> + <<>> + (dst.dptr_, sum_counts_ptr, num_runs_ptr, + sorted.dptr_, index.dptr_, src.dptr_, + static_cast(src.size(0)), + static_cast(src.size(1))); + break; + case 3: + AddTakeGradLargeBatchKernel<3, DType> + <<>> + (dst.dptr_, sum_counts_ptr, num_runs_ptr, + sorted.dptr_, index.dptr_, src.dptr_, + static_cast(src.size(0)), + static_cast(src.size(1))); + break; + case 4: + AddTakeGradLargeBatchKernel<4, DType> + <<>> + (dst.dptr_, sum_counts_ptr, num_runs_ptr, + sorted.dptr_, index.dptr_, src.dptr_, + static_cast(src.size(0)), + static_cast(src.size(1))); + break; + default: + LOG(FATAL) << "AddTakeGradLargeBatch, incorrect value SZ " << SZ; + break; + } + MSHADOW_CUDA_POST_KERNEL_CHECK(AddTakeGradLargeBatchKernel); +} + +} // namespace op +} // namespace mxnet +#endif // MXNET_OPERATOR_TENSOR_INDEXING_OP_CUH_ diff --git a/src/operator/tensor/indexing_op.cc b/src/operator/tensor/indexing_op.cc index f9023054a10f..e5cb41088e22 100644 --- a/src/operator/tensor/indexing_op.cc +++ b/src/operator/tensor/indexing_op.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2017 by Contributors * \file indexing_op.cc * \brief * \author Siyi Li, Chi Zhang @@ -189,7 +207,7 @@ The locations represented by `indices` take value `on_value`, while all other locations take value `off_value`. `one_hot` operation with `indices` of shape ``(i0, i1)`` and `depth` of ``d`` would result - in an output array of shape ``(i0, i1, d)`` with:: +in an output array of shape ``(i0, i1, d)`` with:: output[i,j,:] = off_value output[i,j,indices[i,j]] = on_value diff --git a/src/operator/tensor/indexing_op.cu b/src/operator/tensor/indexing_op.cu index 287ec25d70be..d57628a4389c 100644 --- a/src/operator/tensor/indexing_op.cu +++ b/src/operator/tensor/indexing_op.cu @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2017 by Contributors * \file indexing_op.cu * \brief * \author Siyi Li, Chi Zhang diff --git a/src/operator/tensor/indexing_op.h b/src/operator/tensor/indexing_op.h index 5fd6e81d0b2f..ef42b01fb5b6 100644 --- a/src/operator/tensor/indexing_op.h +++ b/src/operator/tensor/indexing_op.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2017 by Contributors * \file indexing_op.h * \brief * \author Bing Xu, Siyi Li, Chi Zhang diff --git a/src/operator/tensor/init_op.cc b/src/operator/tensor/init_op.cc index 16f71fc7e4e3..8dac22a64966 100644 --- a/src/operator/tensor/init_op.cc +++ b/src/operator/tensor/init_op.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2016 by Contributors * \file init_op.cc * \brief CPU Implementation of init op */ diff --git a/src/operator/tensor/init_op.cu b/src/operator/tensor/init_op.cu index a798f26db60d..6e2b65cc8519 100644 --- a/src/operator/tensor/init_op.cu +++ b/src/operator/tensor/init_op.cu @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2016 by Contributors * \file init_op.cu * \brief GPU Implementation of init op */ diff --git a/src/operator/tensor/init_op.h b/src/operator/tensor/init_op.h index 0b33277ba0d6..bdc74d332491 100644 --- a/src/operator/tensor/init_op.h +++ b/src/operator/tensor/init_op.h @@ -1,7 +1,25 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file init_op.h - * \brief Function defintion of initialization op + * \brief Function definition of initialization op */ #ifndef MXNET_OPERATOR_TENSOR_INIT_OP_H_ #define MXNET_OPERATOR_TENSOR_INIT_OP_H_ diff --git a/src/operator/tensor/la_op.cc b/src/operator/tensor/la_op.cc new file mode 100644 index 000000000000..70d4f9b766ad --- /dev/null +++ b/src/operator/tensor/la_op.cc @@ -0,0 +1,417 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +/*! + * \file la_op.cc + * \brief CPU-Operators for advanced linear algebra. + */ +#include "./la_op.h" +#include "./la_op_inline.h" + +namespace mxnet { +namespace op { + +DMLC_REGISTER_PARAMETER(LaMatrixMacParam); +DMLC_REGISTER_PARAMETER(LaMatrixMultParam); +DMLC_REGISTER_PARAMETER(LaTriangMatrixMultParam); + +NNVM_REGISTER_OP(linalg_gemm) +.describe(R"code(Performs general matrix multiplication and accumulation. +Input are three tensors *A*, *B*, *C* each of dimension *n >= 2* and each +having the same shape on the leading *n-2* dimensions. For every *n-2* dimensional index *i* let +*A*\ :sub:`i`\ , *B*\ :sub:`i`\ , *C*\ :sub:`i` be the matrices given by the last *2* dimensions. +The operator performs the BLAS3 function *gemm* + + *out*\ :sub:`i` = *alpha* \* *op*\ (*A*\ :sub:`i`\ ) \* *op*\ (*B*\ :sub:`i`\ ) + *beta* \* *C*\ :sub:`i` + +on all such triples of matrices. Here *alpha* and *beta* are scalar operator parameters and *op()* +is either the identity or the matrix transposition. + +In case of *n=2*, a single *gemm* function is performed on the matrices *A*, *B*, *C*. + +.. note:: The operator does only support float32 and float64 data types and provides + proper backward gradients. + +Examples:: + + // Single matrix multiply-add + A = [[1.0, 1.0], [1.0, 1.0]] + B = [[1.0, 1.0], [1.0, 1.0], [1.0, 1.0]] + C = [[1.0, 1.0, 1.0], [1.0, 1.0, 1.0]] + linalg_gemm(A, B, C, transpose_b = 1, alpha = 2.0 , beta = 10.0) + = [[14.0, 14.0, 14.0], [14.0, 14.0, 14.0]] + + // Batch matrix multiply-add + A = [[[1.0, 1.0]], [[0.1, 0.1]]] + B = [[[1.0, 1.0]], [[0.1, 0.1]]] + C = [[[10.0]], [[0.01]]] + linalg_gemm(A, B, C, transpose_b = 1, alpha = 2.0 , beta = 10.0) + = [[[104.0]], [[0.14]]] +)code" ADD_FILELINE) +.set_num_inputs(3) +.set_num_outputs(1) +.set_attr_parser(ParamParser) +.set_attr("FListInputNames", [](const NodeAttrs& attrs) + { return std::vector{"A", "B", "C"}; } ) +.set_attr("FInferShape", LaMatrixMultMacOpShape) +.set_attr("FInferType", ElemwiseType<3, 1>) +.set_attr("FInplaceOption", [](const NodeAttrs& attrs) + { return std::vector>{{2, 0}}; }) +.set_attr("FCompute", LaOpForward) +.set_attr("FGradient", ElemwiseGradUseIn{"_backward_linalg_gemm"}) +.add_argument("A", "NDArray-or-Symbol", "Tensor of input matrices") +.add_argument("B", "NDArray-or-Symbol", "Tensor of input matrices") +.add_argument("C", "NDArray-or-Symbol", "Tensor of input matrices") +.add_arguments(LaMatrixMacParam::__FIELDS__()); + +NNVM_REGISTER_OP(_backward_linalg_gemm) +.set_num_inputs(4) +.set_num_outputs(3) +.set_attr_parser(ParamParser) +.set_attr("FInplaceOption", [](const NodeAttrs& attrs) + { return std::vector >{{2, 1}, {3, 2}}; }) +.set_attr("FResourceRequest", [](const NodeAttrs& attrs) + { return std::vector{ResourceRequest::kTempSpace}; }) +.set_attr("TIsBackward", true) +.set_attr("FCompute", LaOpBackward); + +NNVM_REGISTER_OP(linalg_gemm2) +.describe(R"code(Performs general matrix multiplication. +Input are two tensors *A*, *B* each of dimension *n >= 2* and each +having the same shape on the leading *n-2* dimensions. For every *n-2* dimensional index *i* let +*A*\ :sub:`i`\ , *B*\ :sub:`i`\ be the matrices given by the last *2* dimensions. +The operator performs the BLAS3 function *gemm* (restricted to two arguments) + + *out*\ :sub:`i` = *alpha* \* *op*\ (*A*\ :sub:`i`\ ) \* *op*\ (*B*\ :sub:`i`\ ) + +on all such pairs of matrices. Here *alpha* is a scalar operator parameter and *op()* is either +the identity or the matrix transposition. + +In case of *n=2*, a single *gemm* function is performed on the matrices *A*, *B*. + +.. note:: The operator does only support float32 and float64 data types and provides + proper backward gradients. + +Examples:: + + // Single matrix multiply + A = [[1.0, 1.0], [1.0, 1.0]] + B = [[1.0, 1.0], [1.0, 1.0], [1.0, 1.0]] + linalg_gemm2(A, B, transpose_b = 1, alpha = 2.0) + = [[4.0, 4.0, 4.0], [4.0, 4.0, 4.0]] + + // Batch matrix multiply + A = [[[1.0, 1.0]], [[0.1, 0.1]]] + B = [[[1.0, 1.0]], [[0.1, 0.1]]] + linalg_gemm2(A, B, transpose_b = 1, alpha = 2.0 ) + = [[[4.0]], [[0.04 ]]] +)code" ADD_FILELINE) +.set_num_inputs(2) +.set_num_outputs(1) +.set_attr_parser(ParamParser) +.set_attr("FListInputNames", [](const NodeAttrs& attrs) + { return std::vector{"A", "B"}; } ) +.set_attr("FInferShape", LaMatrixMultMacOpShape) +.set_attr("FInferType", ElemwiseType<2, 1>) +.set_attr("FCompute", LaOpForward) +.set_attr("FGradient", ElemwiseGradUseIn{"_backward_linalg_gemm2"}) +.add_argument("A", "NDArray-or-Symbol", "Tensor of input matrices") +.add_argument("B", "NDArray-or-Symbol", "Tensor of input matrices") +.add_arguments(LaMatrixMultParam::__FIELDS__()); + +NNVM_REGISTER_OP(_backward_linalg_gemm2) +.set_num_inputs(3) +.set_num_outputs(2) +.set_attr_parser(ParamParser) +.set_attr("FInplaceOption", [](const NodeAttrs& attrs) + { return std::vector >{{2, 1}}; }) +.set_attr("FResourceRequest", [](const NodeAttrs& attrs) + { return std::vector{ResourceRequest::kTempSpace}; }) +.set_attr("TIsBackward", true) +.set_attr("FCompute", LaOpBackward); + +NNVM_REGISTER_OP(linalg_potrf) +.describe(R"code(Performs Cholesky factorization of a symmetric positive-definite matrix. +Input is a tensor *A* of dimension *n >= 2*. For every *n-2* dimensional index *i* let +*A*\ :sub:`i`\ be the matrix given by the last *2* dimensions. +The operator performs the Cholesky factorization (LAPACK function *potrf*) +on each *A*\ :sub:`i`\ , +i.e. it computes a lower triangular matrix *U*\ :sub:`i` such that + + *A*\ :sub:`i`\ = *U*\ :sub:`i`\ \* *U*\ :sub:`i`\ \ :sup:`T` + +for all such matrices. The matrices *A*\ :sub:`i` must be all symmetric and positive-definite. +The resulting matrices *U*\ :sub:`i` will contain zeros in the upper triangle +apart from the diagonal. + +In case of *n=2*, a single Cholesky factorization is performed on the matrix *A*. + +.. note:: The operator does only support float32 and float64 data types and provides + proper backward gradients. + +Examples:: + + // Single matrix factorization + A = [[4.0, 1.0], [1.0, 4.25]] + linalg_potrf(A) = [[2.0, 0], [0.5, 2.0]] + + // Batch matrix factorization + A = [[[4.0, 1.0], [1.0, 4.25]], [[16.0, 4.0], [4.0, 17.0]]] + linalg_potrf(A) = [[[2.0, 0], [0.5, 2.0]], [[4.0, 0], [1.0, 4.0]]] +)code" ADD_FILELINE) +.set_num_inputs(1) +.set_num_outputs(1) +.set_attr("FListInputNames", [](const NodeAttrs& attrs) + { return std::vector{"A"}; } ) +.set_attr("FInferShape", ElemwiseShape<1, 1>) +.set_attr("FInferType", ElemwiseType<1, 1>) +.set_attr("FInplaceOption", [](const NodeAttrs& attrs) + { return std::vector>{{0, 0}}; }) +.set_attr("FCompute", LaOpForward) +.set_attr("FGradient", ElemwiseGradUseOut{"_backward_linalg_potrf"}) +.add_argument("A", "NDArray-or-Symbol", "Tensor of input matrices to be decomposed"); + +NNVM_REGISTER_OP(_backward_linalg_potrf) +.set_num_inputs(2) +.set_num_outputs(1) +.set_attr("FInplaceOption", [](const NodeAttrs& attrs) + { return std::vector >{{0, 0}}; }) +.set_attr("FResourceRequest", [](const NodeAttrs& attrs) + { return std::vector{ResourceRequest::kTempSpace}; }) +.set_attr("TIsBackward", true) +.set_attr("FCompute", LaOpBackward); + + +NNVM_REGISTER_OP(linalg_potri) +.describe(R"code(Performs matrix inversion from a Cholesky factorization. +Input is a tensor *A* of dimension *n >= 2*. For every *n-2* dimensional index *i* let +*A*\ :sub:`i`\ be the matrix given by the last *2* dimensions. +The operator assumes that each *A*\ :sub:`i` is the Cholesky factorization of some symmetric +positive-definite matrix *B*\ :sub:`i` given as a lower triangular matrix +(so *A* is the output of a prior call to operator *linalg_potrf*). The operator computes the +inverse of each *B*\ :sub:`i` from this decomposition, i.e + + *out*\ :sub:`i` = *B*\ :sub:`i`\ \ :sup:`-1` + +for all such matrices. + +In case of *n=2*, the operation is performed on the matrix *A* itself. + +.. note:: The operator does only support float32 and float64 data types and provides + proper backward gradients. + +Examples:: + + // Single matrix inverse + A = [[2.0, 0], [0.5, 2.0]] + linalg_potri(A) = [[0.26563, -0.0625], [-0.0625, 0.25]] + + // Batch matrix inverse + A = [[[2.0, 0], [0.5, 2.0]], [[4.0, 0], [1.0, 4.0]]] + linalg_potri(A) = [[[0.26563, -0.0625], [-0.0625, 0.25]], + [[0.06641, -0.01562], [-0.01562, 0,0625]]] +)code" ADD_FILELINE) +.set_num_inputs(1) +.set_num_outputs(1) +.set_attr("FListInputNames", [](const NodeAttrs& attrs) + { return std::vector{"A"}; } ) +.set_attr("FInferShape", ElemwiseShape<1, 1>) +.set_attr("FInferType", ElemwiseType<1, 1>) +.set_attr("FInplaceOption", [](const NodeAttrs& attrs) + { return std::vector>{{0, 0}}; }) +.set_attr("FCompute", LaOpForward) +.set_attr("FGradient", ElemwiseGradUseInOut{"_backward_linalg_potri"}) +.add_argument("A", "NDArray-or-Symbol", "Tensor of lower triangular matrices"); + +NNVM_REGISTER_OP(_backward_linalg_potri) +.set_num_inputs(3) +.set_num_outputs(1) +.set_attr("FResourceRequest", [](const NodeAttrs& attrs) + { return std::vector{ResourceRequest::kTempSpace}; }) +.set_attr("TIsBackward", true) +.set_attr("FCompute", LaOpBackward); + +NNVM_REGISTER_OP(linalg_trmm) +.describe(R"code(Performs multiplication with a triangular matrix. +Input are two tensors *A*, *B* each of dimension *n >= 2* and each +having the same shape on the leading *n-2* dimensions. For every *n-2* dimensional index *i* let +*A*\ :sub:`i`\ , *B*\ :sub:`i`\ be the matrices given by the last *2* dimensions. +The operator performs the BLAS3 function *trmm* + + *out*\ :sub:`i` = *alpha* \* *op*\ (*A*\ :sub:`i`\ ) \* *B*\ :sub:`i` + +or + + *out*\ :sub:`i` = *alpha* \* *B*\ :sub:`i` \* *op*\ (*A*\ :sub:`i`\ ) + +on all such pairs of matrices. Here *alpha* is a scalar operator parameter, *op()* is either +the identity or the matrix transposition (depending on the parameter *transpose*) and the +order of matrix multiplication depends on the parameter *rightside*. +All matrices *A*\ :sub:`i` must be lower triangular. + +In case of *n=2*, a single *trmm* function is performed on the matrices *A*, *B*. + +.. note:: The operator does only support float32 and float64 data types and provides + proper backward gradients. + +Examples:: + + // Single matrix multiply + A = [[1.0, 0], [1.0, 1.0]] + B = [[1.0, 1.0, 1.0], [1.0, 1.0, 1.0]] + linalg_trmm(A, B, alpha = 2.0) = [[2.0, 2.0, 2.0], [4.0, 4.0, 4.0]] + + // Batch matrix multiply + A = [[[1.0, 0], [1.0, 1.0]], [[1.0, 0], [1.0, 1.0]]] + B = [[[1.0, 1.0, 1.0], [1.0, 1.0, 1.0]], [[0.5, 0.5, 0.5], [0.5, 0.5, 0.5]]] + linalg_trmm(A, B, alpha = 2.0 ) = [[[2.0, 2.0, 2.0], [4.0, 4.0, 4.0]], + [[1.0, 1.0, 1.0], [2.0, 2.0, 2.0]]] + +)code" ADD_FILELINE) +.set_num_inputs(2) +.set_num_outputs(1) +.set_attr_parser(ParamParser) +.set_attr("FListInputNames", [](const NodeAttrs& attrs) + { return std::vector{"A", "B"}; } ) +.set_attr("FInferShape", LaTriangMatrixMultOpShape) +.set_attr("FInferType", ElemwiseType<2, 1>) +.set_attr("FInplaceOption", [](const NodeAttrs& attrs) + { return std::vector>{{1, 0}}; }) +.set_attr("FCompute", LaOpForward) +.set_attr("FGradient", ElemwiseGradUseInOut{"_backward_linalg_trmm"}) +.add_argument("A", "NDArray-or-Symbol", "Tensor of lower triangular matrices") +.add_argument("B", "NDArray-or-Symbol", "Tensor of matrices") +.add_arguments(LaTriangMatrixMultParam::__FIELDS__()); + +NNVM_REGISTER_OP(_backward_linalg_trmm) +.set_num_inputs(4) +.set_num_outputs(2) +.set_attr_parser(ParamParser) +.set_attr("FInplaceOption", [](const NodeAttrs& attrs) + { return std::vector >{{0, 1}}; }) +.set_attr("FResourceRequest", [](const NodeAttrs& attrs) + { return std::vector{ResourceRequest::kTempSpace}; }) +.set_attr("TIsBackward", true) +.set_attr("FCompute", LaOpBackward); + +NNVM_REGISTER_OP(linalg_trsm) +.describe(R"code(Solves matrix equations involving a triangular matrix. +Input are two tensors *A*, *B* each of dimension *n >= 2* and each +having the same shape on the leading *n-2* dimensions. For every *n-2* dimensional index *i* let +*A*\ :sub:`i`\ , *B*\ :sub:`i`\ be the matrices given by the last *2* dimensions. +The operator performs the BLAS3 function *trsm*, i.e. it solves the equation + + *op*\ (*A*\ :sub:`i`\ ) \* *X*\ :sub:`i` = *alpha* \* *B*\ :sub:`i` + +or + + *X*\ :sub:`i` \* *op*\ (*A*\ :sub:`i`\ ) = *alpha* \* *B*\ :sub:`i` + +on all such pairs of matrices. Here *alpha* is a scalar operator parameter, *op()* is either +the identity or the matrix transposition (depending on the parameter *transpose*) and the +order of multiplication on the left depends on the parameter *rightside*. +All matrices *A*\ :sub:`i` must be lower triangular. + +In case of *n=2*, a single *trsm* function is performed on the matrices *A*, *B*. + +.. note:: The operator does only support float32 and float64 data types and provides + proper backward gradients. + +Examples:: + + // Single matrix solve + A = [[1.0, 0], [1.0, 1.0]] + B = [[2.0, 2.0, 2.0], [4.0, 4.0, 4.0]] + linalg_trsm(A, B, alpha = 0.5) = [[1.0, 1.0, 1.0], [1.0, 1.0, 1.0]] + + // Batch matrix solve + A = [[[1.0, 0], [1.0, 1.0]], [[1.0, 0], [1.0, 1.0]]] + B = [[[2.0, 2.0, 2.0], [4.0, 4.0, 4.0]], + [[4.0, 4.0, 4.0], [8.0, 8.0, 8.0]]] + linalg_trsm(A, B, alpha = 0.5 ) = [[[1.0, 1.0, 1.0], [1.0, 1.0, 1.0]], + [[2.0, 2.0, 2.0 ], [2.0, 2.0, 2.0]]] +)code" ADD_FILELINE) +.set_num_inputs(2) +.set_num_outputs(1) +.set_attr_parser(ParamParser) +.set_attr("FListInputNames", [](const NodeAttrs& attrs) + { return std::vector{"A", "B"}; } ) +.set_attr("FInferShape", LaTriangMatrixMultOpShape) +.set_attr("FInferType", ElemwiseType<2, 1>) +.set_attr("FInplaceOption", [](const NodeAttrs& attrs) + { return std::vector>{{1, 0}}; }) +.set_attr("FCompute", LaOpForward) +.set_attr("FGradient", ElemwiseGradUseInOut{"_backward_linalg_trsm"}) +.add_argument("A", "NDArray-or-Symbol", "Tensor of lower triangular matrices") +.add_argument("B", "NDArray-or-Symbol", "Tensor of matrices") +.add_arguments(LaTriangMatrixMultParam::__FIELDS__()); + +NNVM_REGISTER_OP(_backward_linalg_trsm) +.set_num_inputs(4) +.set_num_outputs(2) +.set_attr_parser(ParamParser) +.set_attr("FInplaceOption", [](const NodeAttrs& attrs) + { return std::vector >{{0, 1}, {1, 0}}; }) +.set_attr("FResourceRequest", [](const NodeAttrs& attrs) + { return std::vector{ResourceRequest::kTempSpace}; }) +.set_attr("TIsBackward", true) +.set_attr("FCompute", LaOpBackward); + +NNVM_REGISTER_OP(linalg_sumlogdiag) +.describe(R"code(Computes the sum of the logarithms of all diagonal elements in a matrix. +Input is a tensor *A* of dimension *n >= 2*. For every *n-2* dimensional index *i* let +*A*\ :sub:`i`\ be the matrix given by the last *2* dimensions. +The operator performs a reduction of each such matrix to a scalar by summing up the logarithms +of all diagonal elements. All matrices must be square and all diagonal elements must be positive. + +In case of *n=2*, *A* represents a single matrix on which the reduction will be performed. + +.. note:: The operator does only support float32 and float64 data types and provides + proper backward gradients. + +Examples:: + + // Single matrix reduction + A = [[1.0, 1.0], [1.0, 7.0]] + linalg_sumlogdiag(A) = [1.9459] + + // Batch matrix reduction + A = [[[1.0, 1.0], [1.0, 7.0]], [[3.0, 0], [0, 17.0]]] + linalg_sumlogdiag(A) = [1.9459, 3.9318] +)code" ADD_FILELINE) +.set_num_inputs(1) +.set_num_outputs(1) +.set_attr("FListInputNames", [](const NodeAttrs& attrs) + { return std::vector{"A"}; } ) +.set_attr("FInferShape", LaReduceShape<2>) +.set_attr("FInferType", ElemwiseType<1, 1>) +.set_attr("FCompute", LaOpForward) +.set_attr("FGradient", ElemwiseGradUseIn{"_backward_linalg_sumlogdiag"}) +.add_argument("A", "NDArray-or-Symbol", "Tensor of square matrices"); + +NNVM_REGISTER_OP(_backward_linalg_sumlogdiag) +.set_num_inputs(2) +.set_num_outputs(1) +.set_attr("FResourceRequest", [](const NodeAttrs& attrs) + { return std::vector{ResourceRequest::kTempSpace}; }) +.set_attr("TIsBackward", true) +.set_attr("FCompute", LaOpBackward); + +} // namespace op +} // namespace mxnet diff --git a/src/operator/tensor/la_op.cu b/src/operator/tensor/la_op.cu new file mode 100644 index 000000000000..a89d98fd7f82 --- /dev/null +++ b/src/operator/tensor/la_op.cu @@ -0,0 +1,77 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +/*! + * \file la_op.cu + * \brief GPU-Operators for advanced linear algebra. + */ +#include "./la_op.h" +#include "./la_op_inline.h" + +namespace mxnet { +namespace op { + +NNVM_REGISTER_OP(linalg_gemm) +.set_attr("FCompute", LaOpForward); + +NNVM_REGISTER_OP(_backward_linalg_gemm) +.set_attr("FCompute", LaOpBackward); + +NNVM_REGISTER_OP(linalg_gemm2) +.set_attr("FCompute", LaOpForward); + +NNVM_REGISTER_OP(_backward_linalg_gemm2) +.set_attr("FCompute", LaOpBackward); + +NNVM_REGISTER_OP(linalg_trmm) +.set_attr("FCompute", LaOpForward); + +NNVM_REGISTER_OP(_backward_linalg_trmm) +.set_attr("FCompute", LaOpBackward); + +NNVM_REGISTER_OP(linalg_trsm) +.set_attr("FCompute", LaOpForward); + +NNVM_REGISTER_OP(_backward_linalg_trsm) +.set_attr("FCompute", LaOpBackward); + +NNVM_REGISTER_OP(linalg_sumlogdiag) +.set_attr("FCompute", LaOpForward); + +NNVM_REGISTER_OP(_backward_linalg_sumlogdiag) +.set_attr("FCompute", LaOpBackward); + +NNVM_REGISTER_OP(linalg_potri) +.set_attr("FCompute", LaOpForward); + +NNVM_REGISTER_OP(_backward_linalg_potri) +.set_attr("FCompute", LaOpBackward); + +#if MXNET_USE_CUSOLVER == 1 + +NNVM_REGISTER_OP(linalg_potrf) +.set_attr("FCompute", LaOpForward); + +NNVM_REGISTER_OP(_backward_linalg_potrf) +.set_attr("FCompute", LaOpBackward); + +#endif + +} // namespace op +} // namespace mxnet diff --git a/src/operator/tensor/la_op.h b/src/operator/tensor/la_op.h new file mode 100644 index 000000000000..dd5fab985e3c --- /dev/null +++ b/src/operator/tensor/la_op.h @@ -0,0 +1,351 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +/*! + * \file la_op.h + * \brief Operators for advanced linear algebra. + */ +#ifndef MXNET_OPERATOR_TENSOR_LA_OP_H_ +#define MXNET_OPERATOR_TENSOR_LA_OP_H_ + +#include +#include +#include +#include "../mshadow_op.h" +#include "../mxnet_op.h" +#include "../operator_common.h" +#include "../elemwise_op_common.h" + +namespace mxnet { +namespace op { + +// Parameters for general matrix-matrix multiply-accumulate (mac) +struct LaMatrixMacParam : public dmlc::Parameter { + bool transpose_a, transpose_b; + double alpha, beta; + DMLC_DECLARE_PARAMETER(LaMatrixMacParam) { + DMLC_DECLARE_FIELD(transpose_a) + .set_default(false) + .describe("Multiply with transposed of first input (A)."); + DMLC_DECLARE_FIELD(transpose_b) + .set_default(false) + .describe("Multiply with transposed of second input (B)."); + DMLC_DECLARE_FIELD(alpha) + .set_default(1.0) + .describe("Scalar factor multiplied with A*B."); + DMLC_DECLARE_FIELD(beta) + .set_default(1.0) + .describe("Scalar factor multiplied with C."); + } +}; + +// Parameters for general matrix-matrix multiply +struct LaMatrixMultParam : public dmlc::Parameter { + bool transpose_a, transpose_b; + double alpha; + DMLC_DECLARE_PARAMETER(LaMatrixMultParam) { + DMLC_DECLARE_FIELD(transpose_a) + .set_default(false) + .describe("Multiply with transposed of first input (A)."); + DMLC_DECLARE_FIELD(transpose_b) + .set_default(false) + .describe("Multiply with transposed of second input (B)."); + DMLC_DECLARE_FIELD(alpha) + .set_default(1.0) + .describe("Scalar factor multiplied with A*B."); + } +}; + +// Parameters for matrix-matrix multiplication where one is a triangular matrix. +struct LaTriangMatrixMultParam : public dmlc::Parameter { + bool transpose; + bool rightside; + double alpha; + DMLC_DECLARE_PARAMETER(LaTriangMatrixMultParam) { + DMLC_DECLARE_FIELD(transpose) + .set_default(false) + .describe("Use transposed of the triangular matrix"); + DMLC_DECLARE_FIELD(rightside) + .set_default(false) + .describe("Multiply triangular matrix from the right to non-triangular one."); + DMLC_DECLARE_FIELD(alpha) + .set_default(1.0) + .describe("Scalar factor to be applied to the result."); + } +}; + +// Common function for shape inference for matrix mult and matrix mac. +inline bool LaMatrixMultMacOpShape(const nnvm::NodeAttrs& attrs, + std::vector* in_attrs, + std::vector* out_attrs) { + CHECK_GE(in_attrs->size(), 2); + CHECK_EQ(out_attrs->size(), 1); + bool transpose_a(false), transpose_b(false); + if ( in_attrs->size() == 2 ) { + // Matrix-Matrix mult + transpose_a = nnvm::get(attrs.parsed).transpose_a; + transpose_b = nnvm::get(attrs.parsed).transpose_b; + } else { + // Matrix-Matrix mac + transpose_a = nnvm::get(attrs.parsed).transpose_a; + transpose_b = nnvm::get(attrs.parsed).transpose_b; + } + if ( (*in_attrs)[0].ndim() >= 2 && (*in_attrs)[0].ndim() == (*in_attrs)[1].ndim() ) { + // Forward shape inference. + const int ndim((*in_attrs)[0].ndim()); + std::vector oshape(ndim); + for ( int i = 0; i < ndim-2; ++i ) { + // Both inputs must have same shape except for last two dimensions. + if ( (*in_attrs)[0][i] != (*in_attrs)[1][i] ) return false; + oshape[i] = (*in_attrs)[0][i]; + } + CHECK_EQ((transpose_a ? (*in_attrs)[0][ndim-2] : (*in_attrs)[0][ndim-1]), + (transpose_b ? (*in_attrs)[1][ndim-1] : (*in_attrs)[1][ndim-2])) + << "Incompatible matrix dimensions for multiplication"; + oshape[ndim-2] = (transpose_a ? (*in_attrs)[0][ndim-1] : (*in_attrs)[0][ndim-2]); + oshape[ndim-1] = (transpose_b ? (*in_attrs)[1][ndim-2] : (*in_attrs)[1][ndim-1]); + TShape tshape(oshape.begin(), oshape.end()); + SHAPE_ASSIGN_CHECK(*out_attrs, 0, tshape); + if ( in_attrs->size() > 2 ) { + // Infer/check shape of third operand of a mac. + SHAPE_ASSIGN_CHECK(*in_attrs, 2, tshape); + } + return true; + } + // Can't do backward inference of shapes for this operator. + return false; +} + +inline bool LaTriangMatrixMultOpShape(const nnvm::NodeAttrs& attrs, + std::vector* in_attrs, + std::vector* out_attrs) { + const LaTriangMatrixMultParam& param = nnvm::get(attrs.parsed); + CHECK_EQ(in_attrs->size(), 2); + CHECK_EQ(out_attrs->size(), 1); + if ( (*in_attrs)[0].ndim() >= 2 && (*in_attrs)[0].ndim() == (*in_attrs)[1].ndim() ) { + // Forward shape inference. + const int ndim((*in_attrs)[0].ndim()); + CHECK_EQ((*in_attrs)[0][ndim-2], (*in_attrs)[0][ndim-1]) + << "First operand must be a tensor of square matrices"; + std::vector oshape(ndim); + for ( int i = 0; i < ndim-2; ++i ) { + // Must have same shape except for last two dimensions. + if ( (*in_attrs)[0][i] != (*in_attrs)[1][i] ) return false; + oshape[i] = (*in_attrs)[0][i]; + } + if ( param.rightside ) { + // We compute B * A where A is the first and B the second input. + CHECK_EQ((*in_attrs)[0][ndim-2], (*in_attrs)[1][ndim-1]) + << "Incompatible matrix dimensions for multiplication"; + oshape[ndim-2] = (*in_attrs)[1][ndim-2]; + oshape[ndim-1] = (param.transpose ? (*in_attrs)[0][ndim-2] : (*in_attrs)[0][ndim-1]); + } else { + // We compute A * B where A is the first and B the second input. + CHECK_EQ((*in_attrs)[1][ndim-2], (*in_attrs)[0][ndim-1]) + << "Incompatible matrix dimensions for multiplication"; + oshape[ndim-2] = (param.transpose ? (*in_attrs)[0][ndim-1] : (*in_attrs)[0][ndim-2]); + oshape[ndim-1] = (*in_attrs)[1][ndim-1]; + } + TShape tshape(oshape.begin(), oshape.end()); + SHAPE_ASSIGN_CHECK(*out_attrs, 0, tshape); + return true; + } + if ( (*out_attrs)[0].ndim() >= 2 ) { + // Backward shape inference. + const int odim((*out_attrs)[0].ndim()); + std::vector ishape1(odim), ishape2(odim); + for ( int i = 0; i < odim-2; ++i ) { + ishape1[i] = ishape2[i] = (*out_attrs)[0][i]; + } + if ( param.rightside ) { + // We compute B * A where A is the first and B the second input. + ishape2[odim-2] = (*out_attrs)[0][odim-2]; + ishape1[odim-2] = ishape1[odim-1] = ishape2[odim-1] = (*out_attrs)[0][odim-1]; + } else { + // We compute A * B where A is the first and B the second input. + ishape2[odim-1] = (*out_attrs)[0][odim-1]; + ishape1[odim-2] = ishape1[odim-1] = ishape2[odim-2] = (*out_attrs)[0][odim-2]; + } + TShape tshape1(ishape1.begin(), ishape1.end()); + SHAPE_ASSIGN_CHECK(*in_attrs, 0, tshape1); + TShape tshape2(ishape2.begin(), ishape2.end()); + SHAPE_ASSIGN_CHECK(*in_attrs, 1, tshape2); + return true; + } + return false; +} + +template +inline bool LaReduceShape(const nnvm::NodeAttrs& attrs, + std::vector* in_attrs, + std::vector* out_attrs) { + // Shape for reduction of the dim lowest dimensions to a scalar. + // Can only deduct in forward direction. + CHECK_EQ(in_attrs->size(), 1); + CHECK_EQ(out_attrs->size(), 1); + const int ndim((*in_attrs)[0].ndim()); + if ( ndim < dim ) { + return false; + } + std::vector oshape(std::max(1, ndim-dim)); + oshape[0] = 1; + for ( int i = 0; i < ndim - dim; ++i ) { + oshape[i] = (*in_attrs)[0][i]; + } + // Will reduce all matrices/vectors to a scalar. + TShape tshape(oshape.begin(), oshape.end()); + SHAPE_ASSIGN_CHECK(*out_attrs, 0, tshape); + return true; +} + +// Adapters for calling the various operators with appropriate signatures. +template +struct LaOpCaller { + static void op(const std::vector& inputs, + const std::vector& outputs, + const nnvm::NodeAttrs& attrs, + mshadow::Stream *s) { + CHECK(false) << "no specialized LaOpCaller defined for template parameters"; + } +}; +template +struct LaOpCaller { + static void op(const std::vector& inputs, + const std::vector& outputs, + const nnvm::NodeAttrs& attrs, + mshadow::Stream *s) { + laop::op(inputs[0].FlatToKD(s), + outputs[0].FlatToKD(s), s, attrs); + } +}; +template +struct LaOpCaller { + static void op(const std::vector& inputs, + const std::vector& outputs, + const nnvm::NodeAttrs& attrs, + mshadow::Stream *s) { + laop::op(inputs[0].FlatToKD(s), + inputs[1].FlatToKD(s), + outputs[0].FlatToKD(s), s, attrs); + } +}; +template +struct LaOpCaller { + static void op(const std::vector& inputs, + const std::vector& outputs, + const nnvm::NodeAttrs& attrs, + mshadow::Stream *s) { + laop::op(inputs[0].FlatToKD(s), + inputs[1].FlatToKD(s), + inputs[2].FlatToKD(s), + outputs[0].FlatToKD(s), s, attrs); + } +}; +template +struct LaOpCaller { + static void op(const std::vector& inputs, + const std::vector& outputs, + const nnvm::NodeAttrs& attrs, + mshadow::Stream *s) { + laop::op(inputs[0].FlatToKD(s), + inputs[1].FlatToKD(s), + inputs[2].FlatToKD(s), + outputs[0].FlatToKD(s), + outputs[1].FlatToKD(s), s, attrs); + } +}; +template +struct LaOpCaller { + static void op(const std::vector& inputs, + const std::vector& outputs, + const nnvm::NodeAttrs& attrs, + mshadow::Stream *s) { + laop::op(inputs[0].FlatToKD(s), + inputs[1].FlatToKD(s), + inputs[2].FlatToKD(s), + inputs[3].FlatToKD(s), + outputs[0].FlatToKD(s), + outputs[1].FlatToKD(s), s, attrs); + } +}; +template +struct LaOpCaller { + static void op(const std::vector& inputs, + const std::vector& outputs, + const nnvm::NodeAttrs& attrs, + mshadow::Stream *s) { + laop::op(inputs[0].FlatToKD(s), + inputs[1].FlatToKD(s), + inputs[2].FlatToKD(s), + inputs[3].FlatToKD(s), + outputs[0].FlatToKD(s), + outputs[1].FlatToKD(s), + outputs[2].FlatToKD(s), s, attrs); + } +}; + + +template +void LaOpForward(const nnvm::NodeAttrs& attrs, + const OpContext& ctx, + const std::vector& inputs, + const std::vector& req, + const std::vector& outputs) { + using namespace mshadow; + Stream *s = ctx.get_stream(); + CHECK_EQ(inputs.size(), inum); + CHECK_EQ(outputs.size(), onum); + MSHADOW_SGL_DBL_TYPE_SWITCH(outputs[0].type_flag_, OType, { + LaOpCaller::op(inputs, outputs, attrs, s); + }); +} + + +template +void LaOpBackward(const nnvm::NodeAttrs& attrs, + const OpContext& ctx, + const std::vector& inputs, + const std::vector& req, + const std::vector& outputs) { + using namespace mshadow; + Stream *s = ctx.get_stream(); + CHECK_EQ(inputs.size(), inum); + CHECK_EQ(outputs.size(), onum); + MSHADOW_SGL_DBL_TYPE_SWITCH(outputs[0].type_flag_, OType, { + std::vector tspace(outputs); + for ( int i = 0; i < onum; ++i ) { + if ( req[i] == kAddTo ) { + tspace[i].dptr_ = ctx.requested[ResourceRequest::kTempSpace] + .get_space_typed(Shape1(outputs[i].Size()), s).dptr_; + } + } + LaOpCaller::op(inputs, tspace, attrs, s); + for ( int i = 0; i < onum; ++i ) { + if ( req[i] == kAddTo ) { + Tensor out = outputs[i].FlatTo1D(s); + out += tspace[i].FlatTo1D(s); + } + } + }); +} + +} // namespace op +} // namespace mxnet + +#endif // MXNET_OPERATOR_TENSOR_LA_OP_H_ diff --git a/src/operator/tensor/la_op_inline.h b/src/operator/tensor/la_op_inline.h new file mode 100644 index 000000000000..34fb441f53f7 --- /dev/null +++ b/src/operator/tensor/la_op_inline.h @@ -0,0 +1,310 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +/*! + * \file la_op_inline.h + * \brief Operators for advanced linear algebra. + */ +#ifndef MXNET_OPERATOR_TENSOR_LA_OP_INLINE_H_ +#define MXNET_OPERATOR_TENSOR_LA_OP_INLINE_H_ + +#include "../linalg.h" + +namespace mxnet { +namespace op { + +using namespace mshadow; + +// Helper functions. +struct CopyLowerToUpper { + template + MSHADOW_XINLINE static void Map(int i, int matrix_size, int stride, DType* data) { + // Below computation works even when we are dealing with a batch of matrices. + const int row((i % matrix_size) / stride), col(i % stride); + if ( row > col ) data[i + (col - row) * (stride - 1)] = data[i]; + } +}; +struct ZeroUpper { + template + MSHADOW_XINLINE static void Map(int i, int matrix_size, int stride, DType* data) { + const int row((i % matrix_size) / stride), col(i % stride); + if ( row < col ) data[i] = 0; + } +}; +struct Scale { + template + MSHADOW_XINLINE static void Map(int i, DType scale, DType* data) { + data[i] *= scale; + } +}; + +// Forward computations (always using batched processing) + +// D = gemm(A,B,C) +struct gemm { + template + static void op(const Tensor& A, const Tensor& B, + const Tensor& C, DType alpha, DType beta, bool tA, bool tB, Stream *s) { + linalg_batch_gemm(A, B, C, alpha, beta, tA, tB, s); + } + template + static void op(const Tensor& A, const Tensor& B, + const Tensor& C, const Tensor& D, + Stream *s, const nnvm::NodeAttrs& attrs) { + if ( C.dptr_ != D.dptr_ ) Copy(D, C, s); + const LaMatrixMacParam& param = nnvm::get(attrs.parsed); + gemm::op(A, B, D, DType(param.alpha), DType(param.beta), + param.transpose_a, param.transpose_b, s); + } +}; + +// C = gemm2(A,B) +struct gemm2 { + template + static void op(const Tensor& A, const Tensor& B, + const Tensor& C, Stream *s, const nnvm::NodeAttrs& attrs) { + const LaMatrixMultParam& param = nnvm::get(attrs.parsed); + gemm::op(A, B, C, DType(param.alpha), DType(0), param.transpose_a, param.transpose_b, s); + } +}; + +// L = potrf(A). +struct potrf { + template + static void op(const Tensor& A, const Tensor& L, + Stream *s, const nnvm::NodeAttrs& attrs) { + if ( A.dptr_ != L.dptr_ ) Copy(L, A, s); + linalg_batch_potrf(L, true, s); + using namespace mxnet_op; + Kernel::Launch(s, L.MSize(), L.size(1)*L.stride_, L.stride_, L.dptr_); + } +}; + +// A = potri(L). +struct potri { + template + static void op(const Tensor& L, const Tensor& A, + Stream *s, const nnvm::NodeAttrs& attrs) { + if ( A.dptr_ != L.dptr_ ) Copy(A, L, s); + linalg_batch_potri(A, true, s); + using namespace mxnet_op; + Kernel::Launch(s, A.MSize(), A.size(1)*A.stride_, A.stride_, A.dptr_); + } +}; + +// B = trsm(L,A) +struct trsm { + template + static void op(const Tensor& L, const Tensor& B, + DType alpha, bool rightside, bool transpose, Stream *s) { + linalg_batch_trsm(L, B, alpha, rightside, true, transpose, s); + } + template + static void op(const Tensor& L, const Tensor& A, + const Tensor& B, + Stream *s, const nnvm::NodeAttrs& attrs) { + if ( A.dptr_ != B.dptr_ ) Copy(B, A, s); + const LaTriangMatrixMultParam& param = nnvm::get(attrs.parsed); + op(L, B, DType(param.alpha), param.rightside, param.transpose, s); + } +}; + +// B = trmm(L,A) +struct trmm { + template + static void op(const Tensor& L, const Tensor& B, + DType alpha, bool rightside, bool transpose, Stream *s) { + linalg_batch_trmm(L, B, alpha, rightside, true, transpose, s); + } + template + static void op(const Tensor& L, const Tensor& A, + const Tensor& B, Stream *s, const nnvm::NodeAttrs& attrs) { + if ( A.dptr_ != B.dptr_ ) Copy(B, A, s); + const LaTriangMatrixMultParam& param = nnvm::get(attrs.parsed); + op(L, B, DType(param.alpha), param.rightside, param.transpose, s); + } +}; + +// Useful operator that is not part of BLAS/LAPACK. +struct ForwardSumLogDiag { + template + MSHADOW_XINLINE static void Map(int i, int N, int stride, DType* A, DType* B) { + DType sum(0); + const int offset(i * N * stride); + for ( int j = 0; j < N; ++j ) { + sum += log(A[offset+j*(stride+1)]); + } + B[i] = sum; + } +}; +struct sumlogdiag { + template + static void op(const Tensor& A, const Tensor& B, + Stream *s, const nnvm::NodeAttrs& attrs) { + CHECK_EQ(A.size(1), A.size(2)) << "sumlogdiag operator requires square matrices as input."; + using namespace mxnet_op; + Kernel::Launch(s, A.size(0), A.size(1), A.stride_, A.dptr_, B.dptr_); + } +}; + +// Backward operators (always using batch processing) + +struct gemm_backward { + template + static void op(const Tensor& dD, const Tensor& A, + const Tensor& B, const Tensor& C, + const Tensor& dA, const Tensor& dB, + const Tensor& dC, + Stream* s, const nnvm::NodeAttrs& attrs) { + const LaMatrixMacParam& param = nnvm::get(attrs.parsed); + bool tA(param.transpose_a), tB(param.transpose_b); + (tA ? gemm::op(B, dD, dA, DType(param.alpha), DType(0), tB, true, s) + : gemm::op(dD, B, dA, DType(param.alpha), DType(0), false, !tB, s)); + (tB ? gemm::op(dD, A, dB, DType(param.alpha), DType(0), true, tA, s) + : gemm::op(A, dD, dB, DType(param.alpha), DType(0), !tA, false, s)); + Copy(dC, dD, s); + using namespace mxnet_op; + Kernel::Launch(s, dC.MSize(), DType(param.beta), dC.dptr_); + } +}; + +struct gemm2_backward { + template + static void op(const Tensor& dC, const Tensor& A, + const Tensor& B, const Tensor& dA, + const Tensor& dB, + Stream* s, const nnvm::NodeAttrs& attrs) { + const LaMatrixMultParam& param = nnvm::get(attrs.parsed); + bool tA(param.transpose_a), tB(param.transpose_b); + (tA ? gemm::op(B, dC, dA, DType(param.alpha), DType(0), tB, true, s) + : gemm::op(dC, B, dA, DType(param.alpha), DType(0), false, !tB, s)); + (tB ? gemm::op(dC, A, dB, DType(param.alpha), DType(0), true, tA, s) + : gemm::op(A, dC, dB, DType(param.alpha), DType(0), !tA, false, s)); + } +}; + +struct potrf_backward { + template + static void op(const Tensor& dL, const Tensor& L, + const Tensor& dA, + Stream* s, const nnvm::NodeAttrs& attrs) { + // Backward of L = potrf(A). + // dA = 0.5 * L**T * symm(L**T * dL # E) * L**(-1) where + // '#' denotes Hadamard product + // E is the matrix having 1 on diagonal, 0 on upper and 2 on lower triagle + // symm(X) = 0.5 * (X + X**T) + // Hadamard product and symm can be realized by a single copy from lower to upper triangle. + if ( dL.dptr_ != dA.dptr_ ) { + Copy(dA, dL, s); + } + trmm::op(L, dA, DType(1.0), false, true, s); + using namespace mxnet_op; + Kernel::Launch + (s, dA.MSize(), dA.size(1)*dA.stride_, dA.stride_, dA.dptr_); + trsm::op(L, dA, DType(1.0), false, true, s); + trsm::op(L, dA, DType(0.5), true, false, s); + } +}; + +struct potri_backward { + template + static void op(const Tensor& dA, const Tensor& L, + const Tensor& A, const Tensor& dL, + Stream* s, const nnvm::NodeAttrs& attrs) { + // Backward of A = potri(L). + // dL = -2 * tril(A * dA * L**(-T)), where tril() extracts lower triangle and diagonal. + gemm::op(A, dA, dL, DType(1.0), DType(0), false, false, s); + trsm::op(L, dL, DType(-2.0), true, true, s); + using namespace mxnet_op; + Kernel::Launch(s, dL.MSize(), dL.size(1)*dL.stride_, dL.stride_, dL.dptr_); + } +}; + +struct trsm_backward { + template + static void op(const Tensor& dB, const Tensor& L, + const Tensor& A, const Tensor& B, + const Tensor& dL, const Tensor& dA, + Stream* s, const nnvm::NodeAttrs& attrs) { + // Backward of B = trsm(L,A). + const LaTriangMatrixMultParam& param = nnvm::get(attrs.parsed); + // Compute dA + if ( dA.dptr_ != dB.dptr_ ) Copy(dA, dB, s); + trsm::op(L, dA, DType(param.alpha), param.rightside, !param.transpose, s); + // Compute dL + const bool da_left(param.rightside == param.transpose); + DType scale(-1.0/param.alpha); + (da_left ? gemm::op(dA, B, dL, scale, DType(0), param.transpose, !param.transpose, s) + : gemm::op(B, dA, dL, scale, DType(0), !param.transpose, param.transpose, s)); + using namespace mxnet_op; + Kernel::Launch(s, dL.MSize(), dL.size(1)*dL.stride_, dL.stride_, dL.dptr_); + } +}; + +struct trmm_backward { + template + static void op(const Tensor& dB, const Tensor& L, + const Tensor& A, const Tensor& B, + const Tensor& dL, const Tensor& dA, + Stream* s, const nnvm::NodeAttrs& attrs) { + // Backward of B = trmm(L,A). + const LaTriangMatrixMultParam& param = nnvm::get(attrs.parsed); + // Compute dL + const bool db_left(param.rightside == param.transpose); + DType scale(param.alpha); + (db_left ? gemm::op(dB, A, dL, scale, DType(0), param.transpose, !param.transpose, s) + : gemm::op(A, dB, dL, scale, DType(0), !param.transpose, param.transpose, s)); + using namespace mxnet_op; + Kernel::Launch(s, dL.MSize(), dL.size(1)*dL.stride_, dL.stride_, dL.dptr_); + // Compute dA + if ( dA.dptr_ != dB.dptr_ ) Copy(dA, dB, s); + trmm::op(L, dA, scale, param.rightside, !param.transpose, s); + } +}; + +struct BackwardSumLogDiag { + template + MSHADOW_XINLINE static void Map(int i, int N, int stride, DType* dB, DType* A, DType* dA) { + const int offset(i * N * stride); + for ( int j = 0; j < N; ++j ) { + dA[offset+j*(stride+1)] = dB[i]/A[offset+j*(stride+1)]; + } + } +}; +struct sumlogdiag_backward { + template + static void op(const Tensor& dB, const Tensor& A, + const Tensor& dA, + Stream* s, const nnvm::NodeAttrs& attrs) { + // Backward of B = sumlogdiag(A). + // dB is actually a 1-d tensor but we convert it to a 3-D one before calling + // this function as the LaOpCaller-adapters can only deal with a uniform + // dimension for all tensor inputs. This doesn't matter as we will interpret + // it correctly internally in this function. + using namespace mxnet_op; + Kernel::Launch(s, dA.MSize(), DType(0), dA.dptr_); + Kernel::Launch + (s, A.size(0), A.size(1), A.stride_, dB.dptr_, A.dptr_, dA.dptr_); + } +}; + +} // namespace op +} // namespace mxnet + +#endif // MXNET_OPERATOR_TENSOR_LA_OP_INLINE_H_ diff --git a/src/operator/tensor/matrix_op-inl.h b/src/operator/tensor/matrix_op-inl.h index 986638750e9f..af0de593c1be 100644 --- a/src/operator/tensor/matrix_op-inl.h +++ b/src/operator/tensor/matrix_op-inl.h @@ -1,7 +1,25 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file matrix_op-inl.h - * \brief Function defintion of matrix related operators + * \brief Function definition of matrix related operators */ #ifndef MXNET_OPERATOR_TENSOR_MATRIX_OP_INL_H_ #define MXNET_OPERATOR_TENSOR_MATRIX_OP_INL_H_ @@ -12,6 +30,7 @@ #include #include "../mshadow_op.h" #include "../elemwise_op_common.h" +#include "../channel_op_common.h" #include "../mxnet_op.h" #include "broadcast_reduce_op.h" @@ -28,7 +47,6 @@ struct ReshapeParam : public dmlc::Parameter { nnvm::Tuple shape; bool reverse; DMLC_DECLARE_PARAMETER(ReshapeParam) { - int tmp[] = {0, 0}; DMLC_DECLARE_FIELD(shape) .set_default(nnvm::Tuple()) .describe("The target shape"); @@ -36,7 +54,7 @@ struct ReshapeParam : public dmlc::Parameter { .set_default(false) .describe("If true then the special values are inferred from right to left"); DMLC_DECLARE_FIELD(target_shape) - .set_default(TShape(tmp, tmp + 2)) + .set_default(TShape()) .describe("(Deprecated! Use ``shape`` instead.) " "Target new shape. One and only one dim can be 0, " "in which case it will be inferred from the rest of dims"); @@ -53,8 +71,6 @@ inline bool ReshapeShape(const nnvm::NodeAttrs& attrs, const ReshapeParam& param_ = nnvm::get(attrs.parsed); CHECK_EQ(in_attrs->size(), 1U) << "Input: [data]"; CHECK_EQ(out_attrs->size(), 1U); - CHECK_EQ(param_.target_shape.ndim() > 0 || - param_.shape.ndim() > 0, true) << "targe_shape or shape must be present."; const TShape &dshape = (*in_attrs)[0]; if (dshape.ndim() == 0) return false; if (param_.shape.ndim() != 0) { @@ -138,9 +154,8 @@ inline bool ReshapeShape(const nnvm::NodeAttrs& attrs, << "Target shape size is different to source. " << "Target: " << oshape << "\nSource: " << dshape; - out_attrs->clear(); - out_attrs->push_back(oshape); - } else { + SHAPE_ASSIGN_CHECK(*out_attrs, 0, oshape); + } else if (param_.target_shape.ndim()) { LOG(INFO) << "Using target_shape will be deprecated."; TShape oshape = param_.target_shape; int neg_count = 0; @@ -164,8 +179,9 @@ inline bool ReshapeShape(const nnvm::NodeAttrs& attrs, << "Target shape size is different to source. " << "Target: " << param_.target_shape.Size() << "\nSource: " << dshape.Size(); - out_attrs->clear(); - out_attrs->push_back(oshape); + SHAPE_ASSIGN_CHECK(*out_attrs, 0, oshape); + } else { + return (*out_attrs)[0].ndim(); } return true; } @@ -177,12 +193,11 @@ inline bool FlattenShape(const nnvm::NodeAttrs& attrs, CHECK_EQ(out_attrs->size(), 1U); const TShape &dshape = (*in_attrs)[0]; if (dshape.ndim() == 0) return false; - out_attrs->clear(); uint32_t target_dim = 1; for (uint32_t i = 1; i < dshape.ndim(); ++i) { target_dim *= dshape[i]; } - out_attrs->push_back(mshadow::Shape2(dshape[0], target_dim)); + SHAPE_ASSIGN_CHECK(*out_attrs, 0, mshadow::Shape2(dshape[0], target_dim)); return true; } @@ -241,8 +256,14 @@ void TransposeImpl(RunContext ctx, out = transpose(in, axes.get<5>()); break; } + case 6: { + Tensor in = src.get(s); + Tensor out = ret.get(s); + out = transpose(in, axes.get<6>()); + break; + } default: - LOG(FATAL) << "Transpose support at most 5 dimensions"; + LOG(FATAL) << "Transpose support at most 6 dimensions"; break; } }); @@ -275,7 +296,7 @@ inline bool TransposeShape(const nnvm::NodeAttrs& attrs, CHECK_EQ(in_attrs->size(), 1U); CHECK_EQ(out_attrs->size(), 1U); TShape& shp = (*in_attrs)[0]; - CHECK_LE(shp.ndim(), 5U) << "Transpose support at most 5 dimensions"; + CHECK_LE(shp.ndim(), 6U) << "Transpose support at most 6 dimensions"; TShape ret(shp.ndim()); if (param.axes.ndim() == 0) { for (index_t i = 0; i < shp.ndim(); ++i) { @@ -283,8 +304,8 @@ inline bool TransposeShape(const nnvm::NodeAttrs& attrs, } } else { CHECK_EQ(shp.ndim(), param.axes.ndim()); - for (index_t i = 0; i < shp.ndim(); ++i) { - CHECK(param.axes[i] < shp.ndim()); + for (size_t i = 0; i < shp.ndim(); ++i) { + CHECK(param.axes[i] < static_cast(shp.ndim())); ret[i] = shp[param.axes[i]]; } } @@ -294,10 +315,12 @@ inline bool TransposeShape(const nnvm::NodeAttrs& attrs, struct ExpandDimParam : public dmlc::Parameter { - index_t axis; + int axis; DMLC_DECLARE_PARAMETER(ExpandDimParam) { DMLC_DECLARE_FIELD(axis) - .describe("Position (amongst axes) where new axis is to be inserted."); + .describe("Position where new axis is to be inserted. Suppose that " + "the input `NDArray`'s dimension is `ndim`, the range of " + "the inserted axis is `[-ndim, ndim]`"); } }; @@ -308,14 +331,40 @@ inline bool ExpandDimShape(const nnvm::NodeAttrs& attrs, const ExpandDimParam& param = nnvm::get(attrs.parsed); CHECK_EQ(in_attrs->size(), 1U); CHECK_EQ(out_attrs->size(), 1U); - TShape& shp = (*in_attrs)[0]; - CHECK_LE(param.axis, shp.ndim()) - << "axis exceeds the dimension of the array"; - TShape ret(shp.ndim() + 1); - for (index_t i = 0; i < param.axis; ++i) ret[i] = shp[i]; - ret[param.axis] = 1; - for (index_t i = param.axis+1; i < ret.ndim(); ++i) ret[i] = shp[i-1]; + if (in_attrs->at(0).ndim() == 0U && out_attrs->at(0).ndim() == 0U) { + return false; + } + + TShape& ishape = (*in_attrs)[0]; + TShape& oshape = (*out_attrs)[0]; + int indim = ishape.ndim(); + bool unknown_ishape = false; + if (0 == indim) { + indim = oshape.ndim() - 1; + unknown_ishape = true; + } + + int axis = param.axis; + if (axis < 0) { + axis += indim; + } + CHECK(axis >= 0 && axis <= indim) + << "axis must be in the range [" << -indim << ", " << indim << "] (" + << param.axis << " provided)"; + TShape ret(indim + 1); + for (int i = 0; i < axis; ++i) { + ret[i] = (unknown_ishape? 0 : ishape[i]); + } + ret[axis] = 1; + for (int i = axis+1; i < indim+1; ++i) { + ret[i] = (unknown_ishape? 0 : ishape[i-1]); + } SHAPE_ASSIGN_CHECK(*out_attrs, 0, ret); + + ret = TShape(indim); + for (int i = 0; i < axis; ++i) ret[i] = oshape[i]; + for (int i = axis+1; i < indim+1; ++i) ret[i-1] = oshape[i]; + SHAPE_ASSIGN_CHECK(*in_attrs, 0, ret); return true; } @@ -346,52 +395,52 @@ void DotForward_(const nnvm::NodeAttrs& attrs, << "Binary function only support input/output with the same type"; CHECK_EQ(outputs[0].type_flag_, inputs[1].type_flag_) << "Binary function only support input/output with the same type"; - CHECK_EQ(outputs[0].type_flag_, kFloat32) - << "dot only support 32 bit float so far"; - - if (inputs[0].ndim() == 1 && inputs[1].ndim() == 1) { - CHECK_NE(req[0], kAddTo) << "AddTo not yet suported"; - Tensor out = outputs[0].get(s); - VectorDot(out, - inputs[0].get(s), - inputs[1].get(s)); - } else { - int ma, na, mb, nb, m, n; - if (param.transpose_a) { - ma = inputs[0].size(0); - na = inputs[0].Size()/ma; - m = na; - } else { - na = inputs[0].size(inputs[0].ndim()-1); - ma = inputs[0].Size()/na; - m = ma; - } - if (param.transpose_b) { - nb = inputs[1].size(inputs[1].ndim()-1); - mb = inputs[1].Size()/nb; - n = mb; - } else { - mb = inputs[1].size(0); - nb = inputs[1].Size()/mb; - n = nb; - } - - Tensor input0 = - inputs[0].get_with_shape(Shape2(ma, na), s); - Tensor input1 = - inputs[1].get_with_shape(Shape2(mb, nb), s); - Tensor out = - outputs[0].get_with_shape(Shape2(m, n), s); - if (param.transpose_a && param.transpose_b) { - ASSIGN_DISPATCH(out, req[0], dot(input0.T(), input1.T())); - } else if (!param.transpose_a && param.transpose_b) { - ASSIGN_DISPATCH(out, req[0], dot(input0, input1.T())); - } else if (param.transpose_a && !param.transpose_b) { - ASSIGN_DISPATCH(out, req[0], dot(input0.T(), input1)); + CHECK(outputs[0].type_flag_ == kFloat32 || outputs[0].type_flag_ == kFloat64) + << "dot only supports float32 and float64"; + MSHADOW_TYPE_SWITCH(outputs[0].type_flag_, DType, { + if (inputs[0].ndim() == 1 && inputs[1].ndim() == 1) { + CHECK_NE(req[0], kAddTo) << "AddTo not yet suported"; + Tensor out = outputs[0].get(s); + VectorDot(out, + inputs[0].get(s), + inputs[1].get(s)); } else { - ASSIGN_DISPATCH(out, req[0], dot(input0, input1)); + int ma, na, mb, nb, m, n; + if (param.transpose_a) { + ma = inputs[0].size(0); + na = inputs[0].Size()/ma; + m = na; + } else { + na = inputs[0].size(inputs[0].ndim()-1); + ma = inputs[0].Size()/na; + m = ma; + } + if (param.transpose_b) { + nb = inputs[1].size(inputs[1].ndim()-1); + mb = inputs[1].Size()/nb; + n = mb; + } else { + mb = inputs[1].size(0); + nb = inputs[1].Size()/mb; + n = nb; + } + Tensor input0 = + inputs[0].get_with_shape(Shape2(ma, na), s); + Tensor input1 = + inputs[1].get_with_shape(Shape2(mb, nb), s); + Tensor out = + outputs[0].get_with_shape(Shape2(m, n), s); + if (param.transpose_a && param.transpose_b) { + ASSIGN_DISPATCH(out, req[0], dot(input0.T(), input1.T())); + } else if (!param.transpose_a && param.transpose_b) { + ASSIGN_DISPATCH(out, req[0], dot(input0, input1.T())); + } else if (param.transpose_a && !param.transpose_b) { + ASSIGN_DISPATCH(out, req[0], dot(input0.T(), input1)); + } else { + ASSIGN_DISPATCH(out, req[0], dot(input0, input1)); + } } - } + }); } template @@ -406,74 +455,76 @@ void DotBackward_(const nnvm::NodeAttrs& attrs, Stream *s = ctx.get_stream(); CHECK_NE(req[0], kWriteInplace); CHECK_NE(req[1], kWriteInplace); - - if (inputs[1].ndim() == 1 && inputs[2].ndim() == 1) { - Tensor mout_grad = inputs[0].get(s); - Tensor mlhs_data = inputs[1].get(s); - Tensor mrhs_data = inputs[2].get(s); - Tensor mlhs_grad = outputs[0].get(s); - Tensor mrhs_grad = outputs[1].get(s); - ASSIGN_DISPATCH(mrhs_grad, req[1], - broadcast_scalar(mout_grad, mlhs_data.shape_) * mlhs_data); - ASSIGN_DISPATCH(mlhs_grad, req[0], - broadcast_scalar(mout_grad, mlhs_data.shape_) * mrhs_data); - } else { - int ma, na, mb, nb, m, n; - if (param.transpose_a) { - ma = outputs[0].size(0); - na = outputs[0].Size()/ma; - m = na; - } else { - na = outputs[0].size(outputs[0].ndim()-1); - ma = outputs[0].Size()/na; - m = ma; - } - if (param.transpose_b) { - nb = outputs[1].size(outputs[1].ndim()-1); - mb = outputs[1].Size()/nb; - n = mb; - } else { - mb = outputs[1].size(0); - nb = outputs[1].Size()/mb; - n = nb; - } - - Tensor mout_grad = - inputs[0].get_with_shape(Shape2(m, n), s); - Tensor mlhs_data = - inputs[1].get_with_shape(Shape2(ma, na), s); - Tensor mrhs_data = - inputs[2].get_with_shape(Shape2(mb, nb), s); - Tensor mlhs_grad = - outputs[0].get_with_shape(Shape2(ma, na), s); - Tensor mrhs_grad = - outputs[1].get_with_shape(Shape2(mb, nb), s); - if (param.transpose_a && param.transpose_b) { - // Gradient of z = dot(x.T, y.T) - // dy = dot(x, dz).T = dot(dz.T, x.T) - // dx = dot(dz, y).T = dot(y.T, dz.T) - ASSIGN_DISPATCH(mrhs_grad, req[1], dot(mout_grad.T(), mlhs_data.T())); - ASSIGN_DISPATCH(mlhs_grad, req[0], dot(mrhs_data.T(), mout_grad.T())); - } else if (!param.transpose_a && param.transpose_b) { - // Gradient of z = dot(x, y.T) - // dy = dot(x.T, dz).T = dot(dz.T, x) - // dx = dot(dz, y) - ASSIGN_DISPATCH(mrhs_grad, req[1], dot(mout_grad.T(), mlhs_data)); - ASSIGN_DISPATCH(mlhs_grad, req[0], dot(mout_grad, mrhs_data)); - } else if (param.transpose_a && !param.transpose_b) { - // Gradient of z = dot(x.T, y) - // dy = dot(x, dz) - // dx = dot(dz, y.T).T = dot(y, dz.T) - ASSIGN_DISPATCH(mrhs_grad, req[1], dot(mlhs_data, mout_grad)); - ASSIGN_DISPATCH(mlhs_grad, req[0], dot(mrhs_data, mout_grad.T())); + CHECK(outputs[0].type_flag_ == kFloat32 || outputs[0].type_flag_ == kFloat64) + << "dot only supports float32 and float64"; + MSHADOW_TYPE_SWITCH(outputs[0].type_flag_, DType, { + if (inputs[1].ndim() == 1 && inputs[2].ndim() == 1) { + Tensor mout_grad = inputs[0].get(s); + Tensor mlhs_data = inputs[1].get(s); + Tensor mrhs_data = inputs[2].get(s); + Tensor mlhs_grad = outputs[0].get(s); + Tensor mrhs_grad = outputs[1].get(s); + ASSIGN_DISPATCH(mrhs_grad, req[1], + broadcast_scalar(mout_grad, mlhs_data.shape_) * mlhs_data); + ASSIGN_DISPATCH(mlhs_grad, req[0], + broadcast_scalar(mout_grad, mlhs_data.shape_) * mrhs_data); } else { - // Gradient of z = dot(x, y) - // dy = dot(x.T, dz) - // dx = dot(dz, y.T) - ASSIGN_DISPATCH(mrhs_grad, req[1], dot(mlhs_data.T(), mout_grad)); - ASSIGN_DISPATCH(mlhs_grad, req[0], dot(mout_grad, mrhs_data.T())); + int ma, na, mb, nb, m, n; + if (param.transpose_a) { + ma = outputs[0].size(0); + na = outputs[0].Size()/ma; + m = na; + } else { + na = outputs[0].size(outputs[0].ndim()-1); + ma = outputs[0].Size()/na; + m = ma; + } + if (param.transpose_b) { + nb = outputs[1].size(outputs[1].ndim()-1); + mb = outputs[1].Size()/nb; + n = mb; + } else { + mb = outputs[1].size(0); + nb = outputs[1].Size()/mb; + n = nb; + } + Tensor mout_grad = + inputs[0].get_with_shape(Shape2(m, n), s); + Tensor mlhs_data = + inputs[1].get_with_shape(Shape2(ma, na), s); + Tensor mrhs_data = + inputs[2].get_with_shape(Shape2(mb, nb), s); + Tensor mlhs_grad = + outputs[0].get_with_shape(Shape2(ma, na), s); + Tensor mrhs_grad = + outputs[1].get_with_shape(Shape2(mb, nb), s); + if (param.transpose_a && param.transpose_b) { + // Gradient of z = dot(x.T, y.T) + // dy = dot(x, dz).T = dot(dz.T, x.T) + // dx = dot(dz, y).T = dot(y.T, dz.T) + ASSIGN_DISPATCH(mrhs_grad, req[1], dot(mout_grad.T(), mlhs_data.T())); + ASSIGN_DISPATCH(mlhs_grad, req[0], dot(mrhs_data.T(), mout_grad.T())); + } else if (!param.transpose_a && param.transpose_b) { + // Gradient of z = dot(x, y.T) + // dy = dot(x.T, dz).T = dot(dz.T, x) + // dx = dot(dz, y) + ASSIGN_DISPATCH(mrhs_grad, req[1], dot(mout_grad.T(), mlhs_data)); + ASSIGN_DISPATCH(mlhs_grad, req[0], dot(mout_grad, mrhs_data)); + } else if (param.transpose_a && !param.transpose_b) { + // Gradient of z = dot(x.T, y) + // dy = dot(x, dz) + // dx = dot(dz, y.T).T = dot(y, dz.T) + ASSIGN_DISPATCH(mrhs_grad, req[1], dot(mlhs_data, mout_grad)); + ASSIGN_DISPATCH(mlhs_grad, req[0], dot(mrhs_data, mout_grad.T())); + } else { + // Gradient of z = dot(x, y) + // dy = dot(x.T, dz) + // dx = dot(dz, y.T) + ASSIGN_DISPATCH(mrhs_grad, req[1], dot(mlhs_data.T(), mout_grad)); + ASSIGN_DISPATCH(mlhs_grad, req[0], dot(mout_grad, mrhs_data.T())); + } } - } + }); } inline bool DotShape(const nnvm::NodeAttrs& attrs, @@ -525,6 +576,7 @@ void BatchDotForward_(const nnvm::NodeAttrs& attrs, const std::vector& inputs, const std::vector& req, const std::vector& outputs) { + using namespace mshadow; using namespace mshadow::expr; mshadow::Stream *s = ctx.get_stream(); const DotParam& param = nnvm::get(attrs.parsed); @@ -532,33 +584,34 @@ void BatchDotForward_(const nnvm::NodeAttrs& attrs, << "Binary function only support input/output with the same type"; CHECK_EQ(outputs[0].type_flag_, inputs[1].type_flag_) << "Binary function only support input/output with the same type"; - CHECK_EQ(outputs[0].type_flag_, mshadow::kFloat32) - << "dot only support 32 bit float so far"; - - mshadow::Tensor out = outputs[0].get(s); - mshadow::Tensor mlhs = inputs[0].get(s); - mshadow::Tensor mrhs = inputs[1].get(s); - mshadow::Tensor workspace = - ctx.requested[0].get_space_typed(mshadow::Shape1(3 * out.size(0)), s); - if (kNullOp != req[0]) { - if (param.transpose_a && param.transpose_b) { - mshadow::BatchGEMM(out, mlhs, mrhs, 1.0f, - (kAddTo == req[0]) ? 1.0f : 0.0f, - workspace); - } else if (!param.transpose_a && param.transpose_b) { - mshadow::BatchGEMM(out, mlhs, mrhs, 1.0f, - (kAddTo == req[0]) ? 1.0f : 0.0f, - workspace); - } else if (param.transpose_a && !param.transpose_b) { - mshadow::BatchGEMM(out, mlhs, mrhs, 1.0f, - (kAddTo == req[0]) ? 1.0f : 0.0f, - workspace); - } else { - mshadow::BatchGEMM(out, mlhs, mrhs, 1.0f, - (kAddTo == req[0]) ? 1.0f : 0.0f, - workspace); + CHECK(outputs[0].type_flag_ == kFloat32 || outputs[0].type_flag_ == kFloat64) + << "dot only supports float32 and float64"; + MSHADOW_TYPE_SWITCH(outputs[0].type_flag_, DType, { + mshadow::Tensor out = outputs[0].get(s); + mshadow::Tensor mlhs = inputs[0].get(s); + mshadow::Tensor mrhs = inputs[1].get(s); + mshadow::Tensor workspace = + ctx.requested[0].get_space_typed(mshadow::Shape1(3 * out.size(0)), s); + if (kNullOp != req[0]) { + if (param.transpose_a && param.transpose_b) { + mshadow::BatchGEMM(out, mlhs, mrhs, (DType)1.0f, + (kAddTo == req[0]) ? (DType)1.0f : (DType)0.0f, + workspace); + } else if (!param.transpose_a && param.transpose_b) { + mshadow::BatchGEMM(out, mlhs, mrhs, (DType)1.0f, + (kAddTo == req[0]) ? (DType)1.0f : (DType)0.0f, + workspace); + } else if (param.transpose_a && !param.transpose_b) { + mshadow::BatchGEMM(out, mlhs, mrhs, (DType)1.0f, + (kAddTo == req[0]) ? (DType)1.0f : (DType)0.0f, + workspace); + } else { + mshadow::BatchGEMM(out, mlhs, mrhs, (DType)1.0f, + (kAddTo == req[0]) ? (DType)1.0f : (DType)0.0f, + workspace); + } } - } + }); } template @@ -567,79 +620,83 @@ void BatchDotBackward_(const nnvm::NodeAttrs& attrs, const std::vector& inputs, const std::vector& req, const std::vector& outputs) { + using namespace mshadow; using namespace mshadow::expr; mshadow::Stream *s = ctx.get_stream(); const DotParam& param = nnvm::get(attrs.parsed); CHECK_NE(req[1], kWriteInplace); CHECK_NE(req[0], kWriteInplace); - - mshadow::Tensor mout_grad = inputs[0].get(s); - mshadow::Tensor mlhs_data = inputs[1].get(s); - mshadow::Tensor mrhs_data = inputs[2].get(s); - mshadow::Tensor mlhs_grad = outputs[0].get(s); - mshadow::Tensor mrhs_grad = outputs[1].get(s); - mshadow::Tensor workspace = - ctx.requested[0].get_space_typed( - mshadow::Shape2(2, 3 * mout_grad.size(0)), s); - mshadow::Tensor rhs_workspace = workspace[0]; - mshadow::Tensor lhs_workspace = workspace[1]; - if (param.transpose_a && param.transpose_b) { - // Gradient of z = dot(x.T, y.T) - // dy = dot(x, dz).T = dot(dz.T, x.T) - // dx = dot(dz, y).T = dot(y.T, dz.T) - if (kNullOp != req[1]) { - mshadow::BatchGEMM(mrhs_grad, mout_grad, mlhs_data, 1.0f, - (kAddTo == req[1]) ? 1.0f : 0.0f, - rhs_workspace); - } - if (kNullOp != req[0]) { - mshadow::BatchGEMM(mlhs_grad, mrhs_data, mout_grad, 1.0f, - (kAddTo == req[0]) ? 1.0f : 0.0f, - lhs_workspace); - } - } else if (!param.transpose_a && param.transpose_b) { - // Gradient of z = dot(x, y.T) - // dy = dot(x.T, dz).T = dot(dz.T, x) - // dx = dot(dz, y) - if (kNullOp != req[1]) { - mshadow::BatchGEMM(mrhs_grad, mout_grad, mlhs_data, 1.0f, - (kAddTo == req[1]) ? 1.0f : 0.0f, - rhs_workspace); - } - if (kNullOp != req[0]) { - mshadow::BatchGEMM(mlhs_grad, mout_grad, mrhs_data, 1.0f, - (kAddTo == req[0]) ? 1.0f : 0.0f, - lhs_workspace); - } - } else if (param.transpose_a && !param.transpose_b) { - // Gradient of z = dot(x.T, y) - // dy = dot(x, dz) - // dx = dot(dz, y.T).T = dot(y, dz.T) - if (kNullOp != req[1]) { - mshadow::BatchGEMM(mrhs_grad, mlhs_data, mout_grad, 1.0f, - (kAddTo == req[1]) ? 1.0f : 0.0f, - rhs_workspace); - } - if (kNullOp != req[0]) { - mshadow::BatchGEMM(mlhs_grad, mrhs_data, mout_grad, 1.0f, - (kAddTo == req[0]) ? 1.0f : 0.0f, - lhs_workspace); - } - } else { - // Gradient of z = dot(x, y) - // dy = dot(x.T, dz) - // dx = dot(dz, y.T) - if (kNullOp != req[1]) { - mshadow::BatchGEMM(mrhs_grad, mlhs_data, mout_grad, 1.0f, - (kAddTo == req[1]) ? 1.0f : 0.0f, - rhs_workspace); - } - if (kNullOp != req[0]) { - mshadow::BatchGEMM(mlhs_grad, mout_grad, mrhs_data, 1.0f, - (kAddTo == req[0]) ? 1.0f : 0.0f, - lhs_workspace); + CHECK(outputs[0].type_flag_ == kFloat32 || outputs[0].type_flag_ == kFloat64) + << "dot only supports float32 and float64"; + MSHADOW_TYPE_SWITCH(outputs[0].type_flag_, DType, { + mshadow::Tensor mout_grad = inputs[0].get(s); + mshadow::Tensor mlhs_data = inputs[1].get(s); + mshadow::Tensor mrhs_data = inputs[2].get(s); + mshadow::Tensor mlhs_grad = outputs[0].get(s); + mshadow::Tensor mrhs_grad = outputs[1].get(s); + mshadow::Tensor workspace = + ctx.requested[0].get_space_typed( + mshadow::Shape2(2, 3 * mout_grad.size(0)), s); + mshadow::Tensor rhs_workspace = workspace[0]; + mshadow::Tensor lhs_workspace = workspace[1]; + if (param.transpose_a && param.transpose_b) { + // Gradient of z = dot(x.T, y.T) + // dy = dot(x, dz).T = dot(dz.T, x.T) + // dx = dot(dz, y).T = dot(y.T, dz.T) + if (kNullOp != req[1]) { + mshadow::BatchGEMM(mrhs_grad, mout_grad, mlhs_data, (DType)1.0f, + (kAddTo == req[1]) ? (DType)1.0f : (DType)0.0f, + rhs_workspace); + } + if (kNullOp != req[0]) { + mshadow::BatchGEMM(mlhs_grad, mrhs_data, mout_grad, (DType)1.0f, + (kAddTo == req[0]) ? (DType)1.0f : (DType)0.0f, + lhs_workspace); + } + } else if (!param.transpose_a && param.transpose_b) { + // Gradient of z = dot(x, y.T) + // dy = dot(x.T, dz).T = dot(dz.T, x) + // dx = dot(dz, y) + if (kNullOp != req[1]) { + mshadow::BatchGEMM(mrhs_grad, mout_grad, mlhs_data, (DType)1.0f, + (kAddTo == req[1]) ? (DType)1.0f : (DType)0.0f, + rhs_workspace); + } + if (kNullOp != req[0]) { + mshadow::BatchGEMM(mlhs_grad, mout_grad, mrhs_data, (DType)1.0f, + (kAddTo == req[0]) ? (DType)1.0f : (DType)0.0f, + lhs_workspace); + } + } else if (param.transpose_a && !param.transpose_b) { + // Gradient of z = dot(x.T, y) + // dy = dot(x, dz) + // dx = dot(dz, y.T).T = dot(y, dz.T) + if (kNullOp != req[1]) { + mshadow::BatchGEMM(mrhs_grad, mlhs_data, mout_grad, (DType)1.0f, + (kAddTo == req[1]) ? (DType)1.0f : (DType)0.0f, + rhs_workspace); + } + if (kNullOp != req[0]) { + mshadow::BatchGEMM(mlhs_grad, mrhs_data, mout_grad, (DType)1.0f, + (kAddTo == req[0]) ? (DType)1.0f : (DType)0.0f, + lhs_workspace); + } + } else { + // Gradient of z = dot(x, y) + // dy = dot(x.T, dz) + // dx = dot(dz, y.T) + if (kNullOp != req[1]) { + mshadow::BatchGEMM(mrhs_grad, mlhs_data, mout_grad, (DType)1.0f, + (kAddTo == req[1]) ? (DType)1.0f : (DType)0.0f, + rhs_workspace); + } + if (kNullOp != req[0]) { + mshadow::BatchGEMM(mlhs_grad, mout_grad, mrhs_data, (DType)1.0f, + (kAddTo == req[0]) ? (DType)1.0f : (DType)0.0f, + lhs_workspace); + } } - } + }); } inline bool BatchDotShape(const nnvm::NodeAttrs& attrs, @@ -684,9 +741,11 @@ inline TShape GetSliceShape(const SliceParam& param, const TShape& dshape) { << "Slicing axis exceeds data dimensions"; CHECK_LE(param.end.ndim(), dshape.ndim()) << "Slicing axis exceeds data dimensions"; + CHECK_EQ(param.begin.ndim(), param.end.ndim()) + << "begin and end must have the same length"; - TShape oshape(dshape.ndim()); - for (index_t i = 0; i < dshape.ndim(); ++i) { + TShape oshape = dshape; + for (index_t i = 0; i < param.begin.ndim(); ++i) { int s = 0, e = dshape[i]; if (e != 0) { if (param.begin[i]) { @@ -780,7 +839,7 @@ void Slice(const nnvm::NodeAttrs& attrs, break; } default: - LOG(FATAL) << "crop supports at most 5 dimensions"; + LOG(FATAL) << "slice supports at most 5 dimensions"; break; } }); @@ -1387,11 +1446,13 @@ void RepeatOpForward(const nnvm::NodeAttrs& attrs, std::pair rshapes = ReshapeInputOutputForRepeatOp(ishape, axisOpt, repeats); // reshaped input tblob - TBlob iblob(inputs[0].dptr_, rshapes.first, inputs[0].dev_mask_, inputs[0].type_flag_); + TBlob iblob(inputs[0].dptr_, rshapes.first, inputs[0].dev_mask(), + inputs[0].type_flag_, inputs[0].dev_id()); std::vector newInputs = {iblob}; // reshaped output tblob - TBlob oblob(outputs[0].dptr_, rshapes.second, outputs[0].dev_mask_, outputs[0].type_flag_); + TBlob oblob(outputs[0].dptr_, rshapes.second, outputs[0].dev_mask(), + outputs[0].type_flag_, outputs[0].dev_id()); std::vector newOutputs = {oblob}; BroadcastCompute(attrs, ctx, newInputs, req, newOutputs); @@ -1429,11 +1490,13 @@ void RepeatOpBackward(const nnvm::NodeAttrs& attrs, ReshapeInputOutputForRepeatOp(oshape, axisOpt, repeats); // reshaped output grad tblob - TBlob oblob(outputs[0].dptr_, rshapes.first, outputs[0].dev_mask_, outputs[0].type_flag_); + TBlob oblob(outputs[0].dptr_, rshapes.first, outputs[0].dev_mask(), + outputs[0].type_flag_, outputs[0].dev_id()); std::vector newOutputs = {oblob}; // reshaped input grad tblob - TBlob iblob(inputs[0].dptr_, rshapes.second, inputs[0].dev_mask_, inputs[0].type_flag_); + TBlob iblob(inputs[0].dptr_, rshapes.second, inputs[0].dev_mask(), + inputs[0].type_flag_, inputs[0].dev_id()); std::vector newInputs = {iblob}; ReduceAxesComputeImpl( @@ -1563,10 +1626,12 @@ void TileOpForward(const nnvm::NodeAttrs& attrs, std::pair rshapes = ReshapeInputOutputForTileOp(ishape, reps); // reshaped input tblob - TBlob iblob(inputs[0].dptr_, rshapes.first, inputs[0].dev_mask_, inputs[0].type_flag_); + TBlob iblob(inputs[0].dptr_, rshapes.first, inputs[0].dev_mask(), + inputs[0].type_flag_, inputs[0].dev_id()); std::vector newInputs = {iblob}; // reshaped output tblob - TBlob oblob(outputs[0].dptr_, rshapes.second, outputs[0].dev_mask_, outputs[0].type_flag_); + TBlob oblob(outputs[0].dptr_, rshapes.second, outputs[0].dev_mask(), + outputs[0].type_flag_, outputs[0].dev_id()); std::vector newOutputs = {oblob}; BroadcastCompute(attrs, ctx, newInputs, req, newOutputs); @@ -1603,10 +1668,12 @@ void TileOpBackward(const nnvm::NodeAttrs& attrs, std::pair rshapes = ReshapeInputOutputForTileOp(oshape, reps); // reshaped output grad tblob - TBlob oblob(outputs[0].dptr_, rshapes.first, outputs[0].dev_mask_, outputs[0].type_flag_); + TBlob oblob(outputs[0].dptr_, rshapes.first, outputs[0].dev_mask(), + outputs[0].type_flag_, outputs[0].dev_id()); std::vector newOutputs = {oblob}; // reshaped input grad tblob - TBlob iblob(inputs[0].dptr_, rshapes.second, inputs[0].dev_mask_, inputs[0].type_flag_); + TBlob iblob(inputs[0].dptr_, rshapes.second, inputs[0].dev_mask(), + inputs[0].type_flag_, inputs[0].dev_id()); std::vector newInputs = {iblob}; ReduceAxesComputeImpl( @@ -1688,7 +1755,7 @@ void ReverseOpForward(const nnvm::NodeAttrs& attrs, CHECK_LT(*axis_iter, static_cast(ishape.ndim())); stride_[reverse_index] = ishape[*axis_iter]; trailing_[reverse_index] = 1; - for (int i2 = *axis_iter + 1; i2 < ishape.ndim(); ++i2) { + for (index_t i2 = *axis_iter + 1; i2 < ishape.ndim(); ++i2) { trailing_[reverse_index] *= ishape[i2]; } reverse_index++; @@ -1727,6 +1794,114 @@ void ReverseOpForward(const nnvm::NodeAttrs& attrs, } +struct StackParam : public dmlc::Parameter { + int axis; + int num_args; + DMLC_DECLARE_PARAMETER(StackParam) { + DMLC_DECLARE_FIELD(axis) + .set_default(0) + .describe("The axis in the result array along which the input arrays are stacked."); + DMLC_DECLARE_FIELD(num_args).set_lower_bound(1) + .describe("Number of inputs to be stacked."); + } +}; + + +inline bool StackOpShape(const nnvm::NodeAttrs& attrs, + std::vector *in_attrs, + std::vector *out_attrs) { + const StackParam& param = dmlc::get(attrs.parsed); + + TShape dshape; + for (const TShape& i : (*in_attrs)) { + shape_assign(&dshape, i); + } + if (dshape.ndim() == 0) return false; + + TShape oshape(dshape.ndim() + 1); + int axis = CheckAxis(param.axis, oshape.ndim()); + for (int i = 0; i < axis; ++i) { + oshape[i] = dshape[i]; + } + oshape[axis] = param.num_args; + for (index_t i = axis + 1; i < oshape.ndim(); ++i) { + oshape[i] = dshape[i-1]; + } + SHAPE_ASSIGN_CHECK(*out_attrs, 0, oshape); + + return true; +} + + +template +void StackOpForward(const nnvm::NodeAttrs& attrs, + const OpContext& ctx, + const std::vector& inputs, + const std::vector& req, + const std::vector& outputs) { + using namespace mshadow; + using namespace mshadow::expr; + const StackParam& param = dmlc::get(attrs.parsed); + int axis = CheckAxis(param.axis, outputs[0].ndim()); + + Stream *s = ctx.get_stream(); + MSHADOW_TYPE_SWITCH(outputs[0].type_flag_, DType, { + std::vector > data(inputs.size()); + Tensor out; + size_t leading = 1, trailing = 1; + for (int i = 0; i < axis; ++i) { + leading *= outputs[0].shape_[i]; + } + for (int i = axis + 1; i < outputs[0].ndim(); ++i) { + trailing *= outputs[0].shape_[i]; + } + size_t mid = outputs[0].shape_[axis]; + Shape<3> oshape = Shape3(leading, mid, trailing); + out = outputs[0].get_with_shape(oshape, s); + + for (index_t i = 0; i < inputs.size(); ++i) { + Shape<3> dshape = Shape3(leading, 1, trailing); + data[i] = inputs[i].get_with_shape(dshape, s); + } + Concatenate(data, &out, 1, req[0]); + }) +} + +template +void StackOpBackward(const nnvm::NodeAttrs& attrs, + const OpContext& ctx, + const std::vector& inputs, + const std::vector& req, + const std::vector& outputs) { + using namespace mshadow; + using namespace mshadow::expr; + const StackParam& param = dmlc::get(attrs.parsed); + int axis = CheckAxis(param.axis, inputs[0].ndim()); + + Stream *s = ctx.get_stream(); + MSHADOW_TYPE_SWITCH(inputs[0].type_flag_, DType, { + std::vector > grad_in(outputs.size()); + Tensor grad; + size_t leading = 1, trailing = 1; + for (int i = 0; i < axis; ++i) { + leading *= inputs[0].shape_[i]; + } + for (int i = axis + 1; i < inputs[0].ndim(); ++i) { + trailing *= inputs[0].shape_[i]; + } + size_t mid = inputs[0].shape_[axis]; + Shape<3> oshape = Shape3(leading, mid, trailing); + grad = inputs[0].get_with_shape(oshape, s); + + for (index_t i = 0; i < outputs.size(); ++i) { + Shape<3> dshape = Shape3(leading, 1, trailing); + grad_in[i] = outputs[i].get_with_shape(dshape, s); + } + Split(grad, &grad_in, 1, req); + }) +} + + } // namespace op } // namespace mxnet diff --git a/src/operator/tensor/matrix_op.cc b/src/operator/tensor/matrix_op.cc index a061b5f4e7e5..e7e8f5548a1c 100644 --- a/src/operator/tensor/matrix_op.cc +++ b/src/operator/tensor/matrix_op.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file matrix_op.cc * \brief CPU Implementation of matrix operations */ @@ -20,6 +38,7 @@ DMLC_REGISTER_PARAMETER(DotParam); DMLC_REGISTER_PARAMETER(RepeatParam); DMLC_REGISTER_PARAMETER(TileParam); DMLC_REGISTER_PARAMETER(ReverseParam); +DMLC_REGISTER_PARAMETER(StackParam); NNVM_REGISTER_OP(Reshape) .add_alias("reshape") @@ -95,7 +114,11 @@ If the argument `reverse` is set to 1, then the special values are inferred from .set_attr("FInplaceOption", [](const NodeAttrs& attrs) { return std::vector >{{0, 0}}; -}) + }) +.set_attr("FInplaceIdentity", + [](const NodeAttrs& attrs){ + return std::vector{true}; + }) .add_argument("data", "NDArray-or-Symbol", "Input data to reshape.") .add_arguments(ReshapeParam::__FIELDS__()); @@ -133,8 +156,12 @@ Example:: .set_attr("FCompute", IdentityCompute) .set_attr("FInplaceOption", [](const NodeAttrs& attrs) { - return std::vector >{{0, 0}}; -}) + return std::vector >{{0, 0}}; + }) +.set_attr("FInplaceIdentity", + [](const NodeAttrs& attrs){ + return std::vector{true}; + }) .add_argument("data", "NDArray-or-Symbol", "Input array."); NNVM_REGISTER_OP(transpose) @@ -211,6 +238,10 @@ will return a new array with shape ``(2,1,3,4)``. [](const NodeAttrs& attrs){ return std::vector >{{0, 0}}; }) +.set_attr("FInplaceIdentity", + [](const NodeAttrs& attrs){ + return std::vector{true}; + }) .set_attr("FGradient", ElemwiseGradUseNone{"_backward_copy"}) .set_attr("FCompute", IdentityCompute) .add_argument("data", "NDArray-or-Symbol", "Source input") @@ -222,7 +253,7 @@ NNVM_REGISTER_OP(slice) .. note:: ``crop`` is deprecated. Use ``slice`` instead. -This function returns a sliced continous region of the array between the indices given +This function returns a sliced continuous region of the array between the indices given by `begin` and `end`. For an input array of `n` dimensions, slice operation with ``begin=(b_0, b_1...b_n-1)`` indices @@ -230,7 +261,7 @@ and ``end=(e_1, e_2, ... e_n)`` indices will result in an array with the shape ``(e_1-b_0, ..., e_n-b_n-1)``. The resulting array's *k*-th dimension contains elements - from the *k*-th dimension of the input array with the open range ``[b_k, e_k)``. +from the *k*-th dimension of the input array with the open range ``[b_k, e_k)``. Example:: @@ -503,7 +534,11 @@ NNVM_REGISTER_OP(_backward_repeat) .set_num_outputs(1) .set_attr_parser(ParamParser) .set_attr("TIsBackward", true) -.set_attr("FCompute", RepeatOpBackward); +.set_attr("FCompute", RepeatOpBackward) +.set_attr("FResourceRequest", +[](const NodeAttrs& attrs) { + return std::vector {ResourceRequest::kTempSpace}; +}); NNVM_REGISTER_OP(tile) .describe(R"code(Repeats the whole array multiple times. @@ -560,7 +595,11 @@ NNVM_REGISTER_OP(_backward_tile) .set_num_outputs(1) .set_attr_parser(ParamParser) .set_attr("TIsBackward", true) -.set_attr("FCompute", TileOpBackward); +.set_attr("FCompute", TileOpBackward) +.set_attr("FResourceRequest", +[](const NodeAttrs& attrs) { + return std::vector {ResourceRequest::kTempSpace}; +}); NNVM_REGISTER_OP(reverse) .describe(R"code(Reverses the order of elements along given axis while preserving array shape. @@ -607,5 +646,56 @@ NNVM_REGISTER_OP(_backward_reverse) return std::vector {ResourceRequest::kTempSpace}; }) .set_attr("FCompute", ReverseOpForward); + +NNVM_REGISTER_OP(stack) +.describe(R"code(Join a sequence of arrays along a new axis. + +The axis parameter specifies the index of the new axis in the dimensions of the +result. For example, if axis=0 it will be the first dimension and if axis=-1 it +will be the last dimension. + +Examples:: + + x = [1, 2] + y = [3, 4] + + stack(x, y) = [[1, 2], + [3, 4]] + stack(x, y, axis=1) = [[1, 3], + [2, 4]] +)code") +.set_num_inputs([](const nnvm::NodeAttrs& attrs) { + const StackParam& param = dmlc::get(attrs.parsed); + return static_cast(param.num_args); + }) +.set_num_outputs(1) +.set_attr_parser(ParamParser) +.set_attr("FListInputNames", + [](const NodeAttrs& attrs) { + uint32_t num_args = dmlc::get(attrs.parsed).num_args; + std::vector ret; + for (uint32_t i = 0; i < num_args; ++i) { + ret.push_back(std::string("arg") + std::to_string(i)); + } + return ret; + }) +.set_attr("key_var_num_args", "num_args") +.set_attr("FInferShape", StackOpShape) +.set_attr("FInferType", ElemwiseType<-1, 1>) +.set_attr("FCompute", StackOpForward) +.set_attr("FGradient", ElemwiseGradUseNone{"_backward_stack"}) +.add_argument("data", "NDArray-or-Symbol[]", "List of arrays to stack") +.add_arguments(StackParam::__FIELDS__()); + +NNVM_REGISTER_OP(_backward_stack) +.set_num_inputs(1) +.set_num_outputs([](const nnvm::NodeAttrs& attrs) { + const StackParam& param = dmlc::get(attrs.parsed); + return static_cast(param.num_args); + }) +.set_attr_parser(ParamParser) +.set_attr("TIsBackward", true) +.set_attr("FCompute", StackOpBackward); + } // namespace op } // namespace mxnet diff --git a/src/operator/tensor/matrix_op.cu b/src/operator/tensor/matrix_op.cu index 96c075a7d483..ca40419a9367 100644 --- a/src/operator/tensor/matrix_op.cu +++ b/src/operator/tensor/matrix_op.cu @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file matrix_op.cu * \brief GPU Implementation of matrix operations */ @@ -74,5 +92,11 @@ NNVM_REGISTER_OP(reverse) NNVM_REGISTER_OP(_backward_reverse) .set_attr("FCompute", ReverseOpForward); + +NNVM_REGISTER_OP(stack) +.set_attr("FCompute", StackOpForward); + +NNVM_REGISTER_OP(_backward_stack) +.set_attr("FCompute", StackOpBackward); } // namespace op } // namespace mxnet diff --git a/src/operator/tensor/ordering_op-inl.h b/src/operator/tensor/ordering_op-inl.h index 98093d1b053d..eb28b010cbd3 100644 --- a/src/operator/tensor/ordering_op-inl.h +++ b/src/operator/tensor/ordering_op-inl.h @@ -1,7 +1,25 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2016 by Contributors * \file ordering_op-inl.h - * \brief Function defintion of matrix related operators + * \brief Function definition of matrix related operators */ #ifndef MXNET_OPERATOR_TENSOR_ORDERING_OP_INL_H_ #define MXNET_OPERATOR_TENSOR_ORDERING_OP_INL_H_ diff --git a/src/operator/tensor/ordering_op.cc b/src/operator/tensor/ordering_op.cc index 3308836c1840..22712a82b4c9 100644 --- a/src/operator/tensor/ordering_op.cc +++ b/src/operator/tensor/ordering_op.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2016 by Contributors * \file ordering.cc * \brief CPU Implementation of the ordering operations */ diff --git a/src/operator/tensor/ordering_op.cu b/src/operator/tensor/ordering_op.cu index 29df70f9e6b4..8e40b4a350d9 100644 --- a/src/operator/tensor/ordering_op.cu +++ b/src/operator/tensor/ordering_op.cu @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file matrix_op.cu * \brief GPU Implementation of matrix operations */ diff --git a/src/operator/tensor/sort_op-inl.cuh b/src/operator/tensor/sort_op-inl.cuh index 10ba61f5431d..5ad31053f92e 100644 --- a/src/operator/tensor/sort_op-inl.cuh +++ b/src/operator/tensor/sort_op-inl.cuh @@ -1,129 +1,148 @@ -/*! - * Copyright (c) 2017 by Contributors - * \file sort_op-inl.cuh - * \brief CUDA implementations for sort_op.h - */ -#ifndef MXNET_OPERATOR_TENSOR_SORT_OP_INL_CUH_ -#define MXNET_OPERATOR_TENSOR_SORT_OP_INL_CUH_ -#include -#include -#if defined(_MSC_VER) && __CUDACC_VER__ != 80044 -// Many CUDA compilers other than V8.0.44 crash on Windows -#pragma warning("Potential crash on CUDA compiler detected. Switching sorting from CUB to Thrust") -#define SORT_WITH_THRUST -#else -#include -#undef SORT_WITH_THRUST -#endif -#if CUDA_VERSION >= 7000 -#include -#endif - -namespace mxnet { -namespace op { - -template -inline typename std::enable_if::value, size_t>::type -SortByKeyWorkspaceSize(const size_t num_keys) { -#ifdef SORT_WITH_THRUST - return 0; -#else - size_t sortpairs_bytes = 0; - cub::DeviceRadixSort::SortPairs(NULL, sortpairs_bytes, - NULL, NULL, NULL, NULL, num_keys); - size_t keys_bytes = num_keys*sizeof(KDType); - size_t values_bytes = num_keys*sizeof(VDType); - return (keys_bytes + values_bytes + sortpairs_bytes); -#endif -} - -template -inline void SortByKey(mshadow::Tensor keys, mshadow::Tensor values, - bool is_ascend, mshadow::Tensor* workspace, - const int begin_bit, const int end_bit) { - CHECK_EQ(keys.CheckContiguous(), true); - CHECK_EQ(values.CheckContiguous(), true); -#if CUDA_VERSION >= 7000 - cudaStream_t stream = mshadow::Stream::GetStream(keys.stream_); -#ifndef SORT_WITH_THRUST - if (workspace != NULL) { - // Workspace given, sort using CUB - CHECK_EQ(workspace->CheckContiguous(), true); - // workspace = [keys_out, values_out, temporary_storage] - size_t keys_bytes = keys.size(0)*sizeof(KDType); - size_t values_bytes = keys.size(0)*sizeof(VDType); - // Get the size of internal storage (for checking purposes only) - size_t sortpairs_bytes = 0; - if (is_ascend) { - cub::DeviceRadixSort::SortPairs(NULL, sortpairs_bytes, - NULL, NULL, NULL, NULL, - keys.size(0), begin_bit, end_bit, stream); - } else { - cub::DeviceRadixSort::SortPairsDescending(NULL, sortpairs_bytes, - NULL, NULL, NULL, NULL, - keys.size(0), begin_bit, end_bit, stream); - } - // Check that we have enough storage - CHECK_GE(workspace->size(0), keys_bytes + values_bytes + sortpairs_bytes); - // - KDType* keys_out_ptr = reinterpret_cast(workspace->dptr_); - VDType* values_out_ptr = reinterpret_cast(workspace->dptr_ + keys_bytes); - void* temp_storage = reinterpret_cast(workspace->dptr_ + keys_bytes + values_bytes); - // Sort - if (is_ascend) { - cub::DeviceRadixSort::SortPairs(temp_storage, sortpairs_bytes, - keys.dptr_, keys_out_ptr, values.dptr_, values_out_ptr, - keys.size(0), begin_bit, end_bit, stream); - } else { - cub::DeviceRadixSort::SortPairsDescending(temp_storage, sortpairs_bytes, - keys.dptr_, keys_out_ptr, values.dptr_, values_out_ptr, - keys.size(0), begin_bit, end_bit, stream); - } - // Copy result back to [keys, values] - mshadow::Tensor keys_out(keys_out_ptr, mshadow::Shape1(keys.size(0)), - keys.stream_); - mshadow::Tensor values_out(values_out_ptr, mshadow::Shape1(keys.size(0)), - keys.stream_); - mshadow::Copy(keys, keys_out, keys.stream_); - mshadow::Copy(values, values_out, values.stream_); - } else { -#endif // SORT_WITH_THRUST - // No workspace, sort using thrust - thrust::device_ptr key_iter = thrust::device_pointer_cast(keys.dptr_); - thrust::device_ptr value_iter = thrust::device_pointer_cast(values.dptr_); - if (is_ascend) { - thrust::stable_sort_by_key( - thrust::cuda::par.on(stream), - key_iter, key_iter + keys.size(0), value_iter, thrust::less()); - } else { - thrust::stable_sort_by_key( - thrust::cuda::par.on(stream), - key_iter, key_iter + keys.size(0), value_iter, thrust::greater()); - } -#ifndef SORT_WITH_THRUST - } -#endif // SORT_WITH_THRUST - MSHADOW_CUDA_POST_KERNEL_CHECK(SortByKey); -#else - LOG(FATAL) << "SortByKey is only supported for CUDA version >=7.0!"; -#endif -} - -template -inline void SortByKey(mshadow::Tensor keys, - mshadow::Tensor values, bool is_ascend, - mshadow::Tensor* workspace, const int begin_bit, const int end_bit) { - LOG(FATAL) << "SortByKey for half_t is not implemented!"; -} - -template -inline void SortByKey(mshadow::Tensor keys, - mshadow::Tensor values, bool is_ascend, - mshadow::Tensor* workspace, const int begin_bit, const int end_bit) { - LOG(FATAL) << "SortByKey for half_t is not implemented!"; -} - -} // namespace op -} // namespace mxnet - -#endif // MXNET_OPERATOR_TENSOR_SORT_OP_INL_CUH_ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +/*! + * Copyright (c) 2017 by Contributors + * \file sort_op-inl.cuh + * \brief CUDA implementations for sort_op.h + */ +#ifndef MXNET_OPERATOR_TENSOR_SORT_OP_INL_CUH_ +#define MXNET_OPERATOR_TENSOR_SORT_OP_INL_CUH_ +#include +#include +#if defined(_MSC_VER) && __CUDACC_VER_MAJOR__ == 8 && __CUDACC_VER_BUILD__ != 44 +// Many CUDA 8 compilers other than V8.0.44 crash on Windows +#pragma warning("Potential crash on CUDA compiler detected. Switching sorting from CUB to Thrust") +#define SORT_WITH_THRUST +#else +#include +#undef SORT_WITH_THRUST +#endif +#if CUDA_VERSION >= 7000 +#include +#endif + +namespace mxnet { +namespace op { + +template +inline typename std::enable_if::value, size_t>::type +SortByKeyWorkspaceSize(const size_t num_keys) { +#ifdef SORT_WITH_THRUST + return 0; +#else + size_t sortpairs_bytes = 0; + cub::DeviceRadixSort::SortPairs(NULL, sortpairs_bytes, + NULL, NULL, NULL, NULL, num_keys); + size_t keys_bytes = num_keys*sizeof(KDType); + size_t values_bytes = num_keys*sizeof(VDType); + return (keys_bytes + values_bytes + sortpairs_bytes); +#endif +} + +template +inline void SortByKey(mshadow::Tensor keys, mshadow::Tensor values, + bool is_ascend, mshadow::Tensor* workspace, + const int begin_bit, const int end_bit) { + CHECK_EQ(keys.CheckContiguous(), true); + CHECK_EQ(values.CheckContiguous(), true); +#if CUDA_VERSION >= 7000 + cudaStream_t stream = mshadow::Stream::GetStream(keys.stream_); +#ifndef SORT_WITH_THRUST + if (workspace != NULL) { + // Workspace given, sort using CUB + CHECK_EQ(workspace->CheckContiguous(), true); + // workspace = [keys_out, values_out, temporary_storage] + size_t keys_bytes = keys.size(0)*sizeof(KDType); + size_t values_bytes = keys.size(0)*sizeof(VDType); + // Get the size of internal storage (for checking purposes only) + size_t sortpairs_bytes = 0; + if (is_ascend) { + cub::DeviceRadixSort::SortPairs(NULL, sortpairs_bytes, + NULL, NULL, NULL, NULL, + keys.size(0), begin_bit, end_bit, stream); + } else { + cub::DeviceRadixSort::SortPairsDescending(NULL, sortpairs_bytes, + NULL, NULL, NULL, NULL, + keys.size(0), begin_bit, end_bit, stream); + } + // Check that we have enough storage + CHECK_GE(workspace->size(0), keys_bytes + values_bytes + sortpairs_bytes); + // + KDType* keys_out_ptr = reinterpret_cast(workspace->dptr_); + VDType* values_out_ptr = reinterpret_cast(workspace->dptr_ + keys_bytes); + void* temp_storage = reinterpret_cast(workspace->dptr_ + keys_bytes + values_bytes); + // Sort + if (is_ascend) { + cub::DeviceRadixSort::SortPairs(temp_storage, sortpairs_bytes, + keys.dptr_, keys_out_ptr, values.dptr_, values_out_ptr, + keys.size(0), begin_bit, end_bit, stream); + } else { + cub::DeviceRadixSort::SortPairsDescending(temp_storage, sortpairs_bytes, + keys.dptr_, keys_out_ptr, values.dptr_, values_out_ptr, + keys.size(0), begin_bit, end_bit, stream); + } + // Copy result back to [keys, values] + mshadow::Tensor keys_out(keys_out_ptr, mshadow::Shape1(keys.size(0)), + keys.stream_); + mshadow::Tensor values_out(values_out_ptr, mshadow::Shape1(keys.size(0)), + keys.stream_); + mshadow::Copy(keys, keys_out, keys.stream_); + mshadow::Copy(values, values_out, values.stream_); + } else { +#endif // SORT_WITH_THRUST + // No workspace, sort using thrust + thrust::device_ptr key_iter = thrust::device_pointer_cast(keys.dptr_); + thrust::device_ptr value_iter = thrust::device_pointer_cast(values.dptr_); + if (is_ascend) { + thrust::stable_sort_by_key( + thrust::cuda::par.on(stream), + key_iter, key_iter + keys.size(0), value_iter, thrust::less()); + } else { + thrust::stable_sort_by_key( + thrust::cuda::par.on(stream), + key_iter, key_iter + keys.size(0), value_iter, thrust::greater()); + } +#ifndef SORT_WITH_THRUST + } +#endif // SORT_WITH_THRUST + MSHADOW_CUDA_POST_KERNEL_CHECK(SortByKey); +#else + LOG(FATAL) << "SortByKey is only supported for CUDA version >=7.0!"; +#endif +} + +template +inline void SortByKey(mshadow::Tensor keys, + mshadow::Tensor values, bool is_ascend, + mshadow::Tensor* workspace, const int begin_bit, const int end_bit) { + LOG(FATAL) << "SortByKey for half_t is not implemented!"; +} + +template +inline void SortByKey(mshadow::Tensor keys, + mshadow::Tensor values, bool is_ascend, + mshadow::Tensor* workspace, const int begin_bit, const int end_bit) { + LOG(FATAL) << "SortByKey for half_t is not implemented!"; +} + +} // namespace op +} // namespace mxnet + +#endif // MXNET_OPERATOR_TENSOR_SORT_OP_INL_CUH_ diff --git a/src/operator/tensor/sort_op.h b/src/operator/tensor/sort_op.h index 42ae43cc7584..a0425a5afe1e 100644 --- a/src/operator/tensor/sort_op.h +++ b/src/operator/tensor/sort_op.h @@ -1,87 +1,105 @@ -/*! - * Copyright (c) 2017 by Contributors - * \file sort_op.h - * \brief SortByKey function - */ -#ifndef MXNET_OPERATOR_TENSOR_SORT_OP_H_ -#define MXNET_OPERATOR_TENSOR_SORT_OP_H_ - -#include -#include -#include -#include - -namespace mxnet { -namespace op { -/*! - * \brief CPU/GPU: Sort key-value pairs stored in separate places. (Stable sort is performed!) - * \param keys the keys to sort - * \param values the values that sorts w.r.t the key - * \param is_ascend whether to sort key in ascending order - */ -template -inline void SortByKey(mshadow::Tensor keys, mshadow::Tensor values, - bool is_ascend = true, mshadow::Tensor* workspace = NULL, - const int begin_bit = 0, const int end_bit = sizeof(KDType)*8) { - CHECK_EQ(keys.CheckContiguous(), true); - CHECK_EQ(values.CheckContiguous(), true); - CHECK_EQ(keys.size(0), values.size(0)) - << "The sizes of key/value are not equal! keys_size: " << keys.size(0) - << "values_size: " << values.size(0); - std::vector idx(keys.size(0)); - std::vector keys_vec(keys.size(0)); - std::vector values_vec(values.size(0)); - for (int i = 0; i < keys.size(0); i++) { - idx[i] = i; - keys_vec[i] = keys[i]; - values_vec[i] = values[i]; - } - if (is_ascend) { - std::stable_sort(idx.begin(), idx.end(), - [&keys_vec](size_t i1, size_t i2) - {return keys_vec[i1] < keys_vec[i2]; }); - } else { - std::stable_sort(idx.begin(), idx.end(), - [&keys_vec](size_t i1, size_t i2) - {return keys_vec[i1] > keys_vec[i2]; }); - } - for (index_t i = 0; i < values.size(0); i++) { - keys[i] = keys_vec[idx[i]]; - values[i] = values_vec[idx[i]]; - } -} - -/*! - * \brief CPU/GPU: Return the amount of temporary storage in bytes required for SortByKey - * \param num_keys number of keys to sort - */ -template -inline typename std::enable_if::value, size_t>::type -SortByKeyWorkspaceSize(const size_t num_keys) { - return 0; -} - -/*! - * \brief CPU/GPU: Sort key-value pairs stored in separate places. (Stable sort is performed!) - * \param keys the keys to sort - * \param values the values that sorts w.r.t the key - * \param is_ascend whether to sort key in ascending order - */ -template -inline void SortByKey(mshadow::Tensor keys, mshadow::Tensor values, - bool is_ascend = true, mshadow::Tensor* workspace = NULL, - const int begin_bit = 0, const int end_bit = sizeof(KDType)*8); -/*! - * \brief CPU/GPU: Return the amount of temporary storage in bytes required for SortByKey - * \param num_keys number of keys to sort - */ -template -inline typename std::enable_if::value, size_t>::type -SortByKeyWorkspaceSize(const size_t num_keys); - -} // namespace op -} // namespace mxnet -#ifdef __CUDACC__ -#include "./sort_op-inl.cuh" -#endif -#endif // MXNET_OPERATOR_TENSOR_SORT_OP_H_ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +/*! + * \file sort_op.h + * \brief SortByKey function + */ +#ifndef MXNET_OPERATOR_TENSOR_SORT_OP_H_ +#define MXNET_OPERATOR_TENSOR_SORT_OP_H_ + +#include +#include +#include +#include + +namespace mxnet { +namespace op { +/*! + * \brief CPU/GPU: Sort key-value pairs stored in separate places. (Stable sort is performed!) + * \param keys the keys to sort + * \param values the values that sorts w.r.t the key + * \param is_ascend whether to sort key in ascending order + */ +template +inline void SortByKey(mshadow::Tensor keys, mshadow::Tensor values, + bool is_ascend = true, mshadow::Tensor* workspace = NULL, + const int begin_bit = 0, const int end_bit = sizeof(KDType)*8) { + CHECK_EQ(keys.CheckContiguous(), true); + CHECK_EQ(values.CheckContiguous(), true); + CHECK_EQ(keys.size(0), values.size(0)) + << "The sizes of key/value are not equal! keys_size: " << keys.size(0) + << "values_size: " << values.size(0); + std::vector idx(keys.size(0)); + std::vector keys_vec(keys.size(0)); + std::vector values_vec(values.size(0)); + for (index_t i = 0; i < keys.size(0); i++) { + idx[i] = i; + keys_vec[i] = keys[i]; + values_vec[i] = values[i]; + } + if (is_ascend) { + std::stable_sort(idx.begin(), idx.end(), + [&keys_vec](size_t i1, size_t i2) + {return keys_vec[i1] < keys_vec[i2]; }); + } else { + std::stable_sort(idx.begin(), idx.end(), + [&keys_vec](size_t i1, size_t i2) + {return keys_vec[i1] > keys_vec[i2]; }); + } + for (index_t i = 0; i < values.size(0); i++) { + keys[i] = keys_vec[idx[i]]; + values[i] = values_vec[idx[i]]; + } +} + +/*! + * \brief CPU/GPU: Return the amount of temporary storage in bytes required for SortByKey + * \param num_keys number of keys to sort + */ +template +inline typename std::enable_if::value, size_t>::type +SortByKeyWorkspaceSize(const size_t num_keys) { + return 0; +} + +/*! + * \brief CPU/GPU: Sort key-value pairs stored in separate places. (Stable sort is performed!) + * \param keys the keys to sort + * \param values the values that sorts w.r.t the key + * \param is_ascend whether to sort key in ascending order + */ +template +inline void SortByKey(mshadow::Tensor keys, mshadow::Tensor values, + bool is_ascend = true, mshadow::Tensor* workspace = NULL, + const int begin_bit = 0, const int end_bit = sizeof(KDType)*8); +/*! + * \brief CPU/GPU: Return the amount of temporary storage in bytes required for SortByKey + * \param num_keys number of keys to sort + */ +template +inline typename std::enable_if::value, size_t>::type +SortByKeyWorkspaceSize(const size_t num_keys); + +} // namespace op +} // namespace mxnet +#ifdef __CUDACC__ +#include "./sort_op-inl.cuh" +#endif +#endif // MXNET_OPERATOR_TENSOR_SORT_OP_H_ diff --git a/src/operator/upsampling-inl.h b/src/operator/upsampling-inl.h index a10ccb1f7626..fec0f74f14c8 100644 --- a/src/operator/upsampling-inl.h +++ b/src/operator/upsampling-inl.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file upsampling-inl.h * \brief * \author Bing Xu diff --git a/src/operator/upsampling.cc b/src/operator/upsampling.cc index 15900878f666..653b5709f120 100644 --- a/src/operator/upsampling.cc +++ b/src/operator/upsampling.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file upsampling_nearest.cc * \brief * \author Bing Xu @@ -44,17 +62,13 @@ Operator *CreateOp(UpSamplingParam param, int dtype) { Operator* UpSamplingProp::CreateOperatorEx(Context ctx, std::vector *in_shape, std::vector *in_type) const { - std::vector out_shape, aux_shape; - std::vector out_type, aux_type; - CHECK(InferType(in_type, &out_type, &aux_type)); - CHECK(InferShape(in_shape, &out_shape, &aux_shape)); DO_BIND_DISPATCH(CreateOp, param_, in_type->at(0)); } DMLC_REGISTER_PARAMETER(UpSamplingParam); MXNET_REGISTER_OP_PROPERTY(UpSampling, UpSamplingProp) -.describe("Performs nearest neighbor/bilinear up sampling to inputs") +.describe("Performs nearest neighbor/bilinear up sampling to inputs.") .add_argument("data", "NDArray-or-Symbol[]", "Array of tensors to upsample") .add_arguments(UpSamplingParam::__FIELDS__()) .set_key_var_num_args("num_args"); diff --git a/src/operator/upsampling.cu b/src/operator/upsampling.cu index 70466d438449..8152535233e4 100644 --- a/src/operator/upsampling.cu +++ b/src/operator/upsampling.cu @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file upsampling_nearest.cc * \brief * \author Bing Xu diff --git a/src/optimizer/sgd-inl.h b/src/optimizer/sgd-inl.h index 36b45c375b95..01a330bece8c 100644 --- a/src/optimizer/sgd-inl.h +++ b/src/optimizer/sgd-inl.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file sgd-inl.h * \brief Operator interface of mxnet. * \author Junyuan Xie diff --git a/src/resource.cc b/src/resource.cc index 60e40d1837a1..4c2dbee33f2b 100644 --- a/src/resource.cc +++ b/src/resource.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file resource.cc * \brief Implementation of resource manager. */ diff --git a/src/storage/cpu_device_storage.h b/src/storage/cpu_device_storage.h index 2afb658bb9c6..ead00dafbf44 100644 --- a/src/storage/cpu_device_storage.h +++ b/src/storage/cpu_device_storage.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file cpu_device_storage.h * \brief CPU storage implementation. */ diff --git a/src/storage/gpu_device_storage.h b/src/storage/gpu_device_storage.h index 10684905a861..3c4f732c80dc 100644 --- a/src/storage/gpu_device_storage.h +++ b/src/storage/gpu_device_storage.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file gpu_device_storage.h * \brief GPU storage implementation. */ diff --git a/src/storage/naive_storage_manager.h b/src/storage/naive_storage_manager.h index 05a8b10c2bb1..731f374bbfd2 100644 --- a/src/storage/naive_storage_manager.h +++ b/src/storage/naive_storage_manager.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file naive_storage_manager.h * \brief Naive storage manager. */ diff --git a/src/storage/pinned_memory_storage.h b/src/storage/pinned_memory_storage.h index 5b0df1041909..69e05f7cf90c 100644 --- a/src/storage/pinned_memory_storage.h +++ b/src/storage/pinned_memory_storage.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file cpu_device_storage.h * \brief CPU storage with pinned memory */ diff --git a/src/storage/pooled_storage_manager.h b/src/storage/pooled_storage_manager.h index 5e0050c04b2f..b2c6633a8082 100644 --- a/src/storage/pooled_storage_manager.h +++ b/src/storage/pooled_storage_manager.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file pooled_storage_manager.h * \brief Storage manager with a memory pool. */ diff --git a/src/storage/storage.cc b/src/storage/storage.cc index 64731cf92456..fa15a44b4fb6 100644 --- a/src/storage/storage.cc +++ b/src/storage/storage.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors */ #include #include @@ -27,18 +45,22 @@ class StorageImpl : public Storage { private: static constexpr size_t kMaxNumberOfDevices = Context::kMaxDevType + 1; static constexpr size_t kMaxNumberOfDeviceIDs = Context::kMaxDevID + 1; +#if MXNET_USE_CUDA + static int num_gpu_device; +#endif // MXNET_USE_CUDA static void ActivateDevice(Context ctx) { switch (ctx.dev_type) { case Context::kCPU: break; case Context::kGPU: - case Context::kCPUPinned: + case Context::kCPUPinned: { #if MXNET_USE_CUDA - CUDA_CALL(cudaSetDevice(ctx.dev_id)); -#else // MXNET_USE_CUDA - LOG(FATAL) << "Please compile with CUDA enabled"; + if (num_gpu_device > 0) { + CUDA_CALL(cudaSetDevice(ctx.dev_id)); + } #endif // MXNET_USE_CUDA - break; + break; + } default: LOG(FATAL) << "Unimplemented device"; } @@ -47,6 +69,9 @@ class StorageImpl : public Storage { std::array, kMaxNumberOfDevices> storage_managers_; }; // struct Storage::Impl +#if MXNET_USE_CUDA +int StorageImpl::num_gpu_device = 0; +#endif // MXNET_USE_CUDA Storage::Handle StorageImpl::Alloc(size_t size, Context ctx) { // space already recycled, ignore request @@ -54,7 +79,7 @@ Storage::Handle StorageImpl::Alloc(size_t size, Context ctx) { hd.ctx = ctx; hd.size = size; auto&& device = storage_managers_.at(ctx.dev_type); - storage::StorageManager *manager = device.Get( + std::shared_ptr manager = device.Get( ctx.dev_id, [ctx]() { storage::StorageManager *ptr = nullptr; switch (ctx.dev_type) { @@ -64,14 +89,25 @@ Storage::Handle StorageImpl::Alloc(size_t size, Context ctx) { } case Context::kCPUPinned: { #if MXNET_USE_CUDA - ptr = new storage::NaiveStorageManager(); + num_gpu_device = 0; + cudaError_t e = cudaGetDeviceCount(&num_gpu_device); + if (e != cudaSuccess) { + num_gpu_device = 0; + } + if (num_gpu_device > 0) { + ptr = new storage::NaiveStorageManager(); + } else { + ptr = new storage::NaiveStorageManager(); + } #else - LOG(FATAL) << "Compile with USE_CUDA=1 to enable GPU usage"; + ptr = new storage::NaiveStorageManager(); #endif // MXNET_USE_CUDA break; } case Context::kGPU: { #if MXNET_USE_CUDA + CUDA_CALL(cudaGetDeviceCount(&num_gpu_device)); + CHECK_GT(num_gpu_device, 0) << "GPU usage requires at least 1 GPU"; ptr = new storage::GPUPooledStorageManager(); #else LOG(FATAL) << "Compile with USE_CUDA=1 to enable GPU usage"; @@ -90,7 +126,7 @@ Storage::Handle StorageImpl::Alloc(size_t size, Context ctx) { void StorageImpl::Free(Storage::Handle handle) { const Context &ctx = handle.ctx; auto&& device = storage_managers_.at(ctx.dev_type); - storage::StorageManager *manager = device.Get( + std::shared_ptr manager = device.Get( ctx.dev_id, []() { LOG(FATAL) << "Cannot Free space to a device you have not allocated"; return nullptr; @@ -102,7 +138,7 @@ void StorageImpl::Free(Storage::Handle handle) { void StorageImpl::DirectFree(Storage::Handle handle) { const Context &ctx = handle.ctx; auto&& device = storage_managers_.at(ctx.dev_type); - storage::StorageManager *manager = device.Get( + std::shared_ptr manager = device.Get( ctx.dev_id, []() { LOG(FATAL) << "Cannot Free space to a device you have not allocated"; return nullptr; diff --git a/src/storage/storage_manager.h b/src/storage/storage_manager.h index de08688c5aac..924d2ed48b1a 100644 --- a/src/storage/storage_manager.h +++ b/src/storage/storage_manager.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file storage_manager.h * \brief Storage manager. */ diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index c0796f8e5e82..418d40e9eb8a 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -39,7 +39,6 @@ if(GTEST_FOUND) ${mxnet_LINKER_LIBS} ) else() - message(STATUS " OpenBLAS_LIB: ${OpenBLAS_LIB}") target_link_libraries(${PROJECT_NAME}_unit_tests ${GTEST_LIBRARY} rt @@ -51,6 +50,7 @@ if(GTEST_FOUND) endif() add_test(AllTestsIn${PROJECT_NAME}UnitTests ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${PROJECT_NAME}_unit_tests) - +else() + message(WARNING "Google Test not found") endif() diff --git a/tests/ci_build/Dockerfile.caffe_gpu b/tests/ci_build/Dockerfile.caffe_gpu index fff5a027e842..4f6522dab8ef 100644 --- a/tests/ci_build/Dockerfile.caffe_gpu +++ b/tests/ci_build/Dockerfile.caffe_gpu @@ -1,14 +1,15 @@ FROM nvidia/cuda:7.5-cudnn5-devel -COPY install/ubuntu_*.sh /install/ - +COPY install/ubuntu_install_core.sh /install/ RUN /install/ubuntu_install_core.sh + +COPY install/ubuntu_install_python.sh /install/ RUN /install/ubuntu_install_python.sh RUN apt-get install -y libprotobuf-dev libleveldb-dev \ libsnappy-dev libopencv-dev libhdf5-serial-dev protobuf-compiler \ libatlas-base-dev python-dev libgflags-dev libgoogle-glog-dev liblmdb-dev \ - python-numpy + python-numpy python-opencv RUN apt-get install -y --no-install-recommends libboost-all-dev diff --git a/tests/ci_build/Dockerfile.cpu b/tests/ci_build/Dockerfile.cpu index 1be21b03b21d..c7bb0af0f79c 100644 --- a/tests/ci_build/Dockerfile.cpu +++ b/tests/ci_build/Dockerfile.cpu @@ -1,7 +1,12 @@ FROM ubuntu:14.04 -COPY install/ubuntu_*.sh /install/ - +COPY install/ubuntu_install_core.sh /install/ RUN /install/ubuntu_install_core.sh +COPY install/ubuntu_install_python.sh /install/ RUN /install/ubuntu_install_python.sh +COPY install/ubuntu_install_scala.sh /install/ RUN /install/ubuntu_install_scala.sh +COPY install/ubuntu_install_r.sh /install/ +RUN /install/ubuntu_install_r.sh +COPY install/ubuntu_install_perl.sh /install/ +RUN /install/ubuntu_install_perl.sh diff --git a/tests/ci_build/Dockerfile.doc b/tests/ci_build/Dockerfile.doc index a09adcba06ef..43d1fa97ac37 100644 --- a/tests/ci_build/Dockerfile.doc +++ b/tests/ci_build/Dockerfile.doc @@ -1,12 +1,15 @@ FROM ubuntu:14.04 -COPY install/ubuntu_*.sh /install/ +COPY install/ubuntu_install_core.sh /install/ RUN /install/ubuntu_install_core.sh +COPY install/ubuntu_install_python.sh /install/ RUN /install/ubuntu_install_python.sh +COPY install/ubuntu_install_scala.sh /install/ RUN /install/ubuntu_install_scala.sh + RUN wget http://downloads.lightbend.com/scala/2.11.8/scala-2.11.8.deb && \ dpkg -i scala-2.11.8.deb && rm scala-2.11.8.deb RUN apt-get install -y doxygen libatlas-base-dev graphviz pandoc -RUN pip install sphinx CommonMark==0.5.4 breathe mock recommonmark pypandoc +RUN pip install sphinx==1.3.5 CommonMark==0.5.4 breathe mock recommonmark pypandoc beautifulsoup4 diff --git a/tests/ci_build/Dockerfile.gpu b/tests/ci_build/Dockerfile.gpu index be669dbd1635..a2893a9fb44f 100644 --- a/tests/ci_build/Dockerfile.gpu +++ b/tests/ci_build/Dockerfile.gpu @@ -1,7 +1,12 @@ FROM nvidia/cuda:7.5-cudnn5-devel -COPY install/ubuntu_*.sh /install/ - +COPY install/ubuntu_install_core.sh /install/ RUN /install/ubuntu_install_core.sh +COPY install/ubuntu_install_python.sh /install/ RUN /install/ubuntu_install_python.sh +COPY install/ubuntu_install_scala.sh /install/ RUN /install/ubuntu_install_scala.sh +COPY install/ubuntu_install_r.sh /install/ +RUN /install/ubuntu_install_r.sh +COPY install/ubuntu_install_perl.sh /install/ +RUN /install/ubuntu_install_perl.sh diff --git a/tests/ci_build/Dockerfile.mklml_gpu b/tests/ci_build/Dockerfile.mklml_gpu index 0bdda62ce9ca..1c29ca3248ec 100644 --- a/tests/ci_build/Dockerfile.mklml_gpu +++ b/tests/ci_build/Dockerfile.mklml_gpu @@ -2,13 +2,14 @@ FROM nvidia/cuda:7.5-cudnn5-devel # the reason we used a gpu base container because we are going to test MKLDNN # operator implementation against GPU implementation -COPY install/ubuntu_*.sh /install/ - +COPY install/ubuntu_install_core.sh /install/ RUN /install/ubuntu_install_core.sh +COPY install/ubuntu_install_python.sh /install/ RUN /install/ubuntu_install_python.sh +COPY install/ubuntu_install_scala.sh /install/ RUN /install/ubuntu_install_scala.sh -RUN wget --no-check-certificate -O /tmp/mklml.tgz https://github.com/dmlc/web-data/raw/master/mxnet/mklml-release/mklml_lnx_2017.0.2.20170209.tgz +RUN wget --no-check-certificate -O /tmp/mklml.tgz https://github.com/01org/mkl-dnn/releases/download/v0.7/mklml_lnx_2018.0.20170425.tgz RUN tar -zxvf /tmp/mklml.tgz && cp -rf mklml_*/* /usr/local/ && rm -rf mklml_* ENV LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/usr/local/lib diff --git a/tests/ci_build/ci_build.sh b/tests/ci_build/ci_build.sh index 9041fab73d9e..79fcd86a5df0 100755 --- a/tests/ci_build/ci_build.sh +++ b/tests/ci_build/ci_build.sh @@ -1,4 +1,22 @@ #!/usr/bin/env bash + +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # # Execute command within a docker container # @@ -69,13 +87,12 @@ function upsearch () { cd .. && upsearch "$1" } -# Set up WORKSPACE and BUILD_TAG. Jenkins will set them for you or we pick +# Set up WORKSPACE. Jenkins will set them for you or we pick # reasonable defaults if you run it outside of Jenkins. WORKSPACE="${WORKSPACE:-${SCRIPT_DIR}/../../}" -BUILD_TAG="${BUILD_TAG:-mx-ci}" # Determine the docker image name -DOCKER_IMG_NAME="${BUILD_TAG}.${CONTAINER_TYPE}" +DOCKER_IMG_NAME="mx-ci.${CONTAINER_TYPE}" # Under Jenkins matrix build, the build tag may contain characters such as # commas (,) and equal signs (=), which are not valid inside docker image names. @@ -98,6 +115,7 @@ echo "CI_DOCKER_EXTRA_PARAMS: ${CI_DOCKER_EXTRA_PARAMS[@]}" echo "COMMAND: ${COMMAND[@]}" echo "CONTAINER_TYPE: ${CONTAINER_TYPE}" echo "BUILD_TAG: ${BUILD_TAG}" +echo "NODE_NAME: ${NODE_NAME}" echo "DOCKER CONTAINER NAME: ${DOCKER_IMG_NAME}" echo "PRE_COMMAND: ${PRE_COMMAND}" echo "" diff --git a/tests/ci_build/install/install_julia.sh b/tests/ci_build/install/install_julia.sh index 80232a10fe53..5007c949537a 100644 --- a/tests/ci_build/install/install_julia.sh +++ b/tests/ci_build/install/install_julia.sh @@ -1,5 +1,23 @@ #!/usr/bin/env bash +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + + set -e wget https://julialang.s3.amazonaws.com/bin/linux/x64/0.5/julia-0.5.0-linux-x86_64.tar.gz diff --git a/tests/ci_build/install/install_library.sh b/tests/ci_build/install/install_library.sh index d65ab21ca4c0..399f6a093793 100644 --- a/tests/ci_build/install/install_library.sh +++ b/tests/ci_build/install/install_library.sh @@ -1,5 +1,23 @@ #!/usr/bin/env bash +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + + yum install graphviz pip install graphviz pip install opencv-python diff --git a/tests/ci_build/install/install_maven.sh b/tests/ci_build/install/install_maven.sh index 66459be8b8d2..666ebde06263 100644 --- a/tests/ci_build/install/install_maven.sh +++ b/tests/ci_build/install/install_maven.sh @@ -1,5 +1,23 @@ #!/usr/bin/env bash +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + + set -e wget http://mirrors.ocf.berkeley.edu/apache/maven/maven-3/3.3.9/binaries/apache-maven-3.3.9-bin.tar.gz diff --git a/tests/ci_build/install/install_openblas.sh b/tests/ci_build/install/install_openblas.sh index 3ac642146026..2ec5eeb4498e 100644 --- a/tests/ci_build/install/install_openblas.sh +++ b/tests/ci_build/install/install_openblas.sh @@ -1,5 +1,23 @@ #!/usr/bin/env bash +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + + set -e git clone https://github.com/xianyi/OpenBLAS diff --git a/tests/ci_build/install/install_opencv.sh b/tests/ci_build/install/install_opencv.sh index 70c7ddbdc382..08a4d9b53cd0 100644 --- a/tests/ci_build/install/install_opencv.sh +++ b/tests/ci_build/install/install_opencv.sh @@ -1,5 +1,23 @@ #!/usr/bin/env bash +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + + set -e yum groupinstall -y "Development Tools" diff --git a/tests/ci_build/install/install_python2.sh b/tests/ci_build/install/install_python2.sh index c818c5d0a7db..ec4bbb9a9b87 100644 --- a/tests/ci_build/install/install_python2.sh +++ b/tests/ci_build/install/install_python2.sh @@ -1,5 +1,23 @@ #!/usr/bin/env bash +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + + set -e yum groupinstall -y "Development Tools" diff --git a/tests/ci_build/install/install_python3.sh b/tests/ci_build/install/install_python3.sh index 5aa1d80bf6f9..ee89161da793 100644 --- a/tests/ci_build/install/install_python3.sh +++ b/tests/ci_build/install/install_python3.sh @@ -1,5 +1,23 @@ #!/usr/bin/env bash +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + + set -e wget https://bootstrap.pypa.io/get-pip.py || exit 1 diff --git a/tests/ci_build/install/install_testdeps.sh b/tests/ci_build/install/install_testdeps.sh index 975eec41b63f..c77734805388 100644 --- a/tests/ci_build/install/install_testdeps.sh +++ b/tests/ci_build/install/install_testdeps.sh @@ -1,5 +1,23 @@ #!/usr/bin/env bash +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + + set -e pip install cpplint 'pylint==1.4.4' 'astroid==1.3.6' diff --git a/tests/ci_build/install/ubuntu_install_core.sh b/tests/ci_build/install/ubuntu_install_core.sh index dacd30b4af71..49475740d210 100755 --- a/tests/ci_build/install/ubuntu_install_core.sh +++ b/tests/ci_build/install/ubuntu_install_core.sh @@ -1,8 +1,26 @@ #!/usr/bin/env bash + +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # install libraries for building mxnet c++ core on ubuntu apt-get update && apt-get install -y \ - build-essential git libopenblas-dev libopencv-dev \ + build-essential git libopenblas-dev liblapack-dev libopencv-dev \ libcurl4-openssl-dev libgtest-dev cmake wget unzip cd /usr/src/gtest && cmake CMakeLists.txt && make && cp *.a /usr/lib diff --git a/tests/ci_build/install/ubuntu_install_perl.sh b/tests/ci_build/install/ubuntu_install_perl.sh new file mode 100755 index 000000000000..a981746bc18d --- /dev/null +++ b/tests/ci_build/install/ubuntu_install_perl.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash + +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +# install libraries for mxnet's perl package on ubuntu +apt-get update && apt-get install -y libmouse-perl pdl cpanminus swig libgraphviz-perl +cpanm -q Function::Parameters diff --git a/tests/ci_build/install/ubuntu_install_python.sh b/tests/ci_build/install/ubuntu_install_python.sh index 0459bb9198c4..bb67e3401a89 100755 --- a/tests/ci_build/install/ubuntu_install_python.sh +++ b/tests/ci_build/install/ubuntu_install_python.sh @@ -1,4 +1,22 @@ #!/usr/bin/env bash + +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # install libraries for mxnet's python package on ubuntu apt-get update && apt-get install -y python-dev python3-dev @@ -6,5 +24,5 @@ apt-get update && apt-get install -y python-dev python3-dev # the version of the pip shipped with ubuntu may be too lower, install a recent version here cd /tmp && wget https://bootstrap.pypa.io/get-pip.py && python3 get-pip.py && python2 get-pip.py -pip2 install nose pylint numpy nose-timer requests -pip3 install nose pylint numpy nose-timer requests +pip2 install nose pylint numpy nose-timer requests h5py +pip3 install nose pylint numpy nose-timer requests h5py diff --git a/tests/ci_build/install/ubuntu_install_r.sh b/tests/ci_build/install/ubuntu_install_r.sh new file mode 100755 index 000000000000..38d89a3e4783 --- /dev/null +++ b/tests/ci_build/install/ubuntu_install_r.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash + +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +# install libraries for mxnet's r package on ubuntu + +echo "deb http://cran.rstudio.com/bin/linux/ubuntu trusty/" >> /etc/apt/sources.list +gpg --keyserver keyserver.ubuntu.com --recv-key E084DAB9 +gpg -a --export E084DAB9 | apt-key add - + +apt-get update +apt-get install -y r-base r-base-dev libxml2-dev libssl-dev + diff --git a/tests/ci_build/install/ubuntu_install_scala.sh b/tests/ci_build/install/ubuntu_install_scala.sh index dcdd4bc72b5d..712eff98b02a 100755 --- a/tests/ci_build/install/ubuntu_install_scala.sh +++ b/tests/ci_build/install/ubuntu_install_scala.sh @@ -1,4 +1,22 @@ #!/usr/bin/env bash + +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # install libraries for mxnet's scala package on ubuntu apt-get update && apt-get install -y \ diff --git a/tests/ci_build/pylintrc b/tests/ci_build/pylintrc index e7dc2340d1bb..a33f4e76bf3d 100644 --- a/tests/ci_build/pylintrc +++ b/tests/ci_build/pylintrc @@ -65,7 +65,7 @@ enable=indexing-exception,old-raise-syntax # --enable=similarities". If you want to run only the classes checker, but have # no Warning level messages displayed, use"--disable=all --enable=classes # --disable=W" -disable=design,similarities,no-self-use,attribute-defined-outside-init,locally-disabled,star-args,pointless-except,bad-option-value,global-statement,fixme,suppressed-message,useless-suppression,locally-enabled,no-member,no-name-in-module,import-error,unsubscriptable-object,unbalanced-tuple-unpacking,undefined-variable,protected-access,superfluous-parens,invalid-name +disable=design,similarities,no-self-use,attribute-defined-outside-init,locally-disabled,star-args,pointless-except,bad-option-value,global-statement,fixme,suppressed-message,useless-suppression,locally-enabled,no-member,no-name-in-module,import-error,unsubscriptable-object,unbalanced-tuple-unpacking,undefined-variable,protected-access,superfluous-parens,invalid-name,no-else-return,useless-super-delegation,len-as-condition,invalid-unary-operand-type # disable=unicode-builtin,delslice-method,using-cmp-argument,setslice-method,dict-view-method,parameter-unpacking,range-builtin-not-iterating,print-statement,file-builtin,old-raise-syntax,basestring-builtin,execfile-builtin,indexing-exception,import-star-module-level,coerce-method,long-builtin,old-ne-operator,old-division,no-absolute-import,raw_input-builtin,old-octal-literal,oct-method,xrange-builtin,hex-method,unpacking-in-except,nonzero-method,raising-string,intern-builtin,reload-builtin,metaclass-assignment,cmp-method,filter-builtin-not-iterating,apply-builtin,map-builtin-not-iterating,next-method-called,unichr-builtin,buffer-builtin,dict-iter-method,input-builtin,coerce-builtin,getslice-method,useless-suppression,standarderror-builtin,zip-builtin-not-iterating,suppressed-message,cmp-builtin,backtick,long-suffix,reduce-builtin,round-builtin diff --git a/tests/cpp/engine/threaded_engine_test.cc b/tests/cpp/engine/threaded_engine_test.cc index 73dc53060b63..58b7e57a509c 100644 --- a/tests/cpp/engine/threaded_engine_test.cc +++ b/tests/cpp/engine/threaded_engine_test.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2017 by Contributors * \file threaded_engine_test.cc * \brief threaded engine tests */ diff --git a/tests/cpp/include/test_op.h b/tests/cpp/include/test_op.h index 2c96092db81c..d8f90df8447e 100644 --- a/tests/cpp/include/test_op.h +++ b/tests/cpp/include/test_op.h @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2017 by Contributors * \file test_op.h * \brief operator unit test utility functions * \author Chris Olivier @@ -17,8 +35,8 @@ * test_perf.h: Performance-related classes * test_op.h: Operator-specific testing classes */ -#ifndef TESTS_CPP_INCLUDE_TEST_OP_H_ -#define TESTS_CPP_INCLUDE_TEST_OP_H_ +#ifndef TEST_OP_H_ +#define TEST_OP_H_ #include "test_perf.h" #include "test_util.h" @@ -705,4 +723,4 @@ static test::op::OpInfo createOpAndInfoF(const boo } // namespace test } // namespace mxnet -#endif // TESTS_CPP_INCLUDE_TEST_OP_H_ +#endif // TEST_OP_H_ diff --git a/tests/cpp/include/test_perf.h b/tests/cpp/include/test_perf.h index 6343863db16e..d74d4d5a8976 100644 --- a/tests/cpp/include/test_perf.h +++ b/tests/cpp/include/test_perf.h @@ -1,12 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2017 by Contributors * \file test_perf.h * \brief operator unit test utility functions * \author Chris Olivier */ -#ifndef TESTS_CPP_INCLUDE_TEST_PERF_H_ -#define TESTS_CPP_INCLUDE_TEST_PERF_H_ +#ifndef TEST_PERF_H_ +#define TEST_PERF_H_ #include #include @@ -286,4 +304,4 @@ class TimingItem { } // namespace test } // namespace mxnet -#endif // TESTS_CPP_INCLUDE_TEST_PERF_H_ +#endif // TEST_PERF_H_ diff --git a/tests/cpp/include/test_util.h b/tests/cpp/include/test_util.h index 6b87312e174a..3f5f4ecbb5bb 100644 --- a/tests/cpp/include/test_util.h +++ b/tests/cpp/include/test_util.h @@ -1,11 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2017 by Contributors * \file test_util.h * \brief unit test performance analysis functions * \author Chris Olivier */ -#ifndef TESTS_CPP_INCLUDE_TEST_UTIL_H_ -#define TESTS_CPP_INCLUDE_TEST_UTIL_H_ +#ifndef TEST_UTIL_H_ +#define TEST_UTIL_H_ #include #include @@ -160,14 +178,14 @@ inline StreamType& print_blob(StreamType *_os, const TBlob &blob, if (dim == 1) { // probably a tensor (mshadow::Tensor is deprecated) - TBlob changed(blob.dptr(), TShape(3), blob.dev_mask_); + TBlob changed(blob.dptr(), TShape(3), blob.dev_mask(), blob.dev_id()); changed.shape_[0] = 1; changed.shape_[1] = 1; changed.shape_[2] = blob.shape_[0]; return print_blob(&os, changed, false, false); } else if (dim == 2) { // probably a tensor (mshadow::Tensor is deprecated) - TBlob changed(blob.dptr(), TShape(4), blob.dev_mask_); + TBlob changed(blob.dptr(), TShape(4), blob.dev_mask(), blob.dev_id()); changed.shape_[0] = 1; changed.shape_[1] = 1; changed.shape_[2] = blob.shape_[0]; @@ -413,4 +431,4 @@ struct ScopeSet { } // namespace test } // namespace mxnet -#endif // TESTS_CPP_INCLUDE_TEST_UTIL_H_ +#endif // TEST_UTIL_H_ diff --git a/tests/cpp/operator/batchnorm_test.cc b/tests/cpp/operator/batchnorm_test.cc index cabddec7b83e..3fef28f79a0a 100644 --- a/tests/cpp/operator/batchnorm_test.cc +++ b/tests/cpp/operator/batchnorm_test.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2017 by Contributors * \file batchnorm_test.cc * \brief operator unit test utility functions * \author Chris Olivier @@ -243,7 +261,7 @@ class BatchNormValidator : public test::op::Validator { CHECK_EQ(info_2.prop_->getParam().use_global_stats, info_1.prop_->getParam().use_global_stats); -#if MXNET_USE_CUDNN != 1 /* CUDNN takes a slightly different approach here on first pass */ +#if MXNET_USE_CUDNN != 1 /* CUDNN takes a different approach here on first pass */ // Aux EXPECT_TRUE(compare(*info_1.data_, *info_2.data_, test::op::BasicOperatorData::kAux, @@ -533,6 +551,8 @@ static test::op::OpInfo runOperatorBackward( return *info; } +static constexpr size_t CYCLE_COUNT = 3; + template static test::op::OpInfoPair testForwardAndBackward( const bool isGPU1, @@ -541,7 +561,7 @@ static test::op::OpInfoPair testFo const test::op::kwargs_t& kwargs, const bool dumpC, const size_t count = 1, - const size_t cycleCount = 5) { + const size_t cycleCount = CYCLE_COUNT) { test::op::OpInfo info_1 = TestBatchNormOperatorForward(isGPU1, inputShape, kwargs, count); @@ -603,13 +623,18 @@ static test::op::OpInfoPair testForwardAndBackward(const bool isGPU, const TShape &inputShape, const test::op::kwargs_t kwargs, - const bool dumpC = false) { + const bool dumpC = false, + const size_t count = 1, + const size_t cycleCount = CYCLE_COUNT +) { return testForwardAndBackward( isGPU, isGPU, inputShape, kwargs, - dumpC); + dumpC, + count, + cycleCount); } template @@ -638,7 +663,6 @@ TEST(BATCH_NORM, Test2DForwardV1V2) { { auto infoA = testBNForwardAndBackward2D( false, {BATCH_SIZE, CHANNELS, DH, DW}, blank_kwargs); - dumpF(&std::cout, infoA); }); } @@ -794,15 +818,18 @@ TEST(BATCH_NORM, TestTiming_2D) { MSHADOW_REAL_TYPE_SWITCH_EX( mshadow::kFloat32, DType, AccReal, { - std::string prefix; -#if MXNET_USE_MKL2017 == 1 - prefix = "MKL "; -#endif timingTest("BatchNormV1Prop 2D", false, false, blank_kwargs, 2, THISCOUNT); - timingTest(prefix + "BatchNormProp 2D", +#if MXNET_USE_MKL2017 == 1 + timingTest("MKL BatchNormProp 2D", + false, false, + blank_kwargs_nocudnn, + 2, THISCOUNT); +#endif + test::ScopeSet disableMKL(&mxnet::op::batchnorm::disable_mkl, true); + timingTest("BatchNormProp 2D", false, false, blank_kwargs_nocudnn, 2, THISCOUNT); @@ -999,7 +1026,7 @@ struct Test2DBackward2DPlusLoadAndCompareLogicUtil { const TShape inputShape({1, 1, 2, 1}); test::op::OpInfoPair bi = testForwardAndBackward( - false, inputShape, blank_kwargs); + false, inputShape, blank_kwargs, false, 1, 5); #if MXNET_DUMP_C bi.info_1_.data_->dumpC(&std::cerr, "Test2DBackward2DPlusLoadAndCompareLogic"); @@ -1045,6 +1072,7 @@ struct Test2DBackward2DPlusLoadAndCompareLogicUtil { TEST(BATCH_NORM, Test2DBackward2DPlusLoadAndCompareLogic) { + test::ScopeSet disableMKL(&mxnet::op::batchnorm::disable_mkl, true); MSHADOW_REAL_TYPE_SWITCH_EX( mshadow::kFloat32, DType, AccReal, { @@ -1156,6 +1184,385 @@ TEST(BATCH_NORM, Test2DBackwardMixed_cpu_cpu_ugs) { }); } +template +class ChannelAxisTestData { + protected: + enum Mode { LOAD, SAVE }; + + void loadOrSave(const TBlob& blob, int channel_axis, const Mode mode) { + mxnet::op::batchnorm::BNTensor3 tensor3(blob, channel_axis); + const TShape &shape = blob.shape_; + CHECK_GT(shape.ndim(), 0); + if (channel_axis < 0) { + channel_axis = shape.ndim() + channel_axis; + } + CHECK_LT(channel_axis, shape.ndim()); + const size_t channel_count = shape[channel_axis]; + std::vector indexes(channel_count, 0); + for (size_t outer = 0, outerCount = tensor3.OuterSize(); outer < outerCount; ++outer) { + for (size_t channel = 0, channelCount = tensor3.ChannelCount(); + channel < channelCount; ++channel) { + CHECK_LT(channel, channel_data_.size()); + for (size_t inner = 0, innerCount = tensor3.InnerSize(); inner < innerCount; ++inner) { + CHECK_LT(indexes[channel], channel_data_[channel].size()); + if (mode == SAVE) { + tensor3.get_ref(outer, channel, inner) = channel_data_[channel][indexes[channel]++]; + } else { // mode == LOAD + channel_data_[channel][indexes[channel]++] = tensor3.get_ref(outer, channel, inner); + } + } + } + } + } + + public: + std::vector> channel_data_; + + static void print(const std::string& label, const std::vector>& m) { + if (test::debugOutput) { + if (!label.empty()) { + std::cout << label << ": "; + } + for (size_t i = 0, n = m.size(); i < n; ++i) { + const std::vector &vec = m[i]; + for (size_t j = 0, jn = vec.size(); j < jn; ++j) { + if (j) { + std::cout << ", "; + } + const DType val = vec[j]; + std::cout << std::fixed << std::setw(7) + << std::setprecision(mxnet::test::MPRINT_PRECISION) + << std::right << val; + } + std::cout << std::endl; + } + std::cout << "-----" << std::endl << std::flush; + } + } + + static void print(const std::string& label, const TBlob& blob) { + if (test::debugOutput) { + if (!label.empty()) { + std::cout << label << ": "; + } + const size_t totalSize = blob.Size(); + for (size_t i = 0; i < totalSize; ++i) { + const float val = blob.dptr()[i]; + if (i) { + std::cout << ", "; + } + std::cout << std::fixed << std::setw(7) << std::setprecision(mxnet::test::MPRINT_PRECISION) + << std::right << val; + } + std::cout << std::endl << std::flush; + } + } + + void save(const TBlob& blob, const int channel_axis) { + loadOrSave(blob, channel_axis, SAVE); + } + + void load(const TBlob& blob, const int channel_axis) { + loadOrSave(blob, channel_axis, LOAD); + } +}; + +template +static void compare(const TBlob& blob, const std::vector& vals) { + CHECK_EQ(blob.Size(), vals.size()); + const DType *v = blob.dptr(); + for (size_t i = 0, n = vals.size(); i < n; ++i) { + const DType vBlob = v[i]; + const DType vVect = vals[i]; + const bool near = test::op::Validator::isNear( + vBlob, vVect, test::op::Validator::ErrorBound(&blob)); + EXPECT_TRUE(near); + if (!near) { + LOG(WARNING) << vBlob << " is not near enough to " << vVect << std::endl; + } + } +} + +template +static void compare(const std::vector>& d1, + const std::vector>& d2) { + CHECK_EQ(d1.size(), d2.size()); + for (size_t x = 0, xn = d1.size(); x < xn; ++x) { + const std::vector &vec1 = d1[x]; + const std::vector &vec2 = d2[x]; + CHECK_EQ(vec1.size(), vec2.size()); + for (size_t i = 0, n = vec1.size(); i < n; ++i) { + const DType v1 = vec1[i]; + const DType v2 = vec2[i]; + const bool near = test::op::Validator::isNear( + v1, v2, test::op::Validator::ERROR_BOUND()); + EXPECT_TRUE(near); + if (!near) { + LOG(WARNING) << v1 << " is not near enough to " << v2 << std::endl; + } + } + } +} + +template +static void testSaveAndLoad(const std::vector& dims, + const int channelAxis, + const std::vector>& inputChannelData, + const std::vector& expectedBlobData) { + ChannelAxisTestData data; + data.channel_data_ = inputChannelData; + + TShape shape(dims.size()); + for (size_t i = 0, n = dims.size(); i < n; ++i) { + shape[i] = index_t(dims[i]); + } + + std::unique_ptr blob(new test::StandaloneBlob( + shape, false, mshadow::DataType::kFlag)); + + data.save(*blob, channelAxis); + ChannelAxisTestData::print("saved to blob", *blob); + compare(*blob, expectedBlobData); + data.load(*blob, channelAxis); + compare(data.channel_data_, inputChannelData); +} + +/*! \brief Check normalization/denormalization of various channel positions */ +TEST(BATCH_NORM, TestChannelAxisSaveAndLoad) { + std::cout << std::endl << std::flush; + + typedef float DType; + typedef float AccReal; + + const std::vector> myData = + { { 1.0f, 1.0f, 1.0, 1.0 }, + { 2.0f, 2.0f, 2.0f, 2.0f }, + { 3.0f, 3.0f, 3.0f, 3.0f } }; + + testSaveAndLoad({ 1, 3, 2, 2 }, 1, myData, + { 1.0f, 1.0f, 1.0f, 1.0f, + 2.0f, 2.0f, 2.0f, 2.0f, + 3.0f, 3.0f, 3.0f, 3.0f}); + + testSaveAndLoad({ 1, 2, 2, 3 }, 3, myData, + { 1.0f, 2.0f, 3.0f, + 1.0f, 2.0f, 3.0f, + 1.0f, 2.0f, 3.0f, + 1.0f, 2.0f, 3.0f}); + + testSaveAndLoad({ 1, 2, 3, 2 }, 2, myData, + { 1.0f, 1.0f, 2.0f, 2.0f, 3.0f, 3.0f, + 1.0f, 1.0f, 2.0f, 2.0f, 3.0f, 3.0f}); +} + +/*! \brief Insert the channel field `channelCount` into the shape at `channelAxis` position */ +static TShape MakeShape(const std::vector& shape, + signed int channelAxis, + const size_t channelCount) { + if (channelAxis < 0) { + channelAxis += shape.size() + 1; + } + CHECK_LT(channelAxis, shape.size() + 1); + const index_t dim = index_t(shape.size()) + 1; + TShape newShape(dim); + for (size_t x = 0; x < channelAxis; ++x) { + newShape[x] = index_t(shape[x]); + } + newShape[channelAxis] = index_t(channelCount); + for (int x = channelAxis + 1; x < dim; ++x) { + newShape[x] = shape[x - 1]; + } + return newShape; +} + +/*! \brief Create and arrange equivalent data with different channel axes, then compare + * normalized results */ +static void runChannelAxisTest( + const bool isGPU1, + const bool isGPU2, + const test::op::kwargs_t& base_kwargs, + const std::vector shape, + const signed int channelAxis1, + const signed int channelAxis2, + const size_t channelCount, + const bool simpleData, + const size_t numberOfPasses = 5 + +) { + typedef float DType; + typedef float AccReal; + + size_t spatialSize = 1; + for (size_t x = 1, n = shape.size(); x < n; ++x) { + spatialSize *= shape[x]; + } + + const size_t batchSize = shape[0]; + + // Create normalized input and output-grad data (inputs to forward and backward pass) + std::vector> myData, myGradOut; + DType ival = 1.0f, gval = 0.1f; + myData.resize(batchSize); + myData.resize(channelCount); + myGradOut.resize(channelCount); + for (size_t c = 0; c < channelCount; ++c) { + for (size_t i = 0; i < spatialSize; ++i) { + if (!simpleData) { + myData[c].push_back(ival += 1.0f); + myGradOut[c].push_back(gval += 0.1f); + } else { + myData[c].push_back(c + 1); + myGradOut[c].push_back(DType(c + 1) / 10.0f); + } + } + } + + ChannelAxisTestData::print("myData", myData); + ChannelAxisTestData::print("myGradOut", myGradOut); + ChannelAxisTestData data_c1, data_c2, grad_c1, grad_c2; + + // For forward pass + data_c1.channel_data_ = data_c2.channel_data_ = myData; + + // For backward pass + grad_c1.channel_data_ = grad_c2.channel_data_ = myGradOut; + + test::op::kwargs_t kwargs = base_kwargs; + + // Insert the channel field into the shape at channelAxis position + const TShape shape_c1 = MakeShape(shape, channelAxis1, channelCount); + const TShape shape_c2 = MakeShape(shape, channelAxis2, channelCount); + + // Create operator 1 with ChannelAxis2 (normally the experimental one) + kwargs.push_back({"axis", std::to_string(channelAxis1)}); + test::op::OpInfo info_c1 = test::op::createOpAndInfoF< + op::BatchNormProp, BNOperatorData, DType, AccReal>( + isGPU1, shape_c1, kwargs); + + // Create operator 2 with ChannelAxis2 (normally the control one) + kwargs.pop_back(); + kwargs.push_back({"axis", std::to_string(channelAxis2)}); + test::op::OpInfo info_c2 = test::op::createOpAndInfoF< + op::BatchNormProp, BNOperatorData, DType, AccReal>( + isGPU2, shape_c2, kwargs); + kwargs.pop_back(); + + // Init operators + info_c1.data_->initForward(*info_c1.prop_, &info_c1.in_type_); + info_c1.data_->initBackward(*info_c1.prop_, &info_c1.in_type_); + info_c2.data_->initForward(*info_c2.prop_, &info_c2.in_type_); + info_c2.data_->initBackward(*info_c2.prop_, &info_c2.in_type_); + + // Save input data to blob with new shape 1 + data_c1.save(info_c1.data_->c_.blob_input_vec_[0], channelAxis1); + ChannelAxisTestData::print("blob 1 input", info_c1.data_->c_.blob_input_vec_[0]); + + // Save input data to blob with new shape 2 + data_c2.save(info_c2.data_->c_.blob_input_vec_[0], channelAxis2); + ChannelAxisTestData::print("blob 2 input", info_c2.data_->c_.blob_input_vec_[0]); + + // Save output grad to blob with new shape 1 + grad_c1.save(info_c1.data_->c_.blob_out_grad_[0], channelAxis1); + ChannelAxisTestData::print("blob 1 output grad", info_c1.data_->c_.blob_out_grad_[0]); + + // Save output grad to blob with new shape 2 + grad_c2.save(info_c2.data_->c_.blob_out_grad_[0], channelAxis2); + ChannelAxisTestData::print("blob 2 output grad", info_c2.data_->c_.blob_out_grad_[0]); + + // Run both operators forward and backwards several times + for (int x = 0; x < numberOfPasses; ++x) { + info_c1.data_->forward(); + info_c2.data_->forward(); + + info_c1.data_->backward(); + info_c2.data_->backward(); + } + + // Transform operator 1's blob output to a normalized shape + data_c1.load(info_c1.data_->c_.blob_output_vec_[0], channelAxis1); + ChannelAxisTestData::print("channel data 1", data_c1.channel_data_); + + // Transform operator 2's blob output to a normalized shape + data_c2.load(info_c2.data_->c_.blob_output_vec_[0], channelAxis2); + ChannelAxisTestData::print("channel data 2", data_c2.channel_data_); + + // Compare the operators' output data while they're in a normalized shape + compare(data_c1.channel_data_, data_c2.channel_data_); + + // Transform operator 1's input-grad blob to a normalized shape + grad_c1.load(info_c1.data_->c_.blob_in_grad_[0], channelAxis1); + ChannelAxisTestData::print("input grad 1", grad_c1.channel_data_); + + // Transform operator 2's input-grad blob to a normalized shape + grad_c2.load(info_c2.data_->c_.blob_in_grad_[0], channelAxis2); + ChannelAxisTestData::print("input grad 2", grad_c2.channel_data_); + + // Compare the operators' input grad data while they're in a normalized shape + compare(grad_c1.channel_data_, grad_c2.channel_data_); +} + +TEST(BATCH_NORM, TestChannelAxisSimple) { + std::cout << std::endl << std::flush; + const size_t CHANNEL_COUNT = 4; + const int DEFAULT_AXIS = 1; + const int NEW_AXIS = -2; + const bool useSimpleData = true; // change to true sometimes for troubleshooting + const std::vector shape = {1, 2, 3}; + // Check against base-case of channel axis position 1 + runChannelAxisTest(false, false, + useglobalstats_kwargs_nocudnn, + shape, + DEFAULT_AXIS, + NEW_AXIS, + CHANNEL_COUNT, + useSimpleData); +} + +/*! \brief Test varying channel axis shapes + * For several channel counts (1-3), test that result data (after reshape) is + * equivalent for the default (channel position 1) and all other channel positions + * in the shape vector + * Channel position 1 (default) is checked everywhere else, so for and + * backward result equivalence here implies correctness for other channel positions + */ +TEST(BATCH_NORM, TestChannelAxis) { + test::ScopeSet noDebugOutput(&test::debugOutput, false); + + test::op::kwargs_t kwargs; + const std::vector> shapes = + { {1, 2}, {1, 2, 1}, {1, 2, 3}, {1, 2, 3, 4} }; + const char *tof[2] = { "False", "True" }; + + for (size_t x1 = 0; x1 < 2U; ++x1) { + kwargs.push_back({"fix_gamma", tof[x1]}); + for (size_t x2 = 0; x2 < 2U; ++x2) { + kwargs.push_back({"use_global_stats", tof[x2]}); + for (size_t x3 = 0; x3 < 2U; ++x3) { + kwargs.push_back({"cudnn_off", tof[x3]}); + for (int g1 = 0; g1 < 2U; ++g1) { + for (int g2 = 0; g2 < 2U; ++g2) { + for (const std::vector &simpleShape : shapes) { + const int dim = static_cast(simpleShape.size()); + for (signed int channelAxis = -dim, shapeDim = dim; + channelAxis <= shapeDim; + ++channelAxis) { + for (size_t channelCount = 1; channelCount <= 3; ++channelCount) { + // Check against base-case of channel axis position 1 + runChannelAxisTest(g1 != 0, g2 != 0, kwargs, simpleShape, + 1, channelAxis, channelCount, false); + } + } + } + } + } + kwargs.pop_back(); + } + kwargs.pop_back(); + } + kwargs.pop_back(); + } +} + #if MXNET_USE_CUDA TEST(BATCH_NORM, Test2DForwardV12D_gpu) { diff --git a/tests/cpp/operator/krprod_test.cc b/tests/cpp/operator/krprod_test.cc new file mode 100644 index 000000000000..31b8ab9dd781 --- /dev/null +++ b/tests/cpp/operator/krprod_test.cc @@ -0,0 +1,447 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +/*! + * \file krprod_test.cc + * \brief Test Khatri-Rao product + * \author Jencir Lee + */ +#include +#include +#include +#include "gtest/gtest.h" +#include "operator/contrib/krprod.h" + +namespace mxnet { +namespace op { + +using namespace mshadow; +using namespace mshadow::expr; +using DType = double; + +#define EXPECT_DOUBLE_EQ_MATRIX(expected, actual) \ +{ \ + for (int i = 0; i < static_cast(actual.size(0)); ++i) \ + for (int j = 0; j < static_cast(actual.size(1)); ++j) \ + EXPECT_LE(std::abs(actual[i][j] - expected[i][j]), 1e-10); \ +} \ + +TEST(row_wise_kronecker, OneInputMatrix) { + // Input matrices of shape (2, 4) which is also the expected result + DType mat[8] {1, 2, 3, 4, 5, 6, 7, 8}; + + // Make input tensors + std::vector > ts_arr; + ts_arr.emplace_back(mat, Shape2(2, 4), 4, nullptr); + + // Compute Khatri-Rao product + Tensor result(Shape2(2, 4)); + AllocSpace(&result); + row_wise_kronecker(result, ts_arr); + + // Check against expected result + EXPECT_DOUBLE_EQ_MATRIX(ts_arr[0], result); + + FreeSpace(&result); +} + +TEST(row_wise_kronecker, TwoInputMatrices) { + // Input matrices of shape (2, 3) and (2, 4) + DType mat1[6] {1, 2, 3, 4, 5, 6}; + DType mat2[8] {1, 2, 3, 4, 5, 6, 7, 8}; + + // Expect result of shape (2, 12) + DType expected[24] {1, 2, 3, 4, 2, 4, 6, 8, 3, 6, 9, 12, + 20, 24, 28, 32, 25, 30, 35, 40, 30, 36, 42, 48}; + + // Make input tensors + std::vector > ts_arr; + ts_arr.emplace_back(mat1, Shape2(2, 3), 3, nullptr); + ts_arr.emplace_back(mat2, Shape2(2, 4), 4, nullptr); + + // Compute Khatri-Rao product + Tensor result(Shape2(2, 12)); + AllocSpace(&result); + row_wise_kronecker(result, ts_arr); + + // Check against expected result + Tensor ts_expected(expected, Shape2(2, 12), 12, nullptr); + EXPECT_DOUBLE_EQ_MATRIX(ts_expected, result); + + FreeSpace(&result); +} + +TEST(row_wise_kronecker, TwoInputMatrices2) { + // Input matrices of shape (2, 3) and (2, 1) + DType mat1[6] {1, 2, 3, 4, 5, 6}; + DType mat2[2] {1, 2}; + + // Expect result of shape (2, 3) + DType expected[6] {1, 2, 3, 8, 10, 12}; + + // Make input tensors + std::vector > ts_arr; + ts_arr.emplace_back(mat1, Shape2(2, 3), 3, nullptr); + ts_arr.emplace_back(mat2, Shape2(2, 1), 1, nullptr); + + // Compute Khatri-Rao product + Tensor result(Shape2(2, 3)); + AllocSpace(&result); + row_wise_kronecker(result, ts_arr); + + // Check against expected result + Tensor ts_expected(expected, Shape2(2, 3), 3, nullptr); + EXPECT_DOUBLE_EQ_MATRIX(ts_expected, result); + + FreeSpace(&result); +} + +TEST(row_wise_kronecker, ThreeInputMatrices) { + std::default_random_engine generator; + std::uniform_int_distribution distribution(1, 6); + + Tensor in1(Shape2(3, 4)), in2(Shape2(3, 2)), + in3(Shape2(3, 3)), kr12(Shape2(3, 8)), kr13(Shape2(3, 24)), + result(Shape2(3, 24)); + AllocSpace(&in1); + AllocSpace(&in2); + AllocSpace(&in3); + AllocSpace(&kr12); + AllocSpace(&kr13); + AllocSpace(&result); + + std::vector > ts_arr {in1, in2, in3}; + for (auto & in : ts_arr) { + for (int i = 0; i < static_cast(in.size(0)); ++i) + for (int j = 0; j < static_cast(in.size(1)); ++j) + in[i][j] = distribution(generator); + } + + row_wise_kronecker(kr12, {in1, in2}); + row_wise_kronecker(kr13, {kr12, in3}); + row_wise_kronecker(result, ts_arr); + EXPECT_DOUBLE_EQ_MATRIX(kr13, result); + + for (auto & in : ts_arr) + FreeSpace(&in); + FreeSpace(&kr12); + FreeSpace(&kr13); + FreeSpace(&result); +} + +TEST(row_wise_kronecker, ThreeInputMatrices2) { + std::default_random_engine generator; + std::uniform_int_distribution distribution(1, 6); + + Tensor in1(Shape2(3, 4)), in2(Shape2(3, 1)), + in3(Shape2(3, 3)), kr12(Shape2(3, 4)), kr13(Shape2(3, 12)), + result(Shape2(3, 12)); + AllocSpace(&in1); + AllocSpace(&in2); + AllocSpace(&in3); + AllocSpace(&kr12); + AllocSpace(&kr13); + AllocSpace(&result); + + std::vector > ts_arr {in1, in2, in3}; + for (auto & in : ts_arr) { + for (int i = 0; i < static_cast(in.size(0)); ++i) + for (int j = 0; j < static_cast(in.size(1)); ++j) + in[i][j] = distribution(generator); + } + + row_wise_kronecker(kr12, {in1, in2}); + row_wise_kronecker(kr13, {kr12, in3}); + row_wise_kronecker(result, ts_arr); + EXPECT_DOUBLE_EQ_MATRIX(kr13, result); + + for (auto & in : ts_arr) + FreeSpace(&in); + FreeSpace(&kr12); + FreeSpace(&kr13); + FreeSpace(&result); +} + +TEST(row_wise_kronecker, ThreeInputMatrices3) { + std::default_random_engine generator; + std::uniform_int_distribution distribution(1, 6); + + Tensor in1(Shape2(3, 1)), in2(Shape2(3, 4)), + in3(Shape2(3, 3)), kr12(Shape2(3, 4)), kr13(Shape2(3, 12)), + result(Shape2(3, 12)); + AllocSpace(&in1); + AllocSpace(&in2); + AllocSpace(&in3); + AllocSpace(&kr12); + AllocSpace(&kr13); + AllocSpace(&result); + + std::vector > ts_arr {in1, in2, in3}; + for (auto & in : ts_arr) { + for (int i = 0; i < static_cast(in.size(0)); ++i) + for (int j = 0; j < static_cast(in.size(1)); ++j) + in[i][j] = distribution(generator); + } + + row_wise_kronecker(kr12, {in1, in2}); + row_wise_kronecker(kr13, {kr12, in3}); + row_wise_kronecker(result, ts_arr); + EXPECT_DOUBLE_EQ_MATRIX(kr13, result); + + for (auto & in : ts_arr) + FreeSpace(&in); + FreeSpace(&kr12); + FreeSpace(&kr13); + FreeSpace(&result); +} + +TEST(row_wise_kronecker, FourInputMatrices) { + std::default_random_engine generator; + std::uniform_int_distribution distribution(1, 6); + + Tensor in1(Shape2(3, 47)), in2(Shape2(3, 1)), + in3(Shape2(3, 5)), in4(Shape2(3, 2173)), kr12(Shape2(3, 47)), + kr13(Shape2(3, 47 * 5)), kr14(Shape2(3, 47 * 5 * 2173)), + result(Shape2(3, 47 * 5 * 2173)); + AllocSpace(&in1); + AllocSpace(&in2); + AllocSpace(&in3); + AllocSpace(&in4); + AllocSpace(&kr12); + AllocSpace(&kr13); + AllocSpace(&kr14); + AllocSpace(&result); + + std::vector > ts_arr {in1, in2, in3, in4}; + for (auto & in : ts_arr) { + for (int i = 0; i < static_cast(in.size(0)); ++i) + for (int j = 0; j < static_cast(in.size(1)); ++j) + in[i][j] = distribution(generator); + } + + row_wise_kronecker(kr12, {in1, in2}); + row_wise_kronecker(kr13, {kr12, in3}); + row_wise_kronecker(kr14, {kr13, in4}); + row_wise_kronecker(result, ts_arr); + EXPECT_DOUBLE_EQ_MATRIX(kr14, result); + + for (auto & in : ts_arr) + FreeSpace(&in); + FreeSpace(&kr12); + FreeSpace(&kr13); + FreeSpace(&kr14); + FreeSpace(&result); +} + +TEST(khatri_rao, OneInputMatrix) { + // Input matrices of shape (2, 4) which is also the expected result + DType mat[8] {1, 2, 3, 4, 5, 6, 7, 8}; + + // Make input tensors + std::vector > ts_arr; + ts_arr.emplace_back(mat, Shape2(2, 4), 4, nullptr); + + // Compute Khatri-Rao product + Tensor result(Shape2(2, 4)); + AllocSpace(&result); + khatri_rao(result, ts_arr); + + // Check against expected result + EXPECT_DOUBLE_EQ_MATRIX(ts_arr[0], result); + + FreeSpace(&result); +} + +TEST(khatri_rao, TwoInputMatrices) { + // Input matrices of shape (3, 2) and (4, 2) + DType mat1[6] {1, 4, 2, 5, 3, 6}; + DType mat2[8] {1, 5, 2, 6, 3, 7, 4, 8}; + + // Expect result of shape (12, 2) + DType expected[24] {1, 20, 2, 24, 3, 28, 4, 32, 2, 25, 4, 30, + 6, 35, 8, 40, 3, 30, 6, 36, 9, 42, 12, 48}; + + // Make input tensors + std::vector > ts_arr; + ts_arr.emplace_back(mat1, Shape2(3, 2), 2, nullptr); + ts_arr.emplace_back(mat2, Shape2(4, 2), 2, nullptr); + + // Compute Khatri-Rao product + Tensor result(Shape2(12, 2)); + AllocSpace(&result); + khatri_rao(result, ts_arr); + + // Check against expected result + Tensor ts_expected(expected, Shape2(12, 2), 2, nullptr); + EXPECT_DOUBLE_EQ_MATRIX(ts_expected, result); + + FreeSpace(&result); +} + +TEST(khatri_rao, ThreeInputMatrices) { + std::default_random_engine generator; + std::uniform_int_distribution distribution(1, 6); + + Tensor in1(Shape2(4, 3)), in2(Shape2(2, 3)), + in3(Shape2(3, 3)), kr12(Shape2(8, 3)), kr13(Shape2(24, 3)), + result(Shape2(24, 3)); + AllocSpace(&in1); + AllocSpace(&in2); + AllocSpace(&in3); + AllocSpace(&kr12); + AllocSpace(&kr13); + AllocSpace(&result); + + std::vector > ts_arr {in1, in2, in3}; + for (auto & in : ts_arr) { + for (int i = 0; i < static_cast(in.size(0)); ++i) + for (int j = 0; j < static_cast(in.size(1)); ++j) + in[i][j] = distribution(generator); + } + + khatri_rao(kr12, {in1, in2}); + khatri_rao(kr13, {kr12, in3}); + khatri_rao(result, ts_arr); + EXPECT_DOUBLE_EQ_MATRIX(kr13, result); + + for (auto & in : ts_arr) + FreeSpace(&in); + FreeSpace(&kr12); + FreeSpace(&kr13); + FreeSpace(&result); +} + +TEST(inv_khatri_rao, OneInputMatrixTransposed) { + DType mat[8] {1, 2, 3, 4, 5, 6, 7, 8}; + + // Make input tensors + std::vector > ts_arr; + ts_arr.emplace_back(mat, Shape2(2, 4), 4, nullptr); + + // Compute inverse Khatri-Rao product + Tensor inv_kr(Shape2(2, 4)); + AllocSpace(&inv_kr); + inv_khatri_rao(inv_kr, ts_arr, true); + + // Check against expected result + Tensor actual_dot(Shape2(2, 4)); + AllocSpace(&actual_dot); + actual_dot = implicit_dot(implicit_dot(inv_kr, ts_arr[0].T()), inv_kr); + EXPECT_DOUBLE_EQ_MATRIX(inv_kr, actual_dot); + + FreeSpace(&inv_kr); + FreeSpace(&actual_dot); +} + +TEST(inv_khatri_rao, TwoInputMatrices) { + // Input matrices of shape (3, 2) and (4, 2) + DType mat1[6] {1, 4, 2, 5, 3, 6}; + DType mat2[8] {1, 5, 2, 6, 3, 7, 4, 8}; + + // Make input tensors + std::vector > ts_arr; + ts_arr.emplace_back(mat1, Shape2(3, 2), 2, nullptr); + ts_arr.emplace_back(mat2, Shape2(4, 2), 2, nullptr); + + // Compute inverse Khatri-Rao product + Tensor inv_kr(Shape2(2, 12)), kr(Shape2(12, 2)); + AllocSpace(&inv_kr); + AllocSpace(&kr); + inv_khatri_rao(inv_kr, ts_arr, false); + khatri_rao(kr, ts_arr); + + // Check against expected result + Tensor actual_dot(Shape2(2, 12)); + AllocSpace(&actual_dot); + actual_dot = implicit_dot(implicit_dot(inv_kr, kr), inv_kr); + EXPECT_DOUBLE_EQ_MATRIX(inv_kr, actual_dot); + + FreeSpace(&inv_kr); + FreeSpace(&kr); + FreeSpace(&actual_dot); +} + +TEST(inv_khatri_rao, TwoInputMatricesTransposed) { + // Transposed input matrices of shape (2, 3) and (2, 4) + DType mat1[6] {1, 2, 3, 4, 5, 6}; + DType mat2[8] {1, 2, 3, 4, 5, 6, 7, 8}; + + // Make input tensors + std::vector > ts_arr; + ts_arr.emplace_back(mat1, Shape2(2, 3), 3, nullptr); + ts_arr.emplace_back(mat2, Shape2(2, 4), 4, nullptr); + + // Compute invser Khatri-Rao product + Tensor inv_kr(Shape2(2, 12)), kr_t(Shape2(2, 12)); + AllocSpace(&inv_kr); + AllocSpace(&kr_t); + inv_khatri_rao(inv_kr, ts_arr, true); + row_wise_kronecker(kr_t, ts_arr); + + // Check against expected result + Tensor actual_dot(Shape2(2, 12)); + AllocSpace(&actual_dot); + actual_dot = implicit_dot(implicit_dot(inv_kr, kr_t.T()), inv_kr); + EXPECT_DOUBLE_EQ_MATRIX(inv_kr, actual_dot); + + FreeSpace(&inv_kr); + FreeSpace(&kr_t); + FreeSpace(&actual_dot); +} + +TEST(inv_khatri_rao, ThreeInputMatricesTranposed) { + // Randomly initialise the transposed input matrices + std::default_random_engine generator; + std::uniform_int_distribution distribution(1, 6); + + Tensor in1(Shape2(3, 4)), in2(Shape2(3, 2)), + in3(Shape2(3, 3)); + AllocSpace(&in1); + AllocSpace(&in2); + AllocSpace(&in3); + + std::vector > ts_arr {in1, in2, in3}; + for (auto & in : ts_arr) { + for (int i = 0; i < static_cast(in.size(0)); ++i) + for (int j = 0; j < static_cast(in.size(1)); ++j) + in[i][j] = distribution(generator); + } + + // Compute inv_kr & kr + Tensor inv_kr(Shape2(3, 24)), kr_t(Shape2(3, 24)); + AllocSpace(&inv_kr); + AllocSpace(&kr_t); + + inv_khatri_rao(inv_kr, ts_arr, true); + row_wise_kronecker(kr_t, ts_arr); + + // Check dot result + Tensor actual_dot(Shape2(3, 24)); + AllocSpace(&actual_dot); + actual_dot = implicit_dot(implicit_dot(inv_kr, kr_t.T()), inv_kr); + EXPECT_DOUBLE_EQ_MATRIX(inv_kr, actual_dot); + + for (auto & in : ts_arr) + FreeSpace(&in); + FreeSpace(&inv_kr); + FreeSpace(&kr_t); + FreeSpace(&actual_dot); +} +} // namespace op +} // namespace mxnet diff --git a/tests/cpp/storage/storage_test.cc b/tests/cpp/storage/storage_test.cc index b2bbc4918599..8af3984eb40f 100644 --- a/tests/cpp/storage/storage_test.cc +++ b/tests/cpp/storage/storage_test.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2017 by Contributors * \file storage_test.cc * \brief cpu/gpu storage tests */ diff --git a/tests/cpp/test_main.cc b/tests/cpp/test_main.cc index 28cdf7b6ec2a..b8ffbbd0ad04 100644 --- a/tests/cpp/test_main.cc +++ b/tests/cpp/test_main.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2017 by Contributors * \file test_main.cc * \brief operator unit test utility functions * \author Chris Olivier diff --git a/tests/cpp/unittest.mk b/tests/cpp/unittest.mk index 808b655e9dba..11ea6d141a53 100644 --- a/tests/cpp/unittest.mk +++ b/tests/cpp/unittest.mk @@ -17,26 +17,26 @@ endif build/tests/cpp/%.o : tests/cpp/%.cc @mkdir -p $(@D) - $(CXX) -std=c++0x $(TEST_CFLAGS) -MM -MT tests/cpp/$* $< > build/tests/cpp/$*.d - $(CXX) -c -std=c++0x $(TEST_CFLAGS) -I$(GTEST_INC) -o build/tests/cpp/$*.o $(filter %.cc %.a, $^) + $(CXX) -std=c++11 $(TEST_CFLAGS) -I$(GTEST_INC) -MM -MT tests/cpp/$* $< > build/tests/cpp/$*.d + $(CXX) -c -std=c++11 $(TEST_CFLAGS) -I$(GTEST_INC) -o build/tests/cpp/$*.o $(filter %.cc %.a, $^) build/tests/cpp/operator/%.o : tests/cpp/operator/%.cc @mkdir -p $(@D) - $(CXX) -std=c++0x $(TEST_CFLAGS) -MM -MT tests/cpp/operator/$* $< > build/tests/cpp/operator/$*.d - $(CXX) -c -std=c++0x $(TEST_CFLAGS) -I$(GTEST_INC) -o build/tests/cpp/operator/$*.o $(filter %.cc %.a, $^) + $(CXX) -std=c++11 $(TEST_CFLAGS) -I$(GTEST_INC) -MM -MT tests/cpp/operator/$* $< > build/tests/cpp/operator/$*.d + $(CXX) -c -std=c++11 $(TEST_CFLAGS) -I$(GTEST_INC) -o build/tests/cpp/operator/$*.o $(filter %.cc %.a, $^) build/tests/cpp/storage/%.o : tests/cpp/storage/%.cc @mkdir -p $(@D) - $(CXX) -std=c++0x $(TEST_CFLAGS) -MM -MT tests/cpp/storage/$* $< > build/tests/cpp/storage/$*.d - $(CXX) -c -std=c++0x $(TEST_CFLAGS) -I$(GTEST_INC) -o build/tests/cpp/storage/$*.o $(filter %.cc %.a, $^) + $(CXX) -std=c++11 $(TEST_CFLAGS) -I$(GTEST_INC) -MM -MT tests/cpp/storage/$* $< > build/tests/cpp/storage/$*.d + $(CXX) -c -std=c++11 $(TEST_CFLAGS) -I$(GTEST_INC) -o build/tests/cpp/storage/$*.o $(filter %.cc %.a, $^) build/tests/cpp/engine/%.o : tests/cpp/engine/%.cc @mkdir -p $(@D) - $(CXX) -std=c++0x $(TEST_CFLAGS) -MM -MT tests/cpp/engine/$* $< > build/tests/cpp/engine/$*.d - $(CXX) -c -std=c++0x $(TEST_CFLAGS) -I$(GTEST_INC) -o build/tests/cpp/engine/$*.o $(filter %.cc %.a, $^) + $(CXX) -std=c++11 $(TEST_CFLAGS) -I$(GTEST_INC) -MM -MT tests/cpp/engine/$* $< > build/tests/cpp/engine/$*.d + $(CXX) -c -std=c++11 $(TEST_CFLAGS) -I$(GTEST_INC) -o build/tests/cpp/engine/$*.o $(filter %.cc %.a, $^) $(TEST): $(TEST_OBJ) lib/libmxnet.so - $(CXX) -std=c++0x $(TEST_CFLAGS) -I$(GTEST_INC) -o $@ $^ $(TEST_LDFLAGS) -L$(GTEST_LIB) -lgtest + $(CXX) -std=c++11 $(TEST_CFLAGS) -I$(GTEST_INC) -o $@ $^ $(TEST_LDFLAGS) -L$(GTEST_LIB) -lgtest runtest: $(TEST) LD_LIBRARY_PATH=$(shell pwd)/lib:$(LD_LIBRARY_PATH) $(TEST) @@ -47,4 +47,4 @@ testclean: -include build/tests/cpp/*.d -include build/tests/cpp/operator/*.d -include build/tests/cpp/storage/*.d --include build/tests/cpp/engine/*.d \ No newline at end of file +-include build/tests/cpp/engine/*.d diff --git a/tests/jenkins/run_as_user.sh b/tests/jenkins/run_as_user.sh index db90f0bd0088..7ed3cdf5c937 100755 --- a/tests/jenkins/run_as_user.sh +++ b/tests/jenkins/run_as_user.sh @@ -1,5 +1,23 @@ #!/bin/bash +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + + # Exit script with error if any errors occur set -e diff --git a/tests/jenkins/run_test.sh b/tests/jenkins/run_test.sh index a8564326443f..bc69ca1d7f39 100755 --- a/tests/jenkins/run_test.sh +++ b/tests/jenkins/run_test.sh @@ -1,5 +1,23 @@ #!/bin/bash +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + + # Exit script with error if any errors occur echo "BUILD make" diff --git a/tests/jenkins/run_test_amzn_linux_gpu.sh b/tests/jenkins/run_test_amzn_linux_gpu.sh index 42c037e67a37..ecfb5211b9e6 100755 --- a/tests/jenkins/run_test_amzn_linux_gpu.sh +++ b/tests/jenkins/run_test_amzn_linux_gpu.sh @@ -1,5 +1,23 @@ #!/bin/bash +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + + # Exit script with error if any errors occur echo "BUILD make" diff --git a/tests/jenkins/run_test_installation_docs.sh b/tests/jenkins/run_test_installation_docs.sh index 921c89a16605..90eecb95a325 100755 --- a/tests/jenkins/run_test_installation_docs.sh +++ b/tests/jenkins/run_test_installation_docs.sh @@ -1,5 +1,23 @@ #!/bin/bash +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + + set -e # Given an array of numbers, removes any numbers of it that fall outside a given range. @@ -30,9 +48,9 @@ function remove_out_of_range() { echo "Error: Min must be less than or equal to Max" exit 1 fi - + return_arr=() - + for number in "${lineno_array[@]}" do if (( ${number} > ${min} && ${number} < ${max} )) diff --git a/tests/jenkins/run_test_pip_installations.sh b/tests/jenkins/run_test_pip_installations.sh index 9122ea4e7c77..44788bfaf772 100755 --- a/tests/jenkins/run_test_pip_installations.sh +++ b/tests/jenkins/run_test_pip_installations.sh @@ -1,5 +1,23 @@ #!/bin/bash +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + + set -e if (( $# < 1 )); then @@ -42,14 +60,13 @@ for DEV in "${DEVICES[@]}"; do echo "Testing ${PYTHON}" DOCKER_CMD="virtualenv -p \"/usr/bin/${PYTHON}\" ${PYTHON}; source \"${PYTHON}/bin/activate\"; cd ${WORKSPACE};" if [[ "${DEV}" == *"cpu"* ]]; then - DOCKER_CMD="${DOCKER_CMD} pip install mxnet; python tests/python/train/test_conv.py" + DOCKER_CMD="${DOCKER_CMD} pip install mxnet --pre; python tests/python/train/test_conv.py" elif [[ "${DEV}" == *"cu75"* ]]; then - DOCKER_CMD="${DOCKER_CMD} pip install mxnet-cu75; python tests/python/train/test_conv.py --gpu" + DOCKER_CMD="${DOCKER_CMD} pip install mxnet-cu75 --pre; python tests/python/train/test_conv.py --gpu" elif [[ "${DEV}" == *"cu80"* ]]; then - DOCKER_CMD="${DOCKER_CMD} pip install mxnet-cu80; python tests/python/train/test_conv.py --gpu" + DOCKER_CMD="${DOCKER_CMD} pip install mxnet-cu80 --pre; python tests/python/train/test_conv.py --gpu" fi - - ${DOCKER_BINARY} run --rm -v ${WORKSPACE}:${WORKSPACE} ${DOCKER_TAG} bash -c "${DOCKER_CMD}" + ${DOCKER_BINARY} run --rm -v ${WORKSPACE}:${WORKSPACE} -w ${WORKSPACE} ${DOCKER_TAG} bash -c "tests/jenkins/run_as_user.sh `id -u` `id -un` `id -g` `id -un` '${DOCKER_CMD}'" done done diff --git a/tests/jenkins/run_test_ubuntu.sh b/tests/jenkins/run_test_ubuntu.sh index 2e458b52599a..cdddd2865ddc 100755 --- a/tests/jenkins/run_test_ubuntu.sh +++ b/tests/jenkins/run_test_ubuntu.sh @@ -1,5 +1,23 @@ #!/bin/bash +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + + set -e echo "BUILD make" diff --git a/tests/jenkins/set_user_permissions.sh b/tests/jenkins/set_user_permissions.sh index d03a97bfdd73..51034c4365b6 100644 --- a/tests/jenkins/set_user_permissions.sh +++ b/tests/jenkins/set_user_permissions.sh @@ -1,5 +1,23 @@ #!/bin/bash +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + + # Exit script with error if any errors occur set -e diff --git a/tests/nightly/Jenkinsfile b/tests/nightly/Jenkinsfile new file mode 100644 index 000000000000..443c811e2709 --- /dev/null +++ b/tests/nightly/Jenkinsfile @@ -0,0 +1,10 @@ +// -*- mode: groovy -*- +// Jenkins pipeline +// See documents at https://jenkins.io/doc/book/pipeline/jenkinsfile/ +// Runs nightly builds + +stage("Hello World") { + node('mxnetlinux') { + sh "echo 'Hello World'" + } +} \ No newline at end of file diff --git a/tests/nightly/TestDoc/doc_spell_checker.py b/tests/nightly/TestDoc/doc_spell_checker.py index 20a5c07afdae..a7b8b250c928 100644 --- a/tests/nightly/TestDoc/doc_spell_checker.py +++ b/tests/nightly/TestDoc/doc_spell_checker.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + #pylint: disable=no-member, too-many-instance-attributes """This script uses pyenchant to check spelling for MXNet documentation website. diff --git a/tests/nightly/TestDoc/doc_spell_grammar.sh b/tests/nightly/TestDoc/doc_spell_grammar.sh index 05b0be138fd6..77c7b86eb7aa 100755 --- a/tests/nightly/TestDoc/doc_spell_grammar.sh +++ b/tests/nightly/TestDoc/doc_spell_grammar.sh @@ -1,4 +1,22 @@ #!/bin/sh + +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + echo "BUILD make" cp ./make/config.mk . echo "USE_CUDA=0" >> ./config.mk diff --git a/tests/nightly/compilation_warnings/compilation_warnings.sh b/tests/nightly/compilation_warnings/compilation_warnings.sh new file mode 100644 index 000000000000..a6c4863f4b58 --- /dev/null +++ b/tests/nightly/compilation_warnings/compilation_warnings.sh @@ -0,0 +1,45 @@ +#!/bin/bash + +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +set -e +runme() { + cmd=$* + echo "$cmd" + $cmd + ret=$? + if [[ ${ret} != 0 ]]; then + echo " " + echo "ERROR: Return value non-zero for: $cmd" + echo " " + exit 1 + fi +} + +sudo add-apt-repository ppa:ubuntu-toolchain-r/test +sudo apt-get update +sudo apt-get -y install time g++-5 +runme make clean >/dev/null +runme mkdir build +echo "Starting make" +cp make/config.mk . +sed -i -e 's/gcc/gcc-5/g' config.mk +sed -i -e 's/g++/g++-5/g' config.mk +runme /usr/bin/time -f "%e" make -j$(nproc) 2>&1 | tee build/compile_output.txt +echo "Finished make. Now processing output" +python tests/nightly/compilation_warnings/process_output.py build/compile_output.txt diff --git a/tests/nightly/compilation_warnings/process_output.py b/tests/nightly/compilation_warnings/process_output.py new file mode 100644 index 000000000000..5f85af592f6c --- /dev/null +++ b/tests/nightly/compilation_warnings/process_output.py @@ -0,0 +1,56 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +import re +import sys +import operator + +def process_output(command_output): + warnings = {} + regex = r"(.*):\swarning:\s(.*)" + lines = command_output.split("\n") + for line in lines[:-2]: + matches = re.finditer(regex, line) + for matchNum, match in enumerate(matches): + try: + warnings[match.group()] +=1 + except KeyError: + warnings[match.group()] =1 + time = lines[-2] + return time, warnings + +def generate_stats(warnings): + total_count = sum(warnings.values()) + sorted_warnings = sorted(warnings.items(), key=operator.itemgetter(1), reverse=True) + return sorted_warnings, total_count + +def print_summary(time, warnings): + sorted_warnings, total_count = generate_stats(warnings) + print "START - Compilation warnings count" + print total_count, 'warnings' + print "END - Compilation warnings count" + print 'START - Compilation warnings summary' + print 'Time taken to compile:', time, 's' + print 'Total number of warnings:', total_count, '\n' + print 'Below is the list of unique warnings and the number of occurrences of that warning' + for warning, count in sorted_warnings: + print count, ': ', warning + print 'END - Compilation warnings summary' + +c_output = open(sys.argv[1],'r') +time, warnings = process_output(c_output.read()) +print_summary(time, warnings) diff --git a/tests/nightly/dist_lenet.py b/tests/nightly/dist_lenet.py index a7ae84cb3200..35f55c8157d2 100644 --- a/tests/nightly/dist_lenet.py +++ b/tests/nightly/dist_lenet.py @@ -1,4 +1,22 @@ #!/usr/bin/env python + +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # distributed lenet import os, sys curr_path = os.path.abspath(os.path.dirname(__file__)) diff --git a/tests/nightly/dist_sync_kvstore.py b/tests/nightly/dist_sync_kvstore.py index ebed6c57586d..3fbf9f910879 100644 --- a/tests/nightly/dist_sync_kvstore.py +++ b/tests/nightly/dist_sync_kvstore.py @@ -1,4 +1,22 @@ #!/usr/bin/env python + +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # pylint: skip-file import sys sys.path.insert(0, "../../python/") diff --git a/tests/nightly/download.sh b/tests/nightly/download.sh index 56f822e6ad42..d07fc6f4ab8a 100644 --- a/tests/nightly/download.sh +++ b/tests/nightly/download.sh @@ -1,5 +1,23 @@ #!/bin/bash +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + + dmlc_download() { url=http://data.mxnet.io/mxnet/datasets/ dir=$1 diff --git a/tests/nightly/multi_lenet.py b/tests/nightly/multi_lenet.py index 1fb2dfa6377e..687588bacbe9 100644 --- a/tests/nightly/multi_lenet.py +++ b/tests/nightly/multi_lenet.py @@ -1,4 +1,22 @@ #!/usr/bin/env python + +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # lenet with multiple gpus # # using different kvstore will get almost identical results diff --git a/tests/nightly/mxnet_keras_integration_tests/assertion_util.py b/tests/nightly/mxnet_keras_integration_tests/assertion_util.py index 1fad6a1eb326..eb3d3bd85fda 100644 --- a/tests/nightly/mxnet_keras_integration_tests/assertion_util.py +++ b/tests/nightly/mxnet_keras_integration_tests/assertion_util.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + from nose.tools import assert_true diff --git a/tests/nightly/mxnet_keras_integration_tests/model_util.py b/tests/nightly/mxnet_keras_integration_tests/model_util.py index 9f73ab60b062..bb9d6374af8f 100644 --- a/tests/nightly/mxnet_keras_integration_tests/model_util.py +++ b/tests/nightly/mxnet_keras_integration_tests/model_util.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import os from keras import backend as K from keras.models import Model diff --git a/tests/nightly/mxnet_keras_integration_tests/profiler.py b/tests/nightly/mxnet_keras_integration_tests/profiler.py index 4b6446a9b8cc..b0d39e19aa00 100644 --- a/tests/nightly/mxnet_keras_integration_tests/profiler.py +++ b/tests/nightly/mxnet_keras_integration_tests/profiler.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import os import signal import time diff --git a/tests/nightly/mxnet_keras_integration_tests/test_mnist_mlp.py b/tests/nightly/mxnet_keras_integration_tests/test_mnist_mlp.py index 7a0c6298d736..89bd2805ce78 100644 --- a/tests/nightly/mxnet_keras_integration_tests/test_mnist_mlp.py +++ b/tests/nightly/mxnet_keras_integration_tests/test_mnist_mlp.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + ''' This code is forked from https://github.com/fchollet/keras/blob/master/examples/mnist_mlp.py and modified to use as MXNet-Keras integration testing for functionality and sanity performance diff --git a/tests/nightly/sh2ju.sh b/tests/nightly/sh2ju.sh index 84c1427227e7..4465cd8f843f 100644 --- a/tests/nightly/sh2ju.sh +++ b/tests/nightly/sh2ju.sh @@ -1,4 +1,22 @@ #!/bin/sh + +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + ### Copyright 2010 Manuel Carrasco Moñino. (manolo at apache.org) ### ### Licensed under the Apache License, Version 2.0. diff --git a/tests/nightly/test_all.sh b/tests/nightly/test_all.sh index 33c39f5f4bd1..32913c9f5f5b 100755 --- a/tests/nightly/test_all.sh +++ b/tests/nightly/test_all.sh @@ -1,5 +1,23 @@ #!/bin/bash +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + + # setup export LD_LIBRARY_PATH=/usr/local/cuda/lib64:$LD_LIBRARY_PATH cd `pwd`/`dirname $0` diff --git a/tests/nightly/test_config.txt b/tests/nightly/test_config.txt deleted file mode 100644 index d3e7db9da9af..000000000000 --- a/tests/nightly/test_config.txt +++ /dev/null @@ -1,6 +0,0 @@ -#Testing folder, seperated by comma -#If test_path is empty, by default all the notebooks under root directory will be tested. -#If test_ignored is set to "@@@ IGNORE_ALL", no notebook will be tested. -[Folder Path] -test_path = mxnet-notebooks/python/basic -test_ignored = mxnet-notebooks/python/basic/advanced_img_io.ipynb, mxnet-notebooks/python/basic/image_io.ipynb, mxnet-notebooks/python/basic/mixed.ipynb, mxnet-notebooks/python/basic/module.ipynb, mxnet-notebooks/python/basic/ndarray.ipynb, mxnet-notebooks/python/basic/record_io.ipynb, mxnet-notebooks/python/basic/symbol.ipynb \ No newline at end of file diff --git a/tests/nightly/test_ipynb.py b/tests/nightly/test_ipynb.py deleted file mode 100644 index a1695bfdc0df..000000000000 --- a/tests/nightly/test_ipynb.py +++ /dev/null @@ -1,265 +0,0 @@ -#pylint: disable=no-member, too-many-locals, too-many-branches, no-self-use, broad-except, lost-exception, too-many-nested-blocks, too-few-public-methods -""" - This script runs notebooks in selected directory and report - errors for each notebook. - - Traceback information can be found in the output notebooks - generated in coresponding output directories. - - Before running this scripe, make sure all the notebooks have - been run at least once and outputs are generated. -""" - -import os -import json -import ConfigParser -import re -import sys -from textwrap import dedent -reload(sys) -sys.setdefaultencoding('utf-8') -#pylint: enable=no-member - -import nbformat -import nbconvert.preprocessors.execute as execute - -TIME_LIMIT_FLAG = '# @@@ AUTOTEST_TIME_LIMT_SECONDS=' -IGNORED_CELL_FLAG = '# @@@ AUTOTEST_OUTPUT_IGNORED_CELL' - -class CustomizedPreprocessor(execute.ExecutePreprocessor): - """A customized preprocessor which allows preset for cell. - In this test script, timeout is set before executing a cell. - """ - def preprocess_cell(self, cell, resources, cell_index): - """ - Executes a code cell with timeout. Default timeout is 900 sec. - """ - if cell.cell_type != 'code': - return cell, resources - - regex = re.compile(TIME_LIMIT_FLAG + '[0-9]+') - time_flag = re.search(regex, cell.source) - if time_flag is not None: - timeout = int(re.search(r'[0-9]+', time_flag).group()) - self.timeout = timeout - - outputs = self.run_cell(cell) - cell.outputs = outputs - - if not self.allow_errors: - for out in outputs: - if out.output_type == 'error': - pattern = u"""\ - An error occurred while executing cell No.{cell.execution_count}: - ------------------ - {cell.source} - ------------------ - {out.ename}: {out.evalue} - """ - msg = dedent(pattern).format(out=out, cell=cell) - raise execute.CellExecutionError(msg) - return cell, resources - - -class NotebookTester(object): - """The class of notebook automated testing. A NotebookTester loads a test_config - file and execute each notebook. A report containing detail traceback information - will be generated. - """ - def __init__(self, test_config): - self.test_config = test_config - - def __read_config(self, test_config): - """Read notebooks to be tested from test config file. - - Parameters - ---------- - test_config : str - test configuration file - - Returns - ------- - nb_list : list - Notebook list to be tested - """ - nb_list = [] - config_parser = ConfigParser.RawConfigParser() - config_parser.read(test_config) - test_dirs = config_parser.get('Folder Path', 'test_path').split(', ') - if len(test_dirs) == 1 and len(test_dirs[0]) == 0: - test_dirs.append('.') - ignored_item = config_parser.get('Folder Path', 'test_ignored').split(', ') - ignored_dir = set() - ignored_nb = set() - for item in ignored_item: - if item == '@@@ IGNORE_ALL': - return nb_list - if item.endswith('.ipynb'): - ignored_nb.add(os.path.abspath(item)) - else: - for root, _, _ in os.walk(item): - ignored_dir.add(os.path.abspath(root)) - for test_dir in test_dirs: - for root, _, files in os.walk(test_dir): - if os.path.abspath(root) in ignored_dir: - continue - for test_file in files: - if test_file.endswith('.ipynb') and not \ - test_file.endswith('-checkpoint.ipynb'): - notebook = os.path.join(root, test_file) - if os.path.abspath(notebook) not in ignored_nb: - if notebook.startswith('./'): - notebook = notebook[2:] - nb_list.append(notebook) - return nb_list - - - def __notebook_run(self, path): - """Execute a notebook via nbconvert and collect output. - - Parameters - ---------- - path : str - notebook file path. - - Returns - ------- - error : str - notebook first cell execution errors. - """ - error = "" - parent_dir, nb_name = os.path.split(path) - with open(path) as nb_file: - notebook = nbformat.read(nb_file, as_version=4) - eprocessor = CustomizedPreprocessor(timeout=900) - #Use a loop to avoid "Kernel died before replying to kernel_info" error, repeat 5 times - for _ in range(0, 5): - error = "" - try: - eprocessor.preprocess(notebook, {'metadata': {'path': parent_dir}}) - except Exception as ex_error: - error = str(ex_error) - finally: - if error != 'Kernel died before replying to kernel_info': - output_nb = os.path.splitext(nb_name)[0] + "_output.ipynb" - with open(output_nb, mode='w') as output_file: - nbformat.write(notebook, output_file) - output_file.close() - nb_file.close() - if len(error) == 0: - cell_num = self.__verify_output(path, output_nb) - if cell_num > 0: - error = "Output in cell No.%d has changed." % cell_num - os.remove(output_nb) - return error - return error - - - def __verify_output(self, origin_nb, output_nb): - """Compare the output cells of testing output notebook with original notebook. - - Parameters - ---------- - origin_nb : str - original notebook file path. - - output_nb : str - output notebook file path. - - Returns - ------- - cell_num : int - First cell number in which outputs are incompatible - """ - cell_num = 0 - origin_nb_file = open(origin_nb) - origin_nb_js = json.load(origin_nb_file) - output_nb_file = open(output_nb) - output_nb_js = json.load(output_nb_file) - for origin_cell, output_cell in zip(origin_nb_js["cells"], output_nb_js["cells"]): - is_ignored_cell = False - if len(origin_cell["source"]) == 0 or not origin_cell.has_key("outputs"): - is_ignored_cell = True - for line in origin_cell["source"]: - if line.startswith(IGNORED_CELL_FLAG): - is_ignored_cell = True - break - if is_ignored_cell: - continue - if self.__extract_output(origin_cell["outputs"]) != \ - self.__extract_output(output_cell["outputs"]): - cell_num = origin_cell["execution_count"] - break - origin_nb_file.close() - output_nb_file.close() - return cell_num - - - def __extract_output(self, outputs): - """Extract text part of output of a notebook cell. - - Parasmeters - ----------- - outputs : list - list of output - - Returns - ------- - ret : str - Concatenation of all text output contents - """ - ret = '' - for out_dict in outputs: - for key, val in out_dict.items(): - if str(key).startswith('text'): - for content in val: - ret += str(content) - elif key == 'data': - for dt_key, dt_val in val.items(): - if str(dt_key).startswith('text') and not \ - str(dt_key).startswith('text/html'): - for dt_content in dt_val: - if not str(dt_content).startswith(' 0: - test_summary.write("\n%d notebook tests failed:\n" % len(fail_nb_dict)) - print "Following are failed notebooks:" - for fail_nb, error in fail_nb_dict.items(): - test_summary.write("\n%s:\n" % fail_nb) - test_summary.write("%s\n" % error) - print fail_nb - else: - test_summary.write("\nAll notebook tests passed!\n") - test_summary.close() - print "Test summarys are stored in test_summary.txt" - -if __name__ == "__main__": - NB_TESTER = NotebookTester('test_config.txt') - NB_TESTER.run_test() diff --git a/tests/nightly/test_kvstore.py b/tests/nightly/test_kvstore.py index c954c1859d64..b39ec89cf728 100644 --- a/tests/nightly/test_kvstore.py +++ b/tests/nightly/test_kvstore.py @@ -1,4 +1,22 @@ #!/usr/bin/env python + +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import sys sys.path.insert(0, "../../python/") import mxnet as mx diff --git a/tests/nightly/test_mxnet_keras_integration_cpu.sh b/tests/nightly/test_mxnet_keras_integration_cpu.sh index 25a1da4ddf46..95cc0d0760e2 100755 --- a/tests/nightly/test_mxnet_keras_integration_cpu.sh +++ b/tests/nightly/test_mxnet_keras_integration_cpu.sh @@ -1,4 +1,22 @@ #!/bin/sh + +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + set -e ### Build MXNet with CPU support echo "BUILD make" diff --git a/tests/nightly/test_mxnet_keras_integration_gpu.sh b/tests/nightly/test_mxnet_keras_integration_gpu.sh index 86fb37acfc47..5d541fa5b7a4 100755 --- a/tests/nightly/test_mxnet_keras_integration_gpu.sh +++ b/tests/nightly/test_mxnet_keras_integration_gpu.sh @@ -1,4 +1,22 @@ #!/bin/sh + +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + set -e ### Install git diff --git a/tests/nightly/test_notebook.sh b/tests/nightly/test_notebook.sh deleted file mode 100755 index e3d1a5018e4c..000000000000 --- a/tests/nightly/test_notebook.sh +++ /dev/null @@ -1,35 +0,0 @@ -#!/bin/sh -echo "BUILD make" -cp ./make/config.mk . -echo "USE_CUDA=0" >> ./config.mk -echo "USE_CUDNN=0" >> ./config.mk -echo "USE_BLAS=openblas" >> ./config.mk -echo "ADD_CFLAGS += -I/usr/include/openblas" >> ./config.mk -echo "GTEST_PATH=/usr/local/gtest" >> ./config.mk -echo 'export PKG_CONFIG_PATH=/usr/local/lib/pkgconfig:$PKG_CONFIG_PATH' >> ~/.profile -echo 'export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH' >> ~/.profile -echo 'export JAVA_HOME=/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.111-1.b15.25.amzn1.x86_64' >> ~/.profile -echo 'export JRE_HOME=/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.111-1.b15.25.amzn1.x86_64/jre' >> ~/.profile -echo 'export PATH=$PATH:/apache-maven-3.3.9/bin/:/usr/bin:/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.111-1.b15.25.amzn1.x86_64/bin:/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.111-1.b15.25.amzn1.x86_64/jre/bin' >> ~/.profile -source ~/.profile -make clean -make -j 4 || exit -1 - -echo "BUILD python2 mxnet" -cd ./python -python setup.py install || exit 1 - -echo "BUILD python3 mxnet" -python3 setup.py install || exit 1 -echo "~/.local" -cd ../tests/nightly - -echo "Pull mxnet-notebook" -git clone https://github.com/dmlc/mxnet-notebooks.git - -echo "Test Jupyter notebook" -python test_ipynb.py - -echo "Test Summary Start" -cat test_summary.txt -echo "Test Summary End" diff --git a/tests/nightly/test_tutorial.py b/tests/nightly/test_tutorial.py new file mode 100644 index 000000000000..56b530a59bb4 --- /dev/null +++ b/tests/nightly/test_tutorial.py @@ -0,0 +1,108 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +#pylint: disable=no-member, too-many-locals, too-many-branches, no-self-use, broad-except, lost-exception, too-many-nested-blocks, too-few-public-methods, invalid-name +""" + This script converts all python tutorials into python script + and tests whether there is any warning or error. + After running python script, it will also convert markdown files + to notebooks to make sure notebook execution has no error. +""" +import os +import warnings +import imp + +import traceback +import nbformat +from nbconvert.preprocessors import ExecutePreprocessor + +fail_dict = {} + +def test_tutorial(file_path): + """Run tutorial python script and save any error or warning. + If no error or warning occurs, run notebook. + + Parameters + ---------- + file_path : str + path of tutorial markdown file + """ + with warnings.catch_warnings(record=True) as w: + tutorial_name = os.path.basename(file_path) + print file_path + '.py' + try: + imp.load_source('tutorial', file_path + '.py') + if len(w) > 0: + err_msg = "%s.py has %d warnings.\n" % (tutorial_name, len(w)) + fail_dict[tutorial_name] = err_msg + else: + test_tutorial_nb(file_path) + except Exception: + err_msg = "%s.py has error:\n%s" % (tutorial_name, traceback.format_exc()) + fail_dict[tutorial_name] = err_msg + +def test_tutorial_nb(file_path): + """Run tutorial jupyter notebook to catch any execution error. + + Parameters + ---------- + file_path : str + path of tutorial markdown file + """ + tutorial_name = os.path.basename(file_path) + notebook = nbformat.read(file_path + '.ipynb', as_version=4) + eprocessor = ExecutePreprocessor(timeout=1800) + try: + eprocessor.preprocess(notebook, {'metadata': {}}) + except Exception as err: + err_msg = str(err) + fail_dict[tutorial_name] = err_msg + finally: + output_nb = open("output.txt", mode='w') + nbformat.write(notebook, output_nb) + output_nb.close() + output_nb = open("output.txt", mode='r') + for line in output_nb: + if "Warning:" in line: + fail_dict[tutorial_name] = "%s has warning." % (tutorial_name) + return + + +if __name__ == "__main__": + tutorial_dir = '../../docs/_build/html/tutorials/' + with open('test_tutorial_config.txt') as config_file: + tutorial_list = [] + for line in config_file: + tutorial_list.append(line.lstrip().rstrip()) + file_dir = tutorial_dir + line.lstrip().rstrip() + test_tutorial_nb(file_dir) + + fail_num = len(fail_dict) + success_num = len(tutorial_list) - fail_num + print "Test Summary Start" + print "%d tutorials tested:" % (len(tutorial_list)) + for tutorial in tutorial_list: + print tutorial + print "\n%d tests failed:" % (fail_num) + for tutorial, msg in fail_dict.items(): + print tutorial + ":" + print msg + print "Test Summary End" + print "Stats start" + print "[Passed: %d of %d]" % (success_num, len(tutorial_list)) + print "Stats end" + diff --git a/tests/nightly/test_tutorial_config.txt b/tests/nightly/test_tutorial_config.txt new file mode 100644 index 000000000000..428309b84c8c --- /dev/null +++ b/tests/nightly/test_tutorial_config.txt @@ -0,0 +1,7 @@ +basic/ndarray +basic/symbol +basic/module +basic/data +python/linear-regression +python/mnist +python/predict_image diff --git a/tests/python/common/get_data.py b/tests/python/common/get_data.py index e385a7186848..35482f8de584 100644 --- a/tests/python/common/get_data.py +++ b/tests/python/common/get_data.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # pylint: skip-file import os, gzip import pickle as pickle diff --git a/tests/python/common/models.py b/tests/python/common/models.py index 2c998afcd1db..b563adc1d760 100644 --- a/tests/python/common/models.py +++ b/tests/python/common/models.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + """This file defines various models used in the test""" import mxnet as mx diff --git a/tests/python/doctest/test_docstring.py b/tests/python/doctest/test_docstring.py index e457e7b9ca55..23a29588c5af 100644 --- a/tests/python/doctest/test_docstring.py +++ b/tests/python/doctest/test_docstring.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import doctest import logging import mxnet diff --git a/tests/python/gpu/test_forward.py b/tests/python/gpu/test_forward.py index dc2c129f5326..cddf9afb9cb2 100644 --- a/tests/python/gpu/test_forward.py +++ b/tests/python/gpu/test_forward.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import os import numpy as np import mxnet as mx diff --git a/tests/python/gpu/test_operator_gpu.py b/tests/python/gpu/test_operator_gpu.py index fd3dd9289836..866f6ad8abc0 100644 --- a/tests/python/gpu/test_operator_gpu.py +++ b/tests/python/gpu/test_operator_gpu.py @@ -1,15 +1,36 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import sys import os +import time +import mxnet as mx +import numpy as np +from mxnet.test_utils import check_consistency, set_default_context, assert_almost_equal +from numpy.testing import assert_allclose + curr_path = os.path.dirname(os.path.abspath(os.path.expanduser(__file__))) sys.path.insert(0, os.path.join(curr_path, '../unittest')) from test_operator import * from test_optimizer import * from test_random import * -import mxnet as mx -import numpy as np -from mxnet.test_utils import check_consistency, set_default_context -from numpy.testing import assert_allclose -import time +from test_gluon import * +#from test_rnn import * +from test_gluon_rnn import * set_default_context(mx.gpu(0)) del test_support_vector_machine_l1_svm @@ -602,7 +623,6 @@ def test_bilinear_sampler_with_type(): check_consistency(sym, ctx_list) check_consistency(sym, ctx_list, grad_req="add") - def test_grid_generator_with_type(): data = mx.sym.Variable('data') sym = mx.sym.GridGenerator(data=data, transform_type='affine', target_shape=(20, 20)) @@ -616,6 +636,19 @@ def test_grid_generator_with_type(): check_consistency(sym, ctx_list) check_consistency(sym, ctx_list, grad_req="add") +def test_spatial_transformer_with_type(): + np.random.seed(1234) + data = mx.sym.Variable('data') + loc = mx.sym.Flatten(data) + loc = mx.sym.FullyConnected(data=loc, num_hidden=10) + loc = mx.sym.Activation(data=loc, act_type='relu') + loc = mx.sym.FullyConnected(data=loc, num_hidden=6) + sym = mx.sym.SpatialTransformer(data=data, loc=loc, target_shape=(10, 10), + transform_type="affine", sampler_type="bilinear") + ctx_list = [{'ctx': mx.gpu(0), 'data': (1, 5, 10, 10), 'type_dict': {'data': np.float32}}, + {'ctx': mx.cpu(0), 'data': (1, 5, 10, 10), 'type_dict': {'data': np.float32}}] + check_consistency(sym, ctx_list) + check_consistency(sym, ctx_list, grad_req="add") # Checking max pooling consistency over the data sets of different float types is problematic # as one max value in a float32 data set may not be the max value in a float16 data set. @@ -1093,34 +1126,227 @@ def test_unfuse(): check_rnn_consistency(fused, stack) check_rnn_consistency(stack, fused) +def test_psroipooling_with_type(): + np.random.seed(1234) + arg_params = { + 'psroipool_rois': np.array([[0, 10, 22, 161, 173], [0, 20, 15, 154, 160]])} + + # plain psroipooling + sym = mx.contrib.sym.PSROIPooling(spatial_scale=0.0625, output_dim=2, pooled_size=3, name='psroipool') + ctx_list = [{'ctx': mx.gpu(0), + 'psroipool_data': (1, 18, 14, 14), + 'psroipool_rois': (2, 5), + 'type_dict': {'psroipool_data': np.float64, 'psroipool_rois': np.float64}}, + {'ctx': mx.gpu(0), + 'psroipool_data': (1, 18, 14, 14), + 'psroipool_rois': (2, 5), + 'type_dict': {'psroipool_data': np.float32, 'psroipool_rois': np.float32}}, + {'ctx': mx.gpu(0), + 'psroipool_data': (1, 18, 14, 14), + 'psroipool_rois': (2, 5), + 'type_dict': {'psroipool_data': np.float16, 'psroipool_rois': np.float16}}, + ] + + check_consistency(sym, ctx_list, grad_req={'psroipool_data': 'write', + 'psroipool_rois': 'null'}, arg_params=arg_params) + +def test_deformable_psroipooling_with_type(): + np.random.seed(1234) + arg_params = { + 'deformable_psroipool_rois': np.array([[0, 10, 22, 161, 173], [0, 20, 15, 154, 160]])} + + # deformable psroipooling + sym = mx.contrib.sym.DeformablePSROIPooling(spatial_scale=0.0625, sample_per_part=4, group_size=3, pooled_size=3, + output_dim=2, trans_std=0.1, no_trans=False, name='deformable_psroipool') + + ctx_list = [{'ctx': mx.gpu(0), + 'deformable_psroipool_data': (1, 18, 14, 14), + 'deformable_psroipool_rois': (2, 5), + 'deformable_psroipool_trans': (2, 4, 3, 3), + 'type_dict': {'deformable_psroipool_data': np.float64, 'deformable_psroipool_rois': np.float64, + 'deformable_psroipool_trans': np.float64}}, + {'ctx': mx.gpu(0), + 'deformable_psroipool_data': (1, 18, 14, 14), + 'deformable_psroipool_rois': (2, 5), + 'deformable_psroipool_trans': (2, 4, 3, 3), + 'type_dict': {'deformable_psroipool_data': np.float32, 'deformable_psroipool_rois': np.float32, + 'deformable_psroipool_trans': np.float32}}, + {'ctx': mx.gpu(0), + 'deformable_psroipool_data': (1, 18, 14, 14), + 'deformable_psroipool_rois': (2, 5), + 'deformable_psroipool_trans': (2, 4, 3, 3), + 'type_dict': {'deformable_psroipool_data': np.float16, 'deformable_psroipool_rois': np.float16, + 'deformable_psroipool_trans': np.float16}}, + ] + + check_consistency(sym, ctx_list, grad_req={'deformable_psroipool_data': 'write', + 'deformable_psroipool_rois': 'null', + 'deformable_psroipool_trans': 'write'}, arg_params=arg_params) + +def test_deformable_convolution_with_type(): + np.random.seed(1234) + sym = mx.contrib.sym.DeformableConvolution(num_filter=3, kernel=(3,3), name='deformable_conv') + # since atomicAdd does not support fp16 (which deformable conv uses in backward), we do not test fp16 here + ctx_list = [{'ctx': mx.gpu(0), + 'deformable_conv_data': (2, 2, 10, 10), + 'deformable_conv_offset': (2, 18, 8, 8), + 'type_dict': {'deformable_conv_data': np.float64, 'deformable_conv_offset': np.float64}}, + {'ctx': mx.gpu(0), + 'deformable_conv_data': (2, 2, 10, 10), + 'deformable_conv_offset': (2, 18, 8, 8), + 'type_dict': {'deformable_conv_data': np.float32, 'deformable_conv_offset': np.float32}}, + # {'ctx': mx.gpu(0), + # 'deformable_conv_data': (2, 2, 10, 10), + # 'deformable_conv_offset': (2, 18, 8, 8), + # 'type_dict': {'deformable_conv_data': np.float16, 'deformable_conv_offset': np.float16}}, + ] + # wider tolerance needed for true-fp16 NCHW test above + tol = {np.dtype(np.float16): 0.5, + np.dtype(np.float32): 1e-3, + np.dtype(np.float64): 1e-5, + np.dtype(np.uint8): 0, + np.dtype(np.int32): 0} + check_consistency(sym, ctx_list, tol=tol) + # test ability to turn off training on bias + check_consistency(sym, ctx_list, grad_req={'deformable_conv_data': 'write', + 'deformable_conv_offset': 'write', + 'deformable_conv_weight': 'write', + 'deformable_conv_bias': 'null'}, tol=tol) +def test_deformable_convolution_options(): + # 2D convolution + + # Pad > 0 + # since atomicAdd does not support fp16 (which deformable conv uses in backward), we do not test fp16 here + ctx_list = [{'ctx': mx.gpu(0), + 'deformable_conv_data': (2, 2, 7, 7), + 'deformable_conv_offset': (2, 18, 7, 7), + 'type_dict': {'deformable_conv_data': np.float64, 'deformable_conv_offset': np.float64}}, + {'ctx': mx.gpu(0), + 'deformable_conv_data': (2, 2, 7, 7), + 'deformable_conv_offset': (2, 18, 7, 7), + 'type_dict': {'deformable_conv_data': np.float32, 'deformable_conv_offset': np.float32}}, + # {'ctx': mx.gpu(0), + # 'deformable_conv_data': (2, 2, 7, 7), + # 'deformable_offset': (2, 18, 7, 7), + # 'type_dict': {'deformable_conv_data': np.float16, 'deformable_offset': np.float16}}, + ] + sym = mx.contrib.sym.DeformableConvolution(num_filter=3, kernel=(3,3), pad=(1,1), name='deformable_conv') + check_consistency(sym, ctx_list) + + # Stride > 1 + # since atomicAdd does not support fp16 (which deformable conv uses in backward), we do not test fp16 here + ctx_list = [{'ctx': mx.gpu(0), + 'deformable_conv_data': (2, 2, 7, 7), + 'deformable_conv_offset': (2, 18, 3, 3), + 'type_dict': {'deformable_conv_data': np.float64, 'deformable_conv_offset': np.float64}}, + {'ctx': mx.gpu(0), + 'deformable_conv_data': (2, 2, 7, 7), + 'deformable_conv_offset': (2, 18, 3, 3), + 'type_dict': {'deformable_conv_data': np.float32, 'deformable_conv_offset': np.float32}}, + # {'ctx': mx.gpu(0), + # 'deformable_conv_data': (2, 2, 7, 7), + # 'deformable_conv_offset': (2, 18, 3, 3), + # 'type_dict': {'deformable_conv_data': np.float16, 'deformable_offset': np.float16}}, + ] + sym = mx.contrib.sym.DeformableConvolution(num_filter=3, kernel=(3,3), stride=(2,2), name='deformable_conv') + check_consistency(sym, ctx_list) + + # Dilate > 1 + # since atomicAdd does not support fp16 (which deformable conv uses in backward), we do not test fp16 here + ctx_list = [{'ctx': mx.gpu(0), + 'deformable_conv_data': (2, 2, 7, 7), + 'deformable_conv_offset': (2, 18, 3, 3), + 'type_dict': {'deformable_conv_data': np.float64, 'deformable_conv_offset': np.float64}}, + {'ctx': mx.gpu(0), + 'deformable_conv_data': (2, 2, 7, 7), + 'deformable_conv_offset': (2, 18, 3, 3), + 'type_dict': {'deformable_conv_data': np.float32, 'deformable_conv_offset': np.float32}}, + # {'ctx': mx.gpu(0), + # 'deformable_conv_data': (2, 2, 7, 7), + # 'deformable_conv_offset': (2, 18, 3, 3), + # 'type_dict': {'deformable_conv_data': np.float16, 'deformable_offset': np.float16}}, + ] + sym = mx.contrib.sym.DeformableConvolution(num_filter=3, kernel=(3,3), dilate=(2,2), name='deformable_conv') + check_consistency(sym, ctx_list) + + # Deformable group > 1 + # since atomicAdd does not support fp16 (which deformable conv uses in backward), we do not test fp16 here + ctx_list = [{'ctx': mx.gpu(0), + 'deformable_conv_data': (2, 2, 7, 7), + 'deformable_conv_offset': (2, 36, 5, 5), + 'type_dict': {'deformable_conv_data': np.float64, 'deformable_conv_offset': np.float64}}, + {'ctx': mx.gpu(0), + 'deformable_conv_data': (2, 2, 7, 7), + 'deformable_conv_offset': (2, 36, 5, 5), + 'type_dict': {'deformable_conv_data': np.float32, 'deformable_conv_offset': np.float32}}, + # {'ctx': mx.gpu(0), + # 'deformable_conv_data': (2, 2, 7, 7), + # 'deformable_conv_offset': (2, 36, 5, 5), + # 'type_dict': {'deformable_conv_data': np.float16, 'deformable_offset': np.float16}}, + ] + sym = mx.contrib.sym.DeformableConvolution(num_filter=4, kernel=(3,3), num_deformable_group=2, + name='deformable_conv') + +def test_residual_fused(): + cell = mx.rnn.ResidualCell( + mx.rnn.FusedRNNCell(50, num_layers=3, mode='lstm', + prefix='rnn_', dropout=0.5)) + + inputs = [mx.sym.Variable('rnn_t%d_data'%i) for i in range(2)] + outputs, _ = cell.unroll(2, inputs, merge_outputs=None) + assert sorted(cell.params._params.keys()) == \ + ['rnn_parameters'] + + args, outs, auxs = outputs.infer_shape(rnn_t0_data=(10, 50), rnn_t1_data=(10, 50)) + assert outs == [(10, 2, 50)] + outputs = outputs.eval(ctx=mx.gpu(0), + rnn_t0_data=mx.nd.ones((10, 50), ctx=mx.gpu(0))+5, + rnn_t1_data=mx.nd.ones((10, 50), ctx=mx.gpu(0))+5, + rnn_parameters=mx.nd.zeros((61200,), ctx=mx.gpu(0))) + expected_outputs = np.ones((10, 2, 50))+5 + assert np.array_equal(outputs[0].asnumpy(), expected_outputs) + +def check_rnn_layer(layer): + layer.collect_params().initialize(ctx=[mx.cpu(0), mx.gpu(0)]) + with mx.gpu(0): + x = mx.nd.ones((10, 16, 30)) + states = layer.begin_state(16) + go, gs = layer(x, states) + + with mx.cpu(0): + x = mx.nd.ones((10, 16, 30)) + states = layer.begin_state(16) + co, cs = layer(x, states) + + assert_almost_equal(go.asnumpy(), co.asnumpy(), rtol=1e-2, atol=1e-8) + for g, c in zip(gs, cs): + assert_almost_equal(g.asnumpy(), c.asnumpy(), rtol=1e-2, atol=1e-8) + + +def test_rnn_layer(): + check_rnn_layer(gluon.rnn.RNN(100, num_layers=3)) + check_rnn_layer(gluon.rnn.RNN(100, activation='tanh', num_layers=3)) + check_rnn_layer(gluon.rnn.LSTM(100, num_layers=3)) + check_rnn_layer(gluon.rnn.GRU(100, num_layers=3)) + + check_rnn_layer(gluon.rnn.LSTM(100, num_layers=3, bidirectional=True)) + + +def test_sequence_reverse(): + check_sequence_reverse(mx.gpu(0)) + + +def test_autograd_save_memory(): + x = mx.nd.zeros((128, 1024, 1024), ctx=mx.gpu(0)) + x.attach_grad() + + with mx.autograd.record(): + for i in range(50): + x = x + 1 + x.wait_to_read() + x.backward() + + if __name__ == '__main__': - test_countsketch() - test_ifft() - test_fft() - test_bidirectional() - test_lstm() - test_lstm_forget_bias() - test_gru() - test_rnn() - test_unfuse() - test_convolution_options() - test_convolution_versions() - test_convolution_with_type() - test_pooling_versions() - test_batchnorm_with_type() - test_batchnorm_versions() - test_deconvolution_with_type() - test_deconvolution_options() - test_upsampling_with_type() - test_concat_with_type() - test_elementwisesum_with_type() - test_reshape_with_type() - test_blockgrad_with_type() - test_swapaxis_with_type() - test_fullyconnected_with_type() - test_activation_with_type() - test_embedding_with_type() - test_svmoutput_with_type() - test_take_with_type() - test_bilinear_sampler_with_type() - test_grid_generator_with_type() + import nose + nose.runmodule() diff --git a/tests/python/gpu/test_rtc.py b/tests/python/gpu/test_rtc.py index d38f038ffd26..756c3d752faa 100644 --- a/tests/python/gpu/test_rtc.py +++ b/tests/python/gpu/test_rtc.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # pylint: skip-file import mxnet as mx import numpy as np @@ -13,4 +30,4 @@ s_rec[threadIdx.x] = x[threadIdx.x]; y[threadIdx.x] = expf(s_rec[threadIdx.x]*5.0);""") rtc.push([x], [y], (1, 1, 1), (10,1,1)) - assert_allclose(y.asnumpy(), np.exp(x.asnumpy()*5.0)) \ No newline at end of file + assert_allclose(y.asnumpy(), np.exp(x.asnumpy()*5.0)) diff --git a/tests/python/predict/mxnet_predict_example.py b/tests/python/predict/mxnet_predict_example.py index 745a1f87b17c..1db3f5c29954 100644 --- a/tests/python/predict/mxnet_predict_example.py +++ b/tests/python/predict/mxnet_predict_example.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import sys, os curr_path = os.path.dirname(os.path.abspath(os.path.expanduser(__file__))) sys.path.append("../../../amalgamation/python/") diff --git a/tests/python/train/common.py b/tests/python/train/common.py index 1622e0294e69..38718fa78175 100644 --- a/tests/python/train/common.py +++ b/tests/python/train/common.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import sys, os curr_path = os.path.dirname(os.path.abspath(os.path.expanduser(__file__))) sys.path.append(os.path.join(curr_path, '../common/')) diff --git a/tests/python/train/test_autograd.py b/tests/python/train/test_autograd.py new file mode 100644 index 000000000000..c9921ecf4f89 --- /dev/null +++ b/tests/python/train/test_autograd.py @@ -0,0 +1,108 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +# pylint: skip-file +from __future__ import print_function + +import mxnet as mx +from mxnet import gluon +from mxnet.gluon import nn +import numpy as np +import logging +from common import get_data +from mxnet import autograd +logging.basicConfig(level=logging.DEBUG) + +# define network + +def get_net(): + net = nn.Sequential() + net.add(nn.Dense(128, activation='relu', prefix='fc1_')) + net.add(nn.Dense(64, activation='relu', prefix='fc2_')) + net.add(nn.Dense(10, prefix='fc3_')) + return net + +get_data.GetMNIST_ubyte() + +batch_size = 100 +train_data = mx.io.MNISTIter( + image="data/train-images-idx3-ubyte", + label="data/train-labels-idx1-ubyte", + data_shape=(784,), + label_name='sm_label', + batch_size=batch_size, shuffle=True, flat=True, silent=False, seed=10) +val_data = mx.io.MNISTIter( + image="data/t10k-images-idx3-ubyte", + label="data/t10k-labels-idx1-ubyte", + data_shape=(784,), + label_name='sm_label', + batch_size=batch_size, shuffle=True, flat=True, silent=False) + +def score(net, ctx_list): + metric = mx.metric.Accuracy() + val_data.reset() + for batch in val_data: + datas = gluon.utils.split_and_load(batch.data[0], ctx_list, batch_axis=0) + labels = gluon.utils.split_and_load(batch.label[0], ctx_list, batch_axis=0) + outputs = [] + for x in datas: + outputs.append(net(x)) + metric.update(labels, outputs) + return metric.get()[1] + +def train(net, epoch, ctx_list): + net.collect_params().initialize(mx.init.Xavier(magnitude=2.24), ctx=ctx_list) + trainer = gluon.Trainer(net.collect_params(), 'sgd', {'learning_rate': 0.5}) + metric = mx.metric.Accuracy() + loss = gluon.loss.SoftmaxCrossEntropyLoss() + + for i in range(epoch): + train_data.reset() + for batch in train_data: + datas = gluon.utils.split_and_load(batch.data[0], ctx_list, batch_axis=0) + labels = gluon.utils.split_and_load(batch.label[0], ctx_list, batch_axis=0) + outputs = [] + with autograd.record(): + for x, y in zip(datas, labels): + z = net(x) + L = loss(z, y) + L.backward() + outputs.append(z) + trainer.step(batch.data[0].shape[0]) + metric.update(labels, outputs) + name, acc = metric.get() + metric.reset() + print('training acc at epoch %d: %s=%f'%(i, name, acc)) + + +def test_autograd(): + net1 = get_net() + train(net1, 5, [mx.cpu(0), mx.cpu(1)]) + acc1 = score(net1, [mx.cpu(0)]) + acc2 = score(net1, [mx.cpu(0), mx.cpu(1)]) + assert acc1 > 0.95 + assert abs(acc1 - acc2) < 0.01 + net1.collect_params().save('mnist.params') + + net2 = get_net() + net2.collect_params().load('mnist.params', ctx=[mx.cpu(0)]) + acc3 = score(net2, [mx.cpu(0)]) + assert abs(acc3 - acc1) < 0.0001 + + +if __name__ == '__main__': + test_autograd() diff --git a/tests/python/train/test_bucketing.py b/tests/python/train/test_bucketing.py new file mode 100644 index 000000000000..1303db09cb54 --- /dev/null +++ b/tests/python/train/test_bucketing.py @@ -0,0 +1,132 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +# pylint: skip-file +import numpy as np +import mxnet as mx +import random +from random import randint + + +def test_bucket_module(): + import logging + head = '%(asctime)-15s %(message)s' + logging.basicConfig(level=logging.DEBUG, format=head) + console = logging.StreamHandler() + console.setLevel(logging.DEBUG) + logging.getLogger('').addHandler(console) + + class DummySentenceIter(mx.rnn.BucketSentenceIter): + """Dummy sentence iterator to output sentences the same as input. + """ + + def __init__(self, sentences, batch_size, buckets=None, invalid_label=-1, + data_name='data', label_name='l2_label', dtype='float32', + layout='NTC'): + super(DummySentenceIter, self).__init__(sentences, batch_size, + buckets=buckets, invalid_label=invalid_label, + data_name=data_name, label_name=label_name, + dtype=dtype, layout=layout) + + def reset(self): + """Resets the iterator to the beginning of the data.""" + self.curr_idx = 0 + random.shuffle(self.idx) + for buck in self.data: + np.random.shuffle(buck) + + self.nddata = [] + self.ndlabel = [] + for buck in self.data: + self.nddata.append(mx.nd.array(buck, dtype=self.dtype)) + self.ndlabel.append(mx.nd.array(buck, dtype=self.dtype)) + + batch_size = 128 + num_epochs = 5 + num_hidden = 25 + num_embed = 25 + num_layers = 2 + len_vocab = 50 + buckets = [10, 20, 30, 40] + + invalid_label = 0 + num_sentence = 1000 + + train_sent = [] + val_sent = [] + + for _ in range(num_sentence): + len_sentence = randint(1, max(buckets) + 10) + train_sentence = [] + val_sentence = [] + for _ in range(len_sentence): + train_sentence.append(randint(1, len_vocab)) + val_sentence.append(randint(1, len_vocab)) + train_sent.append(train_sentence) + val_sent.append(val_sentence) + + data_train = DummySentenceIter(train_sent, batch_size, buckets=buckets, + invalid_label=invalid_label) + data_val = DummySentenceIter(val_sent, batch_size, buckets=buckets, + invalid_label=invalid_label) + + stack = mx.rnn.SequentialRNNCell() + for i in range(num_layers): + stack.add(mx.rnn.LSTMCell(num_hidden=num_hidden, prefix='lstm_l%d_' % i)) + + def sym_gen(seq_len): + data = mx.sym.Variable('data') + label = mx.sym.Variable('l2_label') + embed = mx.sym.Embedding(data=data, input_dim=len_vocab, + output_dim=num_embed, name='embed') + + stack.reset() + outputs, states = stack.unroll(seq_len, inputs=embed, merge_outputs=True) + + pred = mx.sym.Reshape(outputs, shape=(-1, num_hidden)) + pred = mx.sym.FullyConnected(data=pred, num_hidden=1, name='pred') + pred = mx.sym.reshape(pred, shape=(batch_size, -1)) + loss = mx.sym.LinearRegressionOutput(pred, label, name='l2_loss') + + return loss, ('data',), ('l2_label',) + + contexts = mx.cpu(0) + + model = mx.mod.BucketingModule( + sym_gen=sym_gen, + default_bucket_key=data_train.default_bucket_key, + context=contexts) + + logging.info('Begin fit...') + model.fit( + train_data=data_train, + eval_data=data_val, + eval_metric=mx.metric.MSE(), + kvstore='device', + optimizer='sgd', + optimizer_params={'learning_rate': 0.01, + 'momentum': 0, + 'wd': 0.00001}, + initializer=mx.init.Xavier(factor_type="in", magnitude=2.34), + num_epoch=num_epochs, + batch_end_callback=mx.callback.Speedometer(batch_size, 50)) + logging.info('Finished fit...') + assert model.score(data_val, mx.metric.MSE())[0][1] < 350, "High mean square error." + + +if __name__ == "__main__": + test_bucket_module() diff --git a/tests/python/train/test_conv.py b/tests/python/train/test_conv.py index 039790e5612d..46e06848f8ba 100644 --- a/tests/python/train/test_conv.py +++ b/tests/python/train/test_conv.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # pylint: skip-file import sys sys.path.insert(0, '../../python') diff --git a/tests/python/train/test_dtype.py b/tests/python/train/test_dtype.py index 3371f4bcaf4e..b0a524815c6c 100644 --- a/tests/python/train/test_dtype.py +++ b/tests/python/train/test_dtype.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # pylint: skip-file import sys sys.path.insert(0, '../../python') @@ -173,6 +190,6 @@ def test_cifar10(): (train, val) = get_iterator_uint8(kv) run_cifar10(train, val, use_module=False) run_cifar10(train, val, use_module=True) - + if __name__ == "__main__": test_cifar10() diff --git a/tests/python/train/test_mlp.py b/tests/python/train/test_mlp.py index c983b6eeac4f..a0a45b41e19e 100644 --- a/tests/python/train/test_mlp.py +++ b/tests/python/train/test_mlp.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # pylint: skip-file import mxnet as mx import numpy as np diff --git a/tests/python/unittest/common.py b/tests/python/unittest/common.py index 29c489f0bf3c..12ed60d2bc24 100644 --- a/tests/python/unittest/common.py +++ b/tests/python/unittest/common.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import sys, os curr_path = os.path.dirname(os.path.abspath(os.path.expanduser(__file__))) sys.path.append(os.path.join(curr_path, '../common/')) @@ -5,3 +22,13 @@ import models import get_data + + +def assertRaises(expected_exception, func, *args, **kwargs): + try: + func(*args, **kwargs) + except expected_exception as e: + pass + else: + # Did not raise exception + assert False, "%s did not raise %s" % (func.__name__, expected_exception.__name__) diff --git a/tests/python/unittest/legacy_ndarray.v0 b/tests/python/unittest/legacy_ndarray.v0 new file mode 100644 index 000000000000..f4306d837202 Binary files /dev/null and b/tests/python/unittest/legacy_ndarray.v0 differ diff --git a/tests/python/unittest/test_attr.py b/tests/python/unittest/test_attr.py index 4cdecaf9146f..0d7e67dced2d 100644 --- a/tests/python/unittest/test_attr.py +++ b/tests/python/unittest/test_attr.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import os import mxnet as mx from common import models diff --git a/tests/python/unittest/test_autograd.py b/tests/python/unittest/test_autograd.py index c84438d72363..30dd662ff1cc 100644 --- a/tests/python/unittest/test_autograd.py +++ b/tests/python/unittest/test_autograd.py @@ -1,7 +1,96 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +import functools import mxnet.ndarray as nd -from mxnet.contrib.autograd import * +from mxnet.ndarray import zeros_like +from mxnet.autograd import * from mxnet.test_utils import * + +def grad_and_loss(func, argnum=None): + """Return function that computes both gradient of arguments and loss value. + + Parameters + ---------- + func: a python function + The forward (loss) function. + argnum: an int or a list of int + The index of argument to calculate gradient for. + + Returns + ------- + grad_and_loss_func: a python function + A function that would compute both the gradient of arguments and loss value. + """ + @functools.wraps(func) + def wrapped(*args): + """Wrapped function.""" + variables = args + if argnum is not None: + argnum_ = argnum if isinstance(argnum, list) else [argnum] + variables = [args[i] for i in argnum_] + for x in variables: + assert isinstance(x, NDArray), "type of autograd input should NDArray." + grads = [zeros_like(x) for x in variables] + mark_variables(variables, grads) + with record(): + outputs = func(*args) + backward([outputs] if isinstance(outputs, NDArray) else outputs) + return grads, outputs + return wrapped + +def grad(func, argnum=None): + """Return function that computes gradient of arguments. + + Parameters + ---------- + func: a python function + The forward (loss) function. + argnum: an int or a list of int + The index of argument to calculate gradient for. + + Returns + ------- + grad_func: a python function + A function that would compute the gradient of arguments. + + Examples + -------- + >>> # autograd supports dynamic graph which is changed + >>> # every instance + >>> def func(x): + >>> r = random.randint(0, 1) + >>> if r % 2: + >>> return x**2 + >>> else: + >>> return x/3 + >>> # use `grad(func)` to get the gradient function + >>> for x in range(10): + >>> grad_func = grad(func) + >>> inputs = nd.array([[1, 2, 3], [4, 5, 6]]) + >>> grad_vals = grad_func(inputs) + """ + grad_with_loss_func = grad_and_loss(func, argnum) + @functools.wraps(grad_with_loss_func) + def wrapped(*args): + return grad_with_loss_func(*args)[0] + return wrapped + def autograd_assert(*args, **kwargs): func = kwargs["func"] grad_f = kwargs["grad_func"] @@ -76,18 +165,200 @@ def f_with_mode(a, b, mode): def test_training(): x = nd.ones((10, 10)) - with train_section(): + with record(): y = nd.Dropout(x, p=0.5) assert not (y.asnumpy() == x.asnumpy()).all() - with test_section(): + with pause(): y = nd.Dropout(x, p=0.5) assert (y.asnumpy() == x.asnumpy()).all() +def test_out_grads(): + x = nd.ones((3, 5)) + dx = nd.zeros_like(x) + mark_variables([x], [dx]) + da = None + db = nd.array([1,2,3,4,5]) + dc = nd.array([5,4,3,2,1]) + + with record(): + a, b, c = nd.split(x, axis=0, num_outputs=3, squeeze_axis=True) + backward([a, b, c], [da, db, dc]) + + assert (dx.asnumpy() == np.array( + [[1,1,1,1,1], + [1,2,3,4,5], + [5,4,3,2,1]])).all() + + +def test_detach_updated_grad(): + x = nd.ones((2, 2)) + dx = nd.zeros_like(x) + y = nd.ones_like(x) + dy = nd.zeros_like(x) + mark_variables([x, y], [dx, dy]) + assert x._fresh_grad == False + assert y._fresh_grad == False + + with record(): + x2 = x + 2 + y2 = x2 + y + y2.backward() + assert (dx.asnumpy() == 1).all() + assert x._fresh_grad == True + assert y._fresh_grad == True + + dx[:] = 0 + x._fresh_grad = False + y._fresh_grad = False + assert x._fresh_grad == False + assert y._fresh_grad == False + with record(): + x2 = x + 2 + x2 = x2.detach() + y2 = x2 + y + y2.backward() + assert (dx.asnumpy() == 0).all() + assert y._fresh_grad == True + assert x._fresh_grad == False + + +def test_retain_grad(): + x = mx.nd.ones((2, 2)) + dx = mx.nd.zeros((2, 2)) + mark_variables([x], [dx], grad_reqs='add') + with record(): + y = x + 1 + y.backward(retain_graph=False) + assert (dx.asnumpy() == 1).all() + + dx[:] = 0 + with record(): + y = x + 1 + y.backward(retain_graph=True) + y.backward(retain_graph=False) + assert (dx.asnumpy() == 2).all() + + # The following sequence should throw an exception. We discard the expected + # stderr stack trace output for this operation to keep the test logs clean. + with discard_stderr(): + try: + with record(): + y = x + 1 + y.backward() + y.backward() + except Exception: + return + + raise AssertionError( + "differentiating the same graph twice without retain_graph should fail") + + +def test_attach_grad(): + x = mx.nd.zeros((10,)) + assert x.grad is None + x.attach_grad() + with record(): + y = x * 2 + assert y.grad is None + y.backward() + assert (x.grad.asnumpy() == 2).all() + + +def test_is_train(): + x = mx.nd.ones((10, 10)) + x.attach_grad() + with record(train_mode=True): + assert is_recording() + assert is_training() + y = mx.nd.Dropout(x, p=0.5) + assert y.asnumpy().max() == 2 and y.asnumpy().min() == 0 + y.backward() + assert (x.grad.asnumpy() == y.asnumpy()).all() + + with predict_mode(): + assert is_recording() + assert not is_training() + y = mx.nd.Dropout(x, p=0.5) + assert (y.asnumpy() == x.asnumpy()).all() + y.backward(train_mode=False) + assert (x.grad.asnumpy() == x.asnumpy()).all() + + with record(train_mode=False): + assert is_recording() + assert not is_training() + y = mx.nd.Dropout(x, p=0.5) + assert (y.asnumpy() == x.asnumpy()).all() + y.backward(train_mode=False) + assert (x.grad.asnumpy() == x.asnumpy()).all() + + with train_mode(): + assert is_recording() + assert is_training() + y = mx.nd.Dropout(x, p=0.5) + assert y.asnumpy().max() == 2 and y.asnumpy().min() == 0 + y.backward() + assert (x.grad.asnumpy() == y.asnumpy()).all() + + assert not is_recording() + assert not is_training() + y = mx.nd.Dropout(x, p=0.5) + assert (y.asnumpy() == x.asnumpy()).all() + + with train_mode(): + assert not is_recording() + assert is_training() + y = mx.nd.Dropout(x, p=0.5) + assert y.asnumpy().max() == 2 and y.asnumpy().min() == 0 + + +def test_function(): + class func(Function): + def forward(self, x, y): + m = x / y + n = x * y + self.save_for_backward(x, y) + return m, n + + def backward(self, dm, dn): + x, y = self.saved_tensors + dx = dm/y + dn*y + dy = dn*x - dm * x / y / y + return dx, dy + + f = func() + x = mx.nd.random_uniform(shape=(10,)) + x.attach_grad() + y = mx.nd.random_uniform(shape=(10,)) + y.attach_grad() + with record(): + m, n = f(x, y) + backward([m, n]) + + dx1 = x.grad.asnumpy() + dy1 = y.grad.asnumpy() + + with record(): + backward([x/y, x*y]) + + assert_almost_equal(x.grad.asnumpy(), dx1) + assert_almost_equal(y.grad.asnumpy(), dy1) + + +def test_get_symbol(): + x = mx.nd.ones((1,)) + x.attach_grad() + with record(): + y = x*x + 2*x - 1 + assert len(get_symbol(y).list_arguments()) == 1 + + z = mx.nd.ones((1,)) + z.attach_grad() + with record(): + y = x*x + 2*z - 1 + assert len(get_symbol(y).list_arguments()) == 2 + if __name__ == "__main__": - test_training() - test_unary_func() - test_binary_func() - test_operator_with_state() - test_argnum() + import nose + nose.runmodule() diff --git a/tests/python/unittest/test_contrib_autograd.py b/tests/python/unittest/test_contrib_autograd.py new file mode 100644 index 000000000000..a144c3433280 --- /dev/null +++ b/tests/python/unittest/test_contrib_autograd.py @@ -0,0 +1,187 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +import mxnet.ndarray as nd +from mxnet.contrib.autograd import * +from mxnet.test_utils import * + +def autograd_assert(*args, **kwargs): + func = kwargs["func"] + grad_f = kwargs["grad_func"] + argnum = kwargs["argnum"] if 'argnum' in kwargs else None + + grad_func = grad_and_loss(func, argnum) + grad_vals, output = grad_func(*args) + res = func(*args) + assert same(output.asnumpy(), res.asnumpy()) + grad_res = grad_f(*args) + assert len(grad_vals) == len(grad_res) + for a, b in zip(grad_vals, grad_res): + assert same(a.asnumpy(), b.asnumpy()) + +def test_unary_func(): + x = nd.uniform(shape=(4, 5)) + f_exp = lambda x: nd.exp(x) + f_exp_grad = lambda x: [nd.exp(x)] + autograd_assert(x, func=f_exp, grad_func=f_exp_grad) + f_half = lambda x: x/2 + f_half_grad = lambda x: [nd.ones(x.shape) * 0.5] + autograd_assert(x, func=f_half, grad_func=f_half_grad) + f_square = lambda x: x**2 + f_square_grad = lambda x: [2*x] + autograd_assert(x, func=f_square, grad_func=f_square_grad) + +def test_binary_func(): + x = nd.uniform(shape=(4, 5)) + y = nd.uniform(shape=(4, 5)) + f_add = lambda x, y: x+y + f_add_grad = lambda x, y: [nd.ones(x.shape), nd.ones(y.shape)] + autograd_assert(x, y, func=f_add, grad_func=f_add_grad) + f_mul = lambda x, y: x*y + f_mul_grad = lambda x, y: [y, x] + autograd_assert(x, y, func=f_mul, grad_func=f_mul_grad) + f_compose = lambda x, y: x+x*y + f_compose_grad = lambda x, y: [nd.ones(x.shape) + y, x] + autograd_assert(x, y, func=f_compose, grad_func=f_compose_grad) + +def test_operator_with_state(): + def f_fc(a, b, weight, bias): + x = a*b + fc = nd.FullyConnected( + x, weight, bias, num_hidden=32) + return fc + + a = nd.uniform(shape=(64, 50)) + b = nd.uniform(shape=(64, 50)) + weight = nd.uniform(shape=(32, 50)) + bias = nd.uniform(shape=(32, )) + + grad_func = grad_and_loss(f_fc) + grad_vals, outputs = grad_func(a, b, weight, bias) + # (TODO) assert + +def test_argnum(): + def f_with_mode(a, b, mode): + if mode: + return a+b + else: + return a*b + + a = nd.uniform(shape=(3, 2)) + b = nd.uniform(shape=(3, 2)) + f_add_grad = lambda x, y, mode: [nd.ones(x.shape), nd.ones(y.shape)] + f_mul_grad = lambda x, y, mode: [y, x] + autograd_assert(a, b, True, + argnum=[0, 1], func=f_with_mode, grad_func=f_add_grad) + autograd_assert(a, b, False, + argnum=[0, 1], func=f_with_mode, grad_func=f_mul_grad) + + +def test_training(): + x = nd.ones((10, 10)) + with train_section(): + y = nd.Dropout(x, p=0.5) + assert not (y.asnumpy() == x.asnumpy()).all() + with test_section(): + y = nd.Dropout(x, p=0.5) + assert (y.asnumpy() == x.asnumpy()).all() + + +def test_out_grads(): + x = nd.ones((3, 5)) + dx = nd.zeros_like(x) + mark_variables([x], [dx]) + da = None + db = nd.array([1,2,3,4,5]) + dc = nd.array([5,4,3,2,1]) + + with train_section(): + a, b, c = nd.split(x, axis=0, num_outputs=3, squeeze_axis=True) + backward([a, b, c], [da, db, dc]) + + assert (dx.asnumpy() == np.array( + [[1,1,1,1,1], + [1,2,3,4,5], + [5,4,3,2,1]])).all() + + +def test_detach_updated_grad(): + x = nd.ones((2, 2)) + dx = nd.zeros_like(x) + y = nd.ones_like(x) + dy = nd.zeros_like(x) + mark_variables([x, y], [dx, dy]) + assert x._fresh_grad == False + assert y._fresh_grad == False + + with train_section(): + x2 = x + 2 + y2 = x2 + y + y2.backward() + assert (dx.asnumpy() == 1).all() + assert x._fresh_grad == True + assert y._fresh_grad == True + + dx[:] = 0 + x._fresh_grad = False + y._fresh_grad = False + assert x._fresh_grad == False + assert y._fresh_grad == False + with train_section(): + x2 = x + 2 + x2 = x2.detach() + y2 = x2 + y + y2.backward() + assert (dx.asnumpy() == 0).all() + assert y._fresh_grad == True + assert x._fresh_grad == False + + +def test_retain_grad(): + x = mx.nd.ones((2, 2)) + dx = mx.nd.zeros((2, 2)) + mark_variables([x], [dx], grad_reqs='add') + with train_section(): + y = x + 1 + y.backward(retain_graph=False) + assert (dx.asnumpy() == 1).all() + + dx[:] = 0 + with train_section(): + y = x + 1 + y.backward(retain_graph=True) + y.backward(retain_graph=False) + assert (dx.asnumpy() == 2).all() + + # The following sequence should throw an exception. We discard the expected + # stderr stack trace output for this operation to keep the test logs clean. + with discard_stderr(): + try: + with train_section(): + y = x + 1 + y.backward() + y.backward() + except Exception: + return + + raise AssertionError( + "differentiating the same graph twice without retain_graph should fail") + + +if __name__ == "__main__": + import nose + nose.runmodule() diff --git a/tests/python/unittest/test_executor.py b/tests/python/unittest/test_executor.py index b190b2898843..e3d977df65de 100644 --- a/tests/python/unittest/test_executor.py +++ b/tests/python/unittest/test_executor.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import numpy as np import mxnet as mx @@ -121,7 +138,7 @@ def test_reshape(): x = mx.sym.Variable('x') y = mx.sym.FullyConnected(x, num_hidden=4) - exe = y.simple_bind(mx.cpu(), x=(5,4), grad_req=[]) + exe = y.simple_bind(mx.cpu(), x=(5,4), grad_req='null') exe.arg_arrays[0][:] = 1 exe.arg_arrays[1][:] = mx.nd.ones((4,4)) exe.arg_arrays[2][:] = 0 diff --git a/tests/python/unittest/test_gluon.py b/tests/python/unittest/test_gluon.py new file mode 100644 index 000000000000..cafa08bc04ca --- /dev/null +++ b/tests/python/unittest/test_gluon.py @@ -0,0 +1,356 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +import mxnet as mx +from mxnet import gluon +from mxnet.gluon import nn +import numpy as np + + +def test_parameter(): + p = gluon.Parameter('weight', shape=(10, 10)) + p.initialize(init='xavier', ctx=[mx.cpu(0), mx.cpu(1)]) + assert len(p.list_data()) == 2 + assert len(p.list_grad()) == 2 + assert p.data(mx.cpu(1)).context == mx.cpu(1) + assert p.data(mx.cpu(0)).shape == (10, 10) + assert p.var().name == 'weight' + + p.reset_ctx(ctx=[mx.cpu(1), mx.cpu(2)]) + assert p.list_ctx() == [mx.cpu(1), mx.cpu(2)] + + +def test_paramdict(): + params = gluon.ParameterDict('net_') + params.get('weight', shape=(10, 10)) + assert list(params.keys()) == ['net_weight'] + params.initialize(ctx=mx.cpu()) + params.save('test.params') + params.load('test.params', mx.cpu()) + + +def test_parameter_sharing(): + class Net(gluon.Block): + def __init__(self, **kwargs): + super(Net, self).__init__(**kwargs) + with self.name_scope(): + self.dense0 = nn.Dense(5, in_units=5) + self.dense1 = nn.Dense(5, in_units=5) + + def forward(self, x): + return self.dense1(self.dense0(x)) + + net1 = Net(prefix='net1_') + net2 = Net(prefix='net2_', params=net1.collect_params()) + net1.collect_params().initialize() + net2(mx.nd.zeros((3, 5))) + + net1.save_params('net1.params') + + net3 = Net(prefix='net3_') + net3.load_params('net1.params', mx.cpu()) + + +def test_basic(): + model = nn.Sequential() + model.add(nn.Dense(128, activation='tanh', in_units=10)) + model.add(nn.Dropout(0.5)) + model.add(nn.Dense(64, activation='tanh', in_units=128)) + model.add(nn.Dense(32, in_units=64)) + model.add(nn.Activation('relu')) + + # symbol + x = mx.sym.var('data') + y = model(x) + assert len(y.list_arguments()) == 7 + + # ndarray + model.collect_params().initialize(mx.init.Xavier(magnitude=2.24)) + x = model(mx.nd.zeros((32, 10))) + assert x.shape == (32, 32) + x.wait_to_read() + + model.collect_params().setattr('grad_req', 'null') + assert list(model.collect_params().values())[0]._grad is None + model.collect_params().setattr('grad_req', 'write') + assert list(model.collect_params().values())[0]._grad is not None + + +def test_symbol_block(): + model = nn.HybridSequential() + model.add(nn.Dense(128, activation='tanh')) + model.add(nn.Dropout(0.5)) + model.add(nn.Dense(64, activation='tanh')) + model.add(nn.Dense(32, in_units=64)) + model.add(nn.Activation('relu')) + + model.initialize() + + inputs = mx.sym.var('data') + outputs = model(inputs).get_internals() + + smodel = gluon.SymbolBlock(outputs, inputs, params=model.collect_params()) + + assert len(smodel(mx.nd.zeros((16, 10)))) == 14 + + out = smodel(mx.sym.var('in')) + assert len(out.get_internals().list_outputs()) == len(outputs.list_outputs()) + + +def check_layer_forward(layer, dshape): + layer.collect_params().initialize() + x = mx.nd.ones(shape=dshape) + x.attach_grad() + with mx.autograd.record(): + out = layer(x) + out.backward() + + layer.hybridize() + + x = mx.nd.ones(shape=dshape) + x.attach_grad() + with mx.autograd.record(): + out = layer(x) + out.backward() + +def test_conv(): + layers1d = [ + nn.Conv1D(16, 3, in_channels=4), + nn.Conv1D(16, 3, groups=2, in_channels=4), + nn.Conv1D(16, 3, strides=3, groups=2, in_channels=4), + ] + for layer in layers1d: + check_layer_forward(layer, (1, 4, 10)) + + + layers2d = [ + nn.Conv2D(16, (3, 4), in_channels=4), + nn.Conv2D(16, (5, 4), in_channels=4), + nn.Conv2D(16, (3, 4), groups=2, in_channels=4), + nn.Conv2D(16, (3, 4), strides=4, in_channels=4), + nn.Conv2D(16, (3, 4), dilation=4, in_channels=4), + nn.Conv2D(16, (3, 4), padding=4, in_channels=4), + ] + for layer in layers2d: + check_layer_forward(layer, (1, 4, 20, 20)) + + + layers3d = [ + nn.Conv3D(16, (1, 8, 4), in_channels=4, activation='relu'), + nn.Conv3D(16, (5, 4, 3), in_channels=4), + nn.Conv3D(16, (3, 3, 3), groups=2, in_channels=4), + nn.Conv3D(16, 4, strides=4, in_channels=4), + nn.Conv3D(16, (3, 3, 3), padding=4, in_channels=4), + ] + for layer in layers3d: + check_layer_forward(layer, (1, 4, 10, 10, 10)) + + + layer = nn.Conv2D(16, (3, 3), layout='NHWC', in_channels=4) + # check_layer_forward(layer, (1, 10, 10, 4)) + + layer = nn.Conv3D(16, (3, 3, 3), layout='NDHWC', in_channels=4) + # check_layer_forward(layer, (1, 10, 10, 10, 4)) + + +def test_deconv(): + # layers1d = [ + # nn.Conv1DTranspose(16, 3, in_channels=4), + # nn.Conv1DTranspose(16, 3, groups=2, in_channels=4), + # nn.Conv1DTranspose(16, 3, strides=3, groups=2, in_channels=4), + # ] + # for layer in layers1d: + # check_layer_forward(layer, (1, 4, 10)) + + + layers2d = [ + nn.Conv2DTranspose(16, (3, 4), in_channels=4), + nn.Conv2DTranspose(16, (5, 4), in_channels=4), + nn.Conv2DTranspose(16, (3, 4), groups=2, in_channels=4), + nn.Conv2DTranspose(16, (3, 4), strides=4, in_channels=4), + nn.Conv2DTranspose(16, (3, 4), dilation=4, in_channels=4), + nn.Conv2DTranspose(16, (3, 4), padding=4, in_channels=4), + nn.Conv2DTranspose(16, (3, 4), strides=4, output_padding=3, in_channels=4), + ] + for layer in layers2d: + check_layer_forward(layer, (1, 4, 20, 20)) + + + # layers3d = [ + # nn.Conv3DTranspose(16, (1, 8, 4), in_channels=4), + # nn.Conv3DTranspose(16, (5, 4, 3), in_channels=4), + # nn.Conv3DTranspose(16, (3, 3, 3), groups=2, in_channels=4), + # nn.Conv3DTranspose(16, 4, strides=4, in_channels=4), + # nn.Conv3DTranspose(16, (3, 3, 3), padding=4, in_channels=4), + # ] + # for layer in layers3d: + # check_layer_forward(layer, (1, 4, 10, 10, 10)) + # + # + # layer = nn.Conv2DTranspose(16, (3, 3), layout='NHWC', in_channels=4) + # # check_layer_forward(layer, (1, 10, 10, 4)) + # + # layer = nn.Conv3DTranspose(16, (3, 3, 3), layout='NDHWC', in_channels=4) + # # check_layer_forward(layer, (1, 10, 10, 10, 4)) + + + +def test_pool(): + layers1d = [ + nn.MaxPool1D(), + nn.MaxPool1D(3), + nn.MaxPool1D(3, 2), + nn.AvgPool1D(), + nn.GlobalAvgPool1D(), + ] + for layer in layers1d: + check_layer_forward(layer, (1, 2, 10)) + + + layers2d = [ + nn.MaxPool2D(), + nn.MaxPool2D((3, 3)), + nn.MaxPool2D(3, 2), + nn.AvgPool2D(), + nn.GlobalAvgPool2D(), + ] + for layer in layers2d: + check_layer_forward(layer, (1, 2, 10, 10)) + + layers3d = [ + nn.MaxPool3D(), + nn.MaxPool3D((3, 3, 3)), + nn.MaxPool3D(3, 2), + nn.AvgPool3D(), + nn.GlobalAvgPool3D(), + ] + for layer in layers3d: + check_layer_forward(layer, (1, 2, 10, 10, 10)) + + # test ceil_mode + x = mx.nd.zeros((2, 2, 10, 10)) + + layer = nn.MaxPool2D(3, ceil_mode=False) + layer.collect_params().initialize() + assert (layer(x).shape==(2, 2, 3, 3)) + + layer = nn.MaxPool2D(3, ceil_mode=True) + layer.collect_params().initialize() + assert (layer(x).shape==(2, 2, 4, 4)) + +def test_batchnorm(): + layer = nn.BatchNorm(in_channels=10) + check_layer_forward(layer, (2, 10, 10, 10)) + + +def test_reshape(): + x = mx.nd.ones((2, 4, 10, 10)) + layer = nn.Conv2D(10, 2, in_channels=4) + layer.collect_params().initialize() + with mx.autograd.record(): + x = layer(x) + x = x.reshape((-1,)) + x = x + 10 + x.backward() + + +def test_slice(): + x = mx.nd.ones((5, 4, 10, 10)) + layer = nn.Conv2D(10, 2, in_channels=4) + layer.collect_params().initialize() + with mx.autograd.record(): + x = layer(x) + x = x[1:3] + x = x + 10 + x.backward() + + +def test_at(): + x = mx.nd.ones((5, 4, 10, 10)) + layer = nn.Conv2D(10, 2, in_channels=4) + layer.collect_params().initialize() + with mx.autograd.record(): + x = layer(x) + x = x[1] + x = x + 10 + x.backward() + + +def test_deferred_init(): + x = mx.nd.ones((5, 4, 10, 10)) + layer = nn.Conv2D(10, 2) + layer.collect_params().initialize() + layer(x) + + +def check_split_data(x, num_slice, batch_axis, **kwargs): + res = gluon.utils.split_data(x, num_slice, batch_axis, **kwargs) + assert len(res) == num_slice + mx.test_utils.assert_almost_equal(mx.nd.concat(*res, dim=batch_axis).asnumpy(), + x.asnumpy()) + + +def test_split_data(): + x = mx.nd.random_uniform(shape=(128, 33, 64)) + + check_split_data(x, 8, 0) + check_split_data(x, 3, 1) + check_split_data(x, 4, 1, even_split=False) + check_split_data(x, 15, 1, even_split=False) + try: + check_split_data(x, 4, 1) + except ValueError: + return + assert False, "Should have failed" + + +def test_flatten(): + flatten = nn.Flatten() + x = mx.nd.zeros((3,4,5,6)) + assert flatten(x).shape == (3, 4*5*6) + x = mx.nd.zeros((3,6)) + assert flatten(x).shape == (3, 6) + x = mx.nd.zeros((3,)) + assert flatten(x).shape == (3, 1) + + +def test_trainer(): + x = gluon.Parameter('x', shape=(10,)) + x.initialize(ctx=[mx.cpu(0), mx.cpu(1)], init='zeros') + trainer = gluon.Trainer([x], 'sgd', {'learning_rate': 1.0}) + with mx.autograd.record(): + for w in x.list_data(): + y = w + 1 + y.backward() + trainer.step(1) + + assert (x.data(mx.cpu(1)).asnumpy() == -2).all() + + x.lr_mult = 0.5 + + with mx.autograd.record(): + for w in x.list_data(): + y = w + 1 + y.backward() + trainer.step(1) + + assert (x.data(mx.cpu(1)).asnumpy() == -3).all() + + +if __name__ == '__main__': + import nose + nose.runmodule() diff --git a/tests/python/unittest/test_gluon_data.py b/tests/python/unittest/test_gluon_data.py new file mode 100644 index 000000000000..32298fcd57d5 --- /dev/null +++ b/tests/python/unittest/test_gluon_data.py @@ -0,0 +1,85 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +import os +import tarfile +import mxnet as mx +import numpy as np +from mxnet import gluon + +def test_array_dataset(): + X = np.random.uniform(size=(10, 20)) + Y = np.random.uniform(size=(10,)) + dataset = gluon.data.ArrayDataset(X, Y) + loader = gluon.data.DataLoader(dataset, 2) + + for i, (x, y) in enumerate(loader): + assert mx.test_utils.almost_equal(x.asnumpy(), X[i*2:(i+1)*2]) + assert mx.test_utils.almost_equal(y.asnumpy(), Y[i*2:(i+1)*2]) + + +def prepare_record(): + if not os.path.isdir("data/test_images"): + os.makedirs('data/test_images') + if not os.path.isdir("data/test_images/test_images"): + gluon.utils.download("http://data.mxnet.io/data/test_images.tar.gz", "data/test_images.tar.gz") + tarfile.open('data/test_images.tar.gz').extractall('data/test_images/') + if not os.path.exists('data/test.rec'): + imgs = os.listdir('data/test_images/test_images') + record = mx.recordio.MXIndexedRecordIO('data/test.idx', 'data/test.rec', 'w') + for i, img in enumerate(imgs): + str_img = open('data/test_images/test_images/'+img, 'rb').read() + s = mx.recordio.pack((0, i, i, 0), str_img) + record.write_idx(i, s) + return 'data/test.rec' + + +def test_recordimage_dataset(): + recfile = prepare_record() + dataset = gluon.data.vision.ImageRecordDataset(recfile) + loader = gluon.data.DataLoader(dataset, 1) + + for i, (x, y) in enumerate(loader): + assert x.shape[0] == 1 and x.shape[3] == 3 + assert y.asscalar() == i + +def test_sampler(): + seq_sampler = gluon.data.SequentialSampler(10) + assert list(seq_sampler) == list(range(10)) + rand_sampler = gluon.data.RandomSampler(10) + assert sorted(list(rand_sampler)) == list(range(10)) + seq_batch_keep = gluon.data.BatchSampler(seq_sampler, 3, 'keep') + assert sum(list(seq_batch_keep), []) == list(range(10)) + seq_batch_discard = gluon.data.BatchSampler(seq_sampler, 3, 'discard') + assert sum(list(seq_batch_discard), []) == list(range(9)) + rand_batch_keep = gluon.data.BatchSampler(rand_sampler, 3, 'keep') + assert sorted(sum(list(rand_batch_keep), [])) == list(range(10)) + +def test_datasets(): + assert len(gluon.data.vision.MNIST(root='data')) == 60000 + assert len(gluon.data.vision.CIFAR10(root='data', train=False)) == 10000 + +def test_image_folder_dataset(): + prepare_record() + dataset = gluon.data.vision.ImageFolderDataset('data/test_images') + assert dataset.synsets == ['test_images'] + assert len(dataset.items) == 16 + + +if __name__ == '__main__': + import nose + nose.runmodule() diff --git a/tests/python/unittest/test_gluon_model_zoo.py b/tests/python/unittest/test_gluon_model_zoo.py new file mode 100644 index 000000000000..6fbcf8b3dac8 --- /dev/null +++ b/tests/python/unittest/test_gluon_model_zoo.py @@ -0,0 +1,76 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +from __future__ import print_function +import mxnet as mx +from mxnet.gluon import nn +from mxnet.gluon.model_zoo.custom_layers import HybridConcurrent, Identity +from mxnet.gluon.model_zoo.vision import get_model +import sys + +def eprint(*args, **kwargs): + print(*args, file=sys.stderr, **kwargs) + +def test_concurrent(): + model = HybridConcurrent(concat_dim=1) + model.add(nn.Dense(128, activation='tanh', in_units=10)) + model.add(nn.Dense(64, activation='tanh', in_units=10)) + model.add(nn.Dense(32, in_units=10)) + + # symbol + x = mx.sym.var('data') + y = model(x) + assert len(y.list_arguments()) == 7 + + # ndarray + model.collect_params().initialize(mx.init.Xavier(magnitude=2.24)) + x = model(mx.nd.zeros((32, 10))) + assert x.shape == (32, 224) + x.wait_to_read() + + +def test_identity(): + model = Identity() + x = mx.nd.random_uniform(shape=(128, 33, 64)) + mx.test_utils.assert_almost_equal(model(x).asnumpy(), + x.asnumpy()) + + +def test_models(): + all_models = ['resnet18_v1', 'resnet34_v1', 'resnet50_v1', 'resnet101_v1', 'resnet152_v1', + 'resnet18_v2', 'resnet34_v2', 'resnet50_v2', 'resnet101_v2', 'resnet152_v2', + 'vgg11', 'vgg13', 'vgg16', 'vgg19', + 'vgg11_bn', 'vgg13_bn', 'vgg16_bn', 'vgg19_bn', + 'alexnet', 'inceptionv3', + 'densenet121', 'densenet161', 'densenet169', 'densenet201', + 'squeezenet1.0', 'squeezenet1.1'] + pretrained_to_test = set(['squeezenet1.1']) + + for model_name in all_models: + test_pretrain = model_name in pretrained_to_test + model = get_model(model_name, pretrained=test_pretrain) + data_shape = (2, 3, 224, 224) if 'inception' not in model_name else (2, 3, 299, 299) + eprint('testing forward for %s'%model_name) + print(model) + if not test_pretrain: + model.collect_params().initialize() + model(mx.nd.random_uniform(shape=data_shape)).wait_to_read() + + +if __name__ == '__main__': + import nose + nose.runmodule() diff --git a/tests/python/unittest/test_gluon_rnn.py b/tests/python/unittest/test_gluon_rnn.py new file mode 100644 index 000000000000..5dcbdfa65d35 --- /dev/null +++ b/tests/python/unittest/test_gluon_rnn.py @@ -0,0 +1,245 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +import mxnet as mx +from mxnet import gluon +import numpy as np +from numpy.testing import assert_allclose + + +def test_rnn(): + cell = gluon.rnn.RNNCell(100, prefix='rnn_') + inputs = [mx.sym.Variable('rnn_t%d_data'%i) for i in range(3)] + outputs, _ = cell.unroll(3, inputs) + outputs = mx.sym.Group(outputs) + assert sorted(cell.collect_params().keys()) == ['rnn_h2h_bias', 'rnn_h2h_weight', 'rnn_i2h_bias', 'rnn_i2h_weight'] + assert outputs.list_outputs() == ['rnn_t0_out_output', 'rnn_t1_out_output', 'rnn_t2_out_output'] + + args, outs, auxs = outputs.infer_shape(rnn_t0_data=(10,50), rnn_t1_data=(10,50), rnn_t2_data=(10,50)) + assert outs == [(10, 100), (10, 100), (10, 100)] + + +def test_lstm(): + cell = gluon.rnn.LSTMCell(100, prefix='rnn_') + inputs = [mx.sym.Variable('rnn_t%d_data'%i) for i in range(3)] + outputs, _ = cell.unroll(3, inputs) + outputs = mx.sym.Group(outputs) + assert sorted(cell.collect_params().keys()) == ['rnn_h2h_bias', 'rnn_h2h_weight', 'rnn_i2h_bias', 'rnn_i2h_weight'] + assert outputs.list_outputs() == ['rnn_t0_out_output', 'rnn_t1_out_output', 'rnn_t2_out_output'] + + args, outs, auxs = outputs.infer_shape(rnn_t0_data=(10,50), rnn_t1_data=(10,50), rnn_t2_data=(10,50)) + assert outs == [(10, 100), (10, 100), (10, 100)] + + +def test_lstm_forget_bias(): + forget_bias = 2.0 + stack = gluon.rnn.SequentialRNNCell() + stack.add(gluon.rnn.LSTMCell(100, i2h_bias_initializer=mx.init.LSTMBias(forget_bias), prefix='l0_')) + stack.add(gluon.rnn.LSTMCell(100, i2h_bias_initializer=mx.init.LSTMBias(forget_bias), prefix='l1_')) + + dshape = (32, 1, 200) + data = mx.sym.Variable('data') + + sym, _ = stack.unroll(1, data, merge_outputs=True) + mod = mx.mod.Module(sym, label_names=None, context=mx.cpu(0)) + mod.bind(data_shapes=[('data', dshape)], label_shapes=None) + + mod.init_params() + + bias_argument = next(x for x in sym.list_arguments() if x.endswith('i2h_bias')) + expected_bias = np.hstack([np.zeros((100,)), + forget_bias * np.ones(100, ), np.zeros((2 * 100,))]) + assert_allclose(mod.get_params()[0][bias_argument].asnumpy(), expected_bias) + + +def test_gru(): + cell = gluon.rnn.GRUCell(100, prefix='rnn_') + inputs = [mx.sym.Variable('rnn_t%d_data'%i) for i in range(3)] + outputs, _ = cell.unroll(3, inputs) + outputs = mx.sym.Group(outputs) + assert sorted(cell.collect_params().keys()) == ['rnn_h2h_bias', 'rnn_h2h_weight', 'rnn_i2h_bias', 'rnn_i2h_weight'] + assert outputs.list_outputs() == ['rnn_t0_out_output', 'rnn_t1_out_output', 'rnn_t2_out_output'] + + args, outs, auxs = outputs.infer_shape(rnn_t0_data=(10,50), rnn_t1_data=(10,50), rnn_t2_data=(10,50)) + assert outs == [(10, 100), (10, 100), (10, 100)] + + +def test_residual(): + cell = gluon.rnn.ResidualCell(gluon.rnn.GRUCell(50, prefix='rnn_')) + inputs = [mx.sym.Variable('rnn_t%d_data'%i) for i in range(2)] + outputs, _ = cell.unroll(2, inputs) + outputs = mx.sym.Group(outputs) + assert sorted(cell.collect_params().keys()) == \ + ['rnn_h2h_bias', 'rnn_h2h_weight', 'rnn_i2h_bias', 'rnn_i2h_weight'] + # assert outputs.list_outputs() == \ + # ['rnn_t0_out_plus_residual_output', 'rnn_t1_out_plus_residual_output'] + + args, outs, auxs = outputs.infer_shape(rnn_t0_data=(10, 50), rnn_t1_data=(10, 50)) + assert outs == [(10, 50), (10, 50)] + outputs = outputs.eval(rnn_t0_data=mx.nd.ones((10, 50)), + rnn_t1_data=mx.nd.ones((10, 50)), + rnn_i2h_weight=mx.nd.zeros((150, 50)), + rnn_i2h_bias=mx.nd.zeros((150,)), + rnn_h2h_weight=mx.nd.zeros((150, 50)), + rnn_h2h_bias=mx.nd.zeros((150,))) + expected_outputs = np.ones((10, 50)) + assert np.array_equal(outputs[0].asnumpy(), expected_outputs) + assert np.array_equal(outputs[1].asnumpy(), expected_outputs) + + +def test_residual_bidirectional(): + cell = gluon.rnn.ResidualCell( + gluon.rnn.BidirectionalCell( + gluon.rnn.GRUCell(25, prefix='rnn_l_'), + gluon.rnn.GRUCell(25, prefix='rnn_r_'))) + + inputs = [mx.sym.Variable('rnn_t%d_data'%i) for i in range(2)] + outputs, _ = cell.unroll(2, inputs, merge_outputs=False) + outputs = mx.sym.Group(outputs) + assert sorted(cell.collect_params().keys()) == \ + ['rnn_l_h2h_bias', 'rnn_l_h2h_weight', 'rnn_l_i2h_bias', 'rnn_l_i2h_weight', + 'rnn_r_h2h_bias', 'rnn_r_h2h_weight', 'rnn_r_i2h_bias', 'rnn_r_i2h_weight'] + # assert outputs.list_outputs() == \ + # ['bi_t0_plus_residual_output', 'bi_t1_plus_residual_output'] + + args, outs, auxs = outputs.infer_shape(rnn_t0_data=(10, 50), rnn_t1_data=(10, 50)) + assert outs == [(10, 50), (10, 50)] + outputs = outputs.eval(rnn_t0_data=mx.nd.ones((10, 50))+5, + rnn_t1_data=mx.nd.ones((10, 50))+5, + rnn_l_i2h_weight=mx.nd.zeros((75, 50)), + rnn_l_i2h_bias=mx.nd.zeros((75,)), + rnn_l_h2h_weight=mx.nd.zeros((75, 25)), + rnn_l_h2h_bias=mx.nd.zeros((75,)), + rnn_r_i2h_weight=mx.nd.zeros((75, 50)), + rnn_r_i2h_bias=mx.nd.zeros((75,)), + rnn_r_h2h_weight=mx.nd.zeros((75, 25)), + rnn_r_h2h_bias=mx.nd.zeros((75,))) + expected_outputs = np.ones((10, 50))+5 + assert np.array_equal(outputs[0].asnumpy(), expected_outputs) + assert np.array_equal(outputs[1].asnumpy(), expected_outputs) + + +def test_stack(): + cell = gluon.rnn.SequentialRNNCell() + for i in range(5): + if i == 1: + cell.add(gluon.rnn.ResidualCell(gluon.rnn.LSTMCell(100, prefix='rnn_stack%d_' % i))) + else: + cell.add(gluon.rnn.LSTMCell(100, prefix='rnn_stack%d_'%i)) + inputs = [mx.sym.Variable('rnn_t%d_data'%i) for i in range(3)] + outputs, _ = cell.unroll(3, inputs) + outputs = mx.sym.Group(outputs) + keys = sorted(cell.collect_params().keys()) + for i in range(5): + assert 'rnn_stack%d_h2h_weight'%i in keys + assert 'rnn_stack%d_h2h_bias'%i in keys + assert 'rnn_stack%d_i2h_weight'%i in keys + assert 'rnn_stack%d_i2h_bias'%i in keys + assert outputs.list_outputs() == ['rnn_stack4_t0_out_output', 'rnn_stack4_t1_out_output', 'rnn_stack4_t2_out_output'] + + args, outs, auxs = outputs.infer_shape(rnn_t0_data=(10,50), rnn_t1_data=(10,50), rnn_t2_data=(10,50)) + assert outs == [(10, 100), (10, 100), (10, 100)] + + +def test_bidirectional(): + cell = gluon.rnn.BidirectionalCell( + gluon.rnn.LSTMCell(100, prefix='rnn_l0_'), + gluon.rnn.LSTMCell(100, prefix='rnn_r0_'), + output_prefix='rnn_bi_') + inputs = [mx.sym.Variable('rnn_t%d_data'%i) for i in range(3)] + outputs, _ = cell.unroll(3, inputs) + outputs = mx.sym.Group(outputs) + assert outputs.list_outputs() == ['rnn_bi_t0_output', 'rnn_bi_t1_output', 'rnn_bi_t2_output'] + + args, outs, auxs = outputs.infer_shape(rnn_t0_data=(10,50), rnn_t1_data=(10,50), rnn_t2_data=(10,50)) + assert outs == [(10, 200), (10, 200), (10, 200)] + + +def test_zoneout(): + cell = gluon.rnn.ZoneoutCell(gluon.rnn.RNNCell(100, prefix='rnn_'), zoneout_outputs=0.5, + zoneout_states=0.5) + inputs = [mx.sym.Variable('rnn_t%d_data'%i) for i in range(3)] + outputs, _ = cell.unroll(3, inputs) + outputs = mx.sym.Group(outputs) + + args, outs, auxs = outputs.infer_shape(rnn_t0_data=(10,50), rnn_t1_data=(10,50), rnn_t2_data=(10,50)) + assert outs == [(10, 100), (10, 100), (10, 100)] + + +def check_rnn_forward(layer, inputs): + inputs.attach_grad() + layer.collect_params().initialize() + with mx.autograd.record(): + layer.unroll(3, inputs, merge_outputs=True)[0].backward() + mx.autograd.backward(layer.unroll(3, inputs, merge_outputs=False)[0]) + mx.nd.waitall() + + +def test_rnn_cells(): + check_rnn_forward(gluon.rnn.LSTMCell(100, input_size=200), mx.nd.ones((8, 3, 200))) + check_rnn_forward(gluon.rnn.RNNCell(100, input_size=200), mx.nd.ones((8, 3, 200))) + check_rnn_forward(gluon.rnn.GRUCell(100, input_size=200), mx.nd.ones((8, 3, 200))) + + bilayer = gluon.rnn.BidirectionalCell(gluon.rnn.LSTMCell(100, input_size=200), + gluon.rnn.LSTMCell(100, input_size=200)) + check_rnn_forward(bilayer, mx.nd.ones((8, 3, 200))) + + check_rnn_forward(gluon.rnn.DropoutCell(0.5), mx.nd.ones((8, 3, 200))) + + check_rnn_forward(gluon.rnn.ZoneoutCell(gluon.rnn.LSTMCell(100, input_size=200), + 0.5, 0.2), + mx.nd.ones((8, 3, 200))) + + net = gluon.rnn.SequentialRNNCell() + net.add(gluon.rnn.LSTMCell(100, input_size=200)) + net.add(gluon.rnn.RNNCell(100, input_size=100)) + net.add(gluon.rnn.GRUCell(100, input_size=100)) + check_rnn_forward(net, mx.nd.ones((8, 3, 200))) + +def check_rnn_layer_forward(layer, inputs, states=None): + layer.collect_params().initialize() + with mx.autograd.record(): + out = layer(inputs, states) + if states is not None: + assert isinstance(out, tuple) and len(out) == 2 + out = out[0] + else: + assert isinstance(out, mx.nd.NDArray) + out.backward() + mx.nd.waitall() + +def test_rnn_layers(): + check_rnn_layer_forward(gluon.rnn.RNN(10, 2), mx.nd.ones((8, 3, 20))) + check_rnn_layer_forward(gluon.rnn.RNN(10, 2), mx.nd.ones((8, 3, 20)), mx.nd.ones((2, 3, 10))) + check_rnn_layer_forward(gluon.rnn.LSTM(10, 2), mx.nd.ones((8, 3, 20))) + check_rnn_layer_forward(gluon.rnn.LSTM(10, 2), mx.nd.ones((8, 3, 20)), [mx.nd.ones((2, 3, 10)), mx.nd.ones((2, 3, 10))]) + check_rnn_layer_forward(gluon.rnn.GRU(10, 2), mx.nd.ones((8, 3, 20))) + check_rnn_layer_forward(gluon.rnn.GRU(10, 2), mx.nd.ones((8, 3, 20)), mx.nd.ones((2, 3, 10))) + + net = gluon.nn.Sequential() + net.add(gluon.rnn.LSTM(10, 2, bidirectional=True)) + net.add(gluon.nn.BatchNorm(axis=2)) + net.add(gluon.nn.Flatten()) + net.add(gluon.nn.Dense(3, activation='relu')) + net.collect_params().initialize() + with mx.autograd.record(): + net(mx.nd.ones((2, 3, 10))).backward() + + +if __name__ == '__main__': + import nose + nose.runmodule() diff --git a/tests/python/unittest/test_image.py b/tests/python/unittest/test_image.py new file mode 100644 index 000000000000..04b878dc80b0 --- /dev/null +++ b/tests/python/unittest/test_image.py @@ -0,0 +1,184 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +import mxnet as mx +import numpy as np +from mxnet.test_utils import * +from common import assertRaises + + +def _get_data(url, dirname): + import os, tarfile + download(url, dirname=dirname, overwrite=False) + fname = os.path.join(dirname, url.split('/')[-1]) + tar = tarfile.open(fname) + source_images = [os.path.join(dirname, x.name) for x in tar.getmembers() if x.isfile()] + if len(source_images) < 1 or not os.path.isfile(source_images[0]): + # skip extracting if exists + tar.extractall(path=dirname) + tar.close() + return source_images + +def _get_images(): + return _get_data("http://data.mxnet.io/data/test_images.tar.gz", './data') + +def test_init(): + _get_images() + +def test_imdecode(): + try: + import cv2 + except ImportError: + return + sources = _get_images() + for img in sources: + with open(img, 'rb') as fp: + str_image = fp.read() + image = mx.image.imdecode(str_image, to_rgb=0) + cv_image = cv2.imread(img) + assert_almost_equal(image.asnumpy(), cv_image) + +def test_scale_down(): + assert mx.image.scale_down((640, 480), (720, 120)) == (640, 106) + assert mx.image.scale_down((360, 1000), (480, 500)) == (360, 375) + assert mx.image.scale_down((300, 400), (0, 0)) == (0, 0) + +def test_resize_short(): + try: + import cv2 + except ImportError: + return + sources = _get_images() + for img in sources: + cv_img = cv2.imread(img) + mx_img = mx.nd.array(cv_img[:, :, (2, 1, 0)]) + h, w, _ = cv_img.shape + for _ in range(3): + new_size = np.random.randint(1, 1000) + if h > w: + new_h, new_w = new_size * h / w, new_size + else: + new_h, new_w = new_size, new_size * w / h + for interp in range(0, 2): + # area-based/lanczos don't match with cv2? + cv_resized = cv2.resize(cv_img, (new_w, new_h), interpolation=interp) + mx_resized = mx.image.resize_short(mx_img, new_size, interp) + assert_almost_equal(mx_resized.asnumpy()[:, :, (2, 1, 0)], cv_resized, atol=3) + +def test_color_normalize(): + for _ in range(10): + mean = np.random.rand(3) * 255 + std = np.random.rand(3) + 1 + width = np.random.randint(100, 500) + height = np.random.randint(100, 500) + src = np.random.rand(height, width, 3) * 255. + mx_result = mx.image.color_normalize(mx.nd.array(src), + mx.nd.array(mean), mx.nd.array(std)) + assert_almost_equal(mx_result.asnumpy(), (src - mean) / std, atol=1e-3) + + +def test_imageiter(): + sources = _get_images() + im_list = [[np.random.randint(0, 5), x] for x in sources] + test_iter = mx.image.ImageIter(2, (3, 224, 224), label_width=1, imglist=im_list, + path_root='') + for _ in range(3): + for batch in test_iter: + pass + test_iter.reset() + + # test with list file + fname = './data/test_imageiter.lst' + file_list = ['\t'.join([str(k), str(np.random.randint(0, 5)), x]) \ + for k, x in enumerate(sources)] + with open(fname, 'w') as f: + for line in file_list: + f.write(line + '\n') + + test_iter = mx.image.ImageIter(2, (3, 224, 224), label_width=1, path_imglist=fname, + path_root='') + for batch in test_iter: + pass + + +def test_augmenters(): + # only test if all augmenters will work + # TODO(Joshua Zhang): verify the augmenter outputs + sources = _get_images() + im_list = [[0, x] for x in sources] + test_iter = mx.image.ImageIter(2, (3, 224, 224), label_width=1, imglist=im_list, + resize=640, rand_crop=True, rand_resize=True, rand_mirror=True, mean=True, + std=np.array([1.1, 1.03, 1.05]), brightness=0.1, contrast=0.1, saturation=0.1, + hue=0.1, pca_noise=0.1, rand_gray=0.2, inter_method=10, path_root='', shuffle=True) + for batch in test_iter: + pass + +def _generate_objects(): + num = np.random.randint(1, 10) + xy = np.random.rand(num, 2) + wh = np.random.rand(num, 2) / 2 + left = (xy[:, 0] - wh[:, 0])[:, np.newaxis] + right = (xy[:, 0] + wh[:, 0])[:, np.newaxis] + top = (xy[:, 1] - wh[:, 1])[:, np.newaxis] + bot = (xy[:, 1] + wh[:, 1])[:, np.newaxis] + boxes = np.maximum(0., np.minimum(1., np.hstack((left, top, right, bot)))) + cid = np.random.randint(0, 20, size=num) + label = np.hstack((cid[:, np.newaxis], boxes)).ravel().tolist() + return [2, 5] + label + +def test_image_detiter(): + sources = _get_images() + im_list = [_generate_objects() + [x] for x in sources] + det_iter = mx.image.ImageDetIter(2, (3, 300, 300), imglist=im_list, path_root='') + for _ in range(3): + for batch in det_iter: + pass + det_iter.reset() + + val_iter = mx.image.ImageDetIter(2, (3, 300, 300), imglist=im_list, path_root='') + det_iter = val_iter.sync_label_shape(det_iter) + + # test file list + fname = './data/test_imagedetiter.lst' + im_list = [[k] + _generate_objects() + [x] for k, x in enumerate(sources)] + with open(fname, 'w') as f: + for line in im_list: + line = '\t'.join([str(k) for k in line]) + f.write(line + '\n') + + det_iter = mx.image.ImageDetIter(2, (3, 400, 400), path_imglist=fname, + path_root='') + for batch in det_iter: + pass + +def test_det_augmenters(): + # only test if all augmenters will work + # TODO(Joshua Zhang): verify the augmenter outputs + sources = _get_images() + im_list = [_generate_objects() + [x] for x in sources] + det_iter = mx.image.ImageDetIter(2, (3, 300, 300), imglist=im_list, path_root='', + resize=640, rand_crop=1, rand_pad=1, rand_gray=0.1, rand_mirror=True, mean=True, + std=np.array([1.1, 1.03, 1.05]), brightness=0.1, contrast=0.1, saturation=0.1, + pca_noise=0.1, hue=0.1, inter_method=10, min_object_covered=0.5, + aspect_ratio_range=(0.2, 5), area_range=(0.1, 4.0), min_eject_coverage=0.5, + max_attempts=50) + for batch in det_iter: + pass + +if __name__ == '__main__': + import nose + nose.runmodule() diff --git a/tests/python/unittest/test_infer_shape.py b/tests/python/unittest/test_infer_shape.py index 35598bc55be8..d7f52e216659 100644 --- a/tests/python/unittest/test_infer_shape.py +++ b/tests/python/unittest/test_infer_shape.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # pylint: skip-file import mxnet as mx from common import models diff --git a/tests/python/unittest/test_init.py b/tests/python/unittest/test_init.py index 372ad3553c06..e642e65ec3d6 100644 --- a/tests/python/unittest/test_init.py +++ b/tests/python/unittest/test_init.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import mxnet as mx import numpy as np @@ -29,6 +46,6 @@ def test_aux_init(): if __name__ == '__main__': - test_default_init() test_variable_init() + test_default_init() test_aux_init() diff --git a/tests/python/unittest/test_io.py b/tests/python/unittest/test_io.py index 5fe61b185041..c0f2acd4ed47 100644 --- a/tests/python/unittest/test_io.py +++ b/tests/python/unittest/test_io.py @@ -1,9 +1,30 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # pylint: skip-file import mxnet as mx import numpy as np import os, gzip import pickle as pickle import time +try: + import h5py +except ImportError: + h5py = None import sys from common import get_data @@ -63,17 +84,17 @@ def test_Cifar10Rec(): assert(labelcount[i] == 5000) def test_NDArrayIter(): - datas = np.ones([1000, 2, 2]) - labels = np.ones([1000, 1]) + data = np.ones([1000, 2, 2]) + label = np.ones([1000, 1]) for i in range(1000): - datas[i] = i / 100 - labels[i] = i / 100 - dataiter = mx.io.NDArrayIter(datas, labels, 128, True, last_batch_handle='pad') + data[i] = i / 100 + label[i] = i / 100 + dataiter = mx.io.NDArrayIter(data, label, 128, True, last_batch_handle='pad') batchidx = 0 for batch in dataiter: batchidx += 1 assert(batchidx == 8) - dataiter = mx.io.NDArrayIter(datas, labels, 128, False, last_batch_handle='pad') + dataiter = mx.io.NDArrayIter(data, label, 128, False, last_batch_handle='pad') batchidx = 0 labelcount = [0 for i in range(10)] for batch in dataiter: @@ -88,7 +109,53 @@ def test_NDArrayIter(): else: assert(labelcount[i] == 100) +def test_NDArrayIter_h5py(): + if not h5py: + return + + data = np.ones([1000, 2, 2]) + label = np.ones([1000, 1]) + for i in range(1000): + data[i] = i / 100 + label[i] = i / 100 + + try: + os.remove("ndarraytest.h5") + except OSError: + pass + with h5py.File("ndarraytest.h5") as f: + f.create_dataset("data", data=data) + f.create_dataset("label", data=label) + + dataiter = mx.io.NDArrayIter(f["data"], f["label"], 128, True, last_batch_handle='pad') + batchidx = 0 + for batch in dataiter: + batchidx += 1 + assert(batchidx == 8) + + dataiter = mx.io.NDArrayIter(f["data"], f["label"], 128, False, last_batch_handle='pad') + labelcount = [0 for i in range(10)] + for batch in dataiter: + label = batch.label[0].asnumpy().flatten() + assert((batch.data[0].asnumpy()[:,0,0] == label).all()) + for i in range(label.shape[0]): + labelcount[int(label[i])] += 1 + + try: + os.remove("ndarraytest.h5") + except OSError: + pass + + for i in range(10): + if i == 0: + assert(labelcount[i] == 124) + else: + assert(labelcount[i] == 100) + + if __name__ == "__main__": test_NDArrayIter() + if h5py: + test_NDArrayIter_h5py() test_MNISTIter() test_Cifar10Rec() diff --git a/tests/python/unittest/test_kvstore.py b/tests/python/unittest/test_kvstore.py index dd8149d4822e..f1e10c757fad 100644 --- a/tests/python/unittest/test_kvstore.py +++ b/tests/python/unittest/test_kvstore.py @@ -1,9 +1,28 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # pylint: skip-file import mxnet as mx import numpy as np shape = (4, 4) keys = [5, 7, 11] +str_keys = ['b', 'c', 'd'] + def init_kv(): """init kv """ kv = mx.kv.create() @@ -13,6 +32,14 @@ def init_kv(): kv.init(keys, [mx.nd.zeros(shape)] * len(keys)) return kv +def init_kv_with_str(): + """init kv """ + kv = mx.kv.create() + # single + kv.init('a', mx.nd.zeros(shape)) + # list + kv.init(str_keys, [mx.nd.zeros(shape)] * len(keys)) + return kv def check_diff_to_scalar(A, x): """ assert A == x""" @@ -20,59 +47,67 @@ def check_diff_to_scalar(A, x): def test_single_kv_pair(): """single key-value pair push & pull""" + def check_single_kv_pair(kv, key): + kv.push(key, mx.nd.ones(shape)) + val = mx.nd.empty(shape) + kv.pull(key, out = val) + check_diff_to_scalar(val, 1) - kv = init_kv() - kv.push(3, mx.nd.ones(shape)) - val = mx.nd.empty(shape) - kv.pull(3, out = val) - check_diff_to_scalar(val, 1) + check_single_kv_pair(init_kv(), 3) + check_single_kv_pair(init_kv_with_str(), 'a') def test_init(): """test init""" - kv = mx.kv.create() - kv.init(3, mx.nd.ones(shape)*4) - a = mx.nd.zeros(shape) - kv.pull(3, out=a) - check_diff_to_scalar(a, 4) + def check_init(kv, key): + kv.init(key, mx.nd.ones(shape)*4) + a = mx.nd.zeros(shape) + kv.pull(key, out=a) + check_diff_to_scalar(a, 4) + + check_init(mx.kv.create(), 3) + check_init(mx.kv.create(), 'a') def test_list_kv_pair(): """list key-value pair push & pull""" + def check_list_kv_pair(kv, key): + kv.push(key, [mx.nd.ones(shape)*4] * len(key)) + val = [mx.nd.empty(shape)] * len(key) + kv.pull(key, out = val) + for v in val: + check_diff_to_scalar(v, 4) - kv = init_kv() - - kv.push(keys, [mx.nd.ones(shape)*4] * len(keys)) - val = [mx.nd.empty(shape)] * len(keys) - kv.pull(keys, out = val) - for v in val: - check_diff_to_scalar(v, 4) + check_list_kv_pair(init_kv(), keys) + check_list_kv_pair(init_kv_with_str(), str_keys) def test_aggregator(): """aggregate value on muliple devices""" - kv = init_kv() + def check_aggregator(kv, key, key_list): + # devices + num_devs = 4 + devs = [mx.Context('cpu', i) for i in range(num_devs)] - # devices - num_devs = 4 - devs = [mx.Context('cpu', i) for i in range(num_devs)] + # single + vals = [mx.nd.ones(shape, d) for d in devs] - # single - vals = [mx.nd.ones(shape, d) for d in devs] + kv.push(key, vals) + kv.pull(key, out = vals) - kv.push(3, vals) - kv.pull(3, out = vals) + for v in vals: + check_diff_to_scalar(v, num_devs) - for v in vals: - check_diff_to_scalar(v, num_devs) + # list + vals = [[mx.nd.ones(shape, d)*2.0 for d in devs]] * len(key_list) + kv.push(key_list, vals) + kv.pull(key_list, out = vals) - # list - vals = [[mx.nd.ones(shape, d)*2.0 for d in devs]] * len(keys) - kv.push(keys, vals) - kv.pull(keys, out = vals) + for vv in vals: + for v in vv: + check_diff_to_scalar(v, num_devs * 2.0) - for vv in vals: - for v in vv: - check_diff_to_scalar(v, num_devs * 2.0) + check_aggregator(init_kv(), 3, keys) + check_aggregator(init_kv_with_str(), 'a', str_keys) def updater(key, recv, local): @@ -82,34 +117,41 @@ def updater(key, recv, local): def test_updater(dev = 'cpu'): """updater""" - kv = init_kv() - kv._set_updater(updater) + def check_updater(kv, key, key_list): + # devices + num_devs = 4 + devs = [mx.Context(dev, i) for i in range(num_devs)] - # devices - num_devs = 4 - devs = [mx.Context(dev, i) for i in range(num_devs)] + # single + vals = [mx.nd.ones(shape, d) for d in devs] - # single - vals = [mx.nd.ones(shape, d) for d in devs] + kv.push(key, vals) + kv.pull(key, out = vals) - kv.push(3, vals) - kv.pull(3, out = vals) + for v in vals: + check_diff_to_scalar(v, num_devs) - for v in vals: - check_diff_to_scalar(v, num_devs) + # list + vals = [[mx.nd.ones(shape, d) for d in devs]] * len(key_list) - # list - vals = [[mx.nd.ones(shape, d) for d in devs]] * len(keys) + num_push = 4 + for i in range(num_push): + kv.push(key_list, vals) + + kv.pull(key_list, out = vals) + + for vv in vals: + for v in vv: + check_diff_to_scalar(v, num_devs * num_push) - num_push = 4 - for i in range(num_push): - kv.push(keys, vals) + kv = init_kv() + kv._set_updater(updater) + check_updater(kv, 3, keys) - kv.pull(keys, out = vals) + str_kv = init_kv_with_str() + str_kv._set_updater(updater) + check_updater(str_kv, 'a', str_keys) - for vv in vals: - for v in vv: - check_diff_to_scalar(v, num_devs * num_push) def test_get_type(): kvtype = 'local_allreduce_cpu' diff --git a/tests/python/unittest/test_loss.py b/tests/python/unittest/test_loss.py new file mode 100644 index 000000000000..714ea7562fdb --- /dev/null +++ b/tests/python/unittest/test_loss.py @@ -0,0 +1,220 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +import mxnet as mx +import numpy as np +from mxnet import gluon +from mxnet.test_utils import assert_almost_equal + + +def test_loss_ndarray(): + output = mx.nd.array([1, 2, 3, 4]) + label = mx.nd.array([1, 3, 5, 7]) + weighting = mx.nd.array([0.5, 1, 0.5, 1]) + + loss = gluon.loss.L1Loss() + assert mx.nd.sum(loss(output, label)).asscalar() == 6. + loss = gluon.loss.L1Loss(weight=0.5) + assert mx.nd.sum(loss(output, label)).asscalar() == 3. + loss = gluon.loss.L1Loss() + assert mx.nd.sum(loss(output, label, weighting)).asscalar() == 5. + + loss = gluon.loss.L2Loss() + assert mx.nd.sum(loss(output, label)).asscalar() == 7. + loss = gluon.loss.L2Loss(weight=0.25) + assert mx.nd.sum(loss(output, label)).asscalar() == 1.75 + loss = gluon.loss.L2Loss() + assert mx.nd.sum(loss(output, label, weighting)).asscalar() == 6 + + output = mx.nd.array([[0, 2], [1, 4]]) + label = mx.nd.array([0, 1]) + weighting = mx.nd.array([[0.5], [1.0]]) + + loss = gluon.loss.SoftmaxCrossEntropyLoss() + L = loss(output, label).asnumpy() + mx.test_utils.assert_almost_equal(L, np.array([ 2.12692809, 0.04858733])) + + L = loss(output, label, weighting).asnumpy() + mx.test_utils.assert_almost_equal(L, np.array([ 1.06346405, 0.04858733])) + + +def get_net(num_hidden): + data = mx.symbol.Variable('data') + fc1 = mx.symbol.FullyConnected(data, name='fc1', num_hidden=128) + act1 = mx.symbol.Activation(fc1, name='relu1', act_type="relu") + fc2 = mx.symbol.FullyConnected(act1, name = 'fc2', num_hidden = 64) + act2 = mx.symbol.Activation(fc2, name='relu2', act_type="relu") + fc3 = mx.symbol.FullyConnected(act2, name='fc3', num_hidden=num_hidden) + return fc3 + + +def test_ce_loss(): + mx.random.seed(1234) + np.random.seed(1234) + nclass = 10 + N = 20 + data = mx.random.uniform(-1, 1, shape=(N, nclass)) + label = mx.nd.array(np.random.randint(0, nclass, size=(N,)), dtype='int32') + data_iter = mx.io.NDArrayIter(data, label, batch_size=10, label_name='label') + output = get_net(nclass) + fc2 = output.get_internals()['fc2_output'] + l = mx.symbol.Variable('label') + Loss = gluon.loss.SoftmaxCrossEntropyLoss() + loss = Loss(output, l) + loss = mx.sym.make_loss(loss) + mod = mx.mod.Module(loss, data_names=('data',), label_names=('label',)) + mod.fit(data_iter, num_epoch=200, optimizer_params={'learning_rate': 1.}, + eval_metric=mx.metric.Loss()) + assert mod.score(data_iter, eval_metric=mx.metric.Loss())[0][1] < 0.01 + + +def test_bce_loss(): + mx.random.seed(1234) + np.random.seed(1234) + N = 20 + data = mx.random.uniform(-1, 1, shape=(N, 20)) + label = mx.nd.array(np.random.randint(2, size=(N,)), dtype='float32') + data_iter = mx.io.NDArrayIter(data, label, batch_size=10, label_name='label') + output = get_net(1) + fc2 = output.get_internals()['fc2_output'] + l = mx.symbol.Variable('label') + Loss = gluon.loss.SigmoidBinaryCrossEntropyLoss() + loss = Loss(output, l) + loss = mx.sym.make_loss(loss) + mod = mx.mod.Module(loss, data_names=('data',), label_names=('label',)) + mod.fit(data_iter, num_epoch=200, optimizer_params={'learning_rate': 1.}, + eval_metric=mx.metric.Loss()) + assert mod.score(data_iter, eval_metric=mx.metric.Loss())[0][1] < 0.01 + +def test_bce_equal_ce2(): + N = 100 + loss1 = gluon.loss.SigmoidBCELoss(from_sigmoid=True) + loss2 = gluon.loss.SoftmaxCELoss(from_logits=True) + out1 = mx.random.uniform(0, 1, shape=(N, 1)) + out2 = mx.nd.log(mx.nd.concat(1-out1, out1, dim=1) + 1e-8) + label = mx.nd.round(mx.random.uniform(0, 1, shape=(N, 1))) + assert_almost_equal(loss1(out1, label).asnumpy(), loss2(out2, label).asnumpy()) + + +def test_kl_loss(): + mx.random.seed(1234) + np.random.seed(1234) + N = 20 + data = mx.random.uniform(-1, 1, shape=(N, 10)) + label = mx.nd.softmax(mx.random.uniform(0, 1, shape=(N, 2))) + data_iter = mx.io.NDArrayIter(data, label, batch_size=10, label_name='label') + output = mx.sym.log_softmax(get_net(2)) + l = mx.symbol.Variable('label') + Loss = gluon.loss.KLDivLoss() + loss = Loss(output, l) + loss = mx.sym.make_loss(loss) + mod = mx.mod.Module(loss, data_names=('data',), label_names=('label',)) + mod.fit(data_iter, num_epoch=200, optimizer_params={'learning_rate': 1.}, + eval_metric=mx.metric.Loss()) + assert mod.score(data_iter, eval_metric=mx.metric.Loss())[0][1] < 0.05 + + +def test_l2_loss(): + mx.random.seed(1234) + np.random.seed(1234) + N = 20 + data = mx.random.uniform(-1, 1, shape=(N, 10)) + label = mx.random.uniform(-1, 1, shape=(N, 1)) + data_iter = mx.io.NDArrayIter(data, label, batch_size=10, label_name='label') + output = get_net(1) + l = mx.symbol.Variable('label') + Loss = gluon.loss.L2Loss() + Loss(label, label) + loss = Loss(output, l) + loss = mx.sym.make_loss(loss) + mod = mx.mod.Module(loss, data_names=('data',), label_names=('label',)) + mod.fit(data_iter, num_epoch=200, optimizer_params={'learning_rate': 1.}, + eval_metric=mx.metric.Loss()) + assert mod.score(data_iter, eval_metric=mx.metric.Loss())[0][1] < 0.05 + + +def test_l1_loss(): + mx.random.seed(1234) + np.random.seed(1234) + N = 20 + data = mx.random.uniform(-1, 1, shape=(N, 10)) + label = mx.random.uniform(-1, 1, shape=(N, 1)) + data_iter = mx.io.NDArrayIter(data, label, batch_size=10, label_name='label') + output = get_net(1) + l = mx.symbol.Variable('label') + Loss = gluon.loss.L1Loss() + loss = Loss(output, l) + loss = mx.sym.make_loss(loss) + mod = mx.mod.Module(loss, data_names=('data',), label_names=('label',)) + mod.fit(data_iter, num_epoch=200, optimizer_params={'learning_rate': 0.1}, + initializer=mx.init.Uniform(0.5), eval_metric=mx.metric.Loss()) + assert mod.score(data_iter, eval_metric=mx.metric.Loss())[0][1] < 0.1 + + +def test_sample_weight_loss(): + mx.random.seed(1234) + np.random.seed(1234) + nclass = 10 + N = 20 + data = mx.random.uniform(-1, 1, shape=(N, nclass)) + label = mx.nd.array(np.random.randint(0, nclass, size=(N,)), dtype='int32') + weight = mx.nd.array([1 for i in range(10)] + [0 for i in range(10)]) + data_iter = mx.io.NDArrayIter(data, {'label': label, 'w': weight}, batch_size=10) + output = get_net(nclass) + l = mx.symbol.Variable('label') + w = mx.symbol.Variable('w') + Loss = gluon.loss.SoftmaxCrossEntropyLoss() + loss = Loss(output, l, w) + loss = mx.sym.make_loss(loss) + mod = mx.mod.Module(loss, data_names=('data',), label_names=('label', 'w')) + mod.fit(data_iter, num_epoch=200, optimizer_params={'learning_rate': 1.}, + eval_metric=mx.metric.Loss()) + data_iter = mx.io.NDArrayIter(data[10:], {'label': label, 'w': weight}, batch_size=10) + score = mod.score(data_iter, eval_metric=mx.metric.Loss())[0][1] + assert score > 1 + data_iter = mx.io.NDArrayIter(data[:10], {'label': label, 'w': weight}, batch_size=10) + score = mod.score(data_iter, eval_metric=mx.metric.Loss())[0][1] + assert score < 0.05 + + +def test_saveload(): + mx.random.seed(1234) + np.random.seed(1234) + nclass = 10 + N = 20 + data = mx.random.uniform(-1, 1, shape=(N, nclass)) + label = mx.nd.array(np.random.randint(0, nclass, size=(N,)), dtype='int32') + data_iter = mx.io.NDArrayIter(data, label, batch_size=10, label_name='label') + output = get_net(nclass) + l = mx.symbol.Variable('label') + Loss = gluon.loss.SoftmaxCrossEntropyLoss() + loss = Loss(output, l) + loss = mx.sym.make_loss(loss) + mod = mx.mod.Module(loss, data_names=('data',), label_names=('label',)) + mod.fit(data_iter, num_epoch=100, optimizer_params={'learning_rate': 1.}, + eval_metric=mx.metric.Loss()) + mod.save_checkpoint('test', 100, save_optimizer_states=True) + mod = mx.mod.Module.load('test', 100, load_optimizer_states=True, + data_names=('data',), label_names=('label',)) + mod.fit(data_iter, num_epoch=100, optimizer_params={'learning_rate': 1.}, + eval_metric=mx.metric.Loss()) + assert mod.score(data_iter, eval_metric=mx.metric.Loss())[0][1] < 0.05 + + +if __name__ == '__main__': + import nose + nose.runmodule() diff --git a/tests/python/unittest/test_metric.py b/tests/python/unittest/test_metric.py index 98740b05ee32..7ae93bf36299 100644 --- a/tests/python/unittest/test_metric.py +++ b/tests/python/unittest/test_metric.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import mxnet as mx import json @@ -13,6 +30,7 @@ def test_metrics(): check_metric('acc', axis=0) check_metric('f1') check_metric('perplexity', -1) + check_metric('pearsonr') composite = mx.metric.create(['acc', 'f1']) check_metric(composite) diff --git a/tests/python/unittest/test_model_parallel.py b/tests/python/unittest/test_model_parallel.py index a531c5064551..8ff09d5fcb56 100644 --- a/tests/python/unittest/test_model_parallel.py +++ b/tests/python/unittest/test_model_parallel.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import numpy as np import mxnet as mx @@ -10,42 +27,52 @@ def reldiff(a, b): return reldiff def test_chain(): + ctx1 = mx.cpu(0) + ctx2 = mx.cpu(1) n = 2 data1 = mx.sym.Variable('data1') data2 = mx.sym.Variable('data2') + data3 = mx.sym.Variable('data3') with mx.AttrScope(ctx_group='dev1'): net = data1 + data2 net = net * 3 with mx.AttrScope(ctx_group='dev2'): - net = net + data1 + net = net + data3 - with mx.Context(mx.cpu(0)): - shape = (4, 5) - arr = [mx.nd.empty(shape) for i in range(n)] - arr_grad = [mx.nd.empty(shape) for i in range(n)] + arr = [] + arr_grad = [] + shape = (4, 5) + with mx.Context(ctx1): + for i in range(n): + arr.append(mx.nd.empty(shape)) + arr_grad.append(mx.nd.empty(shape)) + with mx.Context(ctx2): + arr.append(mx.nd.empty(shape)) + arr_grad.append(mx.nd.empty(shape)) - exec1 = net.bind(mx.cpu(), + exec1 = net.bind(ctx1, args=arr, args_grad=arr_grad, - group2ctx={'dev1': mx.cpu(0), 'dev2': mx.cpu(1)}) + group2ctx={'dev1': ctx1, 'dev2': ctx2}) arr[0][:] = 1.0 arr[1][:] = 2.0 - arr2 = [a.copyto(mx.cpu()) for a in arr] - arr_grad2 = [a.copyto(mx.cpu()) for a in arr_grad] - exec2 = net.bind(mx.cpu(), + arr[2][:] = 3.0 + arr2 = [a.copyto(ctx1) for a in arr] + arr_grad2 = [a.copyto(ctx1) for a in arr_grad] + exec2 = net.bind(ctx1, args=arr2, args_grad=arr_grad2) # Show the execution plan that involves copynode print(exec1.debug_str()) - exec1.forward() - exec2.forward() + exec1.forward(is_train=True) + exec2.forward(is_train=True) assert reldiff(exec1.outputs[0].asnumpy(), exec2.outputs[0].asnumpy()) < 1e-6 - out_grad = mx.nd.empty(shape, mx.cpu(1)) + out_grad = mx.nd.empty(shape, ctx1) out_grad[:] = 1.0 exec1.backward([out_grad]) - exec2.backward([out_grad.copyto(mx.cpu())]) + exec2.backward([out_grad.copyto(ctx1)]) for a, b in zip(arr_grad, arr_grad2): assert reldiff(a.asnumpy(), b.asnumpy()) < 1e-6 diff --git a/tests/python/unittest/test_module.py b/tests/python/unittest/test_module.py index 5508a37c9567..f522f29dae39 100644 --- a/tests/python/unittest/test_module.py +++ b/tests/python/unittest/test_module.py @@ -1,7 +1,28 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import mxnet as mx import mxnet.ndarray as nd import numpy as np from functools import reduce +from mxnet.module.executor_group import DataParallelExecutorGroup +from common import assertRaises +from collections import namedtuple + def test_module_dtype(): dtype = np.float16 @@ -45,6 +66,7 @@ def test_module_input_grads(): assert np.all(b_grad == 2), b_grad assert np.all(c_grad == 3), c_grad + def test_module_layout(): sym = mx.sym.Variable('data') sym = mx.sym.Activation(data=sym, act_type='relu', __layout__='TNC') @@ -62,6 +84,7 @@ def test_module_layout(): for x in mod.get_outputs(merge_multi_context=False)[0]: assert x.shape == hdshape + def test_save_load(): def dict_equ(a, b): assert set(a) == set(b) @@ -101,6 +124,7 @@ def dict_equ(a, b): dict_equ(mod.get_params()[0], mod2.get_params()[0]) dict_equ(mod._kvstore._updater.states, mod2._updater.states) + def test_module_reshape(): data = mx.sym.Variable('data') sym = mx.sym.FullyConnected(data, num_hidden=20, name='fc') @@ -127,6 +151,7 @@ def test_module_reshape(): assert mod.get_outputs()[0].shape == dshape assert (mod.get_params()[0]['fc_bias'].asnumpy() == -3).all() + def test_module_states(): stack = mx.rnn.SequentialRNNCell() for i in range(2): @@ -153,6 +178,7 @@ def test_module_states(): for x1, x2 in zip(out1, out2): assert not mx.test_utils.almost_equal(x1.asnumpy(), x2.asnumpy(), rtol=1e-3) + def test_module_switch_bucket(): vocab_dim = 5000 num_hidden = 100 @@ -207,6 +233,71 @@ def create_bucketing_module(key): #the default bucket is expected to reuse the bytes allocated assert total_bytes_after == total_bytes_before + + +def test_module_set_params(): + # data iter + mx.random.seed(11) + data = mx.nd.array([[0.05, .10]]); + label = mx.nd.array([[.01, 0.99]]); + train_data = mx.io.NDArrayIter(data, label, batch_size=1) + + # symbols + x = mx.symbol.Variable('data') + x = mx.symbol.FullyConnected(name='fc_0', data=x, num_hidden=2) + x = mx.symbol.Activation(name="act_0", data=x, act_type='sigmoid') + x = mx.symbol.FullyConnected(name='fc_1', data=x, num_hidden=2) + x = mx.symbol.Activation(name="act_1", data=x, act_type='sigmoid') + x = mx.symbol.LinearRegressionOutput(data=x, name='softmax', grad_scale=2) + + # create module + mod = mx.mod.Module(x, context=[mx.cpu()]); + mod.bind(train_data.provide_data, label_shapes=train_data.provide_label, + for_training=True) + + arg_params_correct = {'fc_0_weight': mx.nd.array([[.15, .20], [.25, .30]]), + 'fc_0_bias' : mx.nd.array([.35, .35]), + 'fc_1_weight': mx.nd.array([[.40, .45], [.50, .55]]), + 'fc_1_bias' : mx.nd.array([.60, .60])} + + arg_params_missing = {'fc_0_weight': mx.nd.array([[.15, .20], [.25, .30]]), + 'fc_0_bias' : mx.nd.array([.35, .35]), + 'fc_1_weight': mx.nd.array([[.40, .45], [.50, .55]])} + + arg_params_extra = {'fc_0_weight': mx.nd.array([[.15, .20], [.25, .30]]), + 'fc_0_bias' : mx.nd.array([.35, .35]), + 'fc_1_weight': mx.nd.array([[.40, .45], [.50, .55]]), + 'fc_1_bias' : mx.nd.array([.60, .60]), + 'fc_2_weight': mx.nd.array([.60, .60])} + + arg_params_missing_extra = {'fc_2_weight': mx.nd.array([.60, .60])} + + # test regular set_params + mod.set_params(force_init=True, arg_params=arg_params_correct, aux_params={}) + + # test allow missing + mod.set_params(force_init=True, arg_params=arg_params_missing, aux_params={}, allow_missing=True) + assertRaises(RuntimeError, mod.set_params, + force_init=True, arg_params=arg_params_missing, + aux_params={}, allow_missing=False) + + # test allow extra + mod.set_params(force_init=True, arg_params=arg_params_extra, aux_params={}, allow_missing=True, allow_extra=True) + assertRaises(ValueError, mod.set_params, + force_init=True, arg_params=arg_params_extra, + aux_params={}, allow_missing=True, allow_extra=False) + + # test allow missing + extra, + assertRaises(RuntimeError, mod.set_params, + force_init=True, arg_params=arg_params_missing_extra, + aux_params={}, allow_missing=False, allow_extra=False) + + # test allow missing + extra, this will throw a runtime error + assertRaises(ValueError, mod.set_params, + force_init=True, arg_params=arg_params_missing_extra, + aux_params={}, allow_missing=True, allow_extra=False) + + def test_monitor(): # data iter mx.random.seed(11) @@ -254,12 +345,237 @@ def mean_abs(x): break assert(mon_result_counts == [2, 2, 1, 6, 6, 4]) + +def test_executor_group(): + def get_rnn_sym(num_layers, num_words, num_hidden, num_embed, seq_len): + stack = mx.rnn.SequentialRNNCell() + for i in range(num_layers): + stack.add(mx.rnn.LSTMCell(num_hidden=num_hidden, prefix='lstm_l%d_' % i)) + data = mx.sym.Variable('data') + label = mx.sym.Variable('softmax_label') + embed = mx.sym.Embedding(data=data, input_dim=num_words, + output_dim=num_embed, name='embed') + + stack.reset() + outputs, states = stack.unroll(seq_len, inputs=embed, merge_outputs=True) + + pred = mx.sym.Reshape(outputs, shape=(-1, num_hidden)) + pred = mx.sym.FullyConnected(data=pred, num_hidden=num_words, name='pred') + + label = mx.sym.Reshape(label, shape=(-1,)) + pred = mx.sym.SoftmaxOutput(data=pred, label=label, name='softmax') + return pred + + def test_shared_exec_group(exec_grp_shared, exec_grp_created, shared_arg_names=None, extra_args=None): + # Test shared data arrays + for i in range(len(exec_grp_shared.execs)): + # test same shared_data_arrays for two exec groups + shared_data_array1 = exec_grp_shared.shared_data_arrays[i] + shared_data_array2 = exec_grp_created.shared_data_arrays[i] + if extra_args is not None: + assert len(shared_data_array1) == len(extra_args),\ + "exec_grp_shared.shared_data_arrays[%d] should have same number of args as extra_args" + assert len(shared_data_array1) == len(shared_data_array2),\ + "length of shared_data_array of the shared executor group not equal to the created executor group" + for k, v in shared_data_array1.items(): + if extra_args is not None: + assert k in extra_args, "arg %s is not in extra_args" % k + assert k in shared_data_array2,\ + "arg %s of the shared executor group not in the shared_data_array of the created executor group" % k + assert mx.test_utils.same_array(v, shared_data_array2[k]) + + for data_name, array in exec_grp_shared.shared_data_arrays[i].items(): + assert data_name in exec_grp_created.shared_data_arrays[i], \ + "Shared input data '%s' is not in " \ + "shared_data_arrays of created executor group." % (data_name) + assert mx.test_utils.same_array(array, exec_grp_created.shared_data_arrays[i][data_name]), \ + "Shared input data '%s' does not share memory." % (data_name) + + # Test shared argument arrays and gradient arrays + exec_shared = exec_grp_shared.execs[i] + exec_created = exec_grp_created.execs[i] + if shared_arg_names is not None: + # test shared arguments + for arg_name in shared_arg_names: + assert arg_name in exec_created.arg_dict, \ + "Shared argument '%s' is not in arg_dict of created executor group." % (arg_name) + assert mx.test_utils.same_array(exec_shared.arg_dict[arg_name], exec_created.arg_dict[arg_name]), \ + "Shared argument '%s' does not share memory." % (arg_name) + # test shared argument gradients + for arg_name in shared_arg_names: + assert arg_name in exec_created.grad_dict, \ + "Shared argument gradient '%s' is not in " \ + "grad_dict of created executor group." % (arg_name) + assert mx.test_utils.same_array(exec_shared.grad_dict[arg_name], exec_created.grad_dict[arg_name]), \ + "Shared argument gradient '%s' does not sharing memory." % (arg_name) + + for arg_name, grad in exec_grp_shared.grad_req.items(): + assert grad == exec_grp_created.grad_req[arg_name], \ + "Gradient requirements for shared argument '%s' are inconsistent. " \ + "Shared executor group requires '%s' while created executor group requires '%s'" \ + %(arg_name, grad, exec_grp_created.grad_req[arg_name]) + + contexts = [mx.cpu(0), mx.cpu(1)] + workload = [1] * len(contexts) + batch_size = 32 + max_bucket_size = 80 + num_words = 1000 + num_hidden = 100 + num_embed = 200 + data_shapes = [('data', (batch_size, max_bucket_size))] + label_shapes = [('softmax_label', (batch_size, max_bucket_size))] + + # generate an rnn sym with #layers=5 + sym = get_rnn_sym(num_layers=3, num_words=num_words, num_hidden=num_hidden, + num_embed=num_embed, seq_len=max_bucket_size) + arg_names1 = sym.list_arguments() + input_names = [name[0] for name in data_shapes] + [name[0] for name in label_shapes] + shared_arg_names = [name for name in arg_names1 if name not in input_names] + exec_group1 = DataParallelExecutorGroup(symbol=sym, contexts=contexts, + workload=workload, data_shapes=data_shapes, + label_shapes=label_shapes, param_names=shared_arg_names, + for_training=True, inputs_need_grad=False) + + # shared_data_arrays should only have input "data" and "softmax_label" arrays + for i in range(len(contexts)): + assert len(exec_group1.shared_data_arrays[i]) == len(input_names),\ + "exec_group1.shared_data_arrays[%d] should have the same number of names as in input_names" % i + for name in input_names: + assert name in exec_group1.shared_data_arrays[i],\ + "arg %s should be in exec_group1.shared_data_arrays[%d]" % (name, i) + + # generate an rnn sym with #layers=5 + sym = get_rnn_sym(num_layers=5, num_words=num_words, num_hidden=num_hidden, + num_embed=num_embed, seq_len=max_bucket_size) + arg_names2 = sym.list_arguments() + exec_group2 = DataParallelExecutorGroup(symbol=sym, contexts=contexts, + workload=workload, data_shapes=data_shapes, + label_shapes=label_shapes, param_names=shared_arg_names, + for_training=True, inputs_need_grad=False, + shared_group=exec_group1) + extra_args = [name for name in arg_names2 if name not in shared_arg_names] + test_shared_exec_group(exec_grp_shared=exec_group1, exec_grp_created=exec_group2, + shared_arg_names=shared_arg_names, extra_args=extra_args) + + +def test_forward_reshape(): + num_class=10 + data1 = mx.sym.Variable('data1') + data2 = mx.sym.Variable('data2') + conv1 = mx.sym.Convolution(data=data1, kernel=(2, 2), num_filter=2, stride=(2, 2)) + conv2 = mx.sym.Convolution(data=data2, kernel=(3, 3), num_filter=3, stride=(1, 1)) + pooling1 = mx.sym.Pooling(data=conv1, kernel=(2, 2), stride=(1, 1), pool_type="avg") + pooling2 = mx.sym.Pooling(data=conv2, kernel=(2, 2), stride=(1, 1), pool_type="max") + flatten1 = mx.sym.flatten(data=pooling1) + flatten2 = mx.sym.flatten(data=pooling2) + sum = mx.sym.sum(data=flatten1, axis=1) + mx.sym.sum(data=flatten2, axis=1) + fc = mx.sym.FullyConnected(data=sum, num_hidden=num_class) + sym = mx.sym.SoftmaxOutput(data=fc, name='softmax') + + dshape1 = (10, 3, 64, 64) + dshape2 = (10, 3, 32, 32) + lshape = (10,) + + mod = mx.mod.Module(symbol=sym, data_names=['data1', 'data2'], + label_names=['softmax_label']) + mod.bind(data_shapes=[('data1', dshape1), ('data2', dshape2)], + label_shapes=[('softmax_label', lshape)]) + mod.init_params() + mod.init_optimizer(optimizer_params={'learning_rate': 0.01}) + + # Train with original data shapes + data_batch = mx.io.DataBatch(data=[mx.nd.random_uniform(0, 9, dshape1), + mx.nd.random_uniform(5, 15, dshape2)], + label=[mx.nd.ones(lshape)]) + mod.forward(data_batch) + assert mod.get_outputs()[0].shape == tuple([lshape[0], num_class]) + mod.backward() + mod.update() + + # Train with different batch size + dshape1 = (3, 3, 64, 64) + dshape2 = (3, 3, 32, 32) + lshape = (3,) + data_batch = mx.io.DataBatch(data=[mx.nd.random_uniform(0, 9, dshape1), + mx.nd.random_uniform(5, 15, dshape2)], + label=[mx.nd.ones(lshape)]) + mod.forward(data_batch) + assert mod.get_outputs()[0].shape == tuple([lshape[0], num_class]) + mod.backward() + mod.update() + + dshape1 = (20, 3, 64, 64) + dshape2 = (20, 3, 32, 32) + lshape = (20,) + data_batch = mx.io.DataBatch(data=[mx.nd.random_uniform(3, 5, dshape1), + mx.nd.random_uniform(10, 25, dshape2)], + label=[mx.nd.ones(lshape)]) + mod.forward(data_batch) + assert mod.get_outputs()[0].shape == tuple([lshape[0], num_class]) + mod.backward() + mod.update() + + #Train with both different batch size and data shapes + dshape1 = (20, 3, 120, 120) + dshape2 = (20, 3, 32, 64) + lshape = (20,) + data_batch = mx.io.DataBatch(data=[mx.nd.random_uniform(0, 9, dshape1), + mx.nd.random_uniform(5, 15, dshape2)], + label=[mx.nd.ones(lshape)]) + mod.forward(data_batch) + assert mod.get_outputs()[0].shape == tuple([lshape[0], num_class]) + mod.backward() + mod.update() + + dshape1 = (5, 3, 28, 40) + dshape2 = (5, 3, 24, 16) + lshape = (5,) + data_batch = mx.io.DataBatch(data=[mx.nd.random_uniform(0, 9, dshape1), + mx.nd.random_uniform(15, 25, dshape2)], + label=[mx.nd.ones(lshape)]) + mod.forward(data_batch) + assert mod.get_outputs()[0].shape == tuple([lshape[0], num_class]) + mod.backward() + mod.update() + + #Test score + dataset_shape1 = (30, 3, 30, 30) + dataset_shape2 = (30, 3, 20, 40) + labelset_shape = (30,) + + eval_dataiter = mx.io.NDArrayIter(data=[mx.nd.random_uniform(0, 9, dataset_shape1), + mx.nd.random_uniform(15, 25, dataset_shape2)], + label=[mx.nd.ones(labelset_shape)], + batch_size=5) + assert len(mod.score(eval_data=eval_dataiter, eval_metric='acc')) == 1 + + #Test prediction + dshape1 = (1, 3, 30, 30) + dshape2 = (1, 3, 20, 40) + dataset_shape1 = (10, 3, 30, 30) + dataset_shape2 = (10, 3, 20, 40) + + pred_dataiter = mx.io.NDArrayIter(data=[mx.nd.random_uniform(0, 9, dataset_shape1), + mx.nd.random_uniform(15, 25, dataset_shape2)]) + mod.bind(data_shapes=[('data1', dshape1), ('data2', dshape2)], + for_training=False, force_rebind=True) + assert mod.predict(pred_dataiter).shape == tuple([10, num_class]) + + #Test forward with other data batch API + Batch = namedtuple('Batch', ['data']) + data = mx.sym.Variable('data') + out = data * 2 + mod = mx.mod.Module(symbol=out, label_names=None) + mod.bind(data_shapes=[('data', (1, 10))]) + mod.init_params() + data1 = [mx.nd.ones((1, 10))] + mod.forward(Batch(data1)) + assert mod.get_outputs()[0].shape == (1, 10) + data2 = [mx.nd.ones((3, 5))] + mod.forward(Batch(data2)) + assert mod.get_outputs()[0].shape == (3, 5) + + if __name__ == '__main__': - test_module_dtype() - test_module_input_grads() - test_module_states() - test_module_reshape() - test_save_load() - test_module_layout() - test_module_switch_bucket() - test_monitor() + import nose + nose.runmodule() diff --git a/tests/python/unittest/test_multi_device_exec.py b/tests/python/unittest/test_multi_device_exec.py index 8956c4edebac..6f8eb17ff34e 100644 --- a/tests/python/unittest/test_multi_device_exec.py +++ b/tests/python/unittest/test_multi_device_exec.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import os import mxnet as mx diff --git a/tests/python/unittest/test_ndarray.py b/tests/python/unittest/test_ndarray.py index 7f0a1d2b6301..eae364eeaecf 100644 --- a/tests/python/unittest/test_ndarray.py +++ b/tests/python/unittest/test_ndarray.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import os import mxnet as mx import numpy as np @@ -76,6 +93,14 @@ def test_ndarray_setitem(): x_np[:, 1:3, 1:2] = val.asnumpy() assert same(x.asnumpy(), x_np) + # short all-dim indexing + x = mx.nd.zeros(shape) + val = mx.nd.ones((3, 2)) + x[:, 1:3, 1] = val + x_np = np.zeros(shape, dtype=x.dtype) + x_np[:, 1:3, 1] = val.asnumpy() + assert same(x.asnumpy(), x_np) + x = mx.nd.zeros(shape) x[:, 1:3, 1] = 1 x_np = np.zeros(shape, dtype=x.dtype) @@ -209,11 +234,11 @@ def test_ndarray_pickle(): def test_ndarray_saveload(): np.random.seed(0) - maxdim = 5 nrepeat = 10 fname = 'tmp_list.bin' for repeat in range(nrepeat): data = [] + # test save/load as list for i in range(10): data.append(random_ndarray(np.random.randint(1, 5))) mx.nd.save(fname, data) @@ -221,6 +246,7 @@ def test_ndarray_saveload(): assert len(data) == len(data2) for x, y in zip(data, data2): assert np.sum(x.asnumpy() != y.asnumpy()) == 0 + # test save/load as dict dmap = {'ndarray xx %s' % i : x for i, x in enumerate(data)} mx.nd.save(fname, dmap) dmap2 = mx.nd.load(fname) @@ -228,8 +254,25 @@ def test_ndarray_saveload(): for k, x in dmap.items(): y = dmap2[k] assert np.sum(x.asnumpy() != y.asnumpy()) == 0 + # test save/load as ndarray + # we expect the single ndarray to be converted into a list containing the ndarray + single_ndarray = data[0] + mx.nd.save(fname, single_ndarray) + single_ndarray_loaded = mx.nd.load(fname) + assert len(single_ndarray_loaded) == 1 + single_ndarray_loaded = single_ndarray_loaded[0] + assert np.sum(single_ndarray.asnumpy() != single_ndarray_loaded.asnumpy()) == 0 os.remove(fname) +def test_ndarray_legacy_load(): + data = [] + for i in range(6): + data.append(mx.nd.arange(128)) + path = os.path.dirname(os.path.realpath(__file__)) + legacy_data = mx.nd.load(os.path.join(path, 'legacy_ndarray.v0')) + assert len(data) == len(legacy_data) + for i in range(len(data)): + assert same(data[i].asnumpy(), legacy_data[i].asnumpy()) def test_ndarray_slice(): shape = (10,) @@ -240,6 +283,15 @@ def test_ndarray_slice(): A[3:8] = A2[3:8] assert same(A[3:8].asnumpy(), A2[3:8]) + shape = (3,4,5,6,7) + A = mx.nd.random_uniform(shape=shape) + A2 = A.asnumpy() + + assert same(A[1,3:4,:,1:5].asnumpy(), A2[1,3:4,:,1:5]) + + assert A[1,2,3,4,5].asscalar() == A2[1,2,3,4,5] + + def test_ndarray_crop(): # get crop @@ -618,6 +670,33 @@ def test_iter(): assert same(y[i].asnumpy(), x[i].asnumpy()) +def test_cached(): + sym = mx.sym.Convolution(kernel=(3, 3), num_filter=10) + 2 + op = mx.nd.CachedOp(sym) + data = mx.nd.ones((3, 4, 10, 10)) + weight = mx.nd.ones((10, 4, 3, 3)) + bias = mx.nd.ones((10,)) + o1 = op(data, weight, bias) + bias[:] = 2 + o2 = op(data, weight, bias) + assert_almost_equal(o2.asnumpy(), o1.asnumpy()+1) + o2[:] = 0 + op(data, weight, bias, out=o2) + assert_almost_equal(o2.asnumpy(), o1.asnumpy()+1) + +def test_output(): + shape = (2,2) + ones = mx.nd.ones(shape) + zeros = mx.nd.zeros(shape) + out = mx.nd.zeros(shape) + mx.nd.ones(shape, out=out) + assert_almost_equal(out.asnumpy(), ones.asnumpy()) + mx.nd.zeros(shape, out=out) + assert_almost_equal(out.asnumpy(), zeros.asnumpy()) + mx.nd.full(shape, 2, out=out) + assert_almost_equal(out.asnumpy(), ones.asnumpy() * 2) + + if __name__ == '__main__': import nose nose.runmodule() diff --git a/tests/python/unittest/test_operator.py b/tests/python/unittest/test_operator.py index 82c20cdb17df..a33cb039c849 100644 --- a/tests/python/unittest/test_operator.py +++ b/tests/python/unittest/test_operator.py @@ -1,8 +1,27 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # pylint: skip-file +from __future__ import print_function import numpy as np import mxnet as mx import random -from numpy.testing import assert_allclose +import itertools +from numpy.testing import assert_allclose, assert_array_equal from mxnet.test_utils import * def np_softmax(x, axis=-1): @@ -517,6 +536,19 @@ def test_round_ceil_floor(): npout = np.round(data_tmp) + np.ceil(data_tmp) + np.floor(data_tmp) assert_almost_equal(out, npout) +def test_trunc(): + data_tmp = np.random.rand(3, 4) * 10 - 5 + arr_data = mx.nd.array(data_tmp) + data = mx.symbol.Variable('data') + test = mx.sym.trunc(data) + + exe_test = test.bind(default_context(), args=[arr_data]) + exe_test.forward(is_train=True) + out = exe_test.outputs[0].asnumpy() + npout = np.trunc(data_tmp) + + assert_almost_equal(out, npout) + def test_rsqrt_cos_sin(): data = mx.symbol.Variable('data') shape = (3, 4) @@ -662,7 +694,7 @@ def check_deconvolution_forward_backward(input_shape, num_filter, kernel, stride exe.forward(is_train=True) out = exe.outputs[0].asnumpy() exe.backward(out_grad) - assert_almost_equal(out, args_grad[0].asnumpy(), rtol=1E-3, atol=1e-4) + assert_almost_equal(out, args_grad[0].asnumpy(), rtol=1E-3, atol=1e-3) args_grad_addto_npy = [np.random.normal(size=s) for s in arg_shapes] args_grad_addto = [mx.nd.array(ele) for ele in args_grad_addto_npy] @@ -670,7 +702,7 @@ def check_deconvolution_forward_backward(input_shape, num_filter, kernel, stride exe.forward(is_train=True) out = exe.outputs[0].asnumpy() exe.backward(out_grad) - assert_almost_equal(out + args_grad_addto_npy[0], args_grad_addto[0].asnumpy(), rtol=1e-4, atol=1e-4) + assert_almost_equal(out + args_grad_addto_npy[0], args_grad_addto[0].asnumpy(), rtol=1e-4, atol=1e-3) def check_deconvolution_gradient(input_shape, num_filter, pad): @@ -860,6 +892,39 @@ def test_batchnorm_training(): test = mx.symbol.BatchNorm(data, fix_gamma=False, use_global_stats=True) check_numeric_gradient(test, [data_tmp, gamma, beta], [rolling_mean, rolling_std], numeric_eps=1e-2, rtol=0.16) + # Test varying channel axis + dim = len(shape) + for chaxis in range(-dim, dim): + chaxis_true = chaxis + if chaxis < 0: + chaxis_true = dim + chaxis + + shapex = shape + + channel_count = shapex[chaxis_true] + data_tmp = np.random.normal(-0.1, 0.1, size=shapex) + + gamma = np.ones(channel_count) + beta = np.ones(channel_count) + if channel_count > 1: + gamma[1] = 3 + beta[0] = 3 + + xrolling_mean = np.random.uniform(size=channel_count) + xrolling_std = np.random.uniform(size=channel_count) + + test = mx.symbol.BatchNorm(data, fix_gamma=True, axis=chaxis) + check_numeric_gradient(test, [data_tmp, gamma, beta], [xrolling_mean, xrolling_std], numeric_eps=1e-2, rtol=0.2, atol=0.01) + + test = mx.symbol.BatchNorm(data, fix_gamma=True, use_global_stats=True, axis=chaxis) + check_numeric_gradient(test, [data_tmp, gamma, beta], [xrolling_mean, xrolling_std], numeric_eps=1e-2, rtol=0.2, atol=0.01) + + test = mx.symbol.BatchNorm(data, fix_gamma=False, axis=chaxis) + check_numeric_gradient(test, [data_tmp, gamma, beta], [xrolling_mean, xrolling_std], numeric_eps=1e-2, rtol=0.2, atol=0.01) + + test = mx.symbol.BatchNorm(data, fix_gamma=False, use_global_stats=True, axis=chaxis) + check_numeric_gradient(test, [data_tmp, gamma, beta], [xrolling_mean, xrolling_std], numeric_eps=1e-2, rtol=0.2, atol=0.01) + def test_convolution_grouping(): num_filter = 4 num_group = 2 @@ -890,6 +955,44 @@ def test_convolution_grouping(): for arr1, arr2 in zip(exe1.outputs + exe1.grad_arrays, exe2.outputs + exe2.grad_arrays): np.testing.assert_allclose(arr1.asnumpy(), arr2.asnumpy(), rtol=1e-3, atol=1e-4) + +def test_depthwise_convolution(): + for num_base in [32, 64]: + for kernel in [(3,3), (5,5)]: + for stride in [(1,1), (2,2)]: + for pad in [(0,0), (1,1)]: + num_filter = num_base + num_group = num_base + shape = (2, num_base, 32, 32) + + x = mx.sym.Variable('x') + w = mx.sym.Variable('w') + b = mx.sym.Variable('b') + y1 = mx.sym.Convolution(data=x, weight=w, bias=b, num_filter=num_filter, num_group=num_group, + kernel=kernel, stride=stride, pad=pad) + xslice = mx.sym.SliceChannel(data=x, num_outputs=num_group, axis=1) + wslice = mx.sym.SliceChannel(data=w, num_outputs=num_group, axis=0) + bslice = mx.sym.SliceChannel(data=b, num_outputs=num_group, axis=0) + y2 = mx.sym.Concat(*[mx.sym.Convolution(data=xslice[i], weight=wslice[i], bias=bslice[i], + num_filter=num_filter//num_group, kernel=kernel, + stride=stride, pad=pad) + for i in range(num_group)]) + + dev = default_context() + exe1 = y1.simple_bind(dev, x=shape) + exe2 = y2.simple_bind(mx.cpu(), x=shape, w=(num_filter, shape[1]//num_group, kernel[0], kernel[1]), + b=(num_filter,)) + for arr1, arr2 in zip(exe1.arg_arrays, exe2.arg_arrays): + arr1[:] = np.random.normal(size=arr1.shape) + arr2[:] = arr1 + exe1.forward(is_train=True) + exe1.backward(exe1.outputs[0]) + exe2.forward(is_train=True) + exe2.backward(exe2.outputs[0]) + + for arr1, arr2 in zip(exe1.outputs + exe1.grad_arrays, exe2.outputs + exe2.grad_arrays): + np.testing.assert_allclose(arr1.asnumpy(), arr2.asnumpy(), rtol=1e-3, atol=1e-4) + def gen_broadcast_data(idx): # Manually set test cases binary_op_data_shape = np.array( @@ -947,23 +1050,41 @@ def gen_broadcast_data(idx): def gen_broadcast_data_int(idx): d = gen_broadcast_data(idx); - return [np.round(d[0]*100), np.round(d[1]*100)] + return [np.round(d[0]*100).astype(int), np.round(d[1]*100).astype(int)] def gen_binary_data(dummy): ndim = np.random.randint(1, 6) shape = np.random.randint(1, 6, size=(ndim,)) return [np.random.random(shape), np.random.random(shape)] -def check_binary_op_forward(symbol, baseline, gen_data): +def gen_binary_data_int(dummy): + d = gen_binary_data(dummy); + return [np.round(d[0]*100).astype(int), np.round(d[1]*100).astype(int)] + +def check_binary_op_forward(symbol, baseline, gen_data, rtol=1e-3, atol=1e-5): sample_num = 200 for i in range(sample_num): d = gen_data(i) x = baseline(d[0], d[1]) y = symbol.bind(default_context(), args={'a': mx.nd.array(d[0]), 'b' : mx.nd.array(d[1])}) y.forward(is_train=True) - assert_allclose(x, y.outputs[0].asnumpy(), rtol=1e-3, atol=1e-5) - -def check_binary_op_backward(symbol, baseline, gen_data): + y = y.outputs[0].asnumpy() + idx = np.abs(x-y) > atol+rtol*np.abs(x) + if idx.any(): + print('found precision problem') + d[0] = np.broadcast_to(d[0], x.shape) + d[1] = np.broadcast_to(d[1], x.shape) + print('a: {}'.format(d[0][idx])) + print('b: {}'.format(d[1][idx])) + import struct + print('a hex: {}'.format(struct.pack('d', d[0][idx]).encode('hex'))) + print('b hex: {}'.format(struct.pack('d', np.broadcast_to(d[1], x.shape)[idx]).encode('hex'))) + print('in baseline(a, b): {}'.format(x[idx])) + print('in symbol(a, b): {}'.format(y[idx])) + print('diff: {}'.format(np.abs(x-y)[idx] - atol-rtol*np.abs(x)[idx])) + assert_allclose(y, x, rtol=rtol, atol=atol) + +def check_binary_op_backward(symbol, baseline, gen_data, rtol=1e-3, atol=1e-5): sample_num = 200 for i in range(sample_num): d = gen_data(i) @@ -986,8 +1107,8 @@ def reduce_op(shape, x): args_grad=[y_1, y_2]) y.forward(is_train=True) y.backward([mx.nd.array(out)]) - assert_allclose(x_1, y_1.asnumpy(), rtol=1e-3, atol=1e-5) - assert_allclose(x_2, y_2.asnumpy(), rtol=1e-3, atol=1e-5) + assert_allclose(y_1.asnumpy(), x_1, rtol=rtol, atol=atol) + assert_allclose(y_2.asnumpy(), x_2, rtol=rtol, atol=atol) def test_binary_op(): a = mx.sym.Variable('a') @@ -1013,6 +1134,16 @@ def test_bdiv(a, b): check_binary_op_forward(c, lambda a, b: a / b, gen_binary_data) check_binary_op_backward(c, lambda g_out, a, b: (g_out / b, - g_out * a / (b * b)), gen_binary_data) + def test_bmod(a, b): + c = a % b + check_binary_op_forward(c, lambda a, b: a % b, gen_binary_data) + check_binary_op_backward(c, lambda g_out, a, b: (g_out, - g_out * (a // b)), gen_binary_data) + + def test_bmod_int(a, b): + c = mx.sym.cast(a, dtype='int32') % mx.sym.cast(b, dtype='int32') + check_binary_op_forward(c, lambda a, b: a % b, gen_binary_data_int) + check_binary_op_backward(c, lambda g_out, a, b: (np.zeros_like(a), np.zeros_like(b)), gen_binary_data_int) + def test_bpow(a, b): c = a ** b check_binary_op_forward(c, lambda a, b: a ** b, gen_binary_data) @@ -1028,6 +1159,8 @@ def test_bneq(a, b): test_bminus(a, b) test_bmul(a, b) test_bdiv(a, b) + test_bmod(a, b) + test_bmod_int(a, b) test_bpow(a, b) test_bneq(a, b) @@ -1055,6 +1188,16 @@ def test_bdiv(a, b): check_binary_op_forward(c, lambda a, b: a / b, gen_broadcast_data) check_binary_op_backward(c, lambda g_out, a, b: (g_out / b, - g_out * a / (b * b)), gen_broadcast_data) + def test_bmod(a, b): + c = mx.sym.broadcast_mod(a, b) + check_binary_op_forward(c, lambda a, b: a % b, gen_broadcast_data, atol=1) + check_binary_op_backward(c, lambda g_out, a, b: (g_out, - g_out * (a // b)), gen_broadcast_data, atol=1) + + def test_bmod_int(a, b): + c = mx.sym.broadcast_mod(mx.sym.cast(a, dtype='int32'), mx.sym.cast(b, dtype='int32')) + check_binary_op_forward(c, lambda a, b: a % b, gen_broadcast_data_int) + check_binary_op_backward(c, lambda g_out, a, b: (np.zeros_like(a), np.zeros_like(b)), gen_broadcast_data_int) + def test_bpow(a, b): c = mx.sym.broadcast_power(a, b) check_binary_op_forward(c, lambda a, b: a ** b, gen_broadcast_data) @@ -1070,6 +1213,8 @@ def test_bequal(a, b): test_bminus(a, b) test_bmul(a, b) test_bdiv(a, b) + test_bmod(a, b) + test_bmod_int(a, b) test_bpow(a, b) test_bequal(a, b) @@ -1217,7 +1362,7 @@ def test_reshape_new(src_shape, shape_args, reverse, dst_shape): assert_allclose(exe.grad_arrays[0].asnumpy(), out_grad_npy.reshape((5, 4, 3, 7))) def test_reduce(): - sample_num = 200 + sample_num = 500 def test_reduce_inner(numpy_reduce_func, numpy_reduce_grad_func, mx_reduce_sym, nan_prob = 0): for i in range(sample_num): # Generate random data that has ndim between 1-7 and all the shape dims between 1-5 @@ -1226,6 +1371,7 @@ def test_reduce_inner(numpy_reduce_func, numpy_reduce_grad_func, mx_reduce_sym, shape = np.random.randint(1, 6, size=(ndim,)) axis_num = np.random.randint(0, ndim, size=1) axis_flags = np.random.randint(0, 2, size=ndim) + exclude = np.random.randint(0, 2) axes = [] for (axis, flag) in enumerate(axis_flags): if flag: @@ -1240,6 +1386,9 @@ def test_reduce_inner(numpy_reduce_func, numpy_reduce_grad_func, mx_reduce_sym, a = mx.symbol.Variable('a') if axes is None: b = mx_reduce_sym(a, keepdims=keepdims) + elif exclude and isinstance(axes, tuple) and len(axes) < ndim: + naxes = [i for i in range(ndim) if i not in axes] + b = mx_reduce_sym(a, axis=naxes, keepdims=keepdims, exclude=True) else: b = mx_reduce_sym(a, axis=axes, keepdims=keepdims) dat_npy = np.random.rand(*shape) @@ -1267,6 +1416,7 @@ def test_reduce_inner(numpy_reduce_func, numpy_reduce_grad_func, mx_reduce_sym, bc_grad_groundtruth = np.broadcast_to(grad_groundtruth, grad_nd.shape) equal_backward = almost_equal_ignore_nan(grad_nd.asnumpy(), bc_grad_groundtruth, 1E-4, 1E-4) assert equal_backward + test_reduce_inner(lambda data, axis, keepdims:np_reduce(data, axis, keepdims, np.sum), lambda outgrad, data, outdata, axis, keepdims, keepdim_shape: outgrad.reshape(keepdim_shape), @@ -1328,7 +1478,7 @@ def test_broadcasting_ele(sym_bcast): test_broadcasting_ele(sym_bcast_to) def test_transpose(): - for ndim in range(1, 6): + for ndim in range(1, 7): for t in range(5): dims = list(np.random.randint(1, 10, size=ndim)) axes = list(range(ndim)) @@ -1480,102 +1630,118 @@ def test_stn(): def test_dot(ctx=default_context()): np.random.seed(1234) + dtypes = ['float32', 'float64'] # Test normal dot. - for m in range(1, 5): - for k in range(1, 5): - for n in range(1, 5): - a_npy = np.random.normal(0, 1, (m, k)) - b_npy = np.random.normal(0, 1, (k, n)) - c_npy = np.empty((m, n)) - ograd_npy = np.random.normal(0, 1, (m, n)) - agrad_npy = np.empty((m, k)) - bgrad_npy = np.empty((k, n)) - c_npy[:, :] = np.dot(a_npy[:, :], b_npy[:, :]) - bgrad_npy[:, :] = np.dot(a_npy[:, :].T, ograd_npy[:, :]) - agrad_npy[:, :] = np.dot(ograd_npy[:, :], b_npy[:, :].T) - a = mx.sym.Variable('a') - b = mx.sym.Variable('b') - c = mx.sym.dot(a, b) - exe = c.simple_bind(ctx=ctx, a=a_npy.shape, b=b_npy.shape) - outputs = exe.forward(is_train=True, a=a_npy, b=b_npy) - assert_almost_equal(outputs[0].asnumpy(), c_npy, rtol=1e-3) - exe.backward(out_grads=[mx.nd.array(ograd_npy, ctx=exe._ctx)]) - assert_almost_equal(exe.grad_dict['a'].asnumpy(), agrad_npy, rtol=1e-3) - assert_almost_equal(exe.grad_dict['b'].asnumpy(), bgrad_npy, rtol=1e-3) + for data_type in dtypes: + for m in range(1, 5): + for k in range(1, 5): + for n in range(1, 5): + a_npy = np.random.normal(0, 1, (m, k)) + a_npy = a_npy.astype(data_type) + b_npy = np.random.normal(0, 1, (k, n)) + b_npy = b_npy.astype(data_type) + c_npy = np.empty((m, n), dtype=data_type) + ograd_npy = np.random.normal(0, 1, (m, n)) + ograd_npy = ograd_npy.astype(data_type) + agrad_npy = np.empty((m, k), dtype=data_type) + bgrad_npy = np.empty((k, n), dtype=data_type) + c_npy[:, :] = np.dot(a_npy[:, :], b_npy[:, :]) + bgrad_npy[:, :] = np.dot(a_npy[:, :].T, ograd_npy[:, :]) + agrad_npy[:, :] = np.dot(ograd_npy[:, :], b_npy[:, :].T) + a = mx.sym.Variable('a', dtype=data_type) + b = mx.sym.Variable('b', dtype=data_type) + c = mx.sym.dot(a, b) + exe = c.simple_bind(ctx=ctx, a=a_npy.shape, b=b_npy.shape) + outputs = exe.forward(is_train=True, a=a_npy, b=b_npy) + assert_almost_equal(outputs[0].asnumpy(), c_npy, rtol=1e-3) + exe.backward(out_grads=[mx.nd.array(ograd_npy, mx.cpu())]) + assert_almost_equal(exe.grad_dict['a'].asnumpy(), agrad_npy, rtol=1e-3) + assert_almost_equal(exe.grad_dict['b'].asnumpy(), bgrad_npy, rtol=1e-3) # Test dot with transpose flag using gradient checker. - def dot_sym(): - x = mx.sym.Variable('x') - y = mx.sym.Variable('y') + def dot_sym(data_type): + x = mx.sym.Variable('x', dtype=data_type) + y = mx.sym.Variable('y', dtype=data_type) return mx.sym.dot(x, y) - def dot_sym_xT(): - x = mx.sym.Variable('x') - y = mx.sym.Variable('y') + def dot_sym_xT(data_type): + x = mx.sym.Variable('x', dtype=data_type) + y = mx.sym.Variable('y', dtype=data_type) return mx.sym.dot(x, y, transpose_a=True) - def dot_sym_yT(): - x = mx.sym.Variable('x') - y = mx.sym.Variable('y') + def dot_sym_yT(data_type): + x = mx.sym.Variable('x', dtype=data_type) + y = mx.sym.Variable('y', dtype=data_type) return mx.sym.dot(x, y, transpose_b=True) - def dot_sym_xT_yT(): - x = mx.sym.Variable('x') - y = mx.sym.Variable('y') + def dot_sym_xT_yT(data_type): + x = mx.sym.Variable('x', dtype=data_type) + y = mx.sym.Variable('y', dtype=data_type) return mx.sym.dot(x, y, transpose_a=True, transpose_b=True) - for ashape, bshape in [((3, 4), (4, 5)), ((2,3,4), (4, 5, 6))]: - m1_npy = np.random.uniform(-1, 1, ashape) - m2_npy = np.random.uniform(-1, 1, bshape) - check_numeric_gradient(dot_sym(), [m1_npy, m2_npy], numeric_eps=1e-1, rtol=2e-2, atol=1e-3) - check_numeric_gradient(dot_sym_xT(), [m1_npy.T, m2_npy], numeric_eps=1e-1, rtol=2e-2, atol=1e-3) - check_numeric_gradient(dot_sym_yT(), [m1_npy, m2_npy.T], numeric_eps=1e-1, rtol=2e-2, atol=1e-3) - check_numeric_gradient(dot_sym_xT_yT(), [m1_npy.T, m2_npy.T], numeric_eps=1e-1, rtol=2e-2, atol=1e-3) + for data_type in dtypes: + for ashape, bshape in [((3, 4), (4, 5)), ((2, 3, 4), (4, 5, 6))]: + m1_npy = np.random.uniform(-1, 1, ashape) + m1_npy = m1_npy.astype(data_type) + m2_npy = np.random.uniform(-1, 1, bshape) + m2_npy = m2_npy.astype(data_type) + check_numeric_gradient(dot_sym(data_type), [m1_npy, m2_npy], numeric_eps=1e-1, rtol=2e-2, atol=1e-3) + check_numeric_gradient(dot_sym_xT(data_type), [m1_npy.T, m2_npy], numeric_eps=1e-1, rtol=2e-2, atol=1e-3) + check_numeric_gradient(dot_sym_yT(data_type), [m1_npy, m2_npy.T], numeric_eps=1e-1, rtol=2e-2, atol=1e-3) + check_numeric_gradient(dot_sym_xT_yT(data_type), [m1_npy.T, m2_npy.T], numeric_eps=1e-1, rtol=2e-2, atol=1e-3) def test_batch_dot(): - for batch_size in range(1, 5): - for m in range(1, 5): - for k in range(1, 5): - for n in range(1, 5): - transpose_a = (np.random.rand() > 0.5) - transpose_b = (np.random.rand() > 0.5) - a_npy = np.random.normal(0, 1, (batch_size, m, k)) - b_npy = np.random.normal(0, 1, (batch_size, k, n)) - c_npy = np.empty((batch_size, m, n)) - ograd_npy = np.random.normal(0, 1, (batch_size, m, n)) - agrad_npy = np.empty((batch_size, m, k)) - bgrad_npy = np.empty((batch_size, k, n)) - a_init_grad_npy = np.random.normal(size=(batch_size, m, k)) - b_init_grad_npy = np.random.normal(size=(batch_size, k, n)) - for i in range(batch_size): - c_npy[i, :, :] = np.dot(a_npy[i, :, :], b_npy[i, :, :]) - bgrad_npy[i, :, :] = np.dot(a_npy[i, :, :].T, ograd_npy[i, :, :]) - agrad_npy[i, :, :] = np.dot(ograd_npy[i, :, :], b_npy[i, :, :].T) - a = mx.sym.Variable('a') - b = mx.sym.Variable('b') - c = mx.sym.batch_dot(a, b, transpose_a=transpose_a, transpose_b=transpose_b) - if transpose_a: - a_npy = np.transpose(a_npy, axes=(0, 2, 1)) - agrad_npy = np.transpose(agrad_npy, axes=(0, 2, 1)) - a_init_grad_npy = np.transpose(a_init_grad_npy, axes=(0, 2, 1)) - if transpose_b: - b_npy = np.transpose(b_npy, axes=(0, 2, 1)) - bgrad_npy = np.transpose(bgrad_npy, axes=(0, 2, 1)) - b_init_grad_npy = np.transpose(b_init_grad_npy, axes=(0, 2, 1)) - exe = c.simple_bind(ctx=default_context(), - a=a_npy.shape, b=b_npy.shape, grad_req='write') - exe_add = c.simple_bind(ctx=default_context(), - a=a_npy.shape, b=b_npy.shape, grad_req='add') - exe_add.grad_dict['a'][:] = a_init_grad_npy - exe_add.grad_dict['b'][:] = b_init_grad_npy - outputs = exe.forward(is_train=True, a=a_npy, b=b_npy) - assert_almost_equal(outputs[0].asnumpy(), c_npy, rtol=1e-3, atol=1e-4) - exe.backward(out_grads=[mx.nd.array(ograd_npy, ctx=exe._ctx)]) - assert_almost_equal(exe.grad_dict['a'].asnumpy(), agrad_npy, rtol=1e-3, atol=1e-4) - assert_almost_equal(exe.grad_dict['b'].asnumpy(), bgrad_npy, rtol=1e-3, atol=1e-4) - exe_add.forward(is_train=True, a=a_npy, b=b_npy) - exe_add.backward(out_grads=[mx.nd.array(ograd_npy, ctx=exe._ctx)]) - assert_almost_equal(exe_add.grad_dict['a'].asnumpy(), - agrad_npy + a_init_grad_npy, rtol=1e-3, atol=1e-4) - assert_almost_equal(exe_add.grad_dict['b'].asnumpy(), - bgrad_npy + b_init_grad_npy, rtol=1e-3, atol=1e-4) + dtypes = ['float32', 'float64'] + + for data_type in dtypes: + for batch_size in range(1, 5): + for m in range(1, 5): + for k in range(1, 5): + for n in range(1, 5): + transpose_a = (np.random.rand() > 0.5) + transpose_b = (np.random.rand() > 0.5) + a_npy = np.random.normal(0, 1, (batch_size, m, k)) + a_npy = a_npy.astype(data_type) + b_npy = np.random.normal(0, 1, (batch_size, k, n)) + b_npy = b_npy.astype(data_type) + c_npy = np.empty((batch_size, m, n), dtype=data_type) + ograd_npy = np.random.normal(0, 1, (batch_size, m, n)) + ograd_npy = ograd_npy.astype(data_type) + agrad_npy = np.empty((batch_size, m, k), dtype=data_type) + bgrad_npy = np.empty((batch_size, k, n), dtype=data_type) + a_init_grad_npy = np.random.normal(size=(batch_size, m, k)) + a_init_grad_npy = a_npy.astype(data_type) + b_init_grad_npy = np.random.normal(size=(batch_size, k, n)) + b_init_grad_npy = b_npy.astype(data_type) + for i in range(batch_size): + c_npy[i, :, :] = np.dot(a_npy[i, :, :], b_npy[i, :, :]) + bgrad_npy[i, :, :] = np.dot(a_npy[i, :, :].T, ograd_npy[i, :, :]) + agrad_npy[i, :, :] = np.dot(ograd_npy[i, :, :], b_npy[i, :, :].T) + a = mx.sym.Variable('a', dtype=data_type) + b = mx.sym.Variable('b', dtype=data_type) + c = mx.sym.batch_dot(a, b, transpose_a=transpose_a, transpose_b=transpose_b) + if transpose_a: + a_npy = np.transpose(a_npy, axes=(0, 2, 1)) + agrad_npy = np.transpose(agrad_npy, axes=(0, 2, 1)) + a_init_grad_npy = np.transpose(a_init_grad_npy, axes=(0, 2, 1)) + if transpose_b: + b_npy = np.transpose(b_npy, axes=(0, 2, 1)) + bgrad_npy = np.transpose(bgrad_npy, axes=(0, 2, 1)) + b_init_grad_npy = np.transpose(b_init_grad_npy, axes=(0, 2, 1)) + exe = c.simple_bind(ctx=default_context(), + a=a_npy.shape, b=b_npy.shape, grad_req='write') + exe_add = c.simple_bind(ctx=default_context(), + a=a_npy.shape, b=b_npy.shape, grad_req='add') + exe_add.grad_dict['a'][:] = a_init_grad_npy + exe_add.grad_dict['b'][:] = b_init_grad_npy + outputs = exe.forward(is_train=True, a=a_npy, b=b_npy) + assert_almost_equal(outputs[0].asnumpy(), c_npy, rtol=1e-3, atol=1e-4) + exe.backward(out_grads=[mx.nd.array(ograd_npy, ctx=exe._ctx)]) + assert_almost_equal(exe.grad_dict['a'].asnumpy(), agrad_npy, rtol=1e-3, atol=1e-4) + assert_almost_equal(exe.grad_dict['b'].asnumpy(), bgrad_npy, rtol=1e-3, atol=1e-4) + exe_add.forward(is_train=True, a=a_npy, b=b_npy) + exe_add.backward(out_grads=[mx.nd.array(ograd_npy, ctx=exe._ctx)]) + assert_almost_equal(exe_add.grad_dict['a'].asnumpy(), + agrad_npy + a_init_grad_npy, rtol=1e-3, atol=1e-4) + assert_almost_equal(exe_add.grad_dict['b'].asnumpy(), + bgrad_npy + b_init_grad_npy, rtol=1e-3, atol=1e-4) def get_correlation(data1,data2,kernel_size,max_displacement,stride1,stride2,pad_size,is_multiply): @@ -1862,7 +2028,7 @@ def check_instance_norm_with_shape(shape, xpu): exec1 = Y.bind(xpu, args = {'X':x, 'G':gamma, 'B':beta}) exec1.forward(is_train=False) out = exec1.outputs[0].asnumpy() - assert_almost_equal(out, np_out, rtol=1e-4) + assert_almost_equal(out, np_out, rtol=1e-4, atol=1e-4) check_numeric_gradient(Y, {'X':x.asnumpy(), 'G':gamma.asnumpy(), 'B':beta.asnumpy()}, numeric_eps=1e-2, rtol=1e-2, atol=1e-2) @@ -1875,7 +2041,8 @@ def test_instance_normalization(): def check_l2_normalization(in_shape, mode, ctx=default_context(), norm_eps=1e-10): data = mx.symbol.Variable('data') out = mx.symbol.L2Normalization(data=data, mode=mode, eps=norm_eps) - np.random.seed() + # TODO(szha): Seeding this masks failures. We need to do a deep dive for failures without this seed. + np.random.seed(1234) in_data = np.random.uniform(-1, 1, in_shape) # calculate numpy results if mode == 'channel': @@ -1949,6 +2116,69 @@ def test_sequence_mask(): check_sequence_mask(shape1, default_context(), 2.1) check_sequence_mask(shape2, default_context(), 0.1) +def check_sequence_reverse(xpu): + + # sample data + arr = np.array( + [[[ 1., 2., 3.], + [ 4., 5., 6.]], + [[ 7., 8., 9.], + [ 10., 11., 12.]], + [[ 13., 14., 15.], + [ 16., 17., 18.]]]) + + arr1 = np.array( + [[[ 13., 14., 15.], + [ 16., 17., 18.]], + [[ 7., 8., 9.], + [ 10., 11., 12.]], + [[ 1., 2., 3.], + [ 4., 5., 6.]]]) + + arr2 = np.array( + [[[ 7., 8., 9.], + [ 10., 11., 12.]], + [[ 1., 2., 3.], + [ 4., 5., 6.]], + [[ 13., 14., 15.], + [ 16., 17., 18.]]]) + + arr3 = np.array( + [[[ 7., 8., 9.], + [ 16., 17., 18.]], + [[ 1., 2., 3.], + [ 10., 11., 12.]], + [[ 13., 14., 15.], + [ 4., 5., 6.]]]) + + def test_wrapper(arr, xpu, sequence_length=None, use_sequence_length=False): + # MxNet symbol creation + seq = mx.sym.Variable('seq') + if sequence_length and use_sequence_length: + seq_len = mx.sym.Variable('seq_len') + else: + # ensure that both are disabled, not just one + seq_len=None + use_sequence_length=False + rev = mx.sym.SequenceReverse(data=seq, sequence_length=seq_len, use_sequence_length=use_sequence_length) + # MxNet symbol execution + if sequence_length: + bound = rev.bind(xpu, {'seq': mx.nd.array(arr), 'seq_len': mx.nd.array(sequence_length)}) + else: + bound = rev.bind(xpu, {'seq': mx.nd.array(arr)}) + fwd = bound.forward() + return fwd[0].asnumpy() + + # test cases + assert_array_equal(test_wrapper(arr, xpu, use_sequence_length=False), arr1) + assert_array_equal(test_wrapper(arr, xpu, sequence_length=[3, 3], use_sequence_length=True), arr1) + assert_array_equal(test_wrapper(arr, xpu, sequence_length=[2, 2], use_sequence_length=True), arr2) + assert_array_equal(test_wrapper(arr, xpu, sequence_length=[2, 3], use_sequence_length=True), arr3) + + +def test_sequence_reverse(): + check_sequence_reverse(mx.cpu()) + def mathematical_core_binary(name, forward_mxnet_call, forward_numpy_call, @@ -3012,7 +3242,7 @@ def test_pick_helper(index_type=np.int32): test_pick_helper(np.int32) test_pick_helper(np.float32) - + def check_ctc_loss(acts, labels, loss_truth): in_var = mx.sym.Variable('input') labels_var = mx.sym.Variable('labels') @@ -3053,20 +3283,29 @@ def test_ctc_loss(): true_loss = np.array([7.3557, 5.4091], dtype=np.float32) # from Torch check_ctc_loss(acts2, labels2, true_loss) - -def test_quantization_op(): - min0 = mx.nd.array([0.0]) - max0 = mx.nd.array([1.0]) - a = mx.nd.array([[0.1392, 0.5928], [0.6027, 0.8579]]) - qa, min1, max1 = mx.contrib.nd.quantize(a, min0, max0, out_type='uint8') - a_ = mx.contrib.nd.dequantize(qa, min1, max1, out_type='float32') - qa_real = mx.nd.array([[35, 151], [154, 219]]) - a_real = mx.nd.array([[0.13725491, 0.59215689], [0.60392159, 0.8588236]]) - - assert same(qa.asnumpy(), qa_real.asnumpy()) - assert same(a_.asnumpy(), a_real.asnumpy()) +def test_quantization_op(): + min0 = mx.nd.array([0.0]) + max0 = mx.nd.array([1.0]) + a = mx.nd.array([[0.1392, 0.5928], [0.6027, 0.8579]]) + qa, min1, max1 = mx.contrib.nd.quantize(a, min0, max0, out_type='uint8') + a_ = mx.contrib.nd.dequantize(qa, min1, max1, out_type='float32') + + qa_real = mx.nd.array([[35, 151], [154, 219]]) + a_real = mx.nd.array([[0.13725491, 0.59215689], [0.60392159, 0.8588236]]) + + assert same(qa.asnumpy(), qa_real.asnumpy()) + assert same(a_.asnumpy(), a_real.asnumpy()) + +def test_reciprocal_op(): + data_tmp = np.random.rand(3, 4) * 10 - 5 + # Avoid possible division by 0 errors + data_tmp[data_tmp == 0] = 1.0 + data = mx.symbol.Variable('data') + test = mx.sym.reciprocal(data) + check_numeric_gradient(test, [data_tmp]) + check_symbolic_forward(test, [data_tmp], [np.reciprocal(data_tmp)]) def test_custom_op(): class Sqr(mx.operator.CustomOp): @@ -3108,73 +3347,403 @@ def create_operator(self, ctx, shapes, dtypes): x = mx.nd.array(np.random.uniform(-1, 1, size=(4, 10))) check_numeric_gradient(op, [x]) + dx = mx.nd.zeros_like(x) + mx.contrib.autograd.mark_variables([x], [dx]) + with mx.contrib.autograd.train_section(): + y = mx.nd.Custom(x, op_type='sqr') + y.backward() + + +def test_psroipooling(): + for num_rois in [1, 2]: + for num_classes, num_group in itertools.product([2, 3], [2, 3]): + for image_height, image_width in itertools.product([168, 224], [168, 224]): + for grad_nodes in [['im_data']]: + spatial_scale = 0.0625 + feat_height = np.int(image_height * spatial_scale) + feat_width = np.int(image_width * spatial_scale) + im_data = np.random.rand(1, num_classes*num_group*num_group, feat_height, feat_width) + rois_data = np.zeros([num_rois, 5]) + rois_data[:, [1,3]] = np.sort(np.random.rand(num_rois, 2)*(image_width-1)) + rois_data[:, [2,4]] = np.sort(np.random.rand(num_rois, 2)*(image_height-1)) + + im_data_var = mx.symbol.Variable(name="im_data") + rois_data_var = mx.symbol.Variable(name="rois_data") + op = mx.contrib.sym.PSROIPooling(data=im_data_var, rois=rois_data_var, spatial_scale=spatial_scale, + group_size=num_group, pooled_size=num_group, + output_dim=num_classes, name='test_op') + rtol, atol = 1e-2, 1e-3 + # By now we only have gpu implementation + if mx.Context.default_ctx.device_type == 'gpu': + check_numeric_gradient(op, [im_data, rois_data], rtol=rtol, atol=atol, + grad_nodes=grad_nodes, ctx=mx.gpu(0)) + +def test_deformable_convolution(): + for num_batch in [1, 2]: + for num_channel_data, num_deformable_group in itertools.product([4, 8], [1, 2]): + for input_height, input_width in itertools.product([5, 6], [5, 6]): + for dilate in [(1, 1), (2, 2)]: + for grad_nodes in [['im_data'], ['offset_data'], ['weight']]: + output_height = input_height + output_width = input_width + im_data = np.random.rand(num_batch, num_channel_data, input_height, input_width) + offset_data = \ + np.random.rand(num_batch, num_deformable_group * 3 * 3 * 2, output_height, output_width)\ + * 0.8 + 0.1 + + weight = np.random.normal(0, 0.001, (num_channel_data, num_channel_data, 3, 3)) + bias = np.zeros(num_channel_data) + + im_data_var = mx.symbol.Variable(name="im_data") + offset_data_var = mx.symbol.Variable(name="offset_data") + weight_var = mx.symbol.Variable(name="weight") + bias_var = mx.symbol.Variable(name="bias") + op = mx.contrib.sym.DeformableConvolution(name='test_op', data=im_data_var, + offset=offset_data_var, + weight=weight_var, bias=bias_var, + num_filter=num_channel_data, pad=dilate, + kernel=(3, 3), stride=(1, 1), dilate=dilate, + num_deformable_group=num_deformable_group) + if grad_nodes[0] == 'offset_data': + # wider tolerance needed for coordinate differential + rtol, atol = 1.0, 1e-2 + else: + rtol, atol = 0.05, 1e-3 + # By now we only have gpu implementation + if mx.Context.default_ctx.device_type == 'gpu': + check_numeric_gradient(op, [im_data, offset_data, weight, bias], rtol=rtol, atol=atol, + grad_nodes=grad_nodes, ctx=mx.gpu(0)) + + +def test_deformable_psroipooling(): + for num_rois in [1, 2]: + for num_classes, num_group in itertools.product([2, 3], [2, 3]): + for image_height, image_width in itertools.product([168, 224], [168, 224]): + for grad_nodes in [['im_data'], ['offset_data']]: + spatial_scale = 0.0625 + feat_height = np.int(image_height * spatial_scale) + feat_width = np.int(image_width * spatial_scale) + im_data = np.random.rand(1, num_classes*num_group*num_group, feat_height, feat_width) + rois_data = np.zeros([num_rois, 5]) + rois_data[:, [1,3]] = np.sort(np.random.rand(num_rois, 2)*(image_width-1)) + rois_data[:, [2,4]] = np.sort(np.random.rand(num_rois, 2)*(image_height-1)) + offset_data = np.random.rand(num_rois, 2*num_classes, num_group, num_group) * 0.1 + + im_data_var = mx.symbol.Variable(name="im_data") + rois_data_var = mx.symbol.Variable(name="rois_data") + offset_data_var = mx.symbol.Variable(name="offset_data") + op = mx.contrib.sym.DeformablePSROIPooling(data=im_data_var, rois=rois_data_var, + trans=offset_data_var, spatial_scale=spatial_scale, + sample_per_part=4, group_size=num_group, + pooled_size=num_group, output_dim=num_classes, + trans_std=0.1, no_trans=False, name='test_op') + if grad_nodes[0] == 'offset_data': + # wider tolerance needed for coordinate differential + rtol, atol = 1.0, 1e-2 + else: + rtol, atol = 1e-2, 1e-3 + # By now we only have gpu implementation + if mx.Context.default_ctx.device_type == 'gpu': + check_numeric_gradient(op, [im_data, rois_data, offset_data], rtol=rtol, atol=atol, + grad_nodes=grad_nodes, ctx=mx.gpu(0)) + + + +def test_laop(): + + # enable numerical checking of gradients + grad_check = 1 + + data1 = mx.symbol.Variable('data1') + data2 = mx.symbol.Variable('data2') + data3 = mx.symbol.Variable('data3') + data4 = mx.symbol.Variable('data4') + + # Test gemm separately from other la-operators. + shape1 = (2, 3) + shape2 = (3, 2) + shape3 = (3, 3) + shape4 = (2, 2) + #Ensure that ithis tests don't get changed by other calls to random. + np.random.seed(42) + data_in1 = np.random.uniform(1, 10, shape1) + data_in2 = np.random.uniform(1, 10, shape2) + data_in3 = np.random.uniform(1, 10, shape3) + data_in4 = np.random.uniform(1, 10, shape4) + # Check all transpositions of gemm operator. + data_in1_t = np.transpose(data_in1) + data_in2_t = np.transpose(data_in2) + res_gemm = 4*np.dot(data_in1,data_in2)+7*data_in4 + test_gemm = mx.sym.linalg_gemm(data1, data2, data3, alpha = 4, beta = 7) + check_symbolic_forward(test_gemm, [data_in1, data_in2, data_in4], [res_gemm]) + if grad_check == 1: + check_numeric_gradient(test_gemm, [data_in1, data_in2, data_in4], numeric_eps=1e-3, rtol=1e-1, atol=1e-1) + res_gemm = 4*np.dot(data_in1_t,data_in2_t)+7*data_in3 + test_gemm = mx.sym.linalg_gemm(data1, data2, data3, alpha = 4, beta = 7, transpose_a = 1, transpose_b = 1) + check_symbolic_forward(test_gemm, [data_in1, data_in2, data_in3], [res_gemm]) + if grad_check == 1: + check_numeric_gradient(test_gemm, [data_in1, data_in2, data_in3], numeric_eps=1e-3, rtol=1e-1, atol=1e-1) + res_gemm = 4*np.dot(data_in1_t,data_in1)+7*data_in3 + test_gemm = mx.sym.linalg_gemm(data1, data2, data3, alpha = 4, beta = 7, transpose_a = 1) + check_symbolic_forward(test_gemm, [data_in1, data_in1, data_in3], [res_gemm]) + if grad_check == 1: + check_numeric_gradient(test_gemm, [data_in1, data_in1, data_in3], numeric_eps=1e-3, rtol=1e-1, atol=1e-1) + res_gemm = 4*np.dot(data_in1,data_in1_t)+7*data_in4 + test_gemm = mx.sym.linalg_gemm(data1, data2, data3, alpha = 4, beta = 7, transpose_b = 1) + check_symbolic_forward(test_gemm, [data_in1, data_in1, data_in4], [res_gemm]) + if grad_check == 1: + check_numeric_gradient(test_gemm, [data_in1, data_in1, data_in4], numeric_eps=1e-3, rtol=1e-1, atol=1e-1) + + # Check batch of gemm. + a = np.tile(np.array(data_in1).flatten(),3) + a = np.reshape(a,(3,1,2,3)) + b = np.tile(np.array(data_in2).flatten(),3) + b = np.reshape(b,(3,1,3,2)) + c = np.tile(np.array(data_in4).flatten(),3) + c = np.reshape(c,(3,1,2,2)) + r = 4*np.dot(data_in1,data_in2)+7*data_in4 + r = np.tile(r.flatten(),3) + r = np.reshape(r,(3,1,2,2)) + test_gemm = mx.sym.linalg_gemm(data1, data2, data3, alpha = 4, beta = 7) + check_symbolic_forward(test_gemm, [a, b, c], [r]) + if grad_check == 1: + check_numeric_gradient(test_gemm, [a, b, c], numeric_eps=1e-3, rtol=1e-1, atol=1e-1) + + # Check gemm2 operator same way as gemm. + res_gemm = 4*np.dot(data_in1,data_in2) + test_gemm = mx.sym.linalg_gemm2(data1, data2, alpha = 4) + check_symbolic_forward(test_gemm, [data_in1, data_in2], [res_gemm]) + if grad_check == 1: + check_numeric_gradient(test_gemm, [data_in1, data_in2], numeric_eps=1e-3, rtol=1e-1, atol=1e-1) + res_gemm = 4*np.dot(data_in1_t, data_in2_t) + test_gemm = mx.sym.linalg_gemm2(data1, data2, alpha = 4, transpose_a = 1, transpose_b = 1) + check_symbolic_forward(test_gemm, [data_in1, data_in2], [res_gemm]) + if grad_check == 1: + check_numeric_gradient(test_gemm, [data_in1, data_in2], numeric_eps=1e-3, rtol=1e-1, atol=1e-1) + res_gemm = 4*np.dot(data_in1_t,data_in1) + test_gemm = mx.sym.linalg_gemm2(data1, data2, alpha = 4, transpose_a = 1) + check_symbolic_forward(test_gemm, [data_in1, data_in1], [res_gemm]) + if grad_check == 1: + check_numeric_gradient(test_gemm, [data_in1, data_in1], numeric_eps=1e-3, rtol=1e-1, atol=1e-1) + res_gemm = 4*np.dot(data_in1,data_in1_t) + test_gemm = mx.sym.linalg_gemm2(data1, data2, alpha = 4, transpose_b = 1) + check_symbolic_forward(test_gemm, [data_in1, data_in1], [res_gemm]) + if grad_check == 1: + check_numeric_gradient(test_gemm, [data_in1, data_in1], numeric_eps=1e-3, rtol=1e-1, atol=1e-1) + + # Check batch of gemm2. + a = np.tile(np.array(data_in1).flatten(),3) + a = np.reshape(a,(3,1,2,3)) + b = np.tile(np.array(data_in2).flatten(),3) + b = np.reshape(b,(3,1,3,2)) + r = 4*np.dot(data_in1,data_in2) + r = np.tile(r.flatten(),3) + r = np.reshape(r,(3,1,2,2)) + test_gemm = mx.sym.linalg_gemm2(data1, data2, alpha = 4) + check_symbolic_forward(test_gemm, [a, b], [r]) + if grad_check == 1: + check_numeric_gradient(test_gemm, [a, b], numeric_eps=1e-3, rtol=1e-1, atol=1e-1) + + # Now test all the other operators. + + # Tests with trivial 1x1 matrices. + shape = (4, 4, 1, 1 ) + data_in = np.random.uniform(1, 10, shape) + # test potrf + res_potrf = np.sqrt(data_in) + test_potrf = mx.sym.linalg_potrf(data1) + check_symbolic_forward(test_potrf, [data_in], [res_potrf]) + if grad_check == 1: + check_numeric_gradient(test_potrf, [data_in]) + # test potri + ones = mx.nd.ones(shape).asnumpy() + res_potri = np.divide(ones,data_in*data_in) + test_potri = mx.sym.linalg_potri(data1) + check_symbolic_forward(test_potri, [data_in], [res_potri]) + if grad_check == 1: + check_numeric_gradient(test_potri, [data_in], atol = 0.01, rtol = 1.5) + # test trsm + trian_in = data_in *7 + test_trsm = mx.sym.linalg_trsm(data1,data2,alpha = 7) + check_symbolic_forward(test_trsm, [trian_in,data_in], [ones]) + if grad_check == 1: + check_numeric_gradient(test_trsm, [trian_in,data_in], atol = 0.02, rtol = 2.0) + # test trmm + trian_in = np.divide(ones,trian_in) + test_trmm = mx.sym.linalg_trmm(data1,data2,alpha = 7, transpose = 1, rightside = 1) + check_symbolic_forward(test_trmm, [trian_in,data_in], [ones]) + if grad_check == 1: + check_numeric_gradient(test_trmm, [trian_in,data_in], atol = 0.02, rtol = 2.0) + # test sumlogdiag + res_sumlogdiag = np.reshape(np.log(data_in),(4,4)) + test_sumlogdiag = mx.sym.linalg_sumlogdiag(data1) + check_symbolic_forward(test_sumlogdiag, [data_in], [res_sumlogdiag]) + if grad_check == 1: + check_numeric_gradient(test_sumlogdiag, [data_in], atol = 0.01, rtol = 2.0) + + # more elaborate example of cholesky factorization + matrix = [ 9, 3, -6, 12, 3, 26, -7, -11, -6, -7, 9, 7, 12, -11, 7, 65 ] + trian = [ 3, 0, 0, 0, 1, 5, 0, 0, -2, -1, 2, 0, 4, -3, 6, 2 ] + pow = [ 2, 1, 1, 1, 1, 4, 1, 1, 1, 1, 8, 1, 1, 1, 1, 16 ] + inv = [ 2.98333, 0.01667, 2.65, -0.83333, 0.01667, 0.05, 0.05, 0, 2.65, 0.05, 2.5, -0.75, -0.83333, 0, -0.75, 0.25 ] + ident = [ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 ] + + # Tests for numeric gradients for potrf/potri/trmm/trsm are suppressed by default + # as they are very volatile and may often report false negatives which + # have to be excluded by manual inspection. + grad_check = 0 + + # test potrf + a = np.tile(np.array(matrix),3) + a = np.reshape(a,(3,1,4,4)) + r = np.tile(np.array(trian),3) + r = np.reshape(r,(3,1,4,4)) + check_symbolic_forward(test_potrf, [a], [r]) + if grad_check == 1: + check_numeric_gradient(test_potrf, [a], numeric_eps=1e-3, rtol=1e-2, atol=1e-1) + + #test potri + a = np.tile(np.array(trian),3) + a = np.reshape(a,(3,1,4,4)) + r = np.tile(np.array(inv),3) + r = np.reshape(r,(3,1,4,4)) + check_symbolic_forward(test_potri, [a], [r], atol=0.01) + if grad_check == 1: + check_numeric_gradient(test_potri, [a], numeric_eps=1e-3, rtol=1e-2, atol=1e-1) + + #test trsm + a = np.tile(np.array(trian),3) + a = np.reshape(a,(3,1,4,4)) + b = np.tile(np.array(matrix),3) + b = np.reshape(b,(3,1,4,4)) + r = 7*np.transpose(np.reshape(np.array(trian),(4,4))) + r = np.reshape(np.tile(np.reshape(r,(16)),3),(3,1,4,4)) + check_symbolic_forward(test_trsm, [a,b], [r]) + if grad_check == 1: + check_numeric_gradient(test_trsm, [a,b], numeric_eps=1e-3, rtol=1e-2, atol=1e-1) + + test_trsm2 = mx.sym.linalg_trsm(data1,data2,alpha = -2, rightside = 1, transpose = 1) + r = -2*np.reshape(np.array(trian),(4,4)) + r = np.reshape(np.tile(np.reshape(r,(16)),3),(3,1,4,4)) + check_symbolic_forward(test_trsm2, [a,b], [r]) + if grad_check == 1: + check_numeric_gradient(test_trsm2, [a,b], numeric_eps=1e-3, rtol=1e-2, atol=1e-1) + + test_trsm3 = mx.sym.linalg_trsm(data1,data2,alpha = 0.50, transpose = 1) + b = np.transpose(np.reshape(np.array(trian),(4,4))) + b = np.reshape(np.tile(np.reshape(b,(16)),3),(3,1,4,4)) + r = 0.5*np.reshape(np.array(ident),(4,4)) + r = np.reshape(np.tile(np.reshape(r,(16)),3),(3,1,4,4)) + check_symbolic_forward(test_trsm3, [a,b], [r]) + if grad_check == 1: + check_numeric_gradient(test_trsm3, [a,b], numeric_eps=1e-3, rtol=1e-2, atol=1e-1) + + test_trsm4 = mx.sym.linalg_trsm(data1,data2,alpha = -0.5, rightside = 1) + b = np.tile(np.array(trian),3) + b = np.reshape(b,(3,1,4,4)) + r = -0.5*np.reshape(np.array(ident),(4,4)) + r = np.reshape(np.tile(np.reshape(r,(16)),3),(3,1,4,4)) + check_symbolic_forward(test_trsm4, [a,b], [r]) + if grad_check == 1: + check_numeric_gradient(test_trsm4, [a,b], numeric_eps=1e-3, rtol=1e-2, atol=1e-1) + + #test trmm + a = np.tile(np.array(trian),3) + a = np.reshape(a,(3,1,4,4)) + b = np.tile(np.array(matrix),3) + b = np.reshape(b,(3,1,4,4)) + r = 7*np.dot(np.reshape(np.array(matrix),(4,4)),np.transpose(np.reshape(np.array(trian),(4,4)))) + r = np.reshape(np.tile(np.reshape(r,(16)),3),(3,1,4,4)) + check_symbolic_forward(test_trmm, [a,b], [r]) + if grad_check == 1: + check_numeric_gradient(test_trmm, [a,b], numeric_eps=1e-3, rtol=1e-2, atol=1e-1) + + test_trmm2 = mx.sym.linalg_trmm(data1,data2,alpha = -2) + r = -2*np.dot(np.reshape(np.array(trian),(4,4)),np.reshape(np.array(matrix),(4,4))) + r = np.reshape(np.tile(np.reshape(r,(16)),3),(3,1,4,4)) + check_symbolic_forward(test_trmm2, [a,b], [r]) + if grad_check == 1: + check_numeric_gradient(test_trmm2, [a,b], numeric_eps=1e-3, rtol=1e-2, atol=1e-1) + + test_trmm3 = mx.sym.linalg_trmm(data1,data2,rightside = 1) + r = np.dot(np.reshape(np.array(matrix),(4,4)),np.reshape(np.array(trian),(4,4))) + r = np.reshape(np.tile(np.reshape(r,(16)),3),(3,1,4,4)) + check_symbolic_forward(test_trmm3, [a,b], [r]) + if grad_check == 1: + check_numeric_gradient(test_trmm3, [a,b], numeric_eps=1e-3, rtol=1e-2, atol=1e-1) + + test_trmm4 = mx.sym.linalg_trmm(data1,data2,alpha = 1.2,transpose = 1) + r = 1.2*np.dot(np.transpose(np.reshape(np.array(trian),(4,4))),np.reshape(np.array(matrix),(4,4))) + r = np.reshape(np.tile(np.reshape(r,(16)),3),(3,1,4,4)) + check_symbolic_forward(test_trmm4, [a,b], [r]) + if grad_check == 1: + check_numeric_gradient(test_trmm4, [a,b], numeric_eps=1e-3, rtol=1e-2, atol=1e-1) + + # test sumlogdiag + a = np.array(pow) + a = np.tile(a,3) + a = np.reshape(a,(3,1,4,4)) + r = 10*np.log(np.array([2])) + r = np.tile(r,3) + r = np.reshape(r,(3)) + check_symbolic_forward(test_sumlogdiag, [a], [r]) + if grad_check == 1: + check_numeric_gradient(test_sumlogdiag, [a]) + + +def test_stack(): + for _ in range(100): + ndim = random.randint(1, 5) + axis = random.randint(0, ndim) + if random.randint(0, 1): + axis = axis - ndim - 1 + nin = random.randint(1, 3) + dshape = [random.randint(1, 5) for _ in range(ndim)] + inputs = [np.random.uniform(size=dshape) for _ in range(nin)] + output = np.stack(inputs, axis=axis) + sym_ins = [mx.sym.var('x%d'%i) for i in range(nin)] + out = mx.sym.stack(*sym_ins, axis=axis) + check_symbolic_forward(out, inputs, [output]) + check_numeric_gradient(out, inputs) + + +def test_dropout(): + # test dropout + x = mx.sym.var('data') + y = mx.sym.Dropout(x, p=0.5) + exe = y.simple_bind(ctx=default_context(), data=(10, 10)) + + exe.arg_arrays[0][:] = 1 + exe.forward(is_train=True) + assert exe.outputs[0].asnumpy().max() == 2 + assert exe.outputs[0].asnumpy().min() == 0 + exe.backward([mx.nd.ones((10, 10))]) + assert (exe.grad_arrays[0].asnumpy() == exe.outputs[0].asnumpy()).all() + + exe.forward(is_train=False) + assert (exe.outputs[0].asnumpy() == exe.arg_arrays[0].asnumpy()).all() + exe.backward([mx.nd.ones((10, 10))], is_train=False) + assert (exe.grad_arrays[0].asnumpy() == exe.arg_arrays[0].asnumpy()).all() + + # test permanent dropout + x = mx.sym.var('data') + y = mx.sym.Dropout(x, p=0.5, mode='always') + exe = y.simple_bind(ctx=default_context(), data=(10, 10)) + + exe.arg_arrays[0][:] = 1 + exe.forward(is_train=True) + assert exe.outputs[0].asnumpy().max() == 2 + assert exe.outputs[0].asnumpy().min() == 0 + exe.backward([mx.nd.ones((10, 10))]) + assert (exe.grad_arrays[0].asnumpy() == exe.outputs[0].asnumpy()).all() + + exe.forward(is_train=False) + assert exe.outputs[0].asnumpy().max() == 2 + assert exe.outputs[0].asnumpy().min() == 0 + exe.backward([mx.nd.ones((10, 10))], is_train=False) + assert (exe.grad_arrays[0].asnumpy() == exe.outputs[0].asnumpy()).all() + if __name__ == '__main__': - test_custom_op() - test_log_softmax() - test_new_softmax() - test_pick() - test_l2_normalization() - test_sequence_mask() - test_roipooling() - test_batchnorm_training() - test_order() - test_grid_generator() - test_dot() - test_cast() - test_clip() - test_index2d() - test_scalarop() - test_reduce() - test_init() - test_expand_dims() - test_slice_axis() - test_softmax() - test_broadcast_binary_op() - test_flip() - test_crop() - test_transpose() - test_convolution_grouping() - test_nearest_upsampling() - test_binary_op_duplicate_input() - test_elementwise_sum() - test_concat() - test_slice_channel() - test_regression() - test_python_op() - test_swapaxes() - test_scalar_pow() - test_symbol_pow() - test_pow_fn() - test_embedding() - test_rsqrt_cos_sin() - test_maximum_minimum() - test_maximum_minimum_scalar() - test_abs() - test_round_ceil_floor() - test_deconvolution() - check_softmax_with_ignore_label(default_context()) - test_convolution_dilated_impulse_response() - test_reshape() - test_broadcast() - test_stn() - test_batch_dot() - test_correlation() - test_support_vector_machine_l1_svm() - test_support_vector_machine_l2_svm() - test_pad() - test_instance_normalization() - test_mathematical() - test_special_functions_using_scipy() - test_blockgrad() - test_take() - test_bilinear_sampler() - test_binary_logic() - test_repeat() - test_tile() - test_one_hot() - test_where() - test_ctc_loss() - test_quantization_op() - test_relu() - test_sigmoid() + import nose + nose.runmodule() diff --git a/tests/python/unittest/test_optimizer.py b/tests/python/unittest/test_optimizer.py index 11ca7bed1743..3b3b92b372d8 100644 --- a/tests/python/unittest/test_optimizer.py +++ b/tests/python/unittest/test_optimizer.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import numpy as np import mxnet as mx import math @@ -30,9 +47,9 @@ def test_lr_wd_mult(): assert not mx.test_utils.almost_equal(args1['fc2_weight'], args2['fc2_weight'], 1e-1) -def compare_optimizer(opt1, opt2, shape): - w1 = mx.random.uniform(shape=shape, ctx=default_context()) - g1 = mx.random.uniform(shape=shape, ctx=default_context()) +def compare_optimizer(opt1, opt2, shape, dtype): + w1 = mx.random.uniform(shape=shape, ctx=default_context(), dtype=dtype) + g1 = mx.random.uniform(shape=shape, ctx=default_context(), dtype=dtype) w2 = w1.copyto(default_context()) g2 = g1.copyto(default_context()) @@ -41,22 +58,25 @@ def compare_optimizer(opt1, opt2, shape): state2 = opt2.create_state(0, w2) if state1 is not None and state2 is not None: for s1, s2, in zip(state1, state2): - assert(same(s1.asnumpy(), s2.asnumpy())) + if s1 is not None or s2 is not None: + assert(same(s1.asnumpy(), s2.asnumpy())) opt1.update(0, w1, g1, state1) opt2.update(0, w2, g2, state2) if state1 is not None and state2 is not None: for s1, s2, in zip(state1, state2): - assert_almost_equal(s1.asnumpy(), s2.asnumpy(), rtol=1e-4, atol=1e-5) + if s1 is not None or s2 is not None: + assert_almost_equal(s1.asnumpy(), s2.asnumpy(), rtol=1e-4, atol=1e-5) assert_almost_equal(w1.asnumpy(), w2.asnumpy(), rtol=1e-4, atol=1e-5) # SGD class PySGD(mx.optimizer.Optimizer): """python reference implemenation of sgd""" - def __init__(self, learning_rate=0.01, momentum=0.0, **kwargs): + def __init__(self, learning_rate=0.01, momentum=0.0, multi_precision=False, **kwargs): super(PySGD, self).__init__(learning_rate=learning_rate, **kwargs) self.momentum = momentum + self.multi_precision = multi_precision def create_state(self, index, weight): """Create additional optimizer state: momentum @@ -67,10 +87,18 @@ def create_state(self, index, weight): The weight data """ - if self.momentum == 0.0: - return None + momentum = None + weight_master_copy = None + do_multi_precision = self.multi_precision and weight.dtype == np.float16 + if do_multi_precision: + if self.momentum != 0.0: + momentum = mx.nd.zeros(weight.shape, weight.context, dtype=np.float32) + weight_master_copy = array(weight, ctx=weight.context, dtype=np.float32) + return (momentum, weight_master_copy) else: - return mx.nd.zeros(weight.shape, weight.context, dtype=weight.dtype) + if self.momentum != 0.0: + momentum = mx.nd.zeros(weight.shape, weight.context, dtype=weight.dtype) + return momentum def update(self, index, weight, grad, state): """Update the parameters. @@ -92,43 +120,72 @@ def update(self, index, weight, grad, state): lr = self._get_lr(index) wd = self._get_wd(index) self._update_count(index) - - if self.momentum == 0.0: - if self.clip_gradient is not None: - weight[:] = ((1 - lr*wd)*weight - - lr*mx.nd.clip(grad*self.rescale_grad, -self.clip_gradient, self.clip_gradient)) + use_multi_precision = isinstance(state, list) or isinstance(state, tuple) + + if not use_multi_precision: + if self.momentum == 0.0: + if self.clip_gradient is not None: + weight[:] = ((1 - lr*wd)*weight - + lr*mx.nd.clip(grad*self.rescale_grad, -self.clip_gradient, self.clip_gradient)) + else: + weight[:] = (1 - lr*wd)*weight - lr*self.rescale_grad*grad else: - weight[:] = (1 - lr*wd)*weight - lr*self.rescale_grad*grad + mom = state + if self.clip_gradient is not None: + mom[:] = (self.momentum*mom - lr*wd*weight - + lr*mx.nd.clip(grad*self.rescale_grad, -self.clip_gradient, self.clip_gradient)) + weight += mom + else: + mom[:] = self.momentum*mom - lr*wd*weight - lr*self.rescale_grad*grad + weight += mom else: - mom = state - if self.clip_gradient is not None: - mom[:] = (self.momentum*mom - lr*wd*weight - - lr*mx.nd.clip(grad*self.rescale_grad, -self.clip_gradient, self.clip_gradient)) - weight += mom + grad32 = array(grad, ctx=grad.context, dtype=np.float32) + mom = state[0] + weight32 = state[1] + if self.momentum == 0.0: + if self.clip_gradient is not None: + weight32[:] = ((1 - lr*wd)*weight32 - + lr*mx.nd.clip(grad32*self.rescale_grad, -self.clip_gradient, self.clip_gradient)) + else: + weight32[:] = (1 - lr*wd)*weight32 - lr*self.rescale_grad*grad32 else: - mom[:] = self.momentum*mom - lr*wd*weight - lr*self.rescale_grad*grad - weight += mom + if self.clip_gradient is not None: + mom[:] = (self.momentum*mom - lr*wd*weight32 - + lr*mx.nd.clip(grad32*self.rescale_grad, -self.clip_gradient, self.clip_gradient)) + weight32 += mom + else: + mom[:] = self.momentum*mom - lr*wd*weight32 - lr*self.rescale_grad*grad32 + weight32 += mom + tmp = weight32.astype(weight.dtype) + tmp.copyto(weight) def test_sgd(): mx.random.seed(0) opt1 = PySGD opt2 = mx.optimizer.SGD shape = (3, 4, 5) - kwargs = [{}, {'momentum': 0.9}, - {'clip_gradient': 0.5}, - {'clip_gradient': 0.4, 'rescale_grad': 0.14}, - {'rescale_grad': 0.8}, - {'clip_gradient': 0.5, 'wd': 0.07}, - {'clip_gradient': 0.4, 'rescale_grad': 0.14, 'wd': 0.03}, - {'rescale_grad': 0.8, 'wd': 0.05}, - {'clip_gradient': 0.5, 'momentum': 0.9}, - {'clip_gradient': 0.4, 'rescale_grad': 0.14, 'momentum': 0.9}, - {'rescale_grad': 0.8, 'momentum': 0.9}, - {'clip_gradient': 0.5, 'wd': 0.07, 'momentum': 0.9}, - {'clip_gradient': 0.4, 'rescale_grad': 0.14, 'wd': 0.03, 'momentum': 0.9}, - {'rescale_grad': 0.8, 'wd': 0.05, 'momentum': 0.9}] - for kwarg in kwargs: - compare_optimizer(opt1(**kwarg), opt2(**kwarg), shape) + mom_options = [{}, {'momentum': 0.9}] + cg_options = [{}, {'clip_gradient': 0.4}, {'clip_gradient': 0.5}] + rg_options = [{}, {'rescale_grad': 0.14}, {'rescale_grad': 0.8}] + wd_options = [{}, {'wd': 0.03}, {'wd': 0.05}, {'wd': 0.07}] + mp_options = [{}, {'multi_precision': False}, {'multi_precision': True}] + for dtype in [np.float16, np.float32, np.float64]: + for mom_option in mom_options: + for cg_option in cg_options: + for rg_option in rg_options: + for wd_option in wd_options: + for mp_option in mp_options: + kwarg = {} + kwarg.update(mom_option) + kwarg.update(cg_option) + kwarg.update(rg_option) + kwarg.update(wd_option) + kwarg.update(mp_option) + if (dtype == np.float16 and + ('multi_precision' not in kwarg or + not kwarg['multi_precision'])): + continue + compare_optimizer(opt1(**kwarg), opt2(**kwarg), shape, dtype) # ADAM @@ -208,7 +265,7 @@ def test_adam(): {'clip_gradient': 0.4, 'rescale_grad': 0.14, 'wd': 0.03}, {'rescale_grad': 0.8, 'wd': 0.05}] for kwarg in kwargs: - compare_optimizer(opt1(**kwarg), opt2(**kwarg), shape) + compare_optimizer(opt1(**kwarg), opt2(**kwarg), shape, np.float32) # RMSProp class PyRMSProp(mx.optimizer.Optimizer): @@ -348,7 +405,7 @@ def test_rms(): {'clip_gradient': 0.4, 'rescale_grad': 0.14, 'wd': 0.03, 'centered': True, 'clip_weights': 0.01}, {'rescale_grad': 0.8, 'wd': 0.05, 'centered': True, 'clip_weights': 0.01}] for kwarg in kwargs: - compare_optimizer(opt1(**kwarg), opt2(**kwarg), shape) + compare_optimizer(opt1(**kwarg), opt2(**kwarg), shape, np.float32) if __name__ == '__main__': test_adam() diff --git a/tests/python/unittest/test_profiler.py b/tests/python/unittest/test_profiler.py index 9a0deabdd9f8..724ed3a38790 100644 --- a/tests/python/unittest/test_profiler.py +++ b/tests/python/unittest/test_profiler.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + from __future__ import print_function import mxnet as mx from mxnet import profiler diff --git a/tests/python/unittest/test_random.py b/tests/python/unittest/test_random.py index a3f911cba358..6b8311c145f5 100644 --- a/tests/python/unittest/test_random.py +++ b/tests/python/unittest/test_random.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import os import mxnet as mx import numpy as np @@ -170,6 +187,30 @@ def test_random(): check_with_device(mx.context.current_context(), 'float64') +def test_sample_multinomial(): + x = mx.nd.array([[0,1,2,3,4],[4,3,2,1,0]])/10.0 + dx = mx.nd.ones_like(x) + mx.contrib.autograd.mark_variables([x], [dx]) + with mx.autograd.record(): + y, prob = mx.nd.sample_multinomial(x, shape=1000, get_prob=True) + r = prob * 5 + r.backward() + + y = y.asnumpy() + x = x.asnumpy() + for i in range(x.shape[0]): + + freq = np.bincount(y[i], minlength=5)/1000.0*x[i].sum() + mx.test_utils.assert_almost_equal(freq, x[i], rtol=0.25) + rprob = x[i][y[i]]/x[i].sum() + mx.test_utils.assert_almost_equal(np.log(rprob), prob.asnumpy()[i]) + + real_dx = np.zeros((5,)) + for j in range(1000): + real_dx[y[i][j]] += 5.0 / rprob[j] + mx.test_utils.assert_almost_equal(real_dx, dx.asnumpy()[i]) + if __name__ == '__main__': test_random() + test_sample_multinomial() diff --git a/tests/python/unittest/test_recordio.py b/tests/python/unittest/test_recordio.py index f4489bdfe641..7de582e236dd 100644 --- a/tests/python/unittest/test_recordio.py +++ b/tests/python/unittest/test_recordio.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # pylint: skip-file import sys import mxnet as mx @@ -68,4 +85,4 @@ def test_recordio_pack_label(): if __name__ == '__main__': test_recordio_pack_label() test_recordio() - test_indexed_recordio() \ No newline at end of file + test_indexed_recordio() diff --git a/tests/python/unittest/test_rnn.py b/tests/python/unittest/test_rnn.py index 903ce013e8f0..9fe22ae72df6 100644 --- a/tests/python/unittest/test_rnn.py +++ b/tests/python/unittest/test_rnn.py @@ -1,8 +1,84 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import mxnet as mx import numpy as np from numpy.testing import assert_allclose +def test_deprecated(): + class RNNCell(mx.rnn.BaseRNNCell): + """Simple recurrent neural network cell + + Parameters + ---------- + num_hidden : int + number of units in output symbol + activation : str or Symbol, default 'tanh' + type of activation function + prefix : str, default 'rnn_' + prefix for name of layers + (and name of weight if params is None) + params : RNNParams or None + container for weight sharing between cells. + created if None. + """ + def __init__(self, num_hidden, activation='tanh', prefix='rnn_', params=None): + super(RNNCell, self).__init__(prefix=prefix, params=params) + self._num_hidden = num_hidden + self._activation = activation + self._iW = self.params.get('i2h_weight') + self._iB = self.params.get('i2h_bias') + self._hW = self.params.get('h2h_weight') + self._hB = self.params.get('h2h_bias') + + @property + def state_info(self): + return [{'shape': (0, self._num_hidden), '__layout__': 'NC'}] + + @property + def _gate_names(self): + return ('',) + + def __call__(self, inputs, states): + self._counter += 1 + name = '%st%d_'%(self._prefix, self._counter) + i2h = mx.symbol.FullyConnected(data=inputs, weight=self._iW, bias=self._iB, + num_hidden=self._num_hidden, + name='%si2h'%name) + h2h = mx.symbol.FullyConnected(data=states[0], weight=self._hW, bias=self._hB, + num_hidden=self._num_hidden, + name='%sh2h'%name) + output = self._get_activation(i2h + h2h, self._activation, + name='%sout'%name) + + return output, [output] + + cell = RNNCell(100, prefix='rnn_') + inputs = [mx.sym.Variable('rnn_t%d_data'%i) for i in range(3)] + outputs, _ = cell.unroll(3, inputs) + outputs = mx.sym.Group(outputs) + assert sorted(cell.params._params.keys()) == ['rnn_h2h_bias', 'rnn_h2h_weight', 'rnn_i2h_bias', 'rnn_i2h_weight'] + assert outputs.list_outputs() == ['rnn_t0_out_output', 'rnn_t1_out_output', 'rnn_t2_out_output'] + + args, outs, auxs = outputs.infer_shape(rnn_t0_data=(10,50), rnn_t1_data=(10,50), rnn_t2_data=(10,50)) + assert outs == [(10, 100), (10, 100), (10, 100)] + + def test_rnn(): cell = mx.rnn.RNNCell(100, prefix='rnn_') inputs = [mx.sym.Variable('rnn_t%d_data'%i) for i in range(3)] @@ -67,13 +143,11 @@ def test_residual(): outputs = mx.sym.Group(outputs) assert sorted(cell.params._params.keys()) == \ ['rnn_h2h_bias', 'rnn_h2h_weight', 'rnn_i2h_bias', 'rnn_i2h_weight'] - assert outputs.list_outputs() == \ - ['rnn_t0_out_plus_residual_output', 'rnn_t1_out_plus_residual_output'] + # assert outputs.list_outputs() == \ + # ['rnn_t0_out_plus_residual_output', 'rnn_t1_out_plus_residual_output'] args, outs, auxs = outputs.infer_shape(rnn_t0_data=(10, 50), rnn_t1_data=(10, 50)) assert outs == [(10, 50), (10, 50)] - print(args) - print(outputs.list_arguments()) outputs = outputs.eval(rnn_t0_data=mx.nd.ones((10, 50)), rnn_t1_data=mx.nd.ones((10, 50)), rnn_i2h_weight=mx.nd.zeros((150, 50)), @@ -85,6 +159,38 @@ def test_residual(): assert np.array_equal(outputs[1].asnumpy(), expected_outputs) +def test_residual_bidirectional(): + cell = mx.rnn.ResidualCell( + mx.rnn.BidirectionalCell( + mx.rnn.GRUCell(25, prefix='rnn_l_'), + mx.rnn.GRUCell(25, prefix='rnn_r_'))) + + inputs = [mx.sym.Variable('rnn_t%d_data'%i) for i in range(2)] + outputs, _ = cell.unroll(2, inputs, merge_outputs=False) + outputs = mx.sym.Group(outputs) + assert sorted(cell.params._params.keys()) == \ + ['rnn_l_h2h_bias', 'rnn_l_h2h_weight', 'rnn_l_i2h_bias', 'rnn_l_i2h_weight', + 'rnn_r_h2h_bias', 'rnn_r_h2h_weight', 'rnn_r_i2h_bias', 'rnn_r_i2h_weight'] + # assert outputs.list_outputs() == \ + # ['bi_t0_plus_residual_output', 'bi_t1_plus_residual_output'] + + args, outs, auxs = outputs.infer_shape(rnn_t0_data=(10, 50), rnn_t1_data=(10, 50)) + assert outs == [(10, 50), (10, 50)] + outputs = outputs.eval(rnn_t0_data=mx.nd.ones((10, 50))+5, + rnn_t1_data=mx.nd.ones((10, 50))+5, + rnn_l_i2h_weight=mx.nd.zeros((75, 50)), + rnn_l_i2h_bias=mx.nd.zeros((75,)), + rnn_l_h2h_weight=mx.nd.zeros((75, 25)), + rnn_l_h2h_bias=mx.nd.zeros((75,)), + rnn_r_i2h_weight=mx.nd.zeros((75, 50)), + rnn_r_i2h_bias=mx.nd.zeros((75,)), + rnn_r_h2h_weight=mx.nd.zeros((75, 25)), + rnn_r_h2h_bias=mx.nd.zeros((75,))) + expected_outputs = np.ones((10, 50))+5 + assert np.array_equal(outputs[0].asnumpy(), expected_outputs) + assert np.array_equal(outputs[1].asnumpy(), expected_outputs) + + def test_stack(): cell = mx.rnn.SequentialRNNCell() for i in range(5): @@ -145,12 +251,52 @@ def test_unfuse(): args, outs, auxs = outputs.infer_shape(rnn_t0_data=(10,50), rnn_t1_data=(10,50), rnn_t2_data=(10,50)) assert outs == [(10, 200), (10, 200), (10, 200)] +def test_convrnn(): + cell = mx.rnn.ConvRNNCell(input_shape = (1, 3, 16, 10), num_hidden=10, + h2h_kernel=(3, 3), h2h_dilate=(1, 1), + i2h_kernel=(3, 3), i2h_stride=(1, 1), + i2h_pad=(1, 1), i2h_dilate=(1, 1), + prefix='rnn_') + inputs = [mx.sym.Variable('rnn_t%d_data'%i) for i in range(3)] + outputs, _ = cell.unroll(3, inputs) + outputs = mx.sym.Group(outputs) + assert sorted(cell.params._params.keys()) == ['rnn_h2h_bias', 'rnn_h2h_weight', 'rnn_i2h_bias', 'rnn_i2h_weight'] + assert outputs.list_outputs() == ['rnn_t0_out_output', 'rnn_t1_out_output', 'rnn_t2_out_output'] + + args, outs, auxs = outputs.infer_shape(rnn_t0_data=(1, 3, 16, 10), rnn_t1_data=(1, 3, 16, 10), rnn_t2_data=(1, 3, 16, 10)) + assert outs == [(1, 10, 16, 10), (1, 10, 16, 10), (1, 10, 16, 10)] + +def test_convlstm(): + cell = mx.rnn.ConvLSTMCell(input_shape = (1, 3, 16, 10), num_hidden=10, + h2h_kernel=(3, 3), h2h_dilate=(1, 1), + i2h_kernel=(3, 3), i2h_stride=(1, 1), + i2h_pad=(1, 1), i2h_dilate=(1, 1), + prefix='rnn_') + inputs = [mx.sym.Variable('rnn_t%d_data'%i) for i in range(3)] + outputs, _ = cell.unroll(3, inputs) + outputs = mx.sym.Group(outputs) + assert sorted(cell.params._params.keys()) == ['rnn_h2h_bias', 'rnn_h2h_weight', 'rnn_i2h_bias', 'rnn_i2h_weight'] + assert outputs.list_outputs() == ['rnn_t0_out_output', 'rnn_t1_out_output', 'rnn_t2_out_output'] + + args, outs, auxs = outputs.infer_shape(rnn_t0_data=(1, 3, 16, 10), rnn_t1_data=(1, 3, 16, 10), rnn_t2_data=(1, 3, 16, 10)) + assert outs == [(1, 10, 16, 10), (1, 10, 16, 10), (1, 10, 16, 10)] + +def test_convgru(): + cell = mx.rnn.ConvGRUCell(input_shape = (1, 3, 16, 10), num_hidden=10, + h2h_kernel=(3, 3), h2h_dilate=(1, 1), + i2h_kernel=(3, 3), i2h_stride=(1, 1), + i2h_pad=(1, 1), i2h_dilate=(1, 1), + prefix='rnn_') + inputs = [mx.sym.Variable('rnn_t%d_data'%i) for i in range(3)] + outputs, _ = cell.unroll(3, inputs) + outputs = mx.sym.Group(outputs) + assert sorted(cell.params._params.keys()) == ['rnn_h2h_bias', 'rnn_h2h_weight', 'rnn_i2h_bias', 'rnn_i2h_weight'] + assert outputs.list_outputs() == ['rnn_t0_out_output', 'rnn_t1_out_output', 'rnn_t2_out_output'] + + args, outs, auxs = outputs.infer_shape(rnn_t0_data=(1, 3, 16, 10), rnn_t1_data=(1, 3, 16, 10), rnn_t2_data=(1, 3, 16, 10)) + assert outs == [(1, 10, 16, 10), (1, 10, 16, 10), (1, 10, 16, 10)] if __name__ == '__main__': - test_rnn() - test_lstm() - test_lstm_forget_bias() - test_gru() - test_stack() - test_bidirectional() - test_unfuse() + import nose + nose.runmodule() + diff --git a/tests/python/unittest/test_symbol.py b/tests/python/unittest/test_symbol.py index ab25f48eeb52..c570325a6b66 100644 --- a/tests/python/unittest/test_symbol.py +++ b/tests/python/unittest/test_symbol.py @@ -1,9 +1,27 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import copy import os import re import mxnet as mx import numpy as np from common import models +from mxnet.test_utils import discard_stderr import pickle as pkl def test_symbol_basic(): @@ -216,25 +234,18 @@ def test_zero_prop2(): exe.forward() exe.backward() - try: - y.simple_bind(ctx=mx.cpu(), x=(10, 10), idx=(10,), - type_dict={'x': np.float32, 'idx': np.int32}) - except: - return + # The following bind() should throw an exception. We discard the expected stderr + # output for this operation only in order to keep the test logs clean. + with discard_stderr(): + try: + y.simple_bind(ctx=mx.cpu(), x=(10, 10), idx=(10,), + type_dict={'x': np.float32, 'idx': np.int32}) + except: + return assert False + if __name__ == '__main__': - test_zero_prop2() - test_zero_prop() - test_blockgrad() - test_symbol_children() - test_load_000800() - test_symbol_infer_shape_var() - test_symbol_infer_shape() - test_symbol_infer_type() - test_symbol_internal() - test_symbol_basic() - test_symbol_compose() - test_symbol_saveload() - test_symbol_pickle() + import nose + nose.runmodule() diff --git a/tests/python/unittest/test_viz.py b/tests/python/unittest/test_viz.py index 79c86681035d..73cfa94ba030 100644 --- a/tests/python/unittest/test_viz.py +++ b/tests/python/unittest/test_viz.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import mxnet as mx def test_print_summary(): diff --git a/tests/travis/is_core_changed.sh b/tests/travis/is_core_changed.sh index 1e32d60b2fcc..7b9eb6123847 100755 --- a/tests/travis/is_core_changed.sh +++ b/tests/travis/is_core_changed.sh @@ -1,5 +1,23 @@ #!/bin/bash +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + + # this is a util script to test whether the "core" of # mxnet has changed. Please modify the regex patterns here # to ensure the components are covered if you add new "core" diff --git a/tests/travis/run_test.sh b/tests/travis/run_test.sh index cff4196b6043..fb1869f842b1 100755 --- a/tests/travis/run_test.sh +++ b/tests/travis/run_test.sh @@ -1,5 +1,23 @@ #!/bin/bash +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + + if ! tests/travis/is_core_changed.sh then exit 0 diff --git a/tests/travis/setup.sh b/tests/travis/setup.sh index ec071009bda5..94d674f3943e 100755 --- a/tests/travis/setup.sh +++ b/tests/travis/setup.sh @@ -1,5 +1,23 @@ #!/bin/bash +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + + if ! tests/travis/is_core_changed.sh then exit 0 diff --git a/tests/travis/travis_after_failure.sh b/tests/travis/travis_after_failure.sh index 5a3940a89b7a..50754c9546cd 100755 --- a/tests/travis/travis_after_failure.sh +++ b/tests/travis/travis_after_failure.sh @@ -1,5 +1,23 @@ #!/bin/bash +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + + if [ ${TASK} == "r_test" ]; then echo "Print the install log..." cat mxnet.Rcheck/*.out diff --git a/tools/accnn/acc_conv.py b/tools/accnn/acc_conv.py index 095e386beebc..07717c7b47c9 100644 --- a/tools/accnn/acc_conv.py +++ b/tools/accnn/acc_conv.py @@ -1,77 +1,94 @@ -import numpy as np -from scipy import linalg as LA -import mxnet as mx -import argparse -import utils - -def conv_vh_decomposition(model, args): - W = model.arg_params[args.layer+'_weight'].asnumpy() - N, C, y, x = W.shape - b = model.arg_params[args.layer+'_bias'].asnumpy() - W = W.transpose((1,2,0,3)).reshape((C*y, -1)) - - U, D, Q = np.linalg.svd(W, full_matrices=False) - sqrt_D = LA.sqrtm(np.diag(D)) - K = args.K - V = U[:,:K].dot(sqrt_D[:K, :K]) - H = Q.T[:,:K].dot(sqrt_D[:K, :K]) - V = V.T.reshape(K, C, y, 1) - b_1 = np.zeros((K, )) - H = H.reshape(N, x, 1, K).transpose((0,3,2,1)) - b_2 = b - - W1, b1, W2, b2 = V, b_1, H, b_2 - def sym_handle(data, node): - kernel = eval(node['param']['kernel']) - pad = eval(node['param']['pad']) - name = node['name'] - - name1 = name + '_v' - kernel1 = tuple((kernel[0], 1)) - pad1 = tuple((pad[0], 0)) - num_filter = W1.shape[0] - sym1 = mx.symbol.Convolution(data=data, kernel=kernel1, pad=pad1, num_filter=num_filter, name=name1) - - name2 = name + '_h' - kernel2 = tuple((1, kernel[1])) - pad2 = tuple((0, pad[1])) - num_filter = W2.shape[0] - sym2 = mx.symbol.Convolution(data=sym1, kernel=kernel2, pad=pad2, num_filter=num_filter, name=name2) - return sym2 - - def arg_handle(arg_shape_dic, arg_params): - name1 = args.layer + '_v' - name2 = args.layer + '_h' - weight1 = mx.ndarray.array(W1) - bias1 = mx.ndarray.array(b1) - weight2 = mx.ndarray.array(W2) - bias2 = mx.ndarray.array(b2) - assert weight1.shape == arg_shape_dic[name1+'_weight'], 'weight1' - assert weight2.shape == arg_shape_dic[name2+'_weight'], 'weight2' - assert bias1.shape == arg_shape_dic[name1+'_bias'], 'bias1' - assert bias2.shape == arg_shape_dic[name2+'_bias'], 'bias2' - - arg_params[name1 + '_weight'] = weight1 - arg_params[name1 + '_bias'] = bias1 - arg_params[name2 + '_weight'] = weight2 - arg_params[name2 + '_bias'] = bias2 - - new_model = utils.replace_conv_layer(args.layer, model, sym_handle, arg_handle) - return new_model - -def main(): - model = utils.load_model(args) - new_model = conv_vh_decomposition(model, args) - new_model.save(args.save_model) - -if __name__ == '__main__': - parser=argparse.ArgumentParser() - parser.add_argument('-m', '--model', help='the model to speed up') - parser.add_argument('-g', '--gpus', default='0', help='the gpus to be used in ctx') - parser.add_argument('--load-epoch',type=int,default=1) - parser.add_argument('--layer') - parser.add_argument('--K', type=int) - parser.add_argument('--save-model') - args = parser.parse_args() - main() - +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +import numpy as np +from scipy import linalg as LA +import mxnet as mx +import argparse +import utils + +def conv_vh_decomposition(model, args): + W = model.arg_params[args.layer+'_weight'].asnumpy() + N, C, y, x = W.shape + b = model.arg_params[args.layer+'_bias'].asnumpy() + W = W.transpose((1,2,0,3)).reshape((C*y, -1)) + + U, D, Q = np.linalg.svd(W, full_matrices=False) + sqrt_D = LA.sqrtm(np.diag(D)) + K = args.K + V = U[:,:K].dot(sqrt_D[:K, :K]) + H = Q.T[:,:K].dot(sqrt_D[:K, :K]) + V = V.T.reshape(K, C, y, 1) + b_1 = np.zeros((K, )) + H = H.reshape(N, x, 1, K).transpose((0,3,2,1)) + b_2 = b + + W1, b1, W2, b2 = V, b_1, H, b_2 + def sym_handle(data, node): + kernel = eval(node['param']['kernel']) + pad = eval(node['param']['pad']) + name = node['name'] + + name1 = name + '_v' + kernel1 = tuple((kernel[0], 1)) + pad1 = tuple((pad[0], 0)) + num_filter = W1.shape[0] + sym1 = mx.symbol.Convolution(data=data, kernel=kernel1, pad=pad1, num_filter=num_filter, name=name1) + + name2 = name + '_h' + kernel2 = tuple((1, kernel[1])) + pad2 = tuple((0, pad[1])) + num_filter = W2.shape[0] + sym2 = mx.symbol.Convolution(data=sym1, kernel=kernel2, pad=pad2, num_filter=num_filter, name=name2) + return sym2 + + def arg_handle(arg_shape_dic, arg_params): + name1 = args.layer + '_v' + name2 = args.layer + '_h' + weight1 = mx.ndarray.array(W1) + bias1 = mx.ndarray.array(b1) + weight2 = mx.ndarray.array(W2) + bias2 = mx.ndarray.array(b2) + assert weight1.shape == arg_shape_dic[name1+'_weight'], 'weight1' + assert weight2.shape == arg_shape_dic[name2+'_weight'], 'weight2' + assert bias1.shape == arg_shape_dic[name1+'_bias'], 'bias1' + assert bias2.shape == arg_shape_dic[name2+'_bias'], 'bias2' + + arg_params[name1 + '_weight'] = weight1 + arg_params[name1 + '_bias'] = bias1 + arg_params[name2 + '_weight'] = weight2 + arg_params[name2 + '_bias'] = bias2 + + new_model = utils.replace_conv_layer(args.layer, model, sym_handle, arg_handle) + return new_model + +def main(): + model = utils.load_model(args) + new_model = conv_vh_decomposition(model, args) + new_model.save(args.save_model) + +if __name__ == '__main__': + parser=argparse.ArgumentParser() + parser.add_argument('-m', '--model', help='the model to speed up') + parser.add_argument('-g', '--gpus', default='0', help='the gpus to be used in ctx') + parser.add_argument('--load-epoch',type=int,default=1) + parser.add_argument('--layer') + parser.add_argument('--K', type=int) + parser.add_argument('--save-model') + args = parser.parse_args() + main() + diff --git a/tools/accnn/acc_fc.py b/tools/accnn/acc_fc.py index dcc255452b1d..b66b328e5b7b 100644 --- a/tools/accnn/acc_fc.py +++ b/tools/accnn/acc_fc.py @@ -1,57 +1,74 @@ -import numpy as np -from scipy import linalg as LA -import mxnet as mx -import argparse -import utils -import pdb - -def fc_decomposition(model, args): - W = model.arg_params[args.layer+'_weight'].asnumpy() - b = model.arg_params[args.layer+'_bias'].asnumpy() - W = W.reshape((W.shape[0],-1)) - b = b.reshape((b.shape[0],-1)) - u, s, v = LA.svd(W, full_matrices=False) - s = np.diag(s) - t = u.dot(s.dot(v)) - rk = args.K - P = u[:,:rk] - Q = s[:rk,:rk].dot(v[:rk,:]) - - name1 = args.layer + '_red' - name2 = args.layer + '_rec' - def sym_handle(data, node): - W1, W2 = Q, P - sym1 = mx.symbol.FullyConnected(data=data, num_hidden=W1.shape[0], no_bias=True, name=name1) - sym2 = mx.symbol.FullyConnected(data=sym1, num_hidden=W2.shape[0], no_bias=False, name=name2) - return sym2 - - def arg_handle(arg_shape_dic, arg_params): - W1, W2 = Q, P - W1 = W1.reshape(arg_shape_dic[name1+'_weight']) - weight1 = mx.ndarray.array(W1) - W2 = W2.reshape(arg_shape_dic[name2+'_weight']) - b2 = b.reshape(arg_shape_dic[name2+'_bias']) - weight2 = mx.ndarray.array(W2) - bias2 = mx.ndarray.array(b2) - arg_params[name1 + '_weight'] = weight1 - arg_params[name2 + '_weight'] = weight2 - arg_params[name2 + '_bias'] = bias2 - - new_model = utils.replace_conv_layer(args.layer, model, sym_handle, arg_handle) - return new_model - -def main(): - model = utils.load_model(args) - new_model = fc_decomposition(model, args) - new_model.save(args.save_model) - -if __name__ == '__main__': - parser=argparse.ArgumentParser() - parser.add_argument('-m', '--model', help='the model to speed up') - parser.add_argument('-g', '--gpus', default='0', help='the gpus to be used in ctx') - parser.add_argument('--load-epoch',type=int,default=1) - parser.add_argument('--layer') - parser.add_argument('--K', type=int) - parser.add_argument('--save-model') - args = parser.parse_args() - main() +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +import numpy as np +from scipy import linalg as LA +import mxnet as mx +import argparse +import utils +import pdb + +def fc_decomposition(model, args): + W = model.arg_params[args.layer+'_weight'].asnumpy() + b = model.arg_params[args.layer+'_bias'].asnumpy() + W = W.reshape((W.shape[0],-1)) + b = b.reshape((b.shape[0],-1)) + u, s, v = LA.svd(W, full_matrices=False) + s = np.diag(s) + t = u.dot(s.dot(v)) + rk = args.K + P = u[:,:rk] + Q = s[:rk,:rk].dot(v[:rk,:]) + + name1 = args.layer + '_red' + name2 = args.layer + '_rec' + def sym_handle(data, node): + W1, W2 = Q, P + sym1 = mx.symbol.FullyConnected(data=data, num_hidden=W1.shape[0], no_bias=True, name=name1) + sym2 = mx.symbol.FullyConnected(data=sym1, num_hidden=W2.shape[0], no_bias=False, name=name2) + return sym2 + + def arg_handle(arg_shape_dic, arg_params): + W1, W2 = Q, P + W1 = W1.reshape(arg_shape_dic[name1+'_weight']) + weight1 = mx.ndarray.array(W1) + W2 = W2.reshape(arg_shape_dic[name2+'_weight']) + b2 = b.reshape(arg_shape_dic[name2+'_bias']) + weight2 = mx.ndarray.array(W2) + bias2 = mx.ndarray.array(b2) + arg_params[name1 + '_weight'] = weight1 + arg_params[name2 + '_weight'] = weight2 + arg_params[name2 + '_bias'] = bias2 + + new_model = utils.replace_conv_layer(args.layer, model, sym_handle, arg_handle) + return new_model + +def main(): + model = utils.load_model(args) + new_model = fc_decomposition(model, args) + new_model.save(args.save_model) + +if __name__ == '__main__': + parser=argparse.ArgumentParser() + parser.add_argument('-m', '--model', help='the model to speed up') + parser.add_argument('-g', '--gpus', default='0', help='the gpus to be used in ctx') + parser.add_argument('--load-epoch',type=int,default=1) + parser.add_argument('--layer') + parser.add_argument('--K', type=int) + parser.add_argument('--save-model') + args = parser.parse_args() + main() diff --git a/tools/accnn/accnn.py b/tools/accnn/accnn.py index 1af78ef880de..ec5b101838f7 100644 --- a/tools/accnn/accnn.py +++ b/tools/accnn/accnn.py @@ -1,38 +1,55 @@ -import mxnet as mx -import argparse -import utils -import acc_conv -import acc_fc -import rank_selection -import collections -import json -import sys - -parser = argparse.ArgumentParser() -parser.add_argument('-m', '--model', help='the model to speed up') -parser.add_argument('-g', '--gpus', default='0', help='the gpus will be used, e.g "0,1,2,3"') -parser.add_argument('--load-epoch',type=int, default=1, help="load the model on an epoch using the model-prefix") -parser.add_argument('--save-model', type=str, default='new-model', help='output model prefix') -parser.add_argument('--config', default=None, help='specify the config file') -parser.add_argument('--ratio', type=float, default=2, help='speed up ratio') -args = parser.parse_args() - -model = utils.load_model(args) -if args.config: - args.config = json.load(open(args.config, 'r')) -else: - config = {} - config['conv_params'] = rank_selection.get_ranksel(model, args.ratio) - config['fc_params'] = {} - json.dump(config, open('config-rksel-%.1f.json'%(args.ratio), 'w'), indent=2) - args.config = config - -new_model = model -Args = collections.namedtuple('ConvArgs', 'layer K') -for layer, K in args.config['conv_params'].items(): - arg = Args(layer=layer, K=K) - new_model = acc_conv.conv_vh_decomposition(new_model, arg) -for layer, K in args.config['fc_params'].items(): - arg = Args(layer=layer, K=K) - new_model = acc_fc.fc_decomposition(new_model, arg) -new_model.save(args.save_model, 1) +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +import mxnet as mx +import argparse +import utils +import acc_conv +import acc_fc +import rank_selection +import collections +import json +import sys + +parser = argparse.ArgumentParser() +parser.add_argument('-m', '--model', help='the model to speed up') +parser.add_argument('-g', '--gpus', default='0', help='the gpus will be used, e.g "0,1,2,3"') +parser.add_argument('--load-epoch',type=int, default=1, help="load the model on an epoch using the model-prefix") +parser.add_argument('--save-model', type=str, default='new-model', help='output model prefix') +parser.add_argument('--config', default=None, help='specify the config file') +parser.add_argument('--ratio', type=float, default=2, help='speed up ratio') +args = parser.parse_args() + +model = utils.load_model(args) +if args.config: + args.config = json.load(open(args.config, 'r')) +else: + config = {} + config['conv_params'] = rank_selection.get_ranksel(model, args.ratio) + config['fc_params'] = {} + json.dump(config, open('config-rksel-%.1f.json'%(args.ratio), 'w'), indent=2) + args.config = config + +new_model = model +Args = collections.namedtuple('ConvArgs', 'layer K') +for layer, K in args.config['conv_params'].items(): + arg = Args(layer=layer, K=K) + new_model = acc_conv.conv_vh_decomposition(new_model, arg) +for layer, K in args.config['fc_params'].items(): + arg = Args(layer=layer, K=K) + new_model = acc_fc.fc_decomposition(new_model, arg) +new_model.save(args.save_model, 1) diff --git a/tools/accnn/rank_selection.py b/tools/accnn/rank_selection.py index ee3eca91f934..66937b2859b9 100644 --- a/tools/accnn/rank_selection.py +++ b/tools/accnn/rank_selection.py @@ -1,87 +1,104 @@ -import numpy as np -import mxnet as mx -import json -import utils -import math -import sys - -def calc_complexity(ishape, node): - y, x = map(int, eval(node['param']['kernel'])) - N = int(node['param']['num_filter']) - C, Y, X = ishape - return x*(N+C)*X*Y, x*y*N*C*X*Y - -def calc_eigenvalue(model, node): - W = model.arg_params[node['name'] + '_weight'].asnumpy() - N, C, y, x = W.shape - W = W.transpose((1,2,0,3)).reshape((C*y, -1)) - U, D, Q = np.linalg.svd(W, full_matrices=False) - return D - -def get_ranksel(model, ratio): - conf = json.loads(model.symbol.tojson()) - _, output_shapes, _ = model.symbol.get_internals().infer_shape(data=(1,3,224,224)) - out_names = model.symbol.get_internals().list_outputs() - out_shape_dic = dict(zip(out_names, output_shapes)) - nodes = conf['nodes'] - nodes = utils.topsort(nodes) - C = [] - D = [] - S = [] - conv_names = [] - EC = 0 - for node in nodes: - if node['op'] == 'Convolution': - input_nodes = [nodes[int(j[0])] for j in node['inputs']] - data = [input_node for input_node in input_nodes\ - if not input_node['name'].startswith(node['name'])][0] - - if utils.is_input(data): - ishape = (3, 224, 224) - else: - ishape = out_shape_dic[data['name'] + '_output'][1:] - C.append(calc_complexity(ishape, node)) - D.append(int(node['param']['num_filter'])) - S.append(calc_eigenvalue(model, node)) - conv_names.append(node['name']) - EC += C[-1][1] - for s in S: - ss = sum(s) - for i in xrange(1, len(s)): - s[i] += s[i-1] - n = len(C) - EC /= ratio - dp = [{}, {}] - dpc = [{} for _ in xrange(n)] - now, nxt = 0, 1 - dp[now][0] = 0 - for i in xrange(n): - dp[nxt] = {} - sys.stdout.flush() - for now_c, now_v in dp[now].items(): - for d in xrange(min(len(S[i]), D[i])): - nxt_c = now_c + (d+1)*C[i][0] - if nxt_c > EC: - continue - nxt_v = dp[now][now_c] + math.log(S[i][d]) - if dp[nxt].has_key(nxt_c): - if nxt_v > dp[nxt][nxt_c]: - dp[nxt][nxt_c] = nxt_v - dpc[i][nxt_c] = (d,now_c) - else: - dp[nxt][nxt_c] = nxt_v - dpc[i][nxt_c] = (d,now_c) - now, nxt = nxt, now - maxv = -1e9 - target_c = 0 - for c,v in dp[now].items(): - assert c <= EC, 'False' - if v > maxv: - maxv = v - target_c = c - res = [0]*n - nowc = target_c - for i in xrange(n-1,-1,-1): - res[i] = dpc[i][nowc][0] + 1 - nowc = dpc[i][nowc][1] - return dict(zip(conv_names, res)) +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +import numpy as np +import mxnet as mx +import json +import utils +import math +import sys + +def calc_complexity(ishape, node): + y, x = map(int, eval(node['param']['kernel'])) + N = int(node['param']['num_filter']) + C, Y, X = ishape + return x*(N+C)*X*Y, x*y*N*C*X*Y + +def calc_eigenvalue(model, node): + W = model.arg_params[node['name'] + '_weight'].asnumpy() + N, C, y, x = W.shape + W = W.transpose((1,2,0,3)).reshape((C*y, -1)) + U, D, Q = np.linalg.svd(W, full_matrices=False) + return D + +def get_ranksel(model, ratio): + conf = json.loads(model.symbol.tojson()) + _, output_shapes, _ = model.symbol.get_internals().infer_shape(data=(1,3,224,224)) + out_names = model.symbol.get_internals().list_outputs() + out_shape_dic = dict(zip(out_names, output_shapes)) + nodes = conf['nodes'] + nodes = utils.topsort(nodes) + C = [] + D = [] + S = [] + conv_names = [] + EC = 0 + for node in nodes: + if node['op'] == 'Convolution': + input_nodes = [nodes[int(j[0])] for j in node['inputs']] + data = [input_node for input_node in input_nodes\ + if not input_node['name'].startswith(node['name'])][0] + + if utils.is_input(data): + ishape = (3, 224, 224) + else: + ishape = out_shape_dic[data['name'] + '_output'][1:] + C.append(calc_complexity(ishape, node)) + D.append(int(node['param']['num_filter'])) + S.append(calc_eigenvalue(model, node)) + conv_names.append(node['name']) + EC += C[-1][1] + for s in S: + ss = sum(s) + for i in xrange(1, len(s)): + s[i] += s[i-1] + n = len(C) + EC /= ratio + dp = [{}, {}] + dpc = [{} for _ in xrange(n)] + now, nxt = 0, 1 + dp[now][0] = 0 + for i in xrange(n): + dp[nxt] = {} + sys.stdout.flush() + for now_c, now_v in dp[now].items(): + for d in xrange(min(len(S[i]), D[i])): + nxt_c = now_c + (d+1)*C[i][0] + if nxt_c > EC: + continue + nxt_v = dp[now][now_c] + math.log(S[i][d]) + if dp[nxt].has_key(nxt_c): + if nxt_v > dp[nxt][nxt_c]: + dp[nxt][nxt_c] = nxt_v + dpc[i][nxt_c] = (d,now_c) + else: + dp[nxt][nxt_c] = nxt_v + dpc[i][nxt_c] = (d,now_c) + now, nxt = nxt, now + maxv = -1e9 + target_c = 0 + for c,v in dp[now].items(): + assert c <= EC, 'False' + if v > maxv: + maxv = v + target_c = c + res = [0]*n + nowc = target_c + for i in xrange(n-1,-1,-1): + res[i] = dpc[i][nowc][0] + 1 + nowc = dpc[i][nowc][1] + return dict(zip(conv_names, res)) diff --git a/tools/accnn/utils.py b/tools/accnn/utils.py index 4c0290a0643a..25fb18895620 100644 --- a/tools/accnn/utils.py +++ b/tools/accnn/utils.py @@ -1,101 +1,118 @@ -import mxnet as mx -import copy -import json -import ast - -def load_model(args): - devs = mx.cpu() if args.gpus == None else [mx.gpu(int(i)) for i in args.gpus.split(',')] - return mx.model.FeedForward.load(args.model, args.load_epoch, ctx=devs) - -def topsort(nodes): - n = len(nodes) - deg = [0]*n - g = [[] for _ in xrange(n)] - for i,node in enumerate(nodes): - if node.has_key('inputs'): - for j in node['inputs']: - deg[i] += 1 - g[j[0]].append(i) - from collections import deque - q = deque([i for i in xrange(n) if deg[i]==0]) - res = [] - for its in xrange(n): - i = q.popleft() - res.append(nodes[i]) - for j in g[i]: - deg[j] -= 1 - if deg[j] == 0: - q.append(j) - new_ids=dict([(node['name'],i) for i,node in enumerate(res)]) - for node in res: - if node.has_key('inputs'): - for j in node['inputs']: - j[0]=new_ids[nodes[j[0]]['name']] - return res - -def is_input(node): - name = node['name'] - return len(node['inputs']) == 0 and ('weight' not in name) and ('bias' not in name) and ('label' not in name) - -def sym_factory(node, data): - name = node['name'] - params = {} - if 'param' in node: - for k, v in node['param'].items(): - try: - params[k] = ast.literal_eval(v) - except ValueError, e: - params[k] = v - return getattr(mx.symbol, node['op'])(data=data, name=name, **params) - -def replace_conv_layer(layer_name, old_model, sym_handle, arg_handle): - conf = json.loads(old_model.symbol.tojson()) - sym_dict = {} - nodes = conf['nodes'] - nodes = topsort(nodes) - res_sym = None - new_model = old_model - for i,node in enumerate(nodes): - sym = None - if is_input(node): - sym = mx.symbol.Variable(name='data') - elif node['op'] != 'null': - input_nodes = [nodes[int(j[0])] for j in node['inputs']] - datas = [input_node['name'] for input_node in input_nodes\ - if not input_node['name'].startswith(node['name'])] - try: - data=sym_dict[datas[0]] - except Exception, e: - print 'can not find symbol %s'%(datas[0]) - raise e - if node['name'] == layer_name: - sym = sym_handle(data, node) - else: - sym = sym_factory(node, data) - if sym: - sym_dict[node['name']] = sym - res_sym = sym - - arg_params = copy.deepcopy(old_model.arg_params) - if layer_name: - arg_shapes, _, _ = res_sym.infer_shape(data=(1,3,224,224)) - arg_names = res_sym.list_arguments() - arg_shape_dic = dict(zip(arg_names, arg_shapes)) - try: - arg_handle(arg_shape_dic, arg_params) - except Exception, e: - raise Exception('Exception in arg_handle') - - new_model = mx.model.FeedForward( - symbol=res_sym, - ctx=old_model.ctx, - num_epoch=1, - epoch_size=old_model.epoch_size, - optimizer='sgd', - initializer=old_model.initializer, - numpy_batch_size=old_model.numpy_batch_size, - arg_params=arg_params, - aux_params=old_model.aux_params, - allow_extra_params=True, - begin_epoch=old_model.begin_epoch) - return new_model +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +import mxnet as mx +import copy +import json +import ast + +def load_model(args): + devs = mx.cpu() if args.gpus == None else [mx.gpu(int(i)) for i in args.gpus.split(',')] + return mx.model.FeedForward.load(args.model, args.load_epoch, ctx=devs) + +def topsort(nodes): + n = len(nodes) + deg = [0]*n + g = [[] for _ in xrange(n)] + for i,node in enumerate(nodes): + if node.has_key('inputs'): + for j in node['inputs']: + deg[i] += 1 + g[j[0]].append(i) + from collections import deque + q = deque([i for i in xrange(n) if deg[i]==0]) + res = [] + for its in xrange(n): + i = q.popleft() + res.append(nodes[i]) + for j in g[i]: + deg[j] -= 1 + if deg[j] == 0: + q.append(j) + new_ids=dict([(node['name'],i) for i,node in enumerate(res)]) + for node in res: + if node.has_key('inputs'): + for j in node['inputs']: + j[0]=new_ids[nodes[j[0]]['name']] + return res + +def is_input(node): + name = node['name'] + return len(node['inputs']) == 0 and ('weight' not in name) and ('bias' not in name) and ('label' not in name) + +def sym_factory(node, data): + name = node['name'] + params = {} + if 'param' in node: + for k, v in node['param'].items(): + try: + params[k] = ast.literal_eval(v) + except ValueError, e: + params[k] = v + return getattr(mx.symbol, node['op'])(data=data, name=name, **params) + +def replace_conv_layer(layer_name, old_model, sym_handle, arg_handle): + conf = json.loads(old_model.symbol.tojson()) + sym_dict = {} + nodes = conf['nodes'] + nodes = topsort(nodes) + res_sym = None + new_model = old_model + for i,node in enumerate(nodes): + sym = None + if is_input(node): + sym = mx.symbol.Variable(name='data') + elif node['op'] != 'null': + input_nodes = [nodes[int(j[0])] for j in node['inputs']] + datas = [input_node['name'] for input_node in input_nodes\ + if not input_node['name'].startswith(node['name'])] + try: + data=sym_dict[datas[0]] + except Exception, e: + print 'can not find symbol %s'%(datas[0]) + raise e + if node['name'] == layer_name: + sym = sym_handle(data, node) + else: + sym = sym_factory(node, data) + if sym: + sym_dict[node['name']] = sym + res_sym = sym + + arg_params = copy.deepcopy(old_model.arg_params) + if layer_name: + arg_shapes, _, _ = res_sym.infer_shape(data=(1,3,224,224)) + arg_names = res_sym.list_arguments() + arg_shape_dic = dict(zip(arg_names, arg_shapes)) + try: + arg_handle(arg_shape_dic, arg_params) + except Exception, e: + raise Exception('Exception in arg_handle') + + new_model = mx.model.FeedForward( + symbol=res_sym, + ctx=old_model.ctx, + num_epoch=1, + epoch_size=old_model.epoch_size, + optimizer='sgd', + initializer=old_model.initializer, + numpy_batch_size=old_model.numpy_batch_size, + arg_params=arg_params, + aux_params=old_model.aux_params, + allow_extra_params=True, + begin_epoch=old_model.begin_epoch) + return new_model diff --git a/tools/bandwidth/measure.py b/tools/bandwidth/measure.py index 749f258d98df..66ef7371f11e 100644 --- a/tools/bandwidth/measure.py +++ b/tools/bandwidth/measure.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + import os, sys curr_path = os.path.abspath(os.path.dirname(__file__)) sys.path.insert(0, os.path.join(curr_path, "../../python")) @@ -9,6 +26,7 @@ import numpy as np from importlib import import_module from collections import namedtuple +from functools import reduce logger = logging.getLogger() logger.setLevel(logging.INFO) diff --git a/tools/bandwidth/test_measure.py b/tools/bandwidth/test_measure.py index b490af1cb75c..375290fe6853 100644 --- a/tools/bandwidth/test_measure.py +++ b/tools/bandwidth/test_measure.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + """ test measure.py """ diff --git a/tools/caffe_converter/.gitignore b/tools/caffe_converter/.gitignore index 0447b0d4ac3a..322dff360126 100644 --- a/tools/caffe_converter/.gitignore +++ b/tools/caffe_converter/.gitignore @@ -1 +1,2 @@ model/ +Cat-hd-wallpapers.jpg diff --git a/tools/caffe_converter/caffe_parser.py b/tools/caffe_converter/caffe_parser.py index 45efe4715f03..2ff490c7c6aa 100644 --- a/tools/caffe_converter/caffe_parser.py +++ b/tools/caffe_converter/caffe_parser.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + """Parse caffe's protobuf """ import re @@ -12,7 +29,7 @@ raise ImportError('You used to compile with protoc --python_out=./ ./caffe.proto') use_caffe = False -from google.protobuf import text_format +from google.protobuf import text_format # pylint: disable=relative-import def read_prototxt(fname): """Return a caffe_pb2.NetParameter object that defined in a prototxt file diff --git a/tools/caffe_converter/caffe_proto_utils.py b/tools/caffe_converter/caffe_proto_utils.py new file mode 100644 index 000000000000..8d6183457637 --- /dev/null +++ b/tools/caffe_converter/caffe_proto_utils.py @@ -0,0 +1,204 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +"""Helper functions for parsing caffe prototxt into a workable DAG +""" + + +def process_network_proto(caffe_root, deploy_proto): + """ + Runs the caffe upgrade tool on the prototxt to create a prototxt in the latest format. + This enable us to work just with latest structures, instead of supporting all the variants + + :param caffe_root: link to caffe root folder, where the upgrade tool is located + :param deploy_proto: name of the original prototxt file + :return: name of new processed prototxt file + """ + processed_deploy_proto = deploy_proto + ".processed" + + from shutil import copyfile + copyfile(deploy_proto, processed_deploy_proto) + + # run upgrade tool on new file name (same output file) + import os + upgrade_tool_command_line = caffe_root + '/build/tools/upgrade_net_proto_text.bin ' \ + + processed_deploy_proto + ' ' + processed_deploy_proto + os.system(upgrade_tool_command_line) + + return processed_deploy_proto + + +class LayerRecord(object): + """ + A record which describe basic layer parameters + """ + + def __init__(self, layer_def): + + self.layer_def = layer_def + self.name = layer_def.name + self.type = layer_def.type + + # keep filter, stride and pad + if layer_def.type == 'Convolution': + if LayerRecord._is_iterable(layer_def.convolution_param.kernel_size): + self.filter = list(layer_def.convolution_param.kernel_size) + else: + self.filter = list([layer_def.convolution_param.kernel_size]) + if len(self.filter) == 1: + self.filter *= 2 + if LayerRecord._is_iterable(layer_def.convolution_param.pad): + self.pad = list(layer_def.convolution_param.pad) + else: + self.pad = list([layer_def.convolution_param.pad]) + if len(self.pad) == 0: + self.pad = [0, 0] + elif len(self.pad) == 1: + self.pad *= 2 + if LayerRecord._is_iterable(layer_def.convolution_param.stride): + self.stride = list(layer_def.convolution_param.stride) + else: + self.stride = list([layer_def.convolution_param.stride]) + if len(self.stride) == 0: + self.stride = [1, 1] + elif len(self.stride) == 1: + self.stride *= 2 + + elif layer_def.type == 'Pooling': + self.filter = [layer_def.pooling_param.kernel_size] + if len(self.filter) == 1: + self.filter *= 2 + self.pad = [layer_def.pooling_param.pad] + if len(self.pad) == 0: + self.pad = [0, 0] + elif len(self.pad) == 1: + self.pad *= 2 + self.stride = [layer_def.pooling_param.stride] + if len(self.stride) == 0: + self.stride = [1, 1] + elif len(self.stride) == 1: + self.stride *= 2 + + else: + self.filter = [0, 0] + self.pad = [0, 0] + self.stride = [1, 1] + + # keep tops + self.tops = list(layer_def.top) + + # keep bottoms + self.bottoms = list(layer_def.bottom) + + # list of parent layers + self.parents = [] + + # list of child layers + self.children = [] + + @staticmethod + def _is_iterable(obj): + return hasattr(obj, '__iter__') + +def read_network_dag(processed_deploy_prototxt): + """ + Reads from the caffe prototxt the network structure + :param processed_deploy_prototxt: name of prototxt to load, preferably the prototxt should + be processed before using a call to process_network_proto() + :return: network_def, layer_name_to_record, top_to_layers + network_def: caffe network structure, gives access to *all* the network information + layer_name_to_record: *ordered* dictionary which maps between layer name and a structure which + describes in a simple form the layer parameters + top_to_layers: dictionary which maps a blob name to an ordered list of layers which output it + when a top is used several times, like in inplace layhers, the list will contain all the layers + by order of appearance + """ + + from caffe.proto import caffe_pb2 + from google.protobuf import text_format # pylint: disable=relative-import + from collections import OrderedDict + + # load prototxt file + network_def = caffe_pb2.NetParameter() + with open(processed_deploy_prototxt, 'r') as proto_file: + text_format.Merge(str(proto_file.read()), network_def) + + # map layer name to layer record + layer_name_to_record = OrderedDict() + for layer_def in network_def.layer: + if (len(layer_def.include) == 0) or \ + (caffe_pb2.TEST in [item.phase for item in layer_def.include]): + + layer_name_to_record[layer_def.name] = LayerRecord(layer_def) + + top_to_layers = dict() + for layer in network_def.layer: + # no specific phase, or TEST phase is specifically asked for + if (len(layer.include) == 0) or (caffe_pb2.TEST in [item.phase for item in layer.include]): + for top in layer.top: + if top not in top_to_layers: + top_to_layers[top] = list() + top_to_layers[top].append(layer.name) + + # find parents and children of all layers + for child_layer_name in layer_name_to_record.keys(): # pylint: disable=too-many-nested-blocks + child_layer_def = layer_name_to_record[child_layer_name] + for bottom in child_layer_def.bottoms: + if bottom in top_to_layers: + for parent_layer_name in top_to_layers[bottom]: + if parent_layer_name in layer_name_to_record: + parent_layer_def = layer_name_to_record[parent_layer_name] + if parent_layer_def not in child_layer_def.parents: + child_layer_def.parents.append(parent_layer_def) + if child_layer_def not in parent_layer_def.children: + parent_layer_def.children.append(child_layer_def) + + # update filter, strid, pad for maxout "structures" + for layer_name in layer_name_to_record.keys(): + layer_def = layer_name_to_record[layer_name] + if layer_def.type == 'Eltwise' and \ + len(layer_def.parents) == 1 and \ + layer_def.parents[0].type == 'Slice' and \ + len(layer_def.parents[0].parents) == 1 and \ + layer_def.parents[0].parents[0].type in ['Convolution', 'InnerProduct']: + layer_def.filter = layer_def.parents[0].parents[0].filter + layer_def.stride = layer_def.parents[0].parents[0].stride + layer_def.pad = layer_def.parents[0].parents[0].pad + + return network_def, layer_name_to_record, top_to_layers + + +def read_caffe_mean(caffe_mean_file): + """ + Reads caffe formatted mean file + :param caffe_mean_file: path to caffe mean file, presumably with 'binaryproto' suffix + :return: mean image, converted from BGR to RGB format + """ + + import caffe_parser + import numpy as np + mean_blob = caffe_parser.caffe_pb2.BlobProto() + with open(caffe_mean_file, 'rb') as f: + mean_blob.ParseFromString(f.read()) + + img_mean_np = np.array(mean_blob.data) + img_mean_np = img_mean_np.reshape(mean_blob.channels, mean_blob.height, mean_blob.width) + + # swap channels from Caffe BGR to RGB + img_mean_np[[0, 2], :, :] = img_mean_np[[2, 0], :, :] + + return img_mean_np diff --git a/tools/caffe_converter/compare_layers.py b/tools/caffe_converter/compare_layers.py new file mode 100644 index 000000000000..12568ed2060a --- /dev/null +++ b/tools/caffe_converter/compare_layers.py @@ -0,0 +1,364 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +"""Test converted models layer by layer +""" +import os +import argparse +import logging +import mxnet as mx +import cv2 +import numpy as np + +logging.basicConfig(level=logging.INFO) + + +def read_image(img_path, image_dims=None, mean=None): + """ + Reads an image from file path or URL, optionally resizing to given image dimensions and + subtracting mean. + :param img_path: path to file, or url to download + :param image_dims: image dimensions to resize to, or None + :param mean: mean file to subtract, or None + :return: loaded image, in RGB format + """ + + import urllib + + filename = img_path.split("/")[-1] + if img_path.startswith('http'): + urllib.urlretrieve(img_path, filename) + img = cv2.imread(filename) + else: + img = cv2.imread(img_path) + + img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) + + if image_dims is not None: + img = cv2.resize(img, image_dims) # resize to image_dims to fit model + img = np.rollaxis(img, 2) # change to (c, h, w) order + img = img[np.newaxis, :] # extend to (n, c, h, w) + if mean is not None: + mean = np.array(mean) + if mean.shape == (3,): + mean = mean[np.newaxis, :, np.newaxis, np.newaxis] # extend to (n, c, 1, 1) + img = img.astype(np.float32) - mean # subtract mean + + return img + + +def _ch_dev(arg_params, aux_params, ctx): + """ + Changes device of given mxnet arguments + :param arg_params: arguments + :param aux_params: auxiliary parameters + :param ctx: new device context + :return: arguments and auxiliary parameters on new device + """ + new_args = dict() + new_auxs = dict() + for k, v in arg_params.items(): + new_args[k] = v.as_in_context(ctx) + for k, v in aux_params.items(): + new_auxs[k] = v.as_in_context(ctx) + return new_args, new_auxs + + +def convert_and_compare_caffe_to_mxnet(image_url, gpu, caffe_prototxt_path, caffe_model_path, + caffe_mean, mean_diff_allowed, max_diff_allowed): + """ + Run the layer comparison on a caffe model, given its prototxt, weights and mean. + The comparison is done by inferring on a given image using both caffe and mxnet model + :param image_url: image file or url to run inference on + :param gpu: gpu to use, -1 for cpu + :param caffe_prototxt_path: path to caffe prototxt + :param caffe_model_path: path to caffe weights + :param caffe_mean: path to caffe mean file + """ + + import caffe + from caffe_proto_utils import read_network_dag, process_network_proto, read_caffe_mean + from convert_model import convert_model + + if isinstance(caffe_mean, str): + caffe_mean = read_caffe_mean(caffe_mean) + elif caffe_mean is None: + pass + elif len(caffe_mean) == 3: + # swap channels from Caffe BGR to RGB + caffe_mean = caffe_mean[::-1] + + # get caffe root location, this is needed to run the upgrade network utility, so we only need + # to support parsing of latest caffe + caffe_root = os.path.dirname(os.path.dirname(caffe.__path__[0])) + caffe_prototxt_path = process_network_proto(caffe_root, caffe_prototxt_path) + + _, layer_name_to_record, top_to_layers = read_network_dag(caffe_prototxt_path) + + caffe.set_mode_cpu() + caffe_net = caffe.Net(caffe_prototxt_path, caffe_model_path, caffe.TEST) + + image_dims = tuple(caffe_net.blobs['data'].shape)[2:4] + + logging.info('getting image %s', image_url) + img_rgb = read_image(image_url, image_dims, caffe_mean) + img_bgr = img_rgb[:, ::-1, :, :] + + caffe_net.blobs['data'].reshape(*img_bgr.shape) + caffe_net.blobs['data'].data[...] = img_bgr + _ = caffe_net.forward() + + # read sym and add all outputs + sym, arg_params, aux_params, _ = convert_model(caffe_prototxt_path, caffe_model_path) + sym = sym.get_internals() + + # now mxnet + if gpu < 0: + ctx = mx.cpu(0) + else: + ctx = mx.gpu(gpu) + + arg_params, aux_params = _ch_dev(arg_params, aux_params, ctx) + arg_params["data"] = mx.nd.array(img_rgb, ctx) + arg_params["prob_label"] = mx.nd.empty((1,), ctx) + exe = sym.bind(ctx, arg_params, args_grad=None, grad_req="null", aux_states=aux_params) + exe.forward(is_train=False) + + compare_layers_from_nets(caffe_net, arg_params, aux_params, exe, layer_name_to_record, + top_to_layers, mean_diff_allowed, max_diff_allowed) + + return + + +def _bfs(root_node, process_node): + """ + Implementation of Breadth-first search (BFS) on caffe network DAG + :param root_node: root node of caffe network DAG + :param process_node: function to run on each node + """ + + from collections import deque + + seen_nodes = set() + next_nodes = deque() + + seen_nodes.add(root_node) + next_nodes.append(root_node) + + while next_nodes: + current_node = next_nodes.popleft() + + # process current node + process_node(current_node) + + for child_node in current_node.children: + if child_node not in seen_nodes: + seen_nodes.add(child_node) + next_nodes.append(child_node) + + +def compare_layers_from_nets(caffe_net, arg_params, aux_params, exe, layer_name_to_record, + top_to_layers, mean_diff_allowed, max_diff_allowed): + """ + Compare layer by layer of a caffe network with mxnet network + :param caffe_net: loaded caffe network + :param arg_params: arguments + :param aux_params: auxiliary parameters + :param exe: mxnet model + :param layer_name_to_record: map between caffe layer and information record + :param top_to_layers: map between caffe blob name to layers which outputs it (including inplace) + :param mean_diff_allowed: mean difference allowed between caffe blob and mxnet blob + :param max_diff_allowed: max difference allowed between caffe blob and mxnet blob + """ + + import re + + log_format = ' {0:<40} {1:<40} {2:<8} {3:>10} {4:>10} {5:<1}' + + compare_layers_from_nets.is_first_convolution = True + + def _compare_blob(caf_blob, mx_blob, caf_name, mx_name, blob_type, note): + diff = np.abs(mx_blob - caf_blob) + diff_mean = diff.mean() + diff_max = diff.max() + logging.info(log_format.format(caf_name, mx_name, blob_type, '%4.5f' % diff_mean, + '%4.5f' % diff_max, note)) + assert diff_mean < mean_diff_allowed + assert diff_max < max_diff_allowed + + def _process_layer_parameters(layer): + + logging.debug('processing layer %s of type %s', layer.name, layer.type) + + normalized_layer_name = re.sub('[-/]', '_', layer.name) + + # handle weight and bias of convolution and fully-connected layers + if layer.name in caffe_net.params and layer.type in ['Convolution', 'InnerProduct', + 'Deconvolution']: + + has_bias = len(caffe_net.params[layer.name]) > 1 + + mx_name_weight = '{}_weight'.format(normalized_layer_name) + mx_beta = arg_params[mx_name_weight].asnumpy() + + # first convolution should change from BGR to RGB + if layer.type == 'Convolution' and compare_layers_from_nets.is_first_convolution: + compare_layers_from_nets.is_first_convolution = False + + # if RGB or RGBA + if mx_beta.shape[1] == 3 or mx_beta.shape[1] == 4: + # Swapping BGR of caffe into RGB in mxnet + mx_beta[:, [0, 2], :, :] = mx_beta[:, [2, 0], :, :] + + caf_beta = caffe_net.params[layer.name][0].data + _compare_blob(caf_beta, mx_beta, layer.name, mx_name_weight, 'weight', '') + + if has_bias: + mx_name_bias = '{}_bias'.format(normalized_layer_name) + mx_gamma = arg_params[mx_name_bias].asnumpy() + caf_gamma = caffe_net.params[layer.name][1].data + _compare_blob(caf_gamma, mx_gamma, layer.name, mx_name_bias, 'bias', '') + + elif layer.name in caffe_net.params and layer.type == 'Scale': + + if 'scale' in normalized_layer_name: + bn_name = normalized_layer_name.replace('scale', 'bn') + elif 'sc' in normalized_layer_name: + bn_name = normalized_layer_name.replace('sc', 'bn') + else: + assert False, 'Unknown name convention for bn/scale' + + beta_name = '{}_beta'.format(bn_name) + gamma_name = '{}_gamma'.format(bn_name) + + mx_beta = arg_params[beta_name].asnumpy() + caf_beta = caffe_net.params[layer.name][1].data + _compare_blob(caf_beta, mx_beta, layer.name, beta_name, 'mov_mean', '') + + mx_gamma = arg_params[gamma_name].asnumpy() + caf_gamma = caffe_net.params[layer.name][0].data + _compare_blob(caf_gamma, mx_gamma, layer.name, gamma_name, 'mov_var', '') + + elif layer.name in caffe_net.params and layer.type == 'BatchNorm': + + mean_name = '{}_moving_mean'.format(normalized_layer_name) + var_name = '{}_moving_var'.format(normalized_layer_name) + + caf_rescale_factor = caffe_net.params[layer.name][2].data + + mx_mean = aux_params[mean_name].asnumpy() + caf_mean = caffe_net.params[layer.name][0].data / caf_rescale_factor + _compare_blob(caf_mean, mx_mean, layer.name, mean_name, 'mean', '') + + mx_var = aux_params[var_name].asnumpy() + caf_var = caffe_net.params[layer.name][1].data / caf_rescale_factor + _compare_blob(caf_var, mx_var, layer.name, var_name, 'var', + 'expect 1e-04 change due to cudnn eps') + + elif layer.type in ['Input', 'Pooling', 'ReLU', 'Eltwise', 'Softmax', 'LRN', 'Concat', + 'Dropout', 'Crop']: + # no parameters to check for these layers + pass + + else: + logging.warn('No handling for layer %s of type %s, should we ignore it?', layer.name, + layer.type) + + return + + def _process_layer_output(caffe_blob_name): + + logging.debug('processing blob %s', caffe_blob_name) + + # skip blobs not originating from actual layers, e.g. artificial split layers added by caffe + if caffe_blob_name not in top_to_layers: + return + + caf_blob = caffe_net.blobs[caffe_blob_name].data + + # data should change from BGR to RGB + if caffe_blob_name == 'data': + + # if RGB or RGBA + if caf_blob.shape[1] == 3 or caf_blob.shape[1] == 4: + # Swapping BGR of caffe into RGB in mxnet + caf_blob[:, [0, 2], :, :] = caf_blob[:, [2, 0], :, :] + mx_name = 'data' + + else: + # get last layer name which outputs this blob name + last_layer_name = top_to_layers[caffe_blob_name][-1] + normalized_last_layer_name = re.sub('[-/]', '_', last_layer_name) + mx_name = '{}_output'.format(normalized_last_layer_name) + if 'scale' in mx_name: + mx_name = mx_name.replace('scale', 'bn') + elif 'sc' in mx_name: + mx_name = mx_name.replace('sc', 'bn') + + if mx_name not in exe.output_dict: + logging.error('mxnet blob %s is missing, time to extend the compare tool..', mx_name) + return + + mx_blob = exe.output_dict[mx_name].asnumpy() + _compare_blob(caf_blob, mx_blob, caffe_blob_name, mx_name, 'output', '') + + return + + # check layer parameters + logging.info('\n***** Network Parameters '.ljust(140, '*')) + logging.info(log_format.format('CAFFE', 'MXNET', 'Type', 'Mean(diff)', 'Max(diff)', 'Note')) + first_layer_name = layer_name_to_record.keys()[0] + _bfs(layer_name_to_record[first_layer_name], _process_layer_parameters) + + # check layer output + logging.info('\n***** Network Outputs '.ljust(140, '*')) + logging.info(log_format.format('CAFFE', 'MXNET', 'Type', 'Mean(diff)', 'Max(diff)', 'Note')) + for caffe_blob_name in caffe_net.blobs.keys(): + _process_layer_output(caffe_blob_name) + + return + + +def main(): + """Entrypoint for compare_layers""" + + parser = argparse.ArgumentParser( + description='Tool for testing caffe to mxnet conversion layer by layer') + parser.add_argument('--image_url', type=str, + default='http://writm.com/wp-content/uploads/2016/08/Cat-hd-wallpapers.jpg', + help='input image to test inference, can be either file path or url') + parser.add_argument('--caffe_prototxt_path', type=str, + default='./model.prototxt', + help='path to caffe prototxt') + parser.add_argument('--caffe_model_path', type=str, + default='./model.caffemodel', + help='path to caffe weights') + parser.add_argument('--caffe_mean', type=str, + default='./model_mean.binaryproto', + help='path to caffe mean file') + parser.add_argument('--mean_diff_allowed', type=int, default=1e-03, + help='mean difference allowed between caffe blob and mxnet blob') + parser.add_argument('--max_diff_allowed', type=int, default=1e-01, + help='max difference allowed between caffe blob and mxnet blob') + parser.add_argument('--gpu', type=int, default=-1, help='the gpu id used for predict') + args = parser.parse_args() + convert_and_compare_caffe_to_mxnet(args.image_url, args.gpu, args.caffe_prototxt_path, + args.caffe_model_path, args.caffe_mean, + args.mean_diff_allowed, args.max_diff_allowed) + +if __name__ == '__main__': + main() diff --git a/tools/caffe_converter/convert_caffe_modelzoo.py b/tools/caffe_converter/convert_caffe_modelzoo.py index f900a6cc7d06..ab9042fcc532 100644 --- a/tools/caffe_converter/convert_caffe_modelzoo.py +++ b/tools/caffe_converter/convert_caffe_modelzoo.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + """Convert Caffe's modelzoo """ import os @@ -60,7 +77,7 @@ 'top-1-acc' : 0.753, 'top-5-acc' : 0.922 }, - 'resnt-101' : { + 'resnet-101' : { 'prototxt' : _mx_caffe_model+'ResNet-101-deploy.prototxt', 'caffemodel' : _mx_caffe_model+'ResNet-101-model.caffemodel', 'mean' : _mx_caffe_model+'ResNet_mean.binaryproto', @@ -80,7 +97,7 @@ def get_model_meta_info(model_name): """returns a dict with model information""" return dict(dict(model_meta_info)[model_name]) -def _download_caffe_model(model_name, meta_info, dst_dir='./model'): +def download_caffe_model(model_name, meta_info, dst_dir='./model'): """Download caffe model into disk by the given meta info """ if not os.path.isdir(dst_dir): os.mkdir(dst_dir) @@ -98,7 +115,7 @@ def _download_caffe_model(model_name, meta_info, dst_dir='./model'): def convert_caffe_model(model_name, meta_info, dst_dir='./model'): """Download, convert and save a caffe model""" - (prototxt, caffemodel, mean) = _download_caffe_model(model_name, meta_info, dst_dir) + (prototxt, caffemodel, mean) = download_caffe_model(model_name, meta_info, dst_dir) model_name = os.path.join(dst_dir, model_name) convert_model(prototxt, caffemodel, model_name) if isinstance(mean, str): diff --git a/tools/caffe_converter/convert_mean.py b/tools/caffe_converter/convert_mean.py index 69cf50c65bd6..3b6dc42a7afc 100644 --- a/tools/caffe_converter/convert_mean.py +++ b/tools/caffe_converter/convert_mean.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + """Convert caffe mean """ import argparse diff --git a/tools/caffe_converter/convert_model.py b/tools/caffe_converter/convert_model.py index e4134e272283..1624a017fe0d 100644 --- a/tools/caffe_converter/convert_model.py +++ b/tools/caffe_converter/convert_model.py @@ -1,13 +1,42 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + """Convert caffe model """ from __future__ import print_function import argparse import sys +import re import caffe_parser import mxnet as mx import numpy as np from convert_symbol import convert_symbol +def prob_label(arg_names): + candidates = [arg for arg in arg_names if + not arg.endswith('data') and + not arg.endswith('_weight') and + not arg.endswith('_bias') and + not arg.endswith('_gamma') and + not arg.endswith('_beta')] + if len(candidates) == 0: + return 'prob_label' + return candidates[-1] + def convert_model(prototxt_fname, caffemodel_fname, output_prefix=None): """Convert caffe model @@ -48,12 +77,13 @@ def convert_model(prototxt_fname, caffemodel_fname, output_prefix=None): layers_proto = caffe_parser.get_layers(caffe_parser.read_prototxt(prototxt_fname)) for layer_name, layer_type, layer_blobs in layer_iter: - if layer_type == 'Convolution' or layer_type == 'InnerProduct' \ - or layer_type == 4 or layer_type == 14 or layer_type == 'PReLU': + if layer_type == 'Convolution' or layer_type == 'InnerProduct' \ + or layer_type == 4 or layer_type == 14 or layer_type == 'PReLU' \ + or layer_type == 'Deconvolution' or layer_type == 39: if layer_type == 'PReLU': assert (len(layer_blobs) == 1) - wmat = layer_blobs[0].data weight_name = layer_name + '_gamma' + wmat = np.array(layer_blobs[0].data).reshape(arg_shape_dic[weight_name]) arg_params[weight_name] = mx.nd.zeros(wmat.shape) arg_params[weight_name][:] = wmat continue @@ -82,6 +112,10 @@ def convert_model(prototxt_fname, caffemodel_fname, output_prefix=None): bias = bias.reshape((bias.shape[0], 1)) assert(bias.flags['C_CONTIGUOUS'] is True) bias_name = layer_name + "_bias" + + if bias_name not in arg_shape_dic: + print(bias_name + ' not found in arg_shape_dic.') + continue bias = bias.reshape(arg_shape_dic[bias_name]) arg_params[bias_name] = mx.nd.zeros(bias.shape) arg_params[bias_name][:] = bias @@ -104,9 +138,15 @@ def convert_model(prototxt_fname, caffemodel_fname, output_prefix=None): first_conv = False elif layer_type == 'Scale': - bn_name = layer_name.replace('scale', 'bn') - gamma = layer_blobs[0].data - beta = layer_blobs[1].data + if 'scale' in layer_name: + bn_name = layer_name.replace('scale', 'bn') + elif 'sc' in layer_name: + bn_name = layer_name.replace('sc', 'bn') + else: + assert False, 'Unknown name convention for bn/scale' + + gamma = np.array(layer_blobs[0].data) + beta = np.array(layer_blobs[1].data) # beta = np.expand_dims(beta, 1) beta_name = '{}_beta'.format(bn_name) gamma_name = '{}_gamma'.format(bn_name) @@ -124,9 +164,9 @@ def convert_model(prototxt_fname, caffemodel_fname, output_prefix=None): beta.shape, gamma.shape)) elif layer_type == 'BatchNorm': bn_name = layer_name - mean = layer_blobs[0].data - var = layer_blobs[1].data - rescale_factor = layer_blobs[2].data + mean = np.array(layer_blobs[0].data) + var = np.array(layer_blobs[1].data) + rescale_factor = layer_blobs[2].data[0] if rescale_factor != 0: rescale_factor = 1 / rescale_factor mean_name = '{}_moving_mean'.format(bn_name) @@ -137,7 +177,7 @@ def convert_model(prototxt_fname, caffemodel_fname, output_prefix=None): aux_params[var_name] = mx.nd.zeros(var.shape) # Get the original epsilon for idx, layer in enumerate(layers_proto): - if layer.name == bn_name: + if layer.name == bn_name or re.sub('[-/]', '_', layer.name) == bn_name: bn_index = idx eps_caffe = layers_proto[bn_index].batch_norm_param.eps # Compensate for the epsilon shift performed in convert_symbol @@ -150,12 +190,26 @@ def convert_model(prototxt_fname, caffemodel_fname, output_prefix=None): assert mean.flags['C_CONTIGUOUS'] is True print('converting batchnorm layer, mean shape = {}, var shape = {}'.format( mean.shape, var.shape)) + + fix_gamma = layers_proto[bn_index+1].type != 'Scale' + if fix_gamma: + gamma_name = '{}_gamma'.format(bn_name) + gamma = np.array(np.ones(arg_shape_dic[gamma_name])) + beta_name = '{}_beta'.format(bn_name) + beta = np.array(np.zeros(arg_shape_dic[beta_name])) + arg_params[beta_name] = mx.nd.zeros(beta.shape) + arg_params[gamma_name] = mx.nd.zeros(gamma.shape) + arg_params[beta_name][:] = beta + arg_params[gamma_name][:] = gamma + assert gamma.flags['C_CONTIGUOUS'] is True + assert beta.flags['C_CONTIGUOUS'] is True + else: - assert len(layer_blobs) == 0 print('\tskipping layer {} of type {}'.format(layer_name, layer_type)) + assert len(layer_blobs) == 0 if output_prefix is not None: - model = mx.mod.Module(symbol=sym, label_names=['prob_label', ]) + model = mx.mod.Module(symbol=sym, label_names=[prob_label(arg_names), ]) model.bind(data_shapes=[('data', tuple(input_dim))]) model.init_params(arg_params=arg_params, aux_params=aux_params) model.save_checkpoint(output_prefix, 0) diff --git a/tools/caffe_converter/convert_symbol.py b/tools/caffe_converter/convert_symbol.py index 9771b9737e8e..13b55fef1296 100644 --- a/tools/caffe_converter/convert_symbol.py +++ b/tools/caffe_converter/convert_symbol.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + """Convert caffe prototxt to symbol """ from __future__ import print_function @@ -25,32 +42,58 @@ def _get_input(proto): return input_name, input_dim, layer def _convert_conv_param(param): - """Convert convolution layer parameter from Caffe to MXNet """ - pad = 0 + Convert convolution layer parameter from Caffe to MXNet + """ + param_string = "num_filter=%d" % param.num_output + + pad_w = 0 + pad_h = 0 if isinstance(param.pad, int): pad = param.pad + param_string += ", pad=(%d, %d)" % (pad, pad) + else: + if len(param.pad) > 0: + pad = param.pad[0] + param_string += ", pad=(%d, %d)" % (pad, pad) + else: + if isinstance(param.pad_w, int): + pad_w = param.pad_w + if isinstance(param.pad_h, int): + pad_h = param.pad_h + param_string += ", pad=(%d, %d)" % (pad_h, pad_w) + + if isinstance(param.kernel_size, int): + kernel_size = param.kernel_size + param_string += ", kernel=(%d,%d)" % (kernel_size, kernel_size) else: - pad = 0 if len(param.pad) == 0 else param.pad[0] + if len(param.kernel_size) > 0: + kernel_size = param.kernel_size[0] + param_string += ", kernel=(%d,%d)" % (kernel_size, kernel_size) + else: + assert isinstance(param.kernel_w, int) + kernel_w = param.kernel_w + assert isinstance(param.kernel_h, int) + kernel_h = param.kernel_h + param_string += ", kernel=(%d,%d)" % (kernel_h, kernel_w) + stride = 1 if isinstance(param.stride, int): stride = param.stride else: stride = 1 if len(param.stride) == 0 else param.stride[0] - kernel_size = '' - if isinstance(param.kernel_size, int): - kernel_size = param.kernel_size - else: - kernel_size = param.kernel_size[0] + + param_string += ", stride=(%d,%d)" % (stride, stride) + dilate = 1 - if isinstance(param.dilation, int): - dilate = param.dilation - else: - dilate = 1 if len(param.dilation) == 0 else param.dilation[0] - # convert to string except for dilation - param_string = "num_filter=%d, pad=(%d,%d), kernel=(%d,%d), stride=(%d,%d), no_bias=%s" % \ - (param.num_output, pad, pad, kernel_size, kernel_size, - stride, stride, not param.bias_term) + if hasattr(param, 'dilation'): + if isinstance(param.dilation, int): + dilate = param.dilation + else: + dilate = 1 if len(param.dilation) == 0 else param.dilation[0] + + param_string += ", no_bias=%s" % (not param.bias_term) + # deal with dilation. Won't be in deconvolution if dilate > 1: param_string += ", dilate=(%d, %d)" % (dilate, dilate) @@ -94,6 +137,7 @@ def _parse_proto(prototxt_fname): flatten_count = 0 output_name = "" prev_name = None + _output_name = {} # convert reset layers one by one for i, layer in enumerate(layers): @@ -101,6 +145,16 @@ def _parse_proto(prototxt_fname): param_string = '' skip_layer = False name = re.sub('[-/]', '_', layer.name) + for k in range(len(layer.bottom)): + if layer.bottom[k] in _output_name: + _output_name[layer.bottom[k]]['count'] = _output_name[layer.bottom[k]]['count']+1 + else: + _output_name[layer.bottom[k]] = {'count':0} + for k in range(len(layer.top)): + if layer.top[k] in _output_name: + _output_name[layer.top[k]]['count'] = _output_name[layer.top[k]]['count']+1 + else: + _output_name[layer.top[k]] = {'count':0, 'name':name} if layer.type == 'Convolution' or layer.type == 4: type_string = 'mx.symbol.Convolution' param_string = _convert_conv_param(layer.convolution_param) @@ -164,8 +218,10 @@ def _parse_proto(prototxt_fname): epsilon = param.eps if (epsilon <= 1e-05): epsilon = 1e-04 - param_string = 'use_global_stats=%s, fix_gamma=False, eps=%f' % ( - param.use_global_stats, epsilon) + # if next layer is scale, don't fix gamma + fix_gamma = layers[i+1].type != 'Scale' + param_string = 'use_global_stats=%s, fix_gamma=%s, eps=%f' % ( + param.use_global_stats, fix_gamma, epsilon) need_flatten[name] = need_flatten[mapping[layer.bottom[0]]] if layer.type == 'Scale': assert layers[i-1].type == 'BatchNorm' @@ -179,6 +235,7 @@ def _parse_proto(prototxt_fname): need_flatten[name] = need_flatten[mapping[layer.bottom[0]]] if layer.type == 'Eltwise': type_string = 'mx.symbol.broadcast_add' + param = layer.eltwise_param param_string = "" need_flatten[name] = False if layer.type == 'Reshape': @@ -211,11 +268,23 @@ def _parse_proto(prototxt_fname): symbol_string += "%s = %s(name='%s', data=%s %s)\n" % ( name, type_string, name, mapping[bottom[0]], param_string) else: - symbol_string += "%s = %s(name='%s', *[%s] %s)\n" % ( - name, type_string, name, ','.join([mapping[x] for x in bottom]), param_string) + if layer.type == 'Eltwise' and param.operation == 1 and len(param.coeff) > 0: + symbol_string += "%s = " % name + symbol_string += " + ".join(["%s * %s" % ( + mapping[bottom[i]], param.coeff[i]) for i in range(len(param.coeff))]) + symbol_string += "\n" + else: + symbol_string += "%s = %s(name='%s', *[%s] %s)\n" % ( + name, type_string, name, ','.join( + [mapping[x] for x in bottom]), param_string) for j in range(len(layer.top)): mapping[layer.top[j]] = name output_name = name + output_name = [] + for i in _output_name: + if 'name' in _output_name[i] and _output_name[i]['count'] == 0: + output_name.append(_output_name[i]['name']) + return symbol_string, output_name, input_dim def convert_symbol(prototxt_fname): @@ -236,8 +305,11 @@ def convert_symbol(prototxt_fname): sym, output_name, input_dim = _parse_proto(prototxt_fname) exec(sym) # pylint: disable=exec-used _locals = locals() - exec("ret = " + output_name, globals(), _locals) # pylint: disable=exec-used - ret = _locals['ret'] + ret = [] + for i in output_name: + exec("ret = " + i, globals(), _locals) # pylint: disable=exec-used + ret.append(_locals['ret']) + ret = mx.sym.Group(ret) return ret, input_dim def main(): diff --git a/tools/caffe_converter/make_win32.bat b/tools/caffe_converter/make_win32.bat index 2f3367d000d4..e5bc9143e05c 100644 --- a/tools/caffe_converter/make_win32.bat +++ b/tools/caffe_converter/make_win32.bat @@ -1,3 +1,20 @@ +rem Licensed to the Apache Software Foundation (ASF) under one +rem or more contributor license agreements. See the NOTICE file +rem distributed with this work for additional information +rem regarding copyright ownership. The ASF licenses this file +rem to you under the Apache License, Version 2.0 (the +rem "License"); you may not use this file except in compliance +rem with the License. You may obtain a copy of the License at +rem +rem http://www.apache.org/licenses/LICENSE-2.0 +rem +rem Unless required by applicable law or agreed to in writing, +rem software distributed under the License is distributed on an +rem "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +rem KIND, either express or implied. See the License for the +rem specific language governing permissions and limitations +rem under the License. + @protoc --python_out=./ ./caffe.proto @echo done. @pause diff --git a/tools/caffe_converter/run.sh b/tools/caffe_converter/run.sh index 65876cc42934..bdf5481624d7 100755 --- a/tools/caffe_converter/run.sh +++ b/tools/caffe_converter/run.sh @@ -1,4 +1,22 @@ #!/bin/bash + +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + if [[ $# -ne 1 ]]; then echo "usage: $0 model_name" echo " model_name: [vgg16|vgg19], ..." diff --git a/tools/caffe_converter/test_converter.py b/tools/caffe_converter/test_converter.py index 128e7c276c50..cdf833198eff 100644 --- a/tools/caffe_converter/test_converter.py +++ b/tools/caffe_converter/test_converter.py @@ -1,19 +1,39 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + """Test converted models """ import os +import argparse import sys import logging import mxnet as mx -from convert_caffe_modelzoo import convert_caffe_model, get_model_meta_info +from convert_caffe_modelzoo import convert_caffe_model, get_model_meta_info, download_caffe_model +from compare_layers import convert_and_compare_caffe_to_mxnet + curr_path = os.path.abspath(os.path.dirname(__file__)) sys.path.append(os.path.join(curr_path, "../../example/image-classification")) from test_score import download_data # pylint: disable=wrong-import-position from score import score # pylint: disable=wrong-import-position logging.basicConfig(level=logging.DEBUG) -def test_imagenet_model(model_name, val_data, gpus, batch_size): - """test model on imagenet """ - logging.info('test %s', model_name) +def test_imagenet_model_performance(model_name, val_data, gpus, batch_size): + """test model performance on imagenet """ + logging.info('test performance of model: %s', model_name) meta_info = get_model_meta_info(model_name) [model_name, mean] = convert_caffe_model(model_name, meta_info) sym, arg_params, aux_params = mx.model.load_checkpoint(model_name, 0) @@ -24,30 +44,63 @@ def test_imagenet_model(model_name, val_data, gpus, batch_size): mean_args = {'rgb_mean':','.join([str(i) for i in mean])} print(val_data) + gpus_string = '' if gpus[0] == -1 else ','.join([str(i) for i in gpus]) (speed,) = score(model=(sym, arg_params, aux_params), data_val=val_data, label_name='prob_label', metrics=acc, - gpus=gpus, + gpus=gpus_string, batch_size=batch_size, max_num_examples=500, **mean_args) logging.info('speed : %f image/sec', speed) for a in acc: logging.info(a.get()) - assert acc[0].get()[1] > meta_info['top-1-acc'] - 0.3 - assert acc[1].get()[1] > meta_info['top-5-acc'] - 0.3 + max_performance_diff_allowed = 0.03 + assert acc[0].get()[1] > meta_info['top-1-acc'] - max_performance_diff_allowed + assert acc[1].get()[1] > meta_info['top-5-acc'] - max_performance_diff_allowed + + +def test_model_weights_and_outputs(model_name, image_url, gpu): + """ + Run the layer comparison on one of the known caffe models. + :param model_name: available models are listed in convert_caffe_modelzoo.py + :param image_url: image file or url to run inference on + :param gpu: gpu to use, -1 for cpu + """ + + logging.info('test weights and outputs of model: %s', model_name) + meta_info = get_model_meta_info(model_name) + + (prototxt, caffemodel, mean) = download_caffe_model(model_name, meta_info, dst_dir='./model') + convert_and_compare_caffe_to_mxnet(image_url, gpu, prototxt, caffemodel, mean, + mean_diff_allowed=1e-03, max_diff_allowed=1e-01) + + return + def main(): - gpus = mx.test_utils.list_gpus() - assert len(gpus) > 0 - batch_size = 32 * len(gpus) + """Entrypoint for test_converter""" + parser = argparse.ArgumentParser(description='Test Caffe converter') + parser.add_argument('--cpu', action='store_true', help='use cpu?') + parser.add_argument('--image_url', type=str, + default='http://writm.com/wp-content/uploads/2016/08/Cat-hd-wallpapers.jpg', + help='input image to test inference, can be either file path or url') + args = parser.parse_args() + if args.cpu: + gpus = [-1] + batch_size = 32 + else: + gpus = mx.test_utils.list_gpus() + assert gpus, 'At least one GPU is needed to run test_converter in GPU mode' + batch_size = 32 * len(gpus) models = ['bvlc_googlenet', 'vgg-16', 'resnet-50'] val = download_data() for m in models: - test_imagenet_model(m, val, ','.join([str(i) for i in gpus]), batch_size) + test_model_weights_and_outputs(m, args.image_url, gpus[0]) + test_imagenet_model_performance(m, val, gpus, batch_size) if __name__ == '__main__': main() diff --git a/tools/coreml/README.md b/tools/coreml/README.md new file mode 100644 index 000000000000..e29eebe84bc1 --- /dev/null +++ b/tools/coreml/README.md @@ -0,0 +1,114 @@ +# Convert MXNet models into Apple CoreML format. + +This tool helps convert MXNet models into [Apple CoreML](https://developer.apple.com/documentation/coreml) format which can then be run on Apple devices. + +## Installation +In order to use this tool you need to have these installed: +* MacOS - High Sierra 10.13 +* Xcode 9 +* coremltools 0.5.0 or greater (pip install coremltools) +* mxnet 0.10.0 or greater. [Installation instructions](http://mxnet.io/get_started/install.html). +* yaml (pip install pyyaml) +* python 2.7 + +## How to use +Let's say you want to use your MXNet model in an iPhone App. For the purpose of this example, let's say you want to use squeezenet-v1.1. + +1. Download the model into the directory where this converter resides. Squeezenet can be downloaded from [here](http://data.mxnet.io/models/imagenet/squeezenet/). +2. Run this command: + + ```bash +python mxnet_coreml_converter.py --model-prefix='squeezenet_v1.1' --epoch=0 --input-shape='{"data":"3,227,227"}' --mode=classifier --pre-processing-arguments='{"image_input_names":"data"}' --class-labels classLabels.txt --output-file="squeezenetv11.mlmodel" +``` + + The above command will save the converted model in CoreML format to file squeezenet-v11.mlmodel. Internally, the model is first loaded by MXNet recreating the entire symbolic graph in memory. The converter walks through this symbolic graph converting each operator into its CoreML equivalent. Some of the supplied arguments to the converter are used by MXNet to generate the graph while others are used by CoreML either to pre-process the input (before passing it to the neural network) or to process the output of the neural network in a particular way. + + In the command above: + + * _model-prefix_: refers to the prefix of the file containing the MXNet model that needs to be converted (may include the directory path). E.g. for squeezenet model above the model files are squeezenet_v1.1-symbol.json and squeezenet_v1.1-0000.params and, therefore, model-prefix is "squeezenet_v1.1" (or "/squeezenet_v1.1") + * _epoch_: refers to the suffix of the MXNet model filename. For squeezenet model above, it'll be 0. + * _input-shape_: refers to the input shape information in a JSON string format where the key is the name of the input variable (i.e. "data") and the value is the shape of that variable. If the model takes multiple inputs, input-shape for all of them need to be provided. + * _mode_: refers to the coreml model mode. Can either be 'classifier', 'regressor' or None. In this case, we use 'classifier' since we want the resulting CoreML model to classify images into various categories. + * _pre-processing-arguments_: In the Apple world, images have to be of type "Image". By providing image_input_names as "data", the converter will assume that the input variable "data" is of type "Image". + * _class-labels_: refers to the name of the file which contains the classification labels (a.k.a. synset file). + * _output-file_: the file where resulting CoreML model will be stored. + +3. The generated ".mlmodel" file can directly be integrated into your app. For more instructions on how to do this, please see [Apple CoreML's tutorial](https://developer.apple.com/documentation/coreml/integrating_a_core_ml_model_into_your_app). + + +### Providing class labels +You could provide a file containing class labels (as above) so that CoreML will return the category a given image belongs to. The file should have a label per line and labels can have any special characters. The line number of the label in the file should correspond with the index of softmax output. E.g. + +```bash +python mxnet_coreml_converter.py --model-prefix='squeezenet_v1.1' --epoch=0 --input-shape='{"data":"3,227,227"}' --mode=classifier --class-labels classLabels.txt --output-file="squeezenetv11.mlmodel" +``` + +### Adding a pre-processing layer to CoreML model. +You could ask CoreML to pre-process the images before passing them through the model. The following command provides image re-centering parameters for red, blue and green channel. + +```bash +python mxnet_coreml_converter.py --model-prefix='squeezenet_v1.1' --epoch=0 --input-shape='{"data":"3,224,224"}' --pre-processing-arguments='{"red_bias":127,"blue_bias":117,"green_bias":103}' --output-file="squeezenet_v11.mlmodel" +``` + +If you are building an app for a model that takes "Image" as an input, you will have to provide image_input_names as pre-processing arguments. This tells CoreML that a particular input variable is of type Image. E.g.: + +```bash +python mxnet_coreml_converter.py --model-prefix='squeezenet_v1.1' --epoch=0 --input-shape='{"data":"3,224,224"}' --pre-processing-arguments='{"red_bias":127,"blue_bias":117,"green_bias":103,"image_input_names":"data"}' --output-file="squeezenet_v11.mlmodel" +``` + +## Currently supported +### Layers +List of MXNet layers that can be converted into their CoreML equivalent: + +1. Activation +2. Batchnorm +3. Concat +4. Convolution +5. Deconvolution +6. Dense +7. Elementwise +8. Flatten +9. Pooling +10. Reshape +11. Softmax +12. Transpose + +### Models +Any MXNet model that uses the above operators can be converted easily. For instance, the following standard models can be converted: + +1. [Inception-BN](http://data.mxnet.io/models/imagenet/inception-bn/) + +```bash +python mxnet_coreml_converter.py --model-prefix='Inception-BN' --epoch=126 --input-shape='{"data":"3,224,224"}' --mode=classifier --pre-processing-arguments='{"image_input_names":"data"}' --class-labels classLabels.txt --output-file="InceptionBN.mlmodel" +``` + +2. [NiN](http://data.dmlc.ml/models/imagenet/nin/) + +```bash +python mxnet_coreml_converter.py --model-prefix='nin' --epoch=0 --input-shape='{"data":"3,224,224"}' --mode=classifier --pre-processing-arguments='{"image_input_names":"data"}' --class-labels classLabels.txt --output-file="nin.mlmodel" +``` + +3. [Resnet](http://data.mxnet.io/models/imagenet/resnet/) + +```bash +python mxnet_coreml_converter.py --model-prefix='resnet-50' --epoch=0 --input-shape='{"data":"3,224,224"}' --mode=classifier --pre-processing-arguments='{"image_input_names":"data"}' --class-labels classLabels.txt --output-file="resnet50.mlmodel" +``` + +4. [Squeezenet](http://data.mxnet.io/models/imagenet/squeezenet/) + +```bash +python mxnet_coreml_converter.py --model-prefix='squeezenet_v1.1' --epoch=0 --input-shape='{"data":"3,227,227"}' --mode=classifier --pre-processing-arguments='{"image_input_names":"data"}' --class-labels classLabels.txt --output-file="squeezenetv11.mlmodel" +``` + +5. [Vgg](http://data.mxnet.io/models/imagenet/vgg/) + +```bash +python mxnet_coreml_converter.py --model-prefix='vgg16' --epoch=0 --input-shape='{"data":"3,224,224"}' --mode=classifier --pre-processing-arguments='{"image_input_names":"data"}' --class-labels classLabels.txt --output-file="vgg16.mlmodel" +``` + +## Known issues +* [Inception-V3](http://data.mxnet.io/models/imagenet/inception-v3.tar.gz) model can be converted into CoreML format but is unable to run on Xcode. + +## This tool has been tested with: +* MacOS - High Sierra 10.13 Beta. +* Xcode 9 beta 5. diff --git a/tools/coreml/converter/__init__.py b/tools/coreml/converter/__init__.py new file mode 100644 index 000000000000..245692337bc3 --- /dev/null +++ b/tools/coreml/converter/__init__.py @@ -0,0 +1,17 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + diff --git a/tools/coreml/converter/_add_pooling.py b/tools/coreml/converter/_add_pooling.py new file mode 100644 index 000000000000..51934f22190b --- /dev/null +++ b/tools/coreml/converter/_add_pooling.py @@ -0,0 +1,118 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +from coremltools.proto import NeuralNetwork_pb2 as _NeuralNetwork_pb2 + + +def add_pooling_with_padding_types(builder, name, height, width, stride_height, stride_width, + layer_type, padding_type, input_name, output_name, + padding_top = 0, padding_bottom = 0, padding_left = 0, padding_right = 0, + same_padding_asymmetry_mode = 'BOTTOM_RIGHT_HEAVY', + exclude_pad_area = True, is_global = False): + """ + Add a pooling layer to the model. + + This is our own implementation of add_pooling since current CoreML's version (0.5.0) of builder + doesn't provide support for padding types apart from valid. This support will be added in the + next release of coremltools. When that happens, this can be removed. + + Parameters + + ---------- + builder: NeuralNetworkBuilder + A neural network builder object. + name: str + The name of this layer. + height: int + Height of pooling region. + width: int + Number of elements to be padded on the right side of the input blob. + stride_height: int + Stride along the height direction. + stride_width: int + Stride along the height direction. + layer_type: str + Type of pooling performed. Can either be 'MAX', 'AVERAGE' or 'L2'. + padding_type: str + Option for the output blob shape. Can be either 'VALID' , 'SAME' or 'INCLUDE_LAST_PIXEL'. Kindly look at NeuralNetwork.proto for details. + input_name: str + The input blob name of this layer. + output_name: str + The output blob name of this layer. + + padding_top, padding_bottom, padding_left, padding_right: int + values of height (top, bottom) and width (left, right) padding to be used if padding type is "VALID" or "INCLUDE_LAST_PIXEL" + + same_padding_asymmetry_mode : str. + Type of asymmetric padding to be used when padding_type = 'SAME'. Kindly look at NeuralNetwork.proto for details. Can be either 'BOTTOM_RIGHT_HEAVY' or 'TOP_LEFT_HEAVY'. + + exclude_pad_area: boolean + Whether to exclude padded area in the pooling operation. Defaults to True. + + - If True, the value of the padded area will be excluded. + - If False, the padded area will be included. + This flag is only used with average pooling. + is_global: boolean + Whether the pooling operation is global. Defaults to False. + + - If True, the pooling operation is global -- the pooling region is of the same size of the input blob. + Parameters height, width, stride_height, stride_width will be ignored. + + - If False, the pooling operation is not global. + + See Also + -------- + add_convolution, add_pooling, add_activation + """ + + spec = builder.spec + nn_spec = builder.nn_spec + + # Add a new layer + spec_layer = nn_spec.layers.add() + spec_layer.name = name + spec_layer.input.append(input_name) + spec_layer.output.append(output_name) + spec_layer_params = spec_layer.pooling + + # Set the parameters + spec_layer_params.type = \ + _NeuralNetwork_pb2.PoolingLayerParams.PoolingType.Value(layer_type) + + if padding_type == 'VALID': + height_border = spec_layer_params.valid.paddingAmounts.borderAmounts.add() + height_border.startEdgeSize = padding_top + height_border.endEdgeSize = padding_bottom + width_border = spec_layer_params.valid.paddingAmounts.borderAmounts.add() + width_border.startEdgeSize = padding_left + width_border.endEdgeSize = padding_right + elif padding_type == 'SAME': + if not (same_padding_asymmetry_mode == 'BOTTOM_RIGHT_HEAVY' or same_padding_asymmetry_mode == 'TOP_LEFT_HEAVY'): + raise ValueError("Invalid value %d of same_padding_asymmetry_mode parameter" % same_padding_asymmetry_mode) + spec_layer_params.same.asymmetryMode = _NeuralNetwork_pb2.SamePadding.SamePaddingMode.Value(same_padding_asymmetry_mode) + elif padding_type == 'INCLUDE_LAST_PIXEL': + if padding_top != padding_bottom or padding_left != padding_right: + raise ValueError("Only symmetric padding is supported with the INCLUDE_LAST_PIXEL padding type") + spec_layer_params.includeLastPixel.paddingAmounts.append(padding_top) + spec_layer_params.includeLastPixel.paddingAmounts.append(padding_left) + + spec_layer_params.kernelSize.append(height) + spec_layer_params.kernelSize.append(width) + spec_layer_params.stride.append(stride_height) + spec_layer_params.stride.append(stride_width) + spec_layer_params.avgPoolExcludePadding = exclude_pad_area + spec_layer_params.globalPooling = is_global diff --git a/tools/coreml/converter/_layers.py b/tools/coreml/converter/_layers.py new file mode 100644 index 000000000000..0a089949a1a6 --- /dev/null +++ b/tools/coreml/converter/_layers.py @@ -0,0 +1,569 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +import _add_pooling +from ast import literal_eval + +def _get_input_output_name(net, node, index=0): + name = node['name'] + inputs = node['inputs'] + + if index == 'all': + input_name = [_get_node_name(net, inputs[idx][0]) for idx in range(len(inputs))] + elif type(index) == int: + input_name = _get_node_name(net, inputs[0][0]) + else: + input_name = [_get_node_name(net, inputs[idx][0]) for idx in index] + return input_name, name + + +def _get_node_name(net, node_id): + return net['nodes'][node_id]['name'] + + +def _get_node_shape(net, node_id): + return net['nodes'][node_id]['shape'] + + +# TODO These operators still need to be converted (listing in order of priority): +# High priority: +# mxnet.symbol.repeat -> builder.add_repeat to flatten and repeat the NDArray sequence +# mxnet.symbol.Crop -> builder.add_crop to crop image along spacial dimensions +# mxnet.symbol.Pad -> builder.add_padding putting 0's on height and width for tensor +# Low Priority: +# depthwise seperable convolution support through groups in builder.add_convolution +# add_optional -> for all RNNs defining what goes in and out (to define beam search or if input is streaming) +# mx.symbol.Embedding -> add_embedding takes indicies, word ids from dict that is outside coreml or +# in pipeline only if we have text mapping to indicies +# FusedRNNCell -> add_bidirlstm +# add_unilstm -> reverse_input param true as second and concat on outputs +# Do vanilla (0.9 mxnet) lstm, gru, vanilla_rnn + + +def convert_reshape(net, node, module, builder): + """Converts a reshape layer from mxnet to coreml. + + This doesn't currently handle the deprecated parameters for the reshape layer. + + Parameters + ---------- + network: net + An mxnet network object. + + layer: node + Node to convert. + + module: module + A module for MXNet + + builder: NeuralNetworkBuilder + A neural network builder object. + """ + input_name, output_name = _get_input_output_name(net, node) + name = node['name'] + target_shape = node['shape'] + + if any(item <= 0 for item in target_shape): + raise NotImplementedError('Special dimensional values less than or equal to 0 are not supported yet.' + 'Feel free to file an issue here: https://github.com/dmlc/mxnet/issues.') + + if 'reverse' in node and node['reverse'] == 'True': + raise NotImplementedError('"reverse" parameter is not supported by yet.' + 'Feel free to file an issue here: https://github.com/dmlc/mxnet/issues.') + + mode = 0 # CHANNEL_FIRST + builder.add_reshape(name, input_name, output_name, target_shape, mode) + + +def convert_transpose(net, node, module, builder): + """Convert a transpose layer from mxnet to coreml. + + Parameters + ---------- + network: net + A mxnet network object. + + layer: node + Node to convert. + + module: module + An module for MXNet + + builder: NeuralNetworkBuilder + A neural network builder object. + """ + input_name, output_name = _get_input_output_name(net, node) + name = node['name'] + param = node['attr'] + + axes = literal_eval(param['axes']) + builder.add_permute(name, axes, input_name, output_name) + + +def convert_flatten(net, node, module, builder): + """Convert a flatten layer from mxnet to coreml. + + Parameters + ---------- + network: net + A mxnet network object. + + layer: node + Node to convert. + + module: module + An module for MXNet + + builder: NeuralNetworkBuilder + A neural network builder object. + """ + input_name, output_name = _get_input_output_name(net, node) + name = node['name'] + mode = 0 # CHANNEL_FIRST + builder.add_flatten(name, mode, input_name, output_name) + + +def convert_softmax(net, node, module, builder): + """Convert a softmax layer from mxnet to coreml. + + Parameters + ---------- + network: net + A mxnet network object. + + layer: node + Node to convert. + + module: module + An module for MXNet + + builder: NeuralNetworkBuilder + A neural network builder object. + """ + input_name, output_name = _get_input_output_name(net, node) + name = node['name'] + builder.add_softmax(name=name, + input_name=input_name, + output_name=output_name) + + +def convert_activation(net, node, module, builder): + """Convert an activation layer from mxnet to coreml. + + Parameters + ---------- + network: net + A mxnet network object. + + layer: node + Node to convert. + + module: module + An module for MXNet + + builder: NeuralNetworkBuilder + A neural network builder object. + """ + input_name, output_name = _get_input_output_name(net, node) + name = node['name'] + mx_non_linearity = node['attr']['act_type'] + #TODO add SCALED_TANH, SOFTPLUS, SOFTSIGN, SIGMOID_HARD, LEAKYRELU, PRELU, ELU, PARAMETRICSOFTPLUS, THRESHOLDEDRELU, LINEAR + if mx_non_linearity == 'relu': + non_linearity = 'RELU' + elif mx_non_linearity == 'tanh': + non_linearity = 'TANH' + elif mx_non_linearity == 'sigmoid': + non_linearity = 'SIGMOID' + else: + raise TypeError('Unknown activation type %s' % mx_non_linearity) + builder.add_activation(name = name, + non_linearity = non_linearity, + input_name = input_name, + output_name = output_name) + + +def convert_elementwise_add(net, node, module, builder): + """Convert an elementwise add layer from mxnet to coreml. + + Parameters + ---------- + network: net + A mxnet network object. + + layer: node + Node to convert. + + module: module + An module for MXNet + + builder: NeuralNetworkBuilder + A neural network builder object. + """ + + input_names, output_name = _get_input_output_name(net, node, [0, 1]) + name = node['name'] + + builder.add_elementwise(name, input_names, output_name, 'ADD') + + +def convert_dense(net, node, module, builder): + """Convert a dense layer from mxnet to coreml. + + Parameters + ---------- + network: net + A mxnet network object. + + layer: node + Node to convert. + + module: module + An module for MXNet + + builder: NeuralNetworkBuilder + A neural network builder object. + """ + input_name, output_name = _get_input_output_name(net, node) + has_bias = True + name = node['name'] + + inputs = node['inputs'] + args, _ = module.get_params() + W = args[_get_node_name(net, inputs[1][0])].asnumpy() + if has_bias: + Wb = args[_get_node_name(net, inputs[2][0])].asnumpy() + else: + Wb = None + nC, nB = W.shape + + builder.add_inner_product( + name=name, + W=W, + b=Wb, + input_channels=nB, + output_channels=nC, + has_bias=has_bias, + input_name=input_name, + output_name=output_name + ) + + +def convert_convolution(net, node, module, builder): + """Convert a convolution layer from mxnet to coreml. + + Parameters + ---------- + network: net + A mxnet network object. + + layer: node + Node to convert. + + module: module + An module for MXNet + + builder: NeuralNetworkBuilder + A neural network builder object. + """ + input_name, output_name = _get_input_output_name(net, node) + name = node['name'] + param = node['attr'] + inputs = node['inputs'] + args, _ = module.get_params() + + if 'no_bias' in param.keys(): + has_bias = not literal_eval(param['no_bias']) + else: + has_bias = True + + if literal_eval(param['pad']) != (0, 0): + pad = literal_eval(param['pad']) + builder.add_padding( + name=name+"_pad", + left=pad[1], + right=pad[1], + top=pad[0], + bottom=pad[0], + value=0, + input_name=input_name, + output_name=name+"_pad_output") + input_name = name+"_pad_output" + + border_mode = "valid" + + n_filters = int(param['num_filter']) + + W = args[_get_node_name(net, inputs[1][0])].asnumpy() + if has_bias: + Wb = args[_get_node_name(net, inputs[2][0])].asnumpy() + else: + Wb = None + + channels = W.shape[1] + stride_height, stride_width = literal_eval(param['stride']) + kernel_height, kernel_width = literal_eval(param['kernel']) + + W = W.transpose((2, 3, 1, 0)) + builder.add_convolution( + name=name, + kernel_channels=channels, + output_channels=n_filters, + height=kernel_height, + width=kernel_width, + stride_height=stride_height, + stride_width=stride_width, + border_mode=border_mode, + groups=1, + W=W, + b=Wb, + has_bias=has_bias, + is_deconv=False, + output_shape=None, + input_name=input_name, + output_name=output_name) + + +def convert_pooling(net, node, module, builder): + """Convert a pooling layer from mxnet to coreml. + + Parameters + ---------- + network: net + A mxnet network object. + + layer: node + Node to convert. + + module: module + An module for MXNet + + builder: NeuralNetworkBuilder + A neural network builder object. + """ + input_name, output_name = _get_input_output_name(net, node) + name = node['name'] + param = node['attr'] + + layer_type_mx = param['pool_type'] + if layer_type_mx == 'max': + layer_type = 'MAX' + elif layer_type_mx == 'avg': + layer_type = 'AVERAGE' + else: + raise TypeError("Pooling type %s not supported" % layer_type_mx) + + # Add padding if there is any + if literal_eval(param['pad']) != (0, 0): + pad = literal_eval(param['pad']) + builder.add_padding( + name=name+"_pad", + left=pad[1], + right=pad[1], + top=pad[0], + bottom=pad[0], + value=0, + input_name=input_name, + output_name=name+"_pad_output") + input_name = name+"_pad_output" + + stride_height, stride_width = literal_eval(param['stride']) + kernel_width, kernel_height = literal_eval(param['kernel']) + + type_map = {'valid': 'VALID', 'full': 'INCLUDE_LAST_PIXEL'} + padding_type = param['pooling_convention'] if 'pooling_convention' in param else 'valid' + if padding_type not in type_map: + raise KeyError("%s type is not supported in this converter. It is a Github issue.") + padding_type = type_map[padding_type] + + if 'global_pool' in param.keys(): + is_global = literal_eval(param['global_pool']) + else: + is_global = False + + # For reasons why we are not using the standard builder but having our own implementation, + # see the function documentation. + _add_pooling.add_pooling_with_padding_types( + builder=builder, + name=name, + height=kernel_height, + width=kernel_width, + stride_height=stride_height, + stride_width=stride_width, + layer_type=layer_type, + padding_type=padding_type, + exclude_pad_area=False, + is_global=is_global, + input_name=input_name, + output_name=output_name + ) + + +def convert_batchnorm(net, node, module, builder): + """Convert a transpose layer from mxnet to coreml. + + Parameters + ---------- + network: net + A mxnet network object. + + layer: node + Node to convert. + + module: module + An module for MXNet + + builder: NeuralNetworkBuilder + A neural network builder object. + """ + input_name, output_name = _get_input_output_name(net, node) + name = node['name'] + inputs = node['inputs'] + + + eps = 1e-3 # Default value of eps for MXNet. + use_global_stats = False # Default value of use_global_stats for MXNet. + if 'attr' in node: + if 'eps' in node['attr']: + eps = literal_eval(node['attr']['eps']) + + args, aux = module.get_params() + gamma = args[_get_node_name(net, inputs[1][0])].asnumpy() + beta = args[_get_node_name(net, inputs[2][0])].asnumpy() + mean = aux[_get_node_name(net, inputs[3][0])].asnumpy() + variance = aux[_get_node_name(net, inputs[4][0])].asnumpy() + nb_channels = gamma.shape[0] + builder.add_batchnorm( + name=name, + channels=nb_channels, + gamma=gamma, + beta=beta, + mean=mean, + variance=variance, + input_name=input_name, + output_name=output_name, + epsilon=eps) + + +def convert_concat(net, node, module, builder): + """Convert concat layer from mxnet to coreml. + + Parameters + ---------- + network: net + A mxnet network object. + + layer: node + Node to convert. + + module: module + An module for MXNet + + builder: NeuralNetworkBuilder + A neural network builder object. + """ + # Get input and output names + input_names, output_name = _get_input_output_name(net, node, 'all') + name = node['name'] + mode = 'CONCAT' + builder.add_elementwise(name = name, input_names = input_names, + output_name = output_name, mode = mode) + + +def convert_deconvolution(net, node, module, builder): + """Convert a deconvolution layer from mxnet to coreml. + + Parameters + ---------- + network: net + A mxnet network object. + + layer: node + Node to convert. + + module: module + An module for MXNet + + builder: NeuralNetworkBuilder + A neural network builder object. + """ + input_name, output_name = _get_input_output_name(net, node) + name = node['name'] + param = node['attr'] + inputs = node['inputs'] + args, _ = module.get_params() + + if 'no_bias' in param.keys(): + has_bias = not literal_eval(param['no_bias']) + else: + has_bias = False + + border_mode = "valid" + + n_filters = int(param['num_filter']) + + output_shape = None + if 'target_shape' in param: + target_shape = literal_eval(param['target_shape']) + output_shape = (int(target_shape[0]), int(target_shape[1])) + + W = args[_get_node_name(net, inputs[1][0])].asnumpy() + + if has_bias: + Wb = args[_get_node_name(net, inputs[2][0])].asnumpy() + else: + Wb = None + + channels = W.shape[0] + stride_height, stride_width = literal_eval(param['stride']) + kernel_height, kernel_width = literal_eval(param['kernel']) + W = W.transpose((2, 3, 0, 1)) + + use_crop = False + if literal_eval(param['pad']) != (0, 0) and output_shape is None: + use_crop = True + + builder.add_convolution( + name=name, + kernel_channels=channels, + output_channels=n_filters, + height=kernel_height, + width=kernel_width, + stride_height=stride_height, + stride_width=stride_width, + border_mode=border_mode, + groups=1, + W=W, + b=Wb, + has_bias=has_bias, + is_deconv=True, + output_shape=output_shape, + input_name=input_name, + output_name=output_name+'before_pad' if use_crop else output_name + ) + + if use_crop: + pad = literal_eval(param['pad']) + builder.add_crop( + name=name+"_pad", + left=pad[1], + right=pad[1], + top=pad[0], + bottom=pad[0], + offset=0, + input_names=[output_name+'before_pad'], + output_name=output_name + ) diff --git a/tools/coreml/converter/_mxnet_converter.py b/tools/coreml/converter/_mxnet_converter.py new file mode 100644 index 000000000000..a9ea0f4d7ad6 --- /dev/null +++ b/tools/coreml/converter/_mxnet_converter.py @@ -0,0 +1,231 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +import _layers +import coremltools as _coremltools +import coremltools.models.datatypes as _datatypes +from coremltools.models import neural_network as _neural_network + +import json as _json +import mxnet as _mxnet +import numpy as _np + +_MXNET_LAYER_REGISTRY = { + 'FullyConnected' : _layers.convert_dense, + 'Activation' : _layers.convert_activation, + 'SoftmaxOutput' : _layers.convert_softmax, + 'Convolution' : _layers.convert_convolution, + 'Pooling' : _layers.convert_pooling, + 'Flatten' : _layers.convert_flatten, + 'transpose' : _layers.convert_transpose, + 'Concat' : _layers.convert_concat, + 'BatchNorm' : _layers.convert_batchnorm, + 'elemwise_add' : _layers.convert_elementwise_add, + 'Reshape' : _layers.convert_reshape, + 'Deconvolution' : _layers.convert_deconvolution, +} + +_MXNET_SKIP_LAYERS = [ + '_MulScalar', + 'Dropout', +] + +def _mxnet_remove_batch(input_data): + for blob in input_data: + input_data[blob] = _np.reshape(input_data[blob], input_data[blob].shape[1:]) + return input_data + +def check_error(model, path, shapes, output = 'softmax_output', verbose = True): + """ + Check the difference between predictions from MXNet and CoreML. + """ + coreml_model = _coremltools.models.MLModel(path) + input_data = {} + input_data_copy = {} + for ip in shapes: + input_data[ip] = _np.random.rand(*shapes[ip]).astype('f') + input_data_copy[ip] = _np.copy(input_data[ip]) + + dataIter = _mxnet.io.NDArrayIter(input_data_copy) + mx_out = model.predict(dataIter).flatten() + + e_out_dict = coreml_model.predict(_mxnet_remove_batch(input_data)) + e_out = e_out_dict[output].flatten() + error = _np.linalg.norm(e_out - mx_out) + + if verbose: + print "First few predictions from CoreML : %s" % e_out[0:10] + print "First few predictions from MXNet : %s" % e_out[0:10] + print "L2 Error on random data %s" % error + return error + +def _set_input_output_layers(builder, input_names, output_names): + input_layers_indices = [] + output_layers_indices = [] + layers = builder.spec.neuralNetwork.layers + for idx, l in enumerate(layers): + if set(input_names).intersection(l.input): + input_layers_indices.append(idx) + if set(output_names).intersection(l.output): + output_layers_indices.append(idx) + + builder.input_layers_indices = input_layers_indices + builder.output_layers_indices = output_layers_indices + builder.input_layers_is1d = [False for _ in input_names] + builder.output_layers_is1d = [False for _ in output_names] + +def _get_layer_converter_fn(layer): + """Get the right converter function for MXNet + """ + if layer in _MXNET_LAYER_REGISTRY: + return _MXNET_LAYER_REGISTRY[layer] + else: + raise TypeError("MXNet layer of type %s is not supported." % layer) + + +def convert(model, input_shape, order = None, class_labels = None, mode = None, preprocessor_args = None): + """Convert an MXNet model to the protobuf spec. + + Parameters + ---------- + model: MXNet model + A trained MXNet neural network model. + + order: Order of inputs + + class_labels: A string or list of strings. + As a string it represents the name of the file which contains the classification labels (one per line). + As a list of strings it represents a list of categories that map the index of the output of a neural network to labels in a classifier. + + mode: str ('classifier', 'regressor' or None) + Mode of the converted coreml model. + When mode = 'classifier', a NeuralNetworkClassifier spec will be constructed. + When mode = 'regressor', a NeuralNetworkRegressor spec will be constructed. + + **kwargs : + Provide keyword arguments for: + - input shapes. Supplied as a dictionary object with keyword "input_shape". + - pre-processing arguments: Supplied as a dictionary object with keyword "preprocessor_args". The parameters in the dictionary + tell the converted coreml model how to pre-process any input before an inference is run on it. + For the list of pre-processing arguments see + http://pythonhosted.org/coremltools/generated/coremltools.models.neural_network.html#coremltools.models.neural_network.NeuralNetworkBuilder.set_pre_processing_parameters + + Returns + ------- + model: A coreml model. + """ + if not isinstance(input_shape, dict): + raise TypeError("Must provide a dictionary for input shape. e.g input_shape={'data':(3,224,224)}") + + def remove_batch(dim): + return dim[1:] + + if order is None: + input_names = input_shape.keys() + input_dims = map(remove_batch, input_shape.values()) + else: + names = input_shape.keys() + shapes = map(remove_batch, input_shape.values()) + input_names = [names[i] for i in order] + input_dims = [shapes[i] for i in order] + + net = model.symbol + + # Infer shapes and store in a dictionary + shapes = net.infer_shape(**input_shape) + arg_names = net.list_arguments() + output_names = net.list_outputs() + aux_names = net.list_auxiliary_states() + shape_dict = {} + for idx, op in enumerate(arg_names): + shape_dict[op] = shapes[0][idx] + for idx, op in enumerate(output_names): + shape_dict[op] = shapes[1][idx] + for idx, op in enumerate(aux_names): + shape_dict[op] = shapes[2][idx] + + # Get the inputs and outputs + output_dims = shapes[1] + input_types = [_datatypes.Array(*dim) for dim in input_dims] + output_types = [_datatypes.Array(*dim) for dim in output_dims] + + # Make the builder + input_features = zip(input_names, input_types) + output_features = zip(output_names, output_types) + builder = _neural_network.NeuralNetworkBuilder(input_features, output_features, mode) + # Get out the layers + net = _json.loads(net.tojson()) + nodes = net['nodes'] + + for i, node in enumerate(nodes): + node['id'] = i + + if node['name'] in shape_dict: + node['shape'] = shape_dict[node['name']] + + node['outputs'] = [] + if 'inputs' in node: + for ip in node['inputs']: + nodes[ip[0]]['outputs'].append([i, 0]) + else: + node['inputs'] = [] + + # Mark the head nodes + for head in net['heads']: + head_id = head[0] + head_node = nodes[head_id] + head_node['outputs'] = [head] + head_node['name'] += "_output" + head_node['shape'] = shape_dict[head_node['name']] + + # For skipped layers, make sure nodes are modified + for node in nodes: + op = node['op'] + inputs = node['inputs'] + outputs = node['outputs'] + if op in _MXNET_SKIP_LAYERS: + nodes[inputs[0][0]]['outputs'][0] = outputs[0] + nodes[outputs[0][0]]['inputs'][0] = inputs[0] + + # Find the input and output names for this node + for idx, node in enumerate(nodes): + op = node['op'] + if op == 'null' or op in _MXNET_SKIP_LAYERS: + continue + name = node['name'] + print("%d : %s, %s" % (idx, name, op)) + converter_func = _get_layer_converter_fn(op) + converter_func(net, node, model, builder) + + # Set the right inputs and outputs + _set_input_output_layers(builder, input_names, output_names) + builder.set_input(input_names, input_dims) + builder.set_output(output_names, output_dims) + if preprocessor_args is not None: + builder.set_pre_processing_parameters(**preprocessor_args) + + if class_labels is not None: + if type(class_labels) is str: + labels = [l.strip() for l in open(class_labels).readlines()] + elif type(class_labels) is list: + labels = class_labels + else: + raise TypeError("synset variable of unknown type. Type found: %s. Expected either string or list of strings." % type(class_labels)) + builder.set_class_labels(class_labels = labels) + + # Return the model + return _coremltools.models.MLModel(builder.spec) \ No newline at end of file diff --git a/tools/coreml/mxnet_coreml_converter.py b/tools/coreml/mxnet_coreml_converter.py new file mode 100644 index 000000000000..502377eca864 --- /dev/null +++ b/tools/coreml/mxnet_coreml_converter.py @@ -0,0 +1,114 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +from __future__ import print_function +import argparse +from converter._mxnet_converter import convert +from utils import load_model +import yaml +from ast import literal_eval + + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description='Converts an MXNet model to a CoreML model') + + parser.add_argument( + '--model-prefix', required=True, type=str, + help="Prefix of the existing model. The model is expected to be stored in the same directory from where " + "this tool is being run. E.g. --model-prefix=squeezenet_v1.1. Note that this can include entire " + "directory name too. E.g. --model-prefix=~/Downloads/squeezenet_v1.1." + ) + parser.add_argument( + '--epoch', required=True, type=int, + help="The suffix of the MXNet model name which usually indicate the number of epochs. E.g. --epoch=0" + ) + parser.add_argument( + '--output-file', required=True, type=str, + help="File where the resulting CoreML model will be saved. E.g. --output-file=\"squeezenet-v11.mlmodel\"" + ) + parser.add_argument( + '--input-shape', required=True, type=str, + help="Input shape information in a JSON string format. E.g. --input-shape='{\"data\":\"3,224,224\"}' where" + " 'data' is the name of the input variable of the MXNet model and '3,244,244' is its shape " + "(channel, height and weight) of the input image data." + ) + parser.add_argument( + '--label-names', required=False, type=str, default='softmax_label', + help="label-names of the MXNet model's output variables. E.g. --label-names=softmax_label. " + "(Usually this is the name of the last layer followed by suffix _label.)" + ) + parser.add_argument( + '--mode', required=False, type=str, default=None, + help="When mode='classifier', a CoreML NeuralNetworkClassifier will be constructed. " + "When mode='regressor', a CoreML NeuralNetworkRegressor will be constructed. " + "When mode=None (default), a CoreML NeuralNetwork will be constructed." + ) + parser.add_argument( + '--class-labels', required=False, type=str, default=None, + help="As a string it represents the name of the file which contains the classification labels (synset file)." + ) + parser.add_argument( + '--pre-processing-arguments', required=False, type=str, default=None, + help="The parameters in the dictionary tell the converted coreml model how to pre-process any input " + "before an inference is run on it. For the list of pre-processing arguments see https://goo.gl/GzFe86" + "e.g. --pre-processing-arguments='{\"red_bias\": 127, \"blue_bias\":117, \"green_bias\": 103}'" + ) + + # TODO + # We need to test how to use the order + # parser.add_argument( + # '--order', required=True, type=str, default=None, + # help="" + # ) + + args, unknown = parser.parse_known_args() + + model_name = args.model_prefix + epoch_num = args.epoch + output_file = args.output_file + mode = args.mode + class_labels=args.class_labels + + # parse the input data name/shape and label name/shape + input_shape = yaml.safe_load(args.input_shape) + data_shapes = [] + for key in input_shape: + # We prepend 1 because the coreml model only accept 1 input data at a time. + shape = (1,)+literal_eval(input_shape[key]) + input_shape[key] = shape + data_shapes.append((key, shape)) + + # if label name is not in input then do not use the label + label_names = [args.label_names,] if args.label_names in input_shape else None + + pre_processing_arguments = args.pre_processing_arguments + + mod = load_model( + model_name=model_name, + epoch_num=epoch_num, + data_shapes=data_shapes, + label_shapes=None, + label_names=label_names + ) + + kwargs = {'input_shape': input_shape} + if pre_processing_arguments is not None: + kwargs['preprocessor_args'] = yaml.safe_load(pre_processing_arguments) + + coreml_model = convert(model=mod, mode=mode, class_labels=class_labels, **kwargs) + coreml_model.save(output_file) + print("\nSUCCESS\nModel %s has been converted and saved at %s\n" % (model_name, output_file)) diff --git a/tools/coreml/test/test_mxnet_converter.py b/tools/coreml/test/test_mxnet_converter.py new file mode 100644 index 000000000000..6692b44ec370 --- /dev/null +++ b/tools/coreml/test/test_mxnet_converter.py @@ -0,0 +1,949 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +import unittest +import mxnet as mx +import numpy as np +import sys +import os +current_working_directory = os.getcwd() +sys.path.append(current_working_directory + "/..") +sys.path.append(current_working_directory + "/../converter/") +import _mxnet_converter as mxnet_converter +from collections import namedtuple + + +def _mxnet_remove_batch(input_data): + for blob in input_data: + input_data[blob] = np.reshape(input_data[blob], input_data[blob].shape[1:]) + return input_data + + +def _get_mxnet_module(net, input_shape, mode, label_names, input_names=None): + """ Given a symbolic graph, input shape and the initialization mode, + returns an MXNet module. + """ + mx.random.seed(1993) + + mod = mx.mod.Module( + symbol=net, + context=mx.cpu(), + label_names=label_names + ) + mod.bind( + for_training=False, + data_shapes=[('data', input_shape)], + label_shapes=input_names + ) + if mode == 'random': + mod.init_params( + initializer=mx.init.Uniform(scale=.1) + ) + elif mode == 'zeros': + mod.init_params( + initializer=mx.init.Zero() + ) + elif mode == 'ones': + mod.init_params( + initializer=mx.init.One() + ) + else: + Exception(KeyError("%s is not a valid initialization mode" % mode)) + + return mod + + +class SingleLayerTest(unittest.TestCase): + """ + Unit test class for testing where converter is able to convert individual layers or not. + In order to do so, it converts model and generates preds on both CoreML and MXNet and check they are the same. + """ + def _test_mxnet_model(self, net, input_shape, mode, class_labels=None, coreml_mode=None, label_names=None, delta=1e-3, + pre_processing_args=None): + """ Helper method that convert the CoreML model into CoreML and compares the predictions over random data. + + Parameters + ---------- + net: MXNet Symbol Graph + The graph that we'll be converting into CoreML. + + input_shape: tuple of ints + The shape of input data. Generally of the format (batch-size, channels, height, width) + + mode: (random|zeros|ones) + The mode to use in order to set the parameters (weights and biases). + + label_names: list of strings + The names of the output labels. Default: None + + delta: float + The maximum difference b/w predictions of MXNet and CoreML that is tolerable. + """ + mod = _get_mxnet_module(net, input_shape, mode, label_names) + + # Generate some dummy data + input_data = {'data': np.random.uniform(-10., 10., input_shape)} + Batch = namedtuple('Batch', ['data']) + mod.forward(Batch([mx.nd.array(input_data['data'])])) + mxnet_preds = mod.get_outputs()[0].asnumpy().flatten() + + # Get predictions from coreml + coreml_model = mxnet_converter.convert( + model=mod, + class_labels=class_labels, + mode=coreml_mode, + input_shape={'data': input_shape}, + preprocessor_args=pre_processing_args + ) + coreml_preds = coreml_model.predict(_mxnet_remove_batch(input_data)).values()[0].flatten() + + # Check prediction accuracy + self.assertEquals(len(mxnet_preds), len(coreml_preds)) + for i in range(len(mxnet_preds)): + self.assertAlmostEquals(mxnet_preds[i], coreml_preds[i], delta = delta) + + def test_tiny_inner_product_zero_input(self): + np.random.seed(1988) + input_shape = (1, 10) + net = mx.sym.Variable('data') + net = mx.sym.FullyConnected(data=net, name='fc1', num_hidden=5) + self._test_mxnet_model(net, input_shape=input_shape, mode='zeros') + + def test_really_tiny_inner_product_ones_input(self): + np.random.seed(1988) + input_shape = (1, 1) + net = mx.sym.Variable('data') + net = mx.sym.FullyConnected(data=net, name='fc1', num_hidden=1) + self._test_mxnet_model(net, input_shape=input_shape, mode='ones') + + def test_really_tiny_2_inner_product_ones_input(self): + np.random.seed(1988) + input_shape = (1, 1) + net = mx.sym.Variable('data') + net = mx.sym.FullyConnected(data=net, name='fc1', num_hidden=5) + self._test_mxnet_model(net, input_shape=input_shape, mode='ones') + + def test_tiny_inner_product_ones_input(self): + np.random.seed(1988) + input_shape = (1, 10) + net = mx.sym.Variable('data') + net = mx.sym.FullyConnected(data=net, name='fc1', num_hidden=5) + self._test_mxnet_model(net, input_shape=input_shape, mode='ones') + + def test_tiny_inner_product_random_input(self): + np.random.seed(1988) + input_shape = (1, 10) + net = mx.sym.Variable('data') + net = mx.sym.FullyConnected(data=net, name='fc1', num_hidden=5) + self._test_mxnet_model(net, input_shape=input_shape, mode='random') + + def test_tiny_softmax_random_input(self): + np.random.seed(1988) + input_shape = (1, 10) + net = mx.sym.Variable('data') + net = mx.sym.FullyConnected(data=net, name='fc1', num_hidden=5) + net = mx.sym.SoftmaxOutput(net, name='softmax') + self._test_mxnet_model(net, input_shape=input_shape, mode='random', label_names=['softmax_label']) + + def test_tiny_relu_activation_random_input(self): + np.random.seed(1988) + input_shape = (1, 10) + net = mx.sym.Variable('data') + net = mx.sym.FullyConnected(data=net, name='fc1', num_hidden=5) + net = mx.sym.Activation(net, name='relu1', act_type="relu") + self._test_mxnet_model(net, input_shape=input_shape, mode='random') + + def test_tiny_sigmoid_activation_random_input(self): + np.random.seed(1988) + input_shape = (1, 10) + net = mx.sym.Variable('data') + net = mx.sym.FullyConnected(data=net, name='fc1', num_hidden=5) + net = mx.sym.Activation(net, name='sigmoid1', act_type="sigmoid") + self._test_mxnet_model(net, input_shape=input_shape, mode='random') + + def test_tiny_tanh_activation_random_input(self): + np.random.seed(1988) + input_shape = (1, 10) + + # Define a model + net = mx.sym.Variable('data') + net = mx.sym.FullyConnected(data=net, name='fc1', num_hidden=5) + net = mx.sym.Activation(net, name='tanh1', act_type="tanh") + self._test_mxnet_model(net, input_shape=input_shape, mode='random') + + def test_really_tiny_conv_random_input(self): + np.random.seed(1988) + input_shape = (1, 1, 10, 10) + num_filter = 1 + kernel = (1 ,1) + stride = (1, 1) + pad = (0, 0) + + # Define a model + net = mx.sym.Variable('data') + net = mx.symbol.Convolution( + data=net, + num_filter=num_filter, + kernel=kernel, + stride=stride, + pad=pad, + name='conv_1' + ) + self._test_mxnet_model(net, input_shape=input_shape, mode='random') + + def test_tiny_conv_ones_input(self): + np.random.seed(1988) + input_shape = (1, 1, 10, 10) + num_filter = 1 + kernel = (5, 5) + stride = (1, 1) + pad = (0, 0) + + # Define a model + net = mx.sym.Variable('data') + net = mx.symbol.Convolution( + data=net, + num_filter=num_filter, + kernel=kernel, + stride=stride, + pad=pad, + name='conv_1' + ) + self._test_mxnet_model(net, input_shape=input_shape, mode='ones') + + def test_tiny_conv_random_input(self): + np.random.seed(1988) + input_shape = (1, 1, 10, 10) + num_filter = 1 + kernel = (5, 5) + stride = (1, 1) + pad = (0, 0) + + # define a model + net = mx.sym.Variable('data') + net = mx.symbol.Convolution( + data=net, + num_filter=num_filter, + kernel=kernel, + stride=stride, + pad=pad, + name='conv_1' + ) + self._test_mxnet_model(net, input_shape=input_shape, mode='random') + + def test_tiny_asym_conv_random_input(self): + np.random.seed(1988) + input_shape = (1, 1, 10, 10) + num_filter = 1 + kernel = (5 ,3) + stride = (1, 1) + pad = (0, 0) + net = mx.sym.Variable('data') + net = mx.symbol.Convolution( + data=net, + num_filter=num_filter, + kernel=kernel, + stride=stride, + pad=pad, + name='conv_1' + ) + self._test_mxnet_model(net, input_shape=input_shape, mode='random') + + def test_tiny_asym_conv_random_asym_input(self): + np.random.seed(1988) + input_shape = (1, 1, 28, 18) + num_filter = 16 + kernel = (5, 3) + stride = (1, 1) + pad = (0, 0) + dilate = (1, 1) + # define a model + net = mx.sym.Variable('data') + net = mx.symbol.Convolution( + data=net, + num_filter=num_filter, + kernel=kernel, + stride=stride, + pad=pad, + name='conv_1', + dilate=dilate) + net = mx.sym.Activation(net, name='tanh', act_type="tanh") + self._test_mxnet_model(net, input_shape=input_shape, mode='random') + + def test_tiny_conv_valid_pooling_random_input(self): + np.random.seed(1988) + input_shape = (1, 1, 10, 10) + num_filter = 1 + kernel = (2, 2) + stride = (2, 2) + pad = (0, 0) + net = mx.sym.Variable('data') + net = mx.symbol.Convolution( + data=net, + num_filter=num_filter, + kernel=kernel, + stride=stride, + pad=pad, + name='conv_1' + ) + net = mx.symbol.Pooling( + data=net, + kernel=kernel, + stride=stride, + pad=pad, + name='pool_1', + pool_type='avg', + pooling_convention='valid' + ) + self._test_mxnet_model(net, input_shape=input_shape, mode='random') + + def test_tiny_conv_pooling_full_random_input(self): + np.random.seed(1988) + input_shape = (1, 1, 10, 10) + num_filter = 1 + kernel = (2, 2) + stride = (2, 2) + pad = (0, 0) + net = mx.sym.Variable('data') + net = mx.symbol.Convolution( + data=net, + num_filter=num_filter, + kernel=kernel, + stride=stride, + pad=pad, + name='conv_1' + ) + net = mx.symbol.Pooling( + data=net, + kernel=kernel, + stride=stride, + pad=pad, + name='pool_1', + pool_type='avg', + pooling_convention='full' + ) + self._test_mxnet_model(net, input_shape=input_shape, mode='random') + + def test_tiny_conv_pooling_full_random_input_with_padding(self): + np.random.seed(1988) + input_shape = (1, 3, 10, 10) + num_filter = 2 + kernel = (2, 2) + stride = (2, 2) + pad = (1, 1) + net = mx.sym.Variable('data') + net = mx.symbol.Convolution( + data=net, + num_filter=num_filter, + kernel=kernel, + stride=stride, + pad=pad, + name='conv_1' + ) + net = mx.symbol.Pooling( + data=net, + kernel=kernel, + stride=stride, + pad=pad, + name='pool_1', + pool_type='avg', + pooling_convention='full' + ) + self._test_mxnet_model(net, input_shape=input_shape, mode='random') + + def test_really_tiny_conv_random_3d_input(self): + np.random.seed(1988) + input_shape = (1, 3, 10, 10) + num_filter = 1 + kernel = (1, 1) + stride = (1, 1) + pad = (0, 0) + net = mx.sym.Variable('data') + net = mx.symbol.Convolution( + data=net, + num_filter=num_filter, + kernel=kernel, + stride=stride, + pad=pad, + name='conv_1' + ) + self._test_mxnet_model(net, input_shape=input_shape, mode='random') + + def test_really_tiny_conv_random_input_multi_filter(self): + np.random.seed(1988) + input_shape = (1, 1, 10, 10) + num_filter = 64 + kernel = (1, 1) + stride = (1, 1) + pad = (0, 0) + net = mx.sym.Variable('data') + net = mx.symbol.Convolution( + data=net, + num_filter=num_filter, + kernel=kernel, + stride=stride, + pad=pad, + name='conv_1' + ) + self._test_mxnet_model(net, input_shape=input_shape, mode='random') + + def test_tiny_conv_random_3d_input(self): + np.random.seed(1988) + input_shape = (1, 3, 10, 10) + num_filter = 1 + kernel = (5 ,5) + stride = (1, 1) + pad = (0, 0) + net = mx.sym.Variable('data') + net = mx.symbol.Convolution( + data=net, + num_filter=num_filter, + kernel=kernel, + stride=stride, + pad=pad, + name='conv_1' + ) + self._test_mxnet_model(net, input_shape=input_shape, mode='random') + + def test_tiny_conv_random_input_multi_filter(self): + np.random.seed(1988) + input_shape = (1, 1, 10, 10) + num_filter = 64 + kernel = (5, 5) + stride = (1, 1) + pad = (0, 0) + net = mx.sym.Variable('data') + net = mx.symbol.Convolution( + data=net, + num_filter=num_filter, + kernel=kernel, + stride=stride, + pad=pad, + name='conv_1' + ) + self._test_mxnet_model(net, input_shape=input_shape, mode='random') + + def test_conv_random(self): + np.random.seed(1988) + input_shape = (1, 3, 10, 10) + num_filter = 64 + kernel = (5, 5) + stride = (1, 1) + pad = (0, 0) + net = mx.sym.Variable('data') + net = mx.symbol.Convolution( + data=net, + num_filter=num_filter, + kernel=kernel, + stride=stride, + pad=pad, + name='conv_1' + ) + self._test_mxnet_model(net, input_shape=input_shape, mode='random') + + def test_flatten(self): + np.random.seed(1988) + input_shape = (1, 3, 10, 10) + num_filter = 64 + kernel = (5, 5) + stride = (1, 1) + pad = (0, 0) + + # define a model + net = mx.sym.Variable('data') + net = mx.symbol.Convolution( + data=net, + num_filter=num_filter, + kernel=kernel, + stride=stride, + pad=pad, + name='conv_1' + ) + net = mx.sym.Flatten(data=net, name='flatten1') + net = mx.sym.FullyConnected(data=net, name='fc1', num_hidden=5) + net = mx.sym.SoftmaxOutput(net, name='softmax') + self._test_mxnet_model(net, input_shape=input_shape, mode='random', label_names=['softmax_label']) + + def test_transpose(self): + np.random.seed(1988) + input_shape = (1, 3, 10, 10) + num_filter = 64 + kernel = (5, 5) + stride = (1, 1) + pad = (0, 0) + + net = mx.sym.Variable('data') + net = mx.sym.transpose(data=net, name='transpose', axes=(0, 1, 2, 3)) + net = mx.symbol.Convolution( + data=net, + num_filter=num_filter, + kernel=kernel, + stride=stride, + pad=pad, + name='conv_1' + ) + self._test_mxnet_model(net, input_shape=input_shape, mode='random') + + def test_reshape(self): + np.random.seed(1988) + input_shape = (1, 8) + net = mx.sym.Variable('data') + net = mx.sym.reshape(data=net, shape=(1, 2, 2, 2)) + self._test_mxnet_model(net, input_shape=input_shape, mode='random') + + def test_tiny_synset_random_input(self): + np.random.seed(1989) + input_shape = (1, 10) + net = mx.sym.Variable('data') + net = mx.sym.FullyConnected(data=net, name='fc1', num_hidden=5) + net = mx.sym.SoftmaxOutput(net, name='softmax') + mod = _get_mxnet_module(net, + input_shape=input_shape, + mode='random', + label_names=['softmax_label']) + + # Generate some dummy data + input_data = np.random.uniform(-0.1, 0.1, input_shape) + + Batch = namedtuple('Batch', ['data']) + mod.forward(Batch([mx.nd.array(input_data)])) + + kwargs = {'input_shape': {'data': input_shape}} + # Get predictions from coreml + coreml_model = mxnet_converter.convert( + model=mod, + class_labels=['Category1','Category2','Category3','Category4','Category5'], + mode='classifier', + **kwargs + ) + + prediction = coreml_model.predict(_mxnet_remove_batch({'data': input_data})) + self.assertEqual(prediction['classLabel'], 'Category3') + + def test_really_tiny_deconv_random_input(self): + np.random.seed(1988) + input_shape = (1, 1, 10, 10) + num_filter = 1 + kernel = (1, 1) + stride = (1, 1) + pad = (0, 0) + + # Define a model + net = mx.sym.Variable('data') + net = mx.symbol.Deconvolution( + data=net, + num_filter=num_filter, + kernel=kernel, + stride=stride, + pad=pad, + name='deconv_1' + ) + # Test the mxnet model + self._test_mxnet_model(net, input_shape=input_shape, mode='random') + + def test_tiny_deconv_ones_input(self): + np.random.seed(1988) + input_shape = (1, 1, 10, 10) + num_filter = 1 + kernel = (5, 5) + stride = (1, 1) + pad = (0, 0) + + # Define a model + net = mx.sym.Variable('data') + net = mx.symbol.Deconvolution( + data=net, + num_filter=num_filter, + kernel=kernel, + stride=stride, + pad=pad, + name='deconv_1' + ) + # Test the mxnet model + self._test_mxnet_model(net, input_shape=input_shape, mode='ones') + + def test_tiny_deconv_random_input(self): + np.random.seed(1988) + input_shape = (1, 1, 10, 10) + num_filter = 1 + kernel = (5, 5) + stride = (1, 1) + pad = (0, 0) + + # Define a model + net = mx.sym.Variable('data') + net = mx.symbol.Deconvolution( + data=net, + num_filter=num_filter, + kernel=kernel, + stride=stride, + pad=pad, + name='deconv_1' + ) + # Test the mxnet model + self._test_mxnet_model(net, input_shape=input_shape, mode='random') + + def test_tiny_asym_deconv_random_input(self): + np.random.seed(1988) + input_shape = (1, 1, 10, 10) + num_filter = 1 + kernel = (5, 3) + stride = (1, 1) + pad = (0, 0) + + # Define a model + net = mx.sym.Variable('data') + net = mx.symbol.Deconvolution( + data=net, + num_filter=num_filter, + kernel=kernel, + stride=stride, + pad=pad, + name='deconv_1' + ) + # Test the mxnet model + self._test_mxnet_model(net, input_shape=input_shape, mode='random') + + def test_tiny_asym_deconv_random_asym_input(self): + np.random.seed(1988) + input_shape = (1, 1, 28, 18) + num_filter = 16 + kernel = (5, 3) + stride = (1, 1) + pad = (0, 0) + dilate = (1, 1) + + # define a model + net = mx.sym.Variable('data') + net = mx.symbol.Deconvolution( + data=net, + num_filter=num_filter, + kernel=kernel, + stride=stride, + pad=pad, + dilate=dilate, + name='deconv_1' + ) + net = mx.sym.Activation(net, name = 'tanh', act_type = "tanh") + # Test the mxnet model + self._test_mxnet_model(net, input_shape=input_shape, mode='random') + + def test_tiny_deconv_pooling_random_input(self): + np.random.seed(1988) + input_shape = (1, 1, 10, 10) + num_filter = 1 + kernel = (5, 5) + stride = (1, 1) + pad = (0, 0) + + # define a model + net = mx.sym.Variable('data') + net = mx.symbol.Deconvolution( + data=net, + num_filter=num_filter, + kernel=kernel, + stride=stride, + pad=pad, + name='deconv_1' + ) + net = mx.symbol.Pooling( + data=net, + kernel=kernel, + stride=stride, + pad=pad, + name='pool_1', + pool_type='max' + ) + # Test the mxnet model + self._test_mxnet_model(net, input_shape=input_shape, mode='random') + + def test_really_tiny_deconv_random_3d_input(self): + np.random.seed(1988) + input_shape = (1, 3, 10, 10) + num_filter = 1 + kernel = (1, 1) + stride = (1, 1) + pad = (0, 0) + + # define a model + net = mx.sym.Variable('data') + net = mx.symbol.Deconvolution( + data=net, + num_filter=num_filter, + kernel=kernel, + stride=stride, + pad=pad, + name='deconv_1' + ) + # Test the mxnet model + self._test_mxnet_model(net, input_shape=input_shape, mode='random') + + def test_really_tiny_deconv_random_input_multi_filter(self): + np.random.seed(1988) + input_shape = (1, 1, 10, 10) + num_filter = 64 + kernel = (1, 1) + stride = (1, 1) + pad = (0, 0) + + # define a model + net = mx.sym.Variable('data') + net = mx.symbol.Deconvolution( + data=net, + num_filter=num_filter, + kernel=kernel, + stride=stride, + pad=pad, + name='deconv_1' + ) + # Test the mxnet model + self._test_mxnet_model(net, input_shape=input_shape, mode='random') + + def test_tiny_deconv_random_3d_input(self): + np.random.seed(1988) + input_shape = (1, 3, 10, 10) + num_filter = 1 + kernel = (5, 5) + stride = (1, 1) + pad = (0, 0) + + # define a model + net = mx.sym.Variable('data') + net = mx.symbol.Deconvolution( + data=net, + num_filter=num_filter, + kernel=kernel, + stride=stride, + pad=pad, + name='deconv_1' + ) + # Test the mxnet model + self._test_mxnet_model(net, input_shape=input_shape, mode='random') + + def test_tiny_deconv_random_input_multi_filter(self): + np.random.seed(1988) + input_shape = (1, 1, 10, 10) + num_filter = 64 + kernel = (5 ,5) + stride = (1, 1) + pad = (0, 0) + + # define a model + net = mx.sym.Variable('data') + net = mx.symbol.Deconvolution( + data=net, + num_filter=num_filter, + kernel=kernel, + stride=stride, + pad=pad, + name='deconv_1' + ) + # Test the mxnet model + self._test_mxnet_model(net, input_shape=input_shape, mode='random') + + def test_deconv_random(self): + np.random.seed(1988) + input_shape = (1, 10, 4, 4) + num_filter = 3 + kernel = (2, 2) + stride = (1, 1) + pad = (0, 0) + + # define a model + net = mx.sym.Variable('data') + net = mx.symbol.Deconvolution( + data=net, + num_filter=num_filter, + kernel=kernel, + stride=stride, + pad=pad, + no_bias=False, + name='deconv_1' + ) + # test the mxnet model + self._test_mxnet_model(net, input_shape=input_shape, mode='random') + + def test_deconv_random_output_shape(self): + np.random.seed(1988) + input_shape = (1, 10, 4, 4) + num_filter = 3 + kernel = (2, 2) + stride = (1, 1) + pad = (0, 0) + target_shape = (5, 5) + + # define a model + net = mx.sym.Variable('data') + net = mx.symbol.Deconvolution( + data=net, + num_filter=num_filter, + kernel=kernel, + stride=stride, + pad=pad, + no_bias=False, + target_shape=target_shape, + name='deconv_1' + ) + # test the mxnet model + self._test_mxnet_model(net, input_shape=input_shape, mode='random') + + def test_deconv_random_padding(self): + np.random.seed(1988) + input_shape = (1, 10, 9, 9) + num_filter = 3 + kernel = (3, 3) + stride = (3, 3) + pad = (2, 2) + + # define a model + net = mx.sym.Variable('data') + net = mx.symbol.Deconvolution( + data=net, + num_filter=num_filter, + kernel=kernel, + stride=stride, + pad=pad, + no_bias=False, + name='deconv_1') + # test the mxnet model + self._test_mxnet_model(net, input_shape=input_shape, mode='random') + + def test_conv_random_padding_odd(self): + np.random.seed(1988) + input_shape = (1, 10, 6, 6) + num_filter = 3 + kernel = (5, 5) + stride = (1, 1) + pad = (3, 3) + + # define a model + net = mx.sym.Variable('data') + net = mx.symbol.Convolution( + data=net, + num_filter=num_filter, + kernel=kernel, + stride=stride, + pad=pad, + no_bias=False, + name='conv_1' + ) + self._test_mxnet_model(net, input_shape=input_shape, mode='random') + + def test_conv_random_padding_even(self): + np.random.seed(1988) + input_shape = (1, 10, 6, 6) + num_filter = 3 + kernel = (5, 5) + stride = (1, 1) + pad = (2, 2) + + # define a model + net = mx.sym.Variable('data') + net = mx.symbol.Convolution( + data=net, + num_filter=num_filter, + kernel=kernel, + stride=stride, + pad=pad, + no_bias=False, + name='conv_1' + ) + self._test_mxnet_model(net, input_shape=input_shape, mode='random') + + def test_deconv_random_all_inputs(self): + np.random.seed(1988) + input_shape = (1, 10, 5, 5) + num_filter = 3 + kernel = (3, 3) + stride = (2, 2) + pad = (1, 1) + dilate = (1, 1) + target_shape = (11, 11) + + # define a model + net = mx.sym.Variable('data') + net = mx.symbol.Deconvolution( + data=net, + num_filter=num_filter, + kernel=kernel, + stride=stride, + pad=pad, + no_bias=False, + target_shape=target_shape, + dilate=dilate, + name='deconv_1' + ) + self._test_mxnet_model(net, input_shape=input_shape, mode='random') + + def test_batch_norm(self): + np.random.seed(1988) + input_shape = (1, 1, 2, 3) + + net = mx.sym.Variable('data') + gamma = mx.sym.Variable('gamma') + beta = mx.sym.Variable('beta') + moving_mean = mx.sym.Variable('moving_mean') + moving_var = mx.sym.Variable('moving_var') + net = mx.symbol.BatchNorm( + data=net, + gamma=gamma, + beta=beta, + moving_mean=moving_mean, + moving_var=moving_var, + use_global_stats=True, + name='batch_norm_1') + self._test_mxnet_model(net, input_shape=input_shape, mode='random', delta=1e-2) + + def test_batch_norm_no_global_stats(self): + """ This test should throw an exception since converter doesn't support + conversion of MXNet models that use local batch stats (i.e. + use_global_stats=False). The reason for this is CoreML doesn't support + local batch stats. + """ + np.random.seed(1988) + input_shape = (1, 1, 2, 3) + + net = mx.sym.Variable('data') + gamma = mx.sym.Variable('gamma') + beta = mx.sym.Variable('beta') + moving_mean = mx.sym.Variable('moving_mean') + moving_var = mx.sym.Variable('moving_var') + net = mx.symbol.BatchNorm( + data=net, + gamma=gamma, + beta=beta, + moving_mean=moving_mean, + moving_var=moving_var, + use_global_stats=False, + name='batch_norm_1') + self._test_mxnet_model(net, input_shape=input_shape, mode='random', delta=1e-2) + + def test_pre_processing_args(self): + np.random.seed(1988) + input_shape = (1, 10) + net = mx.sym.Variable('data') + net = mx.sym.FullyConnected(data=net, name='fc1', num_hidden=5) + net = mx.sym.SoftmaxOutput(net, name='softmax') + self._test_mxnet_model(net, input_shape=input_shape, mode='random', label_names=['softmax_label'], + pre_processing_args={'red_bias':0, 'blue_bias':0, 'green_bias':0, 'image_scale':1}) + + # TODO test_concat + + +if __name__ == '__main__': + suite = unittest.TestLoader().loadTestsFromTestCase(SingleLayerTest) + unittest.TextTestRunner(verbosity=2).run(suite) diff --git a/tools/coreml/test/test_mxnet_image.py b/tools/coreml/test/test_mxnet_image.py new file mode 100644 index 000000000000..ac30ac7f5ad9 --- /dev/null +++ b/tools/coreml/test/test_mxnet_image.py @@ -0,0 +1,136 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +import mxnet as mx +import numpy as np +import unittest +import sys +import os +current_working_directory = os.getcwd() +sys.path.append(current_working_directory + "/..") +sys.path.append(current_working_directory + "/../converter/") +import _mxnet_converter as mxnet_converter +from utils import load_model + + +VAL_DATA = 'data/val-5k-256.rec' +URL = 'http://data.mxnet.io/data/val-5k-256.rec' + + +def download_data(): + return mx.test_utils.download(URL, VAL_DATA) + + +def read_image(data_val, label_name): + data = mx.io.ImageRecordIter( + path_imgrec=data_val, + label_width=1, + preprocess_threads=4, + batch_size=32, + data_shape=(3,224,224), + label_name=label_name, + rand_corp=False, + rand_mirror=False, + shuffle=True + ) + return data + + +def is_correct_top_one(predict, label): + assert isinstance(predict, np.ndarray) + assert isinstance(label, np.float32) + predicted_label = np.argmax(predict) + return predicted_label == label + + +def is_correct_top_five(predict, label): + assert isinstance(predict, np.ndarray) + assert isinstance(label, np.float32) + top_five_preds = set(predict.argsort()[-5:]) + return label in top_five_preds + + +class ImageNetTest(unittest.TestCase): + def _test_image_prediction(self, model_name, epoch, label_name): + try: + data = read_image(VAL_DATA, label_name=label_name) + except: + download_data() + data = read_image(VAL_DATA, label_name=label_name) + + mod = load_model( + model_name=model_name, + epoch_num=epoch, + data_shapes=data.provide_data, + label_shapes=data.provide_label, + label_names=[label_name,] + ) + + input_shape = (1, 3, 224, 224) + coreml_model = mxnet_converter.convert(mod, input_shape={'data': input_shape}) + + mxnet_acc = [] + mxnet_top_5_acc = [] + coreml_acc = [] + coreml_top_5_acc = [] + + num_batch = 0 + + for batch in data: + mod.forward(batch, is_train=False) + mxnet_preds = mod.get_outputs()[0].asnumpy() + data_numpy = batch.data[0].asnumpy() + label_numpy = batch.label[0].asnumpy() + for i in xrange(32): + input_data = {'data': data_numpy[i]} + coreml_predict = coreml_model.predict(input_data).values()[0].flatten() + mxnet_predict = mxnet_preds[i] + label = label_numpy[i] + mxnet_acc.append(is_correct_top_one(mxnet_predict, label)) + mxnet_top_5_acc.append(is_correct_top_five(mxnet_predict, label)) + coreml_acc.append(is_correct_top_one(coreml_predict, label)) + coreml_top_5_acc.append(is_correct_top_five(coreml_predict, label)) + num_batch += 1 + if (num_batch == 5): break # we only use a subset of the batches. + + print "MXNet acc %s" % np.mean(mxnet_acc) + print "Coreml acc %s" % np.mean(coreml_acc) + print "MXNet top 5 acc %s" % np.mean(mxnet_top_5_acc) + print "Coreml top 5 acc %s" % np.mean(coreml_top_5_acc) + self.assertAlmostEqual(np.mean(mxnet_acc), np.mean(coreml_acc), delta=1e-4) + self.assertAlmostEqual(np.mean(mxnet_top_5_acc), np.mean(coreml_top_5_acc), delta=1e-4) + + def test_squeezenet(self): + print "Testing Image Classification with Squeezenet" + self._test_image_prediction(model_name='squeezenet_v1.1', epoch=0, label_name='prob_label') + + def test_inception_with_batch_normalization(self): + print "Testing Image Classification with Inception/BatchNorm" + self._test_image_prediction(model_name='Inception-BN', epoch=126, label_name='softmax_label') + + def test_resnet18(self): + print "Testing Image Classification with ResNet18" + self._test_image_prediction(model_name='resnet-18', epoch=0, label_name='softmax_label') + + def test_vgg16(self): + print "Testing Image Classification with vgg16" + self._test_image_prediction(model_name='vgg16', epoch=0, label_name='prob_label') + + +if __name__ == '__main__': + suite = unittest.TestLoader().loadTestsFromTestCase(ImageNetTest) + unittest.TextTestRunner(verbosity=2).run(suite) \ No newline at end of file diff --git a/tools/coreml/test/test_mxnet_models.py b/tools/coreml/test/test_mxnet_models.py new file mode 100644 index 000000000000..1732fb833c5f --- /dev/null +++ b/tools/coreml/test/test_mxnet_models.py @@ -0,0 +1,155 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +import unittest +import mxnet as mx +import numpy as np +import sys +import os +current_working_directory = os.getcwd() +sys.path.append(current_working_directory + "/..") +sys.path.append(current_working_directory + "/../converter/") +import _mxnet_converter as mxnet_converter +from collections import namedtuple + + +def _mxnet_remove_batch(input_data): + for blob in input_data: + input_data[blob] = np.reshape(input_data[blob], input_data[blob].shape[1:]) + return input_data + + +def _kl_divergence(distribution1, distribution2): + """ Calculates Kullback-Leibler Divergence b/w two distributions. + + Parameters + ---------- + distribution1: list of floats + distribution2: list of floats + """ + assert len(distribution1) == len(distribution2) + n = len(distribution1) + result = 1./n * sum(distribution1 * (np.log(distribution1) - np.log(distribution2))) + return result + + +class ModelsTest(unittest.TestCase): + """ + Unit test class that tests converter on entire MXNet models . + In order to test each unit test converts MXNet model into CoreML model using the converter, generate predictions + on both MXNet and CoreML and verifies that predictions are same (or similar). + """ + def _load_model(self, model_name, epoch_num, input_shape): + sym, arg_params, aux_params = mx.model.load_checkpoint(model_name, epoch_num) + mod = mx.mod.Module( + symbol=sym, + context=mx.cpu(), + label_names=None + ) + mod.bind( + for_training=False, + data_shapes=[('data', input_shape)], + label_shapes=mod._label_shapes + ) + mod.set_params( + arg_params=arg_params, + aux_params=aux_params, + allow_missing=True + ) + return mod + + def _test_model(self, model_name, epoch_num, input_shape=(1, 3, 224, 224), files=None): + """ Tests whether the converted CoreML model's preds are equal to MXNet preds for a given model or not. + + Parameters + ---------- + model_name: str + Prefix of the MXNet model name as stored on the local directory. + + epoch_num : int + Epoch number of model we would like to load. + + input_shape: tuple + The shape of the input data in the form of (batch_size, channels, height, width) + + files: list of strings + List of URLs pertaining to files that need to be downloaded in order to use the model. + """ + + if files is not None: + print("Downloading files from urls: %s" % (files)) + for url in files: + mx.test_utils.download(url) + print("Downloaded %s" % (url)) + + module = self._load_model( + model_name=model_name, + epoch_num=epoch_num, + input_shape=input_shape + ) + + coreml_model = mxnet_converter.convert(module, input_shape={'data': input_shape}) + + # Get predictions from MXNet and coreml + div=[] # For storing KL divergence for each input. + for _ in xrange(1): + np.random.seed(1993) + input_data = {'data': np.random.uniform(0, 1, input_shape).astype(np.float32)} + Batch = namedtuple('Batch', ['data']) + module.forward(Batch([mx.nd.array(input_data['data'])]), is_train=False) + mxnet_pred = module.get_outputs()[0].asnumpy().flatten() + coreml_pred = coreml_model.predict(_mxnet_remove_batch(input_data)).values()[0].flatten() + self.assertEqual(len(mxnet_pred), len(coreml_pred)) + div.append(_kl_divergence(mxnet_pred, coreml_pred)) + + print "Average KL divergence is % s" % np.mean(div) + self.assertTrue(np.mean(div) < 1e-4) + + def test_pred_inception_bn(self): + self._test_model(model_name='Inception-BN', epoch_num=126, + files=["http://data.mxnet.io/models/imagenet/inception-bn/Inception-BN-0126.params", + "http://data.mxnet.io/models/imagenet/inception-bn/Inception-BN-symbol.json"]) + + def test_pred_squeezenet_v11(self): + self._test_model(model_name='squeezenet_v1.1', epoch_num=0, + files=["http://data.mxnet.io/models/imagenet/squeezenet/squeezenet_v1.1-symbol.json", + "http://data.mxnet.io/models/imagenet/squeezenet/squeezenet_v1.1-0000.params"]) + + def test_pred_resnet_50(self): + self._test_model(model_name='resnet-50', epoch_num=0, + files=["http://data.mxnet.io/models/imagenet/resnet/50-layers/resnet-50-symbol.json", + "http://data.mxnet.io/models/imagenet/resnet/50-layers/resnet-50-0000.params"]) + + def test_pred_vgg16(self): + self._test_model(model_name='vgg16', epoch_num=0, + files=["http://data.mxnet.io/models/imagenet/vgg/vgg16-symbol.json", + "http://data.mxnet.io/models/imagenet/vgg/vgg16-0000.params"]) + + def test_pred_nin(self): + self._test_model(model_name='nin', epoch_num=0, + files=["http://data.dmlc.ml/models/imagenet/nin/nin-symbol.json", + "http://data.dmlc.ml/models/imagenet/nin/nin-0000.params"]) + + @unittest.skip("You need to download and unzip file: " + "http://data.mxnet.io/models/imagenet/inception-v3.tar.gz in order to run this test.") + def test_pred_inception_v3(self): + self._test_model(model_name='Inception-7', epoch_num=1, input_shape=(1, 3, 299, 299)) + + +if __name__ == '__main__': + suite = unittest.TestLoader().loadTestsFromTestCase(ModelsTest) + unittest.TextTestRunner(verbosity=2).run(suite) diff --git a/tools/coreml/utils.py b/tools/coreml/utils.py new file mode 100644 index 000000000000..1e4ff7a4d975 --- /dev/null +++ b/tools/coreml/utils.py @@ -0,0 +1,77 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +import mxnet as mx + + +def load_model(model_name, epoch_num, data_shapes, label_shapes, label_names, gpus=''): + """Loads and returns a given MXNet model. + + Parameters + ---------- + model_name: str + Prefix of the MXNet model name as stored on the local directory. + + epoch_num : int + Epoch number of model we would like to load. + + input_shape: tuple + The shape of the input data in the form of (batch_size, channels, height, width) + + files: list of strings + List of URLs pertaining to files that need to be downloaded in order to use the model. + + data_shapes: list of tuples. + List of tuples where each tuple is a pair of input variable name and its shape. + + label_shapes: list of (str, tuple) + Typically is ``data_iter.provide_label``. + + label_names: list of str + Name of the output labels in the MXNet symbolic graph. + + gpus: str + Comma separated string of gpu ids on which inferences are executed. E.g. 3,5,6 would refer to GPUs 3, 5 and 6. + If empty, we use CPU. + + Returns + ------- + MXNet module + """ + sym, arg_params, aux_params = mx.model.load_checkpoint(model_name, epoch_num) + if gpus == '': + devices = mx.cpu() + else: + devices = [mx.gpu(int(i)) for i in gpus.split(',')] + mod = mx.mod.Module( + symbol=sym, + context=devices, + label_names=label_names + ) + mod.bind( + for_training=False, + data_shapes=data_shapes, + label_shapes=label_shapes + ) + mod.set_params( + arg_params=arg_params, + aux_params=aux_params, + allow_missing=True + ) + return mod + + diff --git a/tools/im2rec.cc b/tools/im2rec.cc index a7ccfb667a12..856814024037 100644 --- a/tools/im2rec.cc +++ b/tools/im2rec.cc @@ -1,5 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + /*! - * Copyright (c) 2015 by Contributors * \file im2rec.cc * \brief convert images into image recordio format * Image Record Format: zeropad[64bit] imid[64bit] img-binary-content diff --git a/tools/im2rec.py b/tools/im2rec.py index 17b8754b1f63..ec6de1969414 100644 --- a/tools/im2rec.py +++ b/tools/im2rec.py @@ -1,303 +1,324 @@ -# -*- coding: utf-8 -*- -from __future__ import print_function -import os -import sys - -curr_path = os.path.abspath(os.path.dirname(__file__)) -sys.path.append(os.path.join(curr_path, "../python")) -import mxnet as mx -import random -import argparse -import cv2 -import time -import traceback - -try: - import multiprocessing -except ImportError: - multiprocessing = None - -def list_image(root, recursive, exts): - i = 0 - if recursive: - cat = {} - for path, dirs, files in os.walk(root, followlinks=True): - dirs.sort() - files.sort() - for fname in files: - fpath = os.path.join(path, fname) - suffix = os.path.splitext(fname)[1].lower() - if os.path.isfile(fpath) and (suffix in exts): - if path not in cat: - cat[path] = len(cat) - yield (i, os.path.relpath(fpath, root), cat[path]) - i += 1 - for k, v in sorted(cat.items(), key=lambda x: x[1]): - print(os.path.relpath(k, root), v) - else: - for fname in sorted(os.listdir(root)): - fpath = os.path.join(root, fname) - suffix = os.path.splitext(fname)[1].lower() - if os.path.isfile(fpath) and (suffix in exts): - yield (i, os.path.relpath(fpath, root), 0) - i += 1 - -def write_list(path_out, image_list): - with open(path_out, 'w') as fout: - for i, item in enumerate(image_list): - line = '%d\t' % item[0] - for j in item[2:]: - line += '%f\t' % j - line += '%s\n' % item[1] - fout.write(line) - -def make_list(args): - image_list = list_image(args.root, args.recursive, args.exts) - image_list = list(image_list) - if args.shuffle is True: - random.seed(100) - random.shuffle(image_list) - N = len(image_list) - chunk_size = (N + args.chunks - 1) / args.chunks - for i in xrange(args.chunks): - chunk = image_list[i * chunk_size:(i + 1) * chunk_size] - if args.chunks > 1: - str_chunk = '_%d' % i - else: - str_chunk = '' - sep = int(chunk_size * args.train_ratio) - sep_test = int(chunk_size * args.test_ratio) - if args.train_ratio == 1.0: - write_list(args.prefix + str_chunk + '.lst', chunk) - else: - if args.test_ratio: - write_list(args.prefix + str_chunk + '_test.lst', chunk[:sep_test]) - if args.train_ratio + args.test_ratio < 1.0: - write_list(args.prefix + str_chunk + '_val.lst', chunk[sep_test + sep:]) - write_list(args.prefix + str_chunk + '_train.lst', chunk[sep_test:sep_test + sep]) - -def read_list(path_in): - with open(path_in) as fin: - while True: - line = fin.readline() - if not line: - break - line = [i.strip() for i in line.strip().split('\t')] - line_len = len(line) - if line_len < 3: - print('lst should at least has three parts, but only has %s parts for %s' %(line_len, line)) - continue - try: - item = [int(line[0])] + [line[-1]] + [float(i) for i in line[1:-1]] - except Exception, e: - print('Parsing lst met error for %s, detail: %s' %(line, e)) - continue - yield item - -def image_encode(args, i, item, q_out): - fullpath = os.path.join(args.root, item[1]) - - if len(item) > 3 and args.pack_label: - header = mx.recordio.IRHeader(0, item[2:], item[0], 0) - else: - header = mx.recordio.IRHeader(0, item[2], item[0], 0) - - if args.pass_through: - try: - with open(fullpath) as fin: - img = fin.read() - s = mx.recordio.pack(header, img) - q_out.put((i, s, item)) - except Exception, e: - traceback.print_exc() - print('pack_img error:', item[1], e) - q_out.put((i, None, item)) - return - - try: - img = cv2.imread(fullpath, args.color) - except: - traceback.print_exc() - print('imread error trying to load file: %s ' % fullpath) - q_out.put((i, None, item)) - return - if img is None: - print('imread read blank (None) image for file: %s' % fullpath) - q_out.put((i, None, item)) - return - if args.center_crop: - if img.shape[0] > img.shape[1]: - margin = (img.shape[0] - img.shape[1]) / 2; - img = img[margin:margin + img.shape[1], :] - else: - margin = (img.shape[1] - img.shape[0]) / 2; - img = img[:, margin:margin + img.shape[0]] - if args.resize: - if img.shape[0] > img.shape[1]: - newsize = (args.resize, img.shape[0] * args.resize / img.shape[1]) - else: - newsize = (img.shape[1] * args.resize / img.shape[0], args.resize) - img = cv2.resize(img, newsize) - - try: - s = mx.recordio.pack_img(header, img, quality=args.quality, img_fmt=args.encoding) - q_out.put((i, s, item)) - except Exception, e: - traceback.print_exc() - print('pack_img error on file: %s' % fullpath, e) - q_out.put((i, None, item)) - return - -def read_worker(args, q_in, q_out): - while True: - deq = q_in.get() - if deq is None: - break - i, item = deq - image_encode(args, i, item, q_out) - -def write_worker(q_out, fname, working_dir): - pre_time = time.time() - count = 0 - fname = os.path.basename(fname) - fname_rec = os.path.splitext(fname)[0] + '.rec' - fname_idx = os.path.splitext(fname)[0] + '.idx' - record = mx.recordio.MXIndexedRecordIO(os.path.join(working_dir, fname_idx), - os.path.join(working_dir, fname_rec), 'w') - buf = {} - more = True - while more: - deq = q_out.get() - if deq is not None: - i, s, item = deq - buf[i] = (s, item) - else: - more = False - while count in buf: - s, item = buf[count] - del buf[count] - if s is not None: - record.write_idx(item[0], s) - - if count % 1000 == 0: - cur_time = time.time() - print('time:', cur_time - pre_time, ' count:', count) - pre_time = cur_time - count += 1 - -def parse_args(): - parser = argparse.ArgumentParser( - formatter_class=argparse.ArgumentDefaultsHelpFormatter, - description='Create an image list or \ - make a record database by reading from an image list') - parser.add_argument('prefix', help='prefix of input/output lst and rec files.') - parser.add_argument('root', help='path to folder containing images.') - - cgroup = parser.add_argument_group('Options for creating image lists') - cgroup.add_argument('--list', type=bool, default=False, - help='If this is set im2rec will create image list(s) by traversing root folder\ - and output to .lst.\ - Otherwise im2rec will read .lst and create a database at .rec') - cgroup.add_argument('--exts', nargs='+', default=['.jpeg', '.jpg'], - help='list of acceptable image extensions.') - cgroup.add_argument('--chunks', type=int, default=1, help='number of chunks.') - cgroup.add_argument('--train-ratio', type=float, default=1.0, - help='Ratio of images to use for training.') - cgroup.add_argument('--test-ratio', type=float, default=0, - help='Ratio of images to use for testing.') - cgroup.add_argument('--recursive', type=bool, default=False, - help='If true recursively walk through subdirs and assign an unique label\ - to images in each folder. Otherwise only include images in the root folder\ - and give them label 0.') - cgroup.add_argument('--shuffle', type=bool, default=True, help='If this is set as True, \ - im2rec will randomize the image order in .lst') - - rgroup = parser.add_argument_group('Options for creating database') - rgroup.add_argument('--pass-through', type=bool, default=False, - help='whether to skip transformation and save image as is') - rgroup.add_argument('--resize', type=int, default=0, - help='resize the shorter edge of image to the newsize, original images will\ - be packed by default.') - rgroup.add_argument('--center-crop', type=bool, default=False, - help='specify whether to crop the center image to make it rectangular.') - rgroup.add_argument('--quality', type=int, default=95, - help='JPEG quality for encoding, 1-100; or PNG compression for encoding, 1-9') - rgroup.add_argument('--num-thread', type=int, default=1, - help='number of thread to use for encoding. order of images will be different\ - from the input list if >1. the input list will be modified to match the\ - resulting order.') - rgroup.add_argument('--color', type=int, default=1, choices=[-1, 0, 1], - help='specify the color mode of the loaded image.\ - 1: Loads a color image. Any transparency of image will be neglected. It is the default flag.\ - 0: Loads image in grayscale mode.\ - -1:Loads image as such including alpha channel.') - rgroup.add_argument('--encoding', type=str, default='.jpg', choices=['.jpg', '.png'], - help='specify the encoding of the images.') - rgroup.add_argument('--pack-label', type=bool, default=False, - help='Whether to also pack multi dimensional label in the record file') - args = parser.parse_args() - args.prefix = os.path.abspath(args.prefix) - args.root = os.path.abspath(args.root) - return args - -if __name__ == '__main__': - args = parse_args() - if args.list: - make_list(args) - else: - if os.path.isdir(args.prefix): - working_dir = args.prefix - else: - working_dir = os.path.dirname(args.prefix) - files = [os.path.join(working_dir, fname) for fname in os.listdir(working_dir) - if os.path.isfile(os.path.join(working_dir, fname))] - count = 0 - for fname in files: - if fname.startswith(args.prefix) and fname.endswith('.lst'): - print('Creating .rec file from', fname, 'in', working_dir) - count += 1 - image_list = read_list(fname) - # -- write_record -- # - if args.num_thread > 1 and multiprocessing is not None: - q_in = [multiprocessing.Queue(1024) for i in range(args.num_thread)] - q_out = multiprocessing.Queue(1024) - read_process = [multiprocessing.Process(target=read_worker, args=(args, q_in[i], q_out)) \ - for i in range(args.num_thread)] - for p in read_process: - p.start() - write_process = multiprocessing.Process(target=write_worker, args=(q_out, fname, working_dir)) - write_process.start() - - for i, item in enumerate(image_list): - q_in[i % len(q_in)].put((i, item)) - for q in q_in: - q.put(None) - for p in read_process: - p.join() - - q_out.put(None) - write_process.join() - else: - print('multiprocessing not available, fall back to single threaded encoding') - import Queue - q_out = Queue.Queue() - fname = os.path.basename(fname) - fname_rec = os.path.splitext(fname)[0] + '.rec' - fname_idx = os.path.splitext(fname)[0] + '.idx' - record = mx.recordio.MXIndexedRecordIO(os.path.join(working_dir, fname_idx), - os.path.join(working_dir, fname_rec), 'w') - cnt = 0 - pre_time = time.time() - for i, item in enumerate(image_list): - image_encode(args, i, item, q_out) - if q_out.empty(): - continue - _, s, _ = q_out.get() - record.write_idx(item[0], s) - if cnt % 1000 == 0: - cur_time = time.time() - print('time:', cur_time - pre_time, ' count:', cnt) - pre_time = cur_time - cnt += 1 - if not count: - print('Did not find and list file with prefix %s'%args.prefix) +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +# -*- coding: utf-8 -*- +from __future__ import print_function +import os +import sys + +curr_path = os.path.abspath(os.path.dirname(__file__)) +sys.path.append(os.path.join(curr_path, "../python")) +import mxnet as mx +import random +import argparse +import cv2 +import time +import traceback +from builtins import range + +try: + import multiprocessing +except ImportError: + multiprocessing = None + +def list_image(root, recursive, exts): + i = 0 + if recursive: + cat = {} + for path, dirs, files in os.walk(root, followlinks=True): + dirs.sort() + files.sort() + for fname in files: + fpath = os.path.join(path, fname) + suffix = os.path.splitext(fname)[1].lower() + if os.path.isfile(fpath) and (suffix in exts): + if path not in cat: + cat[path] = len(cat) + yield (i, os.path.relpath(fpath, root), cat[path]) + i += 1 + for k, v in sorted(cat.items(), key=lambda x: x[1]): + print(os.path.relpath(k, root), v) + else: + for fname in sorted(os.listdir(root)): + fpath = os.path.join(root, fname) + suffix = os.path.splitext(fname)[1].lower() + if os.path.isfile(fpath) and (suffix in exts): + yield (i, os.path.relpath(fpath, root), 0) + i += 1 + +def write_list(path_out, image_list): + with open(path_out, 'w') as fout: + for i, item in enumerate(image_list): + line = '%d\t' % item[0] + for j in item[2:]: + line += '%f\t' % j + line += '%s\n' % item[1] + fout.write(line) + +def make_list(args): + image_list = list_image(args.root, args.recursive, args.exts) + image_list = list(image_list) + if args.shuffle is True: + random.seed(100) + random.shuffle(image_list) + N = len(image_list) + chunk_size = (N + args.chunks - 1) // args.chunks + for i in range(args.chunks): + chunk = image_list[i * chunk_size:(i + 1) * chunk_size] + if args.chunks > 1: + str_chunk = '_%d' % i + else: + str_chunk = '' + sep = int(chunk_size * args.train_ratio) + sep_test = int(chunk_size * args.test_ratio) + if args.train_ratio == 1.0: + write_list(args.prefix + str_chunk + '.lst', chunk) + else: + if args.test_ratio: + write_list(args.prefix + str_chunk + '_test.lst', chunk[:sep_test]) + if args.train_ratio + args.test_ratio < 1.0: + write_list(args.prefix + str_chunk + '_val.lst', chunk[sep_test + sep:]) + write_list(args.prefix + str_chunk + '_train.lst', chunk[sep_test:sep_test + sep]) + +def read_list(path_in): + with open(path_in) as fin: + while True: + line = fin.readline() + if not line: + break + line = [i.strip() for i in line.strip().split('\t')] + line_len = len(line) + if line_len < 3: + print('lst should at least has three parts, but only has %s parts for %s' %(line_len, line)) + continue + try: + item = [int(line[0])] + [line[-1]] + [float(i) for i in line[1:-1]] + except Exception as e: + print('Parsing lst met error for %s, detail: %s' %(line, e)) + continue + yield item + +def image_encode(args, i, item, q_out): + fullpath = os.path.join(args.root, item[1]) + + if len(item) > 3 and args.pack_label: + header = mx.recordio.IRHeader(0, item[2:], item[0], 0) + else: + header = mx.recordio.IRHeader(0, item[2], item[0], 0) + + if args.pass_through: + try: + with open(fullpath, 'rb') as fin: + img = fin.read() + s = mx.recordio.pack(header, img) + q_out.put((i, s, item)) + except Exception as e: + traceback.print_exc() + print('pack_img error:', item[1], e) + q_out.put((i, None, item)) + return + + try: + img = cv2.imread(fullpath, args.color) + except: + traceback.print_exc() + print('imread error trying to load file: %s ' % fullpath) + q_out.put((i, None, item)) + return + if img is None: + print('imread read blank (None) image for file: %s' % fullpath) + q_out.put((i, None, item)) + return + if args.center_crop: + if img.shape[0] > img.shape[1]: + margin = (img.shape[0] - img.shape[1]) // 2; + img = img[margin:margin + img.shape[1], :] + else: + margin = (img.shape[1] - img.shape[0]) // 2; + img = img[:, margin:margin + img.shape[0]] + if args.resize: + if img.shape[0] > img.shape[1]: + newsize = (args.resize, img.shape[0] * args.resize // img.shape[1]) + else: + newsize = (img.shape[1] * args.resize // img.shape[0], args.resize) + img = cv2.resize(img, newsize) + + try: + s = mx.recordio.pack_img(header, img, quality=args.quality, img_fmt=args.encoding) + q_out.put((i, s, item)) + except Exception as e: + traceback.print_exc() + print('pack_img error on file: %s' % fullpath, e) + q_out.put((i, None, item)) + return + +def read_worker(args, q_in, q_out): + while True: + deq = q_in.get() + if deq is None: + break + i, item = deq + image_encode(args, i, item, q_out) + +def write_worker(q_out, fname, working_dir): + pre_time = time.time() + count = 0 + fname = os.path.basename(fname) + fname_rec = os.path.splitext(fname)[0] + '.rec' + fname_idx = os.path.splitext(fname)[0] + '.idx' + record = mx.recordio.MXIndexedRecordIO(os.path.join(working_dir, fname_idx), + os.path.join(working_dir, fname_rec), 'w') + buf = {} + more = True + while more: + deq = q_out.get() + if deq is not None: + i, s, item = deq + buf[i] = (s, item) + else: + more = False + while count in buf: + s, item = buf[count] + del buf[count] + if s is not None: + record.write_idx(item[0], s) + + if count % 1000 == 0: + cur_time = time.time() + print('time:', cur_time - pre_time, ' count:', count) + pre_time = cur_time + count += 1 + +def parse_args(): + parser = argparse.ArgumentParser( + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + description='Create an image list or \ + make a record database by reading from an image list') + parser.add_argument('prefix', help='prefix of input/output lst and rec files.') + parser.add_argument('root', help='path to folder containing images.') + + cgroup = parser.add_argument_group('Options for creating image lists') + cgroup.add_argument('--list', type=bool, default=False, + help='If this is set im2rec will create image list(s) by traversing root folder\ + and output to .lst.\ + Otherwise im2rec will read .lst and create a database at .rec') + cgroup.add_argument('--exts', nargs='+', default=['.jpeg', '.jpg'], + help='list of acceptable image extensions.') + cgroup.add_argument('--chunks', type=int, default=1, help='number of chunks.') + cgroup.add_argument('--train-ratio', type=float, default=1.0, + help='Ratio of images to use for training.') + cgroup.add_argument('--test-ratio', type=float, default=0, + help='Ratio of images to use for testing.') + cgroup.add_argument('--recursive', type=bool, default=False, + help='If true recursively walk through subdirs and assign an unique label\ + to images in each folder. Otherwise only include images in the root folder\ + and give them label 0.') + cgroup.add_argument('--shuffle', type=bool, default=True, help='If this is set as True, \ + im2rec will randomize the image order in .lst') + + rgroup = parser.add_argument_group('Options for creating database') + rgroup.add_argument('--pass-through', type=bool, default=False, + help='whether to skip transformation and save image as is') + rgroup.add_argument('--resize', type=int, default=0, + help='resize the shorter edge of image to the newsize, original images will\ + be packed by default.') + rgroup.add_argument('--center-crop', type=bool, default=False, + help='specify whether to crop the center image to make it rectangular.') + rgroup.add_argument('--quality', type=int, default=95, + help='JPEG quality for encoding, 1-100; or PNG compression for encoding, 1-9') + rgroup.add_argument('--num-thread', type=int, default=1, + help='number of thread to use for encoding. order of images will be different\ + from the input list if >1. the input list will be modified to match the\ + resulting order.') + rgroup.add_argument('--color', type=int, default=1, choices=[-1, 0, 1], + help='specify the color mode of the loaded image.\ + 1: Loads a color image. Any transparency of image will be neglected. It is the default flag.\ + 0: Loads image in grayscale mode.\ + -1:Loads image as such including alpha channel.') + rgroup.add_argument('--encoding', type=str, default='.jpg', choices=['.jpg', '.png'], + help='specify the encoding of the images.') + rgroup.add_argument('--pack-label', type=bool, default=False, + help='Whether to also pack multi dimensional label in the record file') + args = parser.parse_args() + args.prefix = os.path.abspath(args.prefix) + args.root = os.path.abspath(args.root) + return args + +if __name__ == '__main__': + args = parse_args() + if args.list: + make_list(args) + else: + if os.path.isdir(args.prefix): + working_dir = args.prefix + else: + working_dir = os.path.dirname(args.prefix) + files = [os.path.join(working_dir, fname) for fname in os.listdir(working_dir) + if os.path.isfile(os.path.join(working_dir, fname))] + count = 0 + for fname in files: + if fname.startswith(args.prefix) and fname.endswith('.lst'): + print('Creating .rec file from', fname, 'in', working_dir) + count += 1 + image_list = read_list(fname) + # -- write_record -- # + if args.num_thread > 1 and multiprocessing is not None: + q_in = [multiprocessing.Queue(1024) for i in range(args.num_thread)] + q_out = multiprocessing.Queue(1024) + read_process = [multiprocessing.Process(target=read_worker, args=(args, q_in[i], q_out)) \ + for i in range(args.num_thread)] + for p in read_process: + p.start() + write_process = multiprocessing.Process(target=write_worker, args=(q_out, fname, working_dir)) + write_process.start() + + for i, item in enumerate(image_list): + q_in[i % len(q_in)].put((i, item)) + for q in q_in: + q.put(None) + for p in read_process: + p.join() + + q_out.put(None) + write_process.join() + else: + print('multiprocessing not available, fall back to single threaded encoding') + try: + import Queue as queue + except ImportError: + import queue + q_out = queue.Queue() + fname = os.path.basename(fname) + fname_rec = os.path.splitext(fname)[0] + '.rec' + fname_idx = os.path.splitext(fname)[0] + '.idx' + record = mx.recordio.MXIndexedRecordIO(os.path.join(working_dir, fname_idx), + os.path.join(working_dir, fname_rec), 'w') + cnt = 0 + pre_time = time.time() + for i, item in enumerate(image_list): + image_encode(args, i, item, q_out) + if q_out.empty(): + continue + _, s, _ = q_out.get() + record.write_idx(item[0], s) + if cnt % 1000 == 0: + cur_time = time.time() + print('time:', cur_time - pre_time, ' count:', cnt) + pre_time = cur_time + cnt += 1 + if not count: + print('Did not find and list file with prefix %s'%args.prefix) diff --git a/tools/ipynb2md.py b/tools/ipynb2md.py new file mode 100755 index 000000000000..227174c25eee --- /dev/null +++ b/tools/ipynb2md.py @@ -0,0 +1,76 @@ +#!/usr/bin/env python + +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + + +""" +Convert jupyter notebook into the markdown format. The notebook outputs will be +removed. + +It is heavily adapted from https://gist.github.com/decabyte/0ed87372774cf5d34d7e +""" + +import sys +import io +import os +import argparse +import nbformat + + +def remove_outputs(nb): + """Removes the outputs cells for a jupyter notebook.""" + for cell in nb.cells: + if cell.cell_type == 'code': + cell.outputs = [] + + +def clear_notebook(old_ipynb, new_ipynb): + with io.open(old_ipynb, 'r') as f: + nb = nbformat.read(f, nbformat.NO_CONVERT) + + remove_outputs(nb) + + with io.open(new_ipynb, 'w', encoding='utf8') as f: + nbformat.write(nb, f, nbformat.NO_CONVERT) + + +def main(): + parser = argparse.ArgumentParser( + description="Jupyter Notebooks to markdown" + ) + + parser.add_argument("notebook", nargs=1, help="The notebook to be converted.") + parser.add_argument("-o", "--output", help="output markdown file") + args = parser.parse_args() + + old_ipynb = args.notebook[0] + new_ipynb = 'tmp.ipynb' + md_file = args.output + print md_file + if not md_file: + md_file = os.path.splitext(old_ipynb)[0] + '.md' + + + clear_notebook(old_ipynb, new_ipynb) + os.system('jupyter nbconvert ' + new_ipynb + ' --to markdown --output ' + md_file) + with open(md_file, 'a') as f: + f.write('') + os.system('rm ' + new_ipynb) + +if __name__ == '__main__': + main() diff --git a/tools/kill-mxnet.py b/tools/kill-mxnet.py index 2bdf949893b0..2a4a4303400b 100644 --- a/tools/kill-mxnet.py +++ b/tools/kill-mxnet.py @@ -1,5 +1,23 @@ #!/usr/bin/env python +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + + import os, sys import subprocess diff --git a/tools/launch.py b/tools/launch.py index f5366657c2cc..de42ea2a7dd3 100755 --- a/tools/launch.py +++ b/tools/launch.py @@ -1,4 +1,22 @@ #!/usr/bin/env python + +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + """ Launch a distributed job """ @@ -54,7 +72,7 @@ def main(): args.num_servers = args.num_workers args = dmlc_opts(args) - + if args.host_file is None or args.host_file == 'None': if args.cluster == 'yarn': from dmlc_tracker import yarn diff --git a/tools/license_header.py b/tools/license_header.py new file mode 100644 index 000000000000..db67000837b0 --- /dev/null +++ b/tools/license_header.py @@ -0,0 +1,160 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +"""Add or check license header + +Usuage: + +- add the default license header to source files that do not contain a valid + license: + + python license_header.py add + +- check if every files has a license header + + python license_header.py check +""" + +import re +import os +import argparse + +# the default apache license +_LICENSE = """Licensed to the Apache Software Foundation (ASF) under one +or more contributor license agreements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF licenses this file +to you 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.""" + +# if a file contains any str in the list, then consider it has been licensed +_LICENSE_PATTERNS = ['Licensed to the Apache Software Foundation'] + +# the folders or files that will be ignored +_WHITE_LIST = ['R-package/', + 'cub/', + 'dlpack/', + 'dmlc-core/', + 'mshadow/', + 'nnvm', + 'ps-lite', + 'src/operator/mkl/', + 'src/operator/contrib/ctc_include/'] + +# language extensions and the according commment mark +_LANGS = {'.cc':'*', '.h':'*', '.cu':'*', '.cuh':'*', '.py':'#', + '.pm':'#', '.scala':'*', '.cc':'*', '.sh':'#', '.cmake':'#', + '.java':'*', '.sh':'#', '.cpp':'*', '.hpp':'*', '.c':'*', + '.bat':'rem', '.pl':'#'} + +# Previous license header, which will be removed +_OLD_LICENSE = re.compile('.*Copyright.*by Contributors') + +def _has_license(lines): + return any([any([p in l.decode('utf-8') for p in _LICENSE_PATTERNS]) for l in lines]) + +def _get_license(comment_mark): + if comment_mark == '*': + body = '/*\n' + else: + body = '' + for l in _LICENSE.split('\n'): + if comment_mark == '*': + body += ' ' + body += comment_mark + if len(l): + body += ' ' + l + body += '\n' + + if comment_mark == '*': + body += ' */\n' + body += '\n' + return body + +def _valid_file(fname, verbose=False): + if any([l in fname for l in _WHITE_LIST]): + if verbose: + print('skip ' + fname + ', it matches the white list') + return False + _, ext = os.path.splitext(fname) + if ext not in _LANGS: + if verbose: + print('skip ' + fname + ', unknown file extension') + return False + return True + +def process_file(fname, action, verbose=True): + if not _valid_file(fname, verbose): + return True + with open(fname, 'rb') as f: + lines = f.readlines() + if not lines: + return True + if _has_license(lines): + return True + elif action == 'check': + return False + _, ext = os.path.splitext(fname) + # remove old license + if ext == '.h' or ext == '.cc' or ext == '.cu' or ext == '.cpp' \ + or ext == '.hpp': + for i, l in enumerate(lines): + if _OLD_LICENSE.match(l.decode('utf-8')): + del lines[i] + break + with open(fname, 'wb') as f: + # shebang line + if lines[0].startswith(b'#!'): + f.write(lines[0].rstrip()+b'\n\n') + del lines[0] + f.write(str.encode(_get_license(_LANGS[ext]))) + for l in lines: + f.write(l.rstrip()+b'\n') + print('added license header to ' + fname) + return False + +def process_folder(root, action): + excepts = [] + for root, _, files in os.walk(root): + for f in files: + fname = os.path.normpath(os.path.join(root, f)) + if not process_file(fname, action): + excepts.append(fname) + if action == 'check' and excepts: + raise Exception('The following files do not contain a valid license, '+ + 'you can use `python tools/license_header.py add` to add'+ + 'them automatically', excepts) + +if __name__ == '__main__': + parser = argparse.ArgumentParser( + description='Add or check source license header') + parser.add_argument( + 'action', nargs=1, type=str, + choices=['add', 'check'], default='add', + help = 'add or check') + args = parser.parse_args() + process_folder(os.path.join(os.path.dirname(__file__), '..'), args.action[0]) diff --git a/tools/parse_log.py b/tools/parse_log.py index 070f770b8cf6..f0ce53dbe76f 100755 --- a/tools/parse_log.py +++ b/tools/parse_log.py @@ -1,4 +1,22 @@ #!/usr/bin/env python + +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + """ parse mxnet output log into a markdown table """ diff --git a/tools/pip_package/make_pip_package.sh b/tools/pip_package/make_pip_package.sh index a1af18bad528..46b4938b0785 100755 --- a/tools/pip_package/make_pip_package.sh +++ b/tools/pip_package/make_pip_package.sh @@ -1,5 +1,23 @@ #!/usr/bin/env bash +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + + # Assuming the script is run at mxnet/tools/pip_package # This script builds from scratch the dependencies of mxnet into static # librareis and statically links them to produce a (mostly) standalone diff --git a/tools/pip_package/setup.py b/tools/pip_package/setup.py index 45d761e53dea..e4bf48236bde 100644 --- a/tools/pip_package/setup.py +++ b/tools/pip_package/setup.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + # pylint: disable=invalid-name, exec-used """Setup mxnet package.""" from __future__ import absolute_import