SINGA-244 Separating swig interface and python binding files
- move swig interface files to src/api
- move python to root folder
- move python related cmake functions to python cmake files
- use add_library OBJECT command to build singa_objects, then be used by
singa shared library and python wraper library, avoiding build twice
- todo, add java binding
Project: http://git-wip-us.apache.org/repos/asf/incubator-singa/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-singa/commit/d76caea3
Tree: http://git-wip-us.apache.org/repos/asf/incubator-singa/tree/d76caea3
Diff: http://git-wip-us.apache.org/repos/asf/incubator-singa/diff/d76caea3
Branch: refs/heads/master
Commit: d76caea339ea8c2b006af8f32e0b382f64dab873
Parents: 0416a0f
Author: aaronwwf <[email protected]>
Authored: Tue Sep 6 22:24:36 2016 +0800
Committer: aaronwwf <[email protected]>
Committed: Wed Sep 7 09:47:31 2016 +0800
----------------------------------------------------------------------
CMakeLists.txt | 19 +-
cmake/Dependencies.cmake | 2 +-
cmake/Protobuf.cmake | 31 -
cmake/Utils.cmake | 70 -
include/singa/core/common.h | 2 +-
python/CMakeLists.txt | 147 ++
python/setup.py.in | 98 +
python/singa/__init__.py | 19 +
python/singa/command.py | 240 +++
python/singa/device.py | 123 ++
python/singa/initializer.py | 122 ++
python/singa/layer.py | 933 ++++++++++
python/singa/loss.py | 141 ++
python/singa/metric.py | 85 +
python/singa/model.py | 21 +
python/singa/net.py | 213 +++
python/singa/optimizer.py | 377 ++++
python/singa/tensor.py | 1011 +++++++++++
python/singa/utils.py | 47 +
src/CMakeLists.txt | 68 +-
src/api/config.i | 4 +
src/api/config.i.in | 4 +
src/api/core_device.i | 69 +
src/api/core_tensor.i | 371 ++++
src/api/model_layer.i | 102 ++
src/api/model_loss.i | 62 +
src/api/model_metric.i | 43 +
src/api/model_optimizer.i | 70 +
src/api/numpy.i | 3119 ++++++++++++++++++++++++++++++++
src/api/singa.i | 31 +
src/python/setup.py.in | 98 -
src/python/singa/__init__.py | 19 -
src/python/singa/command.py | 240 ---
src/python/singa/device.py | 123 --
src/python/singa/initializer.py | 122 --
src/python/singa/layer.py | 933 ----------
src/python/singa/loss.py | 141 --
src/python/singa/metric.py | 85 -
src/python/singa/model.py | 21 -
src/python/singa/net.py | 213 ---
src/python/singa/optimizer.py | 377 ----
src/python/singa/tensor.py | 1011 -----------
src/python/singa/utils.py | 47 -
src/python/swig/config.i.in | 4 -
src/python/swig/core_device.i | 69 -
src/python/swig/core_tensor.i | 371 ----
src/python/swig/model_layer.i | 102 --
src/python/swig/model_loss.i | 62 -
src/python/swig/model_metric.i | 43 -
src/python/swig/model_optimizer.i | 70 -
src/python/swig/numpy.i | 3119 --------------------------------
src/python/swig/singa.i | 31 -
52 files changed, 7482 insertions(+), 7463 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-singa/blob/d76caea3/CMakeLists.txt
----------------------------------------------------------------------
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 611cee4..762839b 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -38,22 +38,23 @@ SET(SINGA_INCLUDE_DIR
INCLUDE_DIRECTORIES(${SINGA_INCLUDE_DIR})
OPTION(USE_CBLAS "Use CBlas libs" ON)
-OPTION(USE_CUDA "Use Cuda libs" OFF)
+OPTION(USE_CUDA "Use Cuda libs" ON)
OPTION(USE_CUDNN "Use Cudnn libs" ON)
OPTION(USE_OPENCV "Use opencv" OFF)
OPTION(USE_LMDB "Use LMDB libs" OFF)
-OPTION(USE_PYTHON "Generate py wrappers" OFF)
+OPTION(USE_PYTHON "Generate py wrappers" ON)
+OPTION(USE_JAVA "Generate java wrappers" OFF)
OPTION(USE_OPENCL "Use OpenCL" OFF)
OPTION(ENABLE_DIST "enable distributed training" OFF)
INCLUDE("cmake/Dependencies.cmake")
-INCLUDE("cmake/Utils.cmake")
+#INCLUDE("cmake/Utils.cmake")
ADD_DEFINITIONS(-DUSE_CMAKE)
#message(STATUS "${SINGA_INCLUDE_DIR}")
CONFIGURE_FILE (
"${PROJECT_SOURCE_DIR}/cmake/Templates/singa_config.h.in"
- "${PROJECT_BINARY_DIR}/include/singa/singa_config.h")
+ "${PROJECT_SOURCE_DIR}/include/singa/singa_config.h")
#set(SINGA_CONFIGURE_SRC "${PROJECT_BINARY_DIR}/singa_config.h")
#LIST(APPEND SRCS ${SINGA_CONFIGURE_SRCS} ${PROJECT_BINARY_DIR}/singa_config.h)
@@ -64,6 +65,7 @@ SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)
IF (USE_CUDA)
ADD_SUBDIRECTORY(lib/cnmem)
LIST(APPEND SINGA_LINKER_LIBS cnmem)
+ SET(global_cuda_objs "")
ENDIF()
# TODO(wangwei) detect the ev lib
@@ -71,10 +73,19 @@ IF (ENABLE_DIST)
LIST(APPEND SINGA_LINKER_LIBS ev)
ENDIF()
+INCLUDE_DIRECTORIES("${CMAKE_BINARY_DIR}/include")
ADD_SUBDIRECTORY(src)
ADD_SUBDIRECTORY(test)
ADD_SUBDIRECTORY(examples)
+IF (USE_PYTHON)
+ ADD_SUBDIRECTORY(python)
+ENDIF()
+
+IF (USE_JAVA)
+ ADD_SUBDIRECTORY(java)
+ENDIF()
+
INSTALL(DIRECTORY include/singa DESTINATION ${CMAKE_INSTALL_PREFIX}/include)
INSTALL(FILES ${CMAKE_BINARY_DIR}/include/singa/singa_config.h DESTINATION
${CMAKE_INSTALL_PREFIX}/include/singa)
http://git-wip-us.apache.org/repos/asf/incubator-singa/blob/d76caea3/cmake/Dependencies.cmake
----------------------------------------------------------------------
diff --git a/cmake/Dependencies.cmake b/cmake/Dependencies.cmake
index a412151..d4e68ac 100644
--- a/cmake/Dependencies.cmake
+++ b/cmake/Dependencies.cmake
@@ -24,7 +24,7 @@ FIND_PACKAGE( Protobuf REQUIRED )
INCLUDE_DIRECTORIES(SYSTEM ${PROTOBUF_INCLUDE_DIR})
MESSAGE(STATUS "proto libs " ${PROTOBUF_LIBRARIES})
LIST(APPEND SINGA_LINKER_LIBS ${PROTOBUF_LIBRARIES})
-INCLUDE("cmake/Protobuf.cmake")
+#INCLUDE("cmake/Protobuf.cmake")
FIND_PACKAGE(Glog)
IF(GLOG_FOUND)
http://git-wip-us.apache.org/repos/asf/incubator-singa/blob/d76caea3/cmake/Protobuf.cmake
----------------------------------------------------------------------
diff --git a/cmake/Protobuf.cmake b/cmake/Protobuf.cmake
deleted file mode 100644
index 70cf0fe..0000000
--- a/cmake/Protobuf.cmake
+++ /dev/null
@@ -1,31 +0,0 @@
-# This script is taken from
-# https://github.com/Kitware/CMake/blob/master/Modules/FindProtobuf.cmake
-# and modified to our compilation.
-
-function(PROTOBUF_GENERATE_PYTHON OUTPUT)
- if(NOT ARGN)
- message(SEND_ERROR "Error: PROTOBUF_GENERATE_PYTHON() called
- without any proto files")
- return()
- endif(NOT ARGN)
-
- set(${OUTPUT})
- foreach(FIL ${ARGN})
- get_filename_component(ABS_FIL ${FIL} ABSOLUTE)
- get_filename_component(FIL_WE ${FIL} NAME_WE)
- get_filename_component(PATH ${FIL} PATH)
-
- list(APPEND ${OUTPUT}
"${CMAKE_BINARY_DIR}/python/singa/proto/${FIL_WE}_pb2.py")
-
- add_custom_command(
- OUTPUT "${CMAKE_BINARY_DIR}/python/singa/proto/${FIL_WE}_pb2.py"
- COMMAND ${PROTOBUF_PROTOC_EXECUTABLE}
- ARGS --python_out ${CMAKE_BINARY_DIR}/python/singa/proto
- --proto_path ${PATH} ${ABS_FIL}
- DEPENDS ${ABS_FIL}
- COMMENT "Running Python protocol buffer compiler on ${FIL}"
VERBATIM)
- endforeach()
-
- set_source_files_properties(${${SRCS}} ${${HDRS}} PROPERTIES GENERATED
TRUE)
- set(${OUTPUT} ${${OUTPUT}} PARENT_SCOPE)
-endfunction()
http://git-wip-us.apache.org/repos/asf/incubator-singa/blob/d76caea3/cmake/Utils.cmake
----------------------------------------------------------------------
diff --git a/cmake/Utils.cmake b/cmake/Utils.cmake
deleted file mode 100644
index a0373b8..0000000
--- a/cmake/Utils.cmake
+++ /dev/null
@@ -1,70 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-
-macro(swig_generate_cxx pylist_variable)
- if(NOT EXISTS "${CMKAE_BINARY_DIR}/python")
- execute_process(
- COMMAND mkdir ${CMAKE_BINARY_DIR}/python
- COMMAND mkdir ${CMAKE_BINARY_DIR}/python/singa
- COMMAND mkdir ${CMAKE_BINARY_DIR}/python/singa/proto
- ERROR_QUIET)
- endif()
- execute_process(
- COMMAND swig -c++ -python -I${CMAKE_SOURCE_DIR}/include
- -outdir ${CMAKE_BINARY_DIR}/python/singa
- ${ARGN})
-
- set(${pylist_variable}
"${CMAKE_SOURCE_DIR}/src/python/swig/singa_wrap.cxx")
-endmacro()
-
-function (create_symlinks)
- # Do nothing if building in-source
- if (${CMAKE_CURRENT_BINARY_DIR} STREQUAL ${CMAKE_CURRENT_SOURCE_DIR})
- return()
- endif()
-
- foreach (path_file ${ARGN})
- get_filename_component(folder ${path_file} PATH)
-
- # Create REAL folder
- file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/${folder}")
-
- # Delete symlink if it exists
- file(REMOVE "${CMAKE_BINARY_DIR}/${path_file}")
-
- # Get OS dependent path to use in `execute_process`
- file(TO_NATIVE_PATH "${CMAKE_BINARY_DIR}/${path_file}" link)
- file(TO_NATIVE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/${path_file}" target)
-
- if (UNIX)
- set(command ln -s ${target} ${link})
- else()
- set(command cmd.exe /c mklink ${link} ${target})
- endif()
-
- execute_process(COMMAND ${command}
- RESULT_VARIABLE result
- ERROR_VARIABLE output)
-
- if (NOT ${result} EQUAL 0)
- message(FATAL_ERROR "Could not create symbolic link for: ${target}
--> ${output}")
- endif()
-
- endforeach(path_file)
-endfunction(create_symlinks)
http://git-wip-us.apache.org/repos/asf/incubator-singa/blob/d76caea3/include/singa/core/common.h
----------------------------------------------------------------------
diff --git a/include/singa/core/common.h b/include/singa/core/common.h
index dc552c1..2c6d1d8 100644
--- a/include/singa/core/common.h
+++ b/include/singa/core/common.h
@@ -20,7 +20,7 @@
#define SINGA_CORE_COMMON_H_
#include <random>
#include <chrono>
-#include "./singa/singa_config.h"
+#include "singa/singa_config.h"
#include <atomic>
#include <memory>
#include "singa/utils/logging.h"
http://git-wip-us.apache.org/repos/asf/incubator-singa/blob/d76caea3/python/CMakeLists.txt
----------------------------------------------------------------------
diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt
new file mode 100644
index 0000000..8bf8319
--- /dev/null
+++ b/python/CMakeLists.txt
@@ -0,0 +1,147 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT 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 following function is taken from
+# https://github.com/Kitware/CMake/blob/master/Modules/FindProtobuf.cmake
+# and modified to our compilation.
+function(PROTOBUF_GENERATE_PYTHON OUTPUT)
+ if(NOT ARGN)
+ message(SEND_ERROR "Error: PROTOBUF_GENERATE_PYTHON() called
+ without any proto files")
+ return()
+ endif(NOT ARGN)
+
+ set(${OUTPUT})
+ foreach(FIL ${ARGN})
+ get_filename_component(ABS_FIL ${FIL} ABSOLUTE)
+ get_filename_component(FIL_WE ${FIL} NAME_WE)
+ get_filename_component(PATH ${FIL} PATH)
+
+ list(APPEND ${OUTPUT}
"${CMAKE_BINARY_DIR}/python/singa/proto/${FIL_WE}_pb2.py")
+
+ add_custom_command(
+ OUTPUT "${CMAKE_BINARY_DIR}/python/singa/proto/${FIL_WE}_pb2.py"
+ COMMAND ${PROTOBUF_PROTOC_EXECUTABLE}
+ ARGS --python_out ${CMAKE_BINARY_DIR}/python/singa/proto
+ --proto_path ${PATH} ${ABS_FIL}
+ DEPENDS ${ABS_FIL}
+ COMMENT "Running Python protocol buffer compiler on ${FIL}"
VERBATIM)
+ endforeach()
+
+ set_source_files_properties(${${SRCS}} ${${HDRS}} PROPERTIES GENERATED
TRUE)
+ set(${OUTPUT} ${${OUTPUT}} PARENT_SCOPE)
+endfunction()
+
+function (create_symlinks)
+ # Do nothing if building in-source
+ if (${CMAKE_CURRENT_BINARY_DIR} STREQUAL ${CMAKE_CURRENT_SOURCE_DIR})
+ return()
+ endif()
+
+ foreach (path_file ${ARGN})
+ get_filename_component(folder ${path_file} PATH)
+
+ # Create REAL folder
+ #file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/${folder}")
+
+ # Delete symlink if it exists
+ file(REMOVE "${CMAKE_CURRENT_BINARY_DIR}/${path_file}")
+
+ # Get OS dependent path to use in `execute_process`
+ file(TO_NATIVE_PATH "${CMAKE_CURRENT_BINARY_DIR}/${path_file}" link)
+ file(TO_NATIVE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/${path_file}" target)
+
+ if (UNIX)
+ set(command ln -s ${target} ${link})
+ else()
+ set(command cmd.exe /c mklink ${link} ${target})
+ endif()
+
+ execute_process(COMMAND ${command}
+ RESULT_VARIABLE result
+ ERROR_VARIABLE output)
+
+ if (NOT ${result} EQUAL 0)
+ message(FATAL_ERROR "Could not create symbolic link for: ${target}
--> ${output}")
+ endif()
+
+ endforeach(path_file)
+endfunction(create_symlinks)
+
+
+# generate protobuf sources
+FILE(GLOB proto_files ${CMAKE_SOURCE_DIR}/src/proto/*.proto)
+PROTOBUF_GENERATE_PYTHON(proto_pys ${proto_files})
+#MESSAGE(STATUS "proto pys: ${proto_pys}")
+
+# generate cxx and wrap.py
+if(NOT EXISTS "${CMKAE_BINARY_DIR}/python")
+ execute_process(
+ COMMAND mkdir ${CMAKE_BINARY_DIR}/python
+ COMMAND mkdir ${CMAKE_BINARY_DIR}/python/singa
+ COMMAND mkdir ${CMAKE_BINARY_DIR}/python/singa/proto
+ ERROR_QUIET)
+endif()
+execute_process(
+ COMMAND mkdir ${CMAKE_BINARY_DIR}/src/api
+ COMMAND swig -c++ -python -I${CMAKE_SOURCE_DIR}/include
+ -outdir ${CMAKE_BINARY_DIR}/python/singa
+ -o ${CMAKE_BINARY_DIR}/src/api/singa_wrap.cxx
+ ${CMAKE_SOURCE_DIR}/src/api/singa.i )
+
+set(python_srcs "${CMAKE_BINARY_DIR}/src/api/singa_wrap.cxx")
+
+#Create symlinks for all python source files Do not omit !!!RELATIVE!!!
+file(GLOB_RECURSE python_source_files RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
*.py)
+create_symlinks(${python_source_files})
+
+
+IF(USE_CUDA)
+# remain this custom command to avoid cuda objs can't find
+ADD_CUSTOM_COMMAND(
+ OUTPUT ${global_cuda_objs}
+ COMMAND ${CMAKE_COMMAND} -E make_directory "${CMAKE_BINARY_DIR}/"
+ )
+ENDIF(USE_CUDA)
+
+ADD_LIBRARY(_singa_wrap SHARED $<TARGET_OBJECTS:singa_objects> ${python_srcs}
${proto_pys} ${global_cuda_objs})
+TARGET_LINK_LIBRARIES(_singa_wrap ${SINGA_LINKER_LIBS} ${PYTHON_LIBRARIES})
+TARGET_INCLUDE_DIRECTORIES(_singa_wrap PRIVATE ${PYTHON_INCLUDE_DIRS})
+SET_TARGET_PROPERTIES(_singa_wrap
+PROPERTIES PREFIX ""
+LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/python/singa
+)
+
+#SETUP
+SET(SETUP_PY_IN "setup.py.in")
+SET(SETUP_PY "${CMAKE_BINARY_DIR}/python/setup.py")
+CONFIGURE_FILE(${SETUP_PY_IN} ${SETUP_PY})
+
+#create python/singa/proto/__init__.py
+FILE(WRITE ${CMAKE_BINARY_DIR}/python/singa/proto/__init__.py "")
+#MESSAGE(STATUS "apple: ${APPLE}")
+IF(APPLE)
+ADD_CUSTOM_TARGET(
+ change_suffix ALL
+ COMMAND ${CMAKE_COMMAND} -E rename
"${CMAKE_BINARY_DIR}/python/singa/_singa_wrap.dylib"
"${CMAKE_BINARY_DIR}/python/singa/_singa_wrap.so"
+ COMMENT "change .dylib to .so in mac system"
+)
+ADD_DEPENDENCIES(change_suffix _singa_wrap)
+ENDIF(APPLE)
+
+
http://git-wip-us.apache.org/repos/asf/incubator-singa/blob/d76caea3/python/setup.py.in
----------------------------------------------------------------------
diff --git a/python/setup.py.in b/python/setup.py.in
new file mode 100644
index 0000000..881cd30
--- /dev/null
+++ b/python/setup.py.in
@@ -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.
+#
+
+# Always prefer setuptools over distutils
+from setuptools import setup
+
+
+setup(
+ name='singa',
+
+ version='${PACKAGE_VERSION}',
+
+ description='A General Deep Learning System',
+
+ url='https://github.com/apache/incubator-singa',
+
+ author='Apache SINGA (incubating)',
+ author_email='[email protected]',
+
+ license='Apache 2',
+
+ classifiers=[
+ # 3 - Alpha
+ # 4 - Beta
+ # 5 - Production/Stable
+ 'Development Status :: 3 - Alpha',
+
+ 'Intended Audience :: Developers',
+ 'Topic :: Deep Learning System ',
+
+ 'License :: Apache License',
+
+ # Specify the Python versions you support here. In particular, ensure
+ # that you indicate whether you support Python 2, Python 3 or both.
+ 'Programming Language :: Python :: 2',
+ 'Programming Language :: Python :: 2.6',
+ 'Programming Language :: Python :: 2.7',
+ ],
+
+ keywords='deep learning singa apache',
+
+ packages= ['singa', 'singa.proto'],
+
+ #py_modules=["singa"],
+
+ install_requires=[
+ 'numpy>=1.11.0',
+ 'protobuf>=2.5.0,<3'
+ ],
+
+ #List additional groups of dependencies here (e.g. development
+ #dependencies). You can install these using the following syntax,
+ #for example:
+ #$ pip install -e .[dev,test]
+ #extras_require={
+ # 'dev': ['check-manifest'],
+ # 'test': ['coverage'],
+ #},
+
+ #If there are data files included in your packages that need to be
+ #installed, specify them here. If using Python 2.6 or less, then these
+ #have to be included in MANIFEST.in as well.
+
+ package_data={
+ 'singa': ['_singa_wrap.so'],
+ },
+
+ #Although 'package_data' is the preferred approach, in some case you may
+ #need to place data files outside of your packages. See:
+
#http://docs.python.org/3.4/distutils/setupscript.html#installing-additional-files
# noqa
+ #In this case, 'data_file' will be installed into '<sys.prefix>/my_data'
+ #data_files=[('my_data', ['data/data_file'])],
+
+ #To provide executable scripts, use entry points in preference to the
+ #"scripts" keyword. Entry points provide cross-platform support and allow
+ #pip to create the appropriate form of executable for the target platform.
+
+ entry_points={
+ 'console_scripts': [
+ 'singa=singa.command:main',
+ ],
+ },
+)
http://git-wip-us.apache.org/repos/asf/incubator-singa/blob/d76caea3/python/singa/__init__.py
----------------------------------------------------------------------
diff --git a/python/singa/__init__.py b/python/singa/__init__.py
new file mode 100644
index 0000000..c81c6ef
--- /dev/null
+++ b/python/singa/__init__.py
@@ -0,0 +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.
+#
+
+
http://git-wip-us.apache.org/repos/asf/incubator-singa/blob/d76caea3/python/singa/command.py
----------------------------------------------------------------------
diff --git a/python/singa/command.py b/python/singa/command.py
new file mode 100644
index 0000000..f14c8c5
--- /dev/null
+++ b/python/singa/command.py
@@ -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.
+# =============================================================================
+
+'''
+This script is the main entrance for user to run singa inside a model workspace
+
+To use this script, user sudo install these dependencies: flask pillow and
protobuf
+'''
+
+import sys, glob, os, random, shutil, time
+from flask import Flask, request, redirect, url_for
+import numpy as np
+import ConfigParser
+import urllib, traceback
+
+
+from argparse import ArgumentParser
+from argparse import RawDescriptionHelpFormatter
+sys.path.append(os.getcwd())
+
+__all__ = []
+__version__ = 0.1
+__date__ = '2016-07-20'
+__updated__ = '2016-07-20'
+__shortdesc__ = '''
+welcome to singa
+'''
+
+app = Flask(__name__)
+config = ConfigParser.RawConfigParser()
+service = {}
+data_path = "data_"
+parameter_path = "parameter_"
+
+debug = False
+
+class CLIError(Exception):
+ '''Generic exception to raise and log different fatal errors.'''
+ def __init__(self, msg):
+ super(CLIError).__init__(type(self))
+ self.msg = "E: %s" % msg
+ def __str__(self):
+ return self.msg
+ def __unicode__(self):
+ return self.msg
+
+def main(argv=None): # IGNORE:C0111
+ '''Command line options.'''
+
+ from . import device
+
+ if argv is None:
+ argv = sys.argv
+ else:
+ sys.argv.extend(argv)
+
+ program_name = os.path.basename(sys.argv[0])
+ program_version = "v%s" % __version__
+ program_build_date = str(__updated__)
+ program_version_message = '%%(prog)s %s (%s)' % (program_version,
program_build_date)
+ program_shortdesc = __shortdesc__
+ program_license = '''%s
+
+ Created by dbsystem group on %s.
+ Copyright 2016 NUS School of Computing. All rights reserved.
+
+ Licensed under the Apache License 2.0
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Distributed on an "AS IS" basis without warranties
+ or conditions of any kind, either express or implied.
+
+USAGE
+''' % (program_shortdesc, str(__date__))
+
+ global debug
+
+ try:
+ # Setup argument parser
+ parser = ArgumentParser(description=program_license,
formatter_class=RawDescriptionHelpFormatter)
+ parser.add_argument("-p", "--port", dest="port", default=5000,
help="the port to listen to, default is 5000")
+ parser.add_argument("-param", "--parameter", dest="parameter",
help="the parameter file path to be loaded")
+ parser.add_argument("-D", "--debug", dest="debug",
action="store_true", help="whether need to debug")
+ parser.add_argument("-R", "--reload", dest="reload_data",
action="store_true", help="whether need to reload data")
+ parser.add_argument("-C", "--cpu", dest="use_cpu",
action="store_true", help="Using cpu or not, default is using gpu")
+ parser.add_argument("-m", "--mode", dest="mode",
choices=['train','test','serve'], default='serve', help="On Which mode
(train,test,serve) to run singa")
+ parser.add_argument('-V', '--version', action='version',
version=program_version_message)
+
+ # Process arguments
+ args = parser.parse_args()
+
+ port = args.port
+ parameter_file = args.parameter
+ mode = args.mode
+ need_reload = args.reload_data
+ use_cpu = args.use_cpu
+ debug = args.debug
+
+ #prepare data files
+ config.read('file.cfg')
+ file_prepare(need_reload)
+
+
+ import network as net
+ model = net.create()
+
+ #load parameter
+ parameter_file=get_parameter(parameter_file)
+
+ if parameter_file:
+ print "load parameter file: %s" % parameter_file
+ model.load(parameter_file)
+
+ if use_cpu:
+ raise CLIError("Currently cpu is not support!")
+ else:
+ print "runing with gpu"
+ d = device.create_cuda_gpu()
+
+ model.to_device(d)
+
+ if mode == "serve":
+ print "runing singa in serve mode, listen to port: %s " % port
+ global service
+ from serve import Service
+ service =Service(model,d)
+
+ app.debug = debug
+ app.run(host='0.0.0.0', port= port)
+ elif mode == "train":
+ print "runing singa in train mode"
+ global trainer
+ from train import Trainer
+ trainer= Trainer(model,d)
+ if not parameter_file:
+ trainer.initialize()
+ trainer.train()
+ else:
+ raise CLIError("Currently only serve mode is surpported!")
+ return 0
+ except KeyboardInterrupt:
+ ### handle keyboard interrupt ###
+ return 0
+ except Exception, e:
+ if debug:
+ traceback.print_exc()
+ raise(e)
+ indent = len(program_name) * " "
+ sys.stderr.write(program_name + ": " + str(e) + "\n")
+ sys.stderr.write(indent + " for help use --help \n\n")
+ return 2
+
+def file_prepare(reload_data=False):
+ '''
+ download all files and generate data.py
+ '''
+ if not reload_data and os.path.exists("data_.py"):
+ return
+
+ print "download file"
+ #clean data
+ shutil.rmtree("data_.py",ignore_errors=True)
+ shutil.rmtree("data_",ignore_errors=True)
+
+ data_py=open("data_.py",'w')
+ data_py.write("#%s" % "This file is Generated by SINGA, please don't
edit\n\n")
+ if config.has_section("data"):
+ file_list = config.items("data")
+ #download files
+ for f in file_list:
+ name,path=download_file(f[0],f[1],data_path)
+ data_py.write("%s=\"%s\"\n" % (name,path))
+
+ data_py.flush()
+ data_py.close()
+
+ if config.has_section("parameter"):
+ parameter_list = config.items("parameter")
+ for p in parameter_list:
+ download_file(p[0],p[1],parameter_path)
+
+def download_file(name,path,dest):
+ '''
+ download one file to dest
+ '''
+ if not os.path.exists(dest):
+ os.makedirs(dest)
+ if (path.startswith('http')):
+ file_name = path.split('/')[-1]
+ target = os.path.join(dest,file_name)
+ urllib.urlretrieve(path,target)
+ return name,target
+
+
+def get_parameter(file_name=None):
+ '''
+ get the paticular file name or get the last parameter file
+ '''
+ if not os.path.exists(parameter_path):
+ os.makedirs(parameter_path)
+ return
+
+ if file_name:
+ return os.path.join(parameter_path,file_name)
+
+ parameter_list = [ os.path.join(parameter_path,f) for f in
os.listdir(parameter_path)]
+ if len(parameter_list)==0:
+ return
+ parameter_list.sort()
+
+ return parameter_list[-1]
+
[email protected]("/")
+def index():
+ return "Hello SINGA User!"
+
[email protected]('/predict', methods=['POST'])
+def predict():
+ if request.method == 'POST':
+ try:
+ response=service.serve(request)
+ except Exception as e:
+ return e
+ return response
+ return "error, should be post request"
http://git-wip-us.apache.org/repos/asf/incubator-singa/blob/d76caea3/python/singa/device.py
----------------------------------------------------------------------
diff --git a/python/singa/device.py b/python/singa/device.py
new file mode 100644
index 0000000..2d93823
--- /dev/null
+++ b/python/singa/device.py
@@ -0,0 +1,123 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT 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 includes Device class and its subclasses for python users
+to call singa::Device and its methods.
+
+TODO(wangwei) implement py CudaGPU class.
+'''
+
+from . import singa_wrap as singa
+
+
+class Device(object):
+ """ Class and member functions for singa::Device.
+
+ Create Device instances using the CreateXXXDevice.
+ """
+
+ def __init__(self, id, device):
+ """Device constructor given device ID.
+
+ Args:
+ id (int): device ID.
+ device: swig shared_ptr<Device>
+ """
+ self.id = id
+ self.singa_device = device
+
+ def set_rand_seed(self, seed):
+ self.singa_device.SetRandSeed(seed)
+
+ def get_host(self):
+ return self.singa_device.host()
+
+ def get_id(self):
+ return self.singa_device.id()
+
+
+def get_num_gpus():
+ return singa.Platform.GetNumGPUs()
+
+
+def get_gpu_ids():
+ return singa.Platform.GetGPUIDs()
+
+
+def get_gpu_mem_size(id):
+ return singa.Platform.GetGPUMemSize(id)
+
+
+def device_query(id, verbose=False):
+ return singa.Platform.DeviceQuery(id, verbose)
+
+
+def create_cuda_gpus(num):
+ '''Create a list of CudaGPU devices.
+
+ Args:
+ num (int): number of device to create.
+ Returns:
+ a list of swig converted CudaGPU devices.
+ '''
+
+ return singa.Platform.CreateCudaGPUs(num)
+
+
+def create_cuda_gpu():
+ '''Create a single CudaGPU device.
+
+ Returns:
+ a swig converted CudaGPU device.
+ '''
+
+ return singa.Platform.CreateCudaGPUs(1)[0]
+
+
+def create_cuda_gpus_on(device_ids):
+ '''Create a list of CudaGPU devices.
+
+ Args:
+ device_ids (list): a list of GPU card IDs.
+
+ Returns:
+ a list of swig converted CudaGPU devices.
+ '''
+ return singa.Platform.CreateCudaGPUsOn(device_ids)
+
+
+def create_cuda_gpu_on(device_id):
+ '''Create a CudaGPU device on the given device ID.
+
+ Args:
+ device_id (int): GPU card ID.
+
+ Returns:
+ a swig converted CudaGPU device.
+ '''
+ devices = create_cuda_gpus_on([device_id])
+ return devices[0]
+
+
+default_device = singa.Platform.GetDefaultDevice()
+
+
+def get_default_device():
+ '''Get the default host device which is a CppCPU device'''
+ return default_device
+
http://git-wip-us.apache.org/repos/asf/incubator-singa/blob/d76caea3/python/singa/initializer.py
----------------------------------------------------------------------
diff --git a/python/singa/initializer.py b/python/singa/initializer.py
new file mode 100644
index 0000000..fb99663
--- /dev/null
+++ b/python/singa/initializer.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.
+# =============================================================================
+'''Popular initialization methods for parameter values (Tensor objects).
+
+Example usages::
+
+ from singa import tensor
+ from singa import initializer
+
+ x = tensor.Tensor((3, 5))
+ initializer.uniform(x, 3, 5) # use both fan_in and fan_out
+ initializer.uniform(x, 3, 0) # use only fan_in
+'''
+
+import math
+
+
+def uniform(t, fan_in=0, fan_out=0):
+ '''Initialize the values of the input tensor following a uniform
+ distribution with specific bounds.
+
+ Args:
+ fan_in(int): for the weight Tensor of a convolution layer,
+ fan_in = nb_channel * kh * kw; for dense layer,
+ fan_in = input_feature_length
+ fan_out(int): for the convolution layer weight Tensor,
+ fan_out = nb_filter * kh * kw; for the weight Tensor of a dense
+ layer, fan_out = output_feature_length
+
+ Ref: [Bengio and Glorot 2010]: Understanding the difficulty of
+ training deep feedforward neuralnetworks.
+
+ '''
+ assert fan_in > 0 or fan_out > 0, \
+ 'fan_in and fan_out cannot be 0 at the same time'
+ avg = 2
+ if fan_in * fan_out == 0:
+ avg = 1
+ x = math.sqrt(3.0 * avg / (fan_in + fan_out))
+ t.uniform(-x, x)
+
+
+def gaussian(t, fan_in=0, fan_out=0):
+ '''Initialize the values of the input tensor following a Gaussian
+ distribution with specific std.
+
+ Args:
+ fan_in(int): for the weight Tensor of a convolution layer,
+ fan_in = nb_channel * kh * kw; for dense layer,
+ fan_in = input_feature_length
+ fan_out(int): for the convolution layer weight Tensor,
+ fan_out = nb_filter * kh * kw; for the weight Tensor of a dense
+ layer, fan_out = output_feature_length
+
+ Ref Kaiming He, Xiangyu Zhang, Shaoqing Ren, Jian Sun: Delving Deep into
+ Rectifiers: Surpassing Human-Level Performance on ImageNet Classification
+ '''
+ assert fan_in > 0 or fan_out > 0, \
+ 'fan_in and fan_out cannot be 0 at the same time'
+ avg = 2
+ if fan_in * fan_out == 0:
+ avg = 1
+ std = math.sqrt(2.0 * avg / (fan_in + fan_out))
+ t.gaussian(0, std)
+
+
+def xavier(t):
+ '''Initialize the matrix parameter follow a Uniform distribution from
+ [-sqrt(6/(fan_in + fan_out)), sqrt(6/(fan_in + fan_out))].
+
+ Deprecated. Please use uniform()
+
+ Args:
+ t (Tensor): the parater tensor
+ '''
+
+ scale = math.sqrt(6.0 / (t.shape[0] + t.shape[1]))
+ t.uniform(-scale, scale)
+
+
+def glorot(t):
+ '''Initialize the matrix parameter follow a Gaussian distribution with
+ mean = 0 and std = sqrt(2.0 / (nb_row + nb_col))
+
+ Deprecated. Please use gaussian()
+
+ Args:
+ t (Tensor): the parater tensor
+ '''
+ scale = math.sqrt(2.0 / (t.shape[0] + t.shape[1]))
+ t.gaussian(0, 1)
+ t *= scale
+
+
+def msra(t):
+ '''Initialize the matrix parameter follow a Guassian distribution with
+ mean = 0, std = math.sqrt(2.0 / nb_row).
+
+ Deprecated. Please use gaussian()
+
+ Ref [He, Zhang, Ren and Sun 2015]: Specifically accounts for ReLU
+ nonlinearities.
+
+ Args:
+ t (Tensor): the parater tensor
+ '''
+ t.gaussian(0, math.sqrt(2.0 / t.shape[0]))
http://git-wip-us.apache.org/repos/asf/incubator-singa/blob/d76caea3/python/singa/layer.py
----------------------------------------------------------------------
diff --git a/python/singa/layer.py b/python/singa/layer.py
new file mode 100644
index 0000000..f22b3d1
--- /dev/null
+++ b/python/singa/layer.py
@@ -0,0 +1,933 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# =============================================================================
+""" Python layers wrap the C++ layers to provide simpler construction APIs.
+
+Example usages::
+
+ from singa import layer
+ from singa import tensor
+ from singa import device
+ from singa.model_pb2 import kTrain
+
+ layer.engine = 'cudnn' # to use cudnn layers
+ dev = device.create_cuda_gpu()
+
+ # create a convolution layer
+ conv = layer.Conv2D('conv', 32, 3, 1, pad=1, input_sample_shape=(3, 32,
32))
+ conv.to_device(dev) # move the layer data onto a CudaGPU device
+ x = tensor.Tensor((3, 32, 32), dev)
+ x.uniform(-1, 1)
+ y = conv.foward(kTrain, x)
+
+ dy = tensor.Tensor()
+ dy.reset_like(y)
+ dy.set_value(0.1)
+ # dp is a list of tensors for parameter gradients
+ dx, dp = conv.backward(kTrain, dy)
+"""
+
+from sets import Set
+from . import singa_wrap
+from .proto import model_pb2
+import tensor
+
+
+engine = 'cudnn'
+'''engine is the prefix of layer identifier.
+
+The value could be one of [**'cudnn', 'singacpp', 'singacuda', 'singacl'**],
for
+layers implemented using the cudnn library, Cpp, Cuda and OpenCL respectively.
+For example, CudnnConvolution layer is identified by 'cudnn_convolution';
+'singacpp_convolution' is for Convolution layer;
+Some layers' implementation use only Tensor functions, thererfore they are
+transparent to the underlying devices. For threse layers, they would have
+multiple identifiers, e.g., singacpp_dropout, singacuda_dropout and
+singacl_dropout are all for the Dropout layer. In addition, it has an extra
+identifier 'singa', i.e. 'singa_dropout' also stands for the Dropout layer.
+
+engine is case insensitive. Each python layer would create the correct specific
+layer using the engine attribute.
+'''
+
+
+class Layer(object):
+ '''Base Python layer class.
+
+ Typically, the life cycle of a layer instance includes:
+ 1. construct layer without input_sample_shapes, goto 2;
+ construct layer with input_sample_shapes, goto 3;
+ 2. call setup to create the parameters and setup other meta fields
+ 3. call forward or access layer members
+ 4. call backward and get parameters for update
+
+ Args:
+ name (str): layer name
+ '''
+
+ def __init__(self, name, **kwargs):
+ self.layer = None # layer converted by swig
+ self.name = name # TODO(wangwei) duplicate with self.conf.name
+ self.conf = model_pb2.LayerConf()
+ self.conf.name = name
+ self.param_specs = []
+ self.has_setup = False
+
+ def param_names(self):
+ '''
+ Returns:
+ a list of strings, one for the name of one parameter Tensor
+ '''
+ names = []
+ for x in self.param_specs:
+ names.append(x['name'])
+ return names
+
+ def setup(self, in_shapes):
+ '''Call the C++ setup function to create params and set some meta data.
+
+ Args:
+ in_shapes: if the layer accepts a single input Tensor, in_shapes is
+ a single tuple specifying the inpute Tensor shape; if the layer
+ accepts multiple input Tensor (e.g., the concatenation layer),
+ in_shapes is a tuple of tuples, each for one input Tensor
+ '''
+ if self.has_setup:
+ return
+ self.layer.Setup(list(in_shapes),
+ self.conf.SerializeToString())
+ self.has_setup = True
+
+ def get_output_sample_shape(self):
+ '''Called after setup to get the shape of the output sample(s).
+
+ Returns:
+ a tuple for a single output Tensor or a list of tuples if this
layer
+ has multiple outputs
+ '''
+ assert self.has_setup, \
+ 'Must call setup() before get_output_sample_shape()'
+ return self.layer.GetOutputSampleShape()
+
+ def param_values(self):
+ '''Return param value tensors.
+
+ Parameter tensors are not stored as layer members because cpp Tensor
+ could be moved onto diff devices due to the change of layer device,
+ which would result in inconsistency.
+
+ Returns:
+ a list of tensors, one for each paramter
+ '''
+ if self.layer is None:
+ return []
+ else:
+ return tensor.from_raw_tensors(self.layer.param_values())
+
+ def forward(self, flag, x):
+ '''Forward propagate through this layer.
+
+ Args:
+ flag (int): kTrain or kEval
+ x (Tensor or list<Tensor>): an input tensor if the layer is
+ connected from a single layer; a list of tensors if the layer
+ is connected from multiple layers.
+
+ Return:
+ a tensor if the layer is connected to a single layer; a list of
+ tensors if the layer is connected to multiple layers;
+ '''
+ assert self.has_setup, 'Must call setup() before forward()'
+ if type(x) == list:
+ xs = []
+ for t in x:
+ x.append(t.singa_tensor)
+ else:
+ assert isinstance(x, tensor.Tensor), \
+ 'input must be a Tensor or a list of Tensor'
+ xs = x.singa_tensor
+ y = self.layer.Forward(flag, xs)
+ if type(y) == list:
+ return tensor.from_raw_tensors(y)
+ else:
+ return tensor.from_raw_tensor(y)
+
+ def backward(self, flag, dy):
+ '''Backward propagate gradients through this layer.
+
+ Args:
+ flag (int): for future use.
+ dy (Tensor or list<Tensor>): the gradient tensor(s) y w.r.t the
+ objective loss
+ Return:
+ <dx, <dp1, dp2..>>, dx is a (set of) tensor(s) for the gradient of
x
+ , dpi is the gradient of the i-th parameter
+ '''
+ if type(dy) == list:
+ dys = []
+ for t in dy:
+ dys.append(t.singa_tensor)
+ else:
+ assert isinstance(dy, tensor.Tensor), \
+ 'the input must be a Tensor or a set of Tensor'
+ dys = dy.singa_tensor
+ ret = self.layer.Backward(flag, dys)
+ if type(ret[0]) == list:
+ dxs = tensor.from_raw_tensors(ret[0])
+ else:
+ dxs = tensor.from_raw_tensor(ret[0])
+ return dxs, tensor.from_raw_tensors(ret[1])
+
+ def to_device(self, device):
+ '''Move layer state tensors onto the given device.
+
+ Args:
+ device: swig converted device, created using singa.device
+ '''
+ if self.layer is not None:
+ self.layer.ToDevice(device)
+
+ def as_type(self, dtype):
+ pass
+
+ def __copy__(self):
+ pass
+
+ def __deepcopy__(self):
+ pass
+
+
+class Conv2D(Layer):
+ """Construct a layer for 2D convolution.
+
+ Args:
+ nb_kernels (int): num of the channels (kernels) of the input Tensor
+ kernel: an integer or a pair of integers for kernel height and width
+ stride: an integer or a pair of integers for stride height and width
+ border_mode (string): padding mode, case in-sensitive,
+ 'valid' -> padding is 0 for height and width
+ 'same' -> padding is half of the kernel (floor), the kernel must be
+ odd number.
+ cudnn_prefer (string): the preferred algorithm for cudnn convolution
+ which could be 'fatest', 'autotune', 'limited_workspace' and
+ 'no_workspace'
+ data_format (string): either 'NCHW' or 'NHWC'
+ use_bias (bool): True or False
+ pad: an integer or a pair of integers for padding height and width
+ W_specs (dict): used to specify the weight matrix specs, fields
+ include,
+ 'name' for parameter name
+ 'lr_mult' for learning rate multiplier
+ 'decay_mult' for weight decay multiplier
+ 'init' for init method, which could be 'gaussian', 'uniform',
+ 'xavier' and ''
+ 'std', 'mean', 'high', 'low' for corresponding init methods
+ TODO(wangwei) 'clamp' for gradient constraint, value is scalar
+ 'regularizer' for regularization, currently support 'l2'
+ b_specs (dict): hyper-parameters for bias vector, similar as W_specs
+ name (string): layer name.
+ input_sample_shape: 3d tuple for the shape of the input Tensor
+ without the batchsize, e.g., (channel, height, width) or
+ (height, width, channel)
+ """
+ def __init__(self, name, nb_kernels, kernel=3, stride=1,
border_mode='same',
+ cudnn_prefer='fatest', data_format='NCHW',
+ use_bias=True, W_specs=None, b_specs=None,
+ pad=None, input_sample_shape=None):
+ super(Conv2D, self).__init__(name)
+ assert data_format == 'NCHW', 'Not supported data format: %s ' \
+ 'only "NCHW" is enabled currently' % (data_format)
+ conf = self.conf.convolution_conf
+ conf.num_output = nb_kernels
+ conf = _set_kernel_stride_pad(conf, kernel, stride, border_mode, pad)
+ conf.bias_term = use_bias
+ # TODO(wangwei) enable data format for cpp code
+ # conf.data_format = data_format
+ if W_specs is None:
+ W_specs = {'init': 'xavier'}
+ if b_specs is None:
+ b_specs = {'init': 'constant'}
+ if 'name' not in W_specs:
+ W_specs['name'] = name + '_weight'
+ if 'name' not in b_specs:
+ b_specs['name'] = name + '_bias'
+ wspecs = _construct_param_specs_from_dict(W_specs)
+ self.conf.param.extend([wspecs])
+ self.param_specs.append(wspecs)
+ bspecs = _construct_param_specs_from_dict(b_specs)
+ self.conf.param.extend([bspecs])
+ self.param_specs.append(bspecs)
+
+ _check_engine(engine, ['cudnn', 'singacpp'])
+ self.layer = _create_layer(engine, 'Convolution')
+ if input_sample_shape is not None:
+ self.setup(input_sample_shape)
+
+
+class Conv1D(Conv2D):
+ """Construct a layer for 1D convolution.
+
+ Most of the args are the same as those for Conv2D except the kernel,
+ stride, pad, which is a scalar instead of a tuple.
+ input_sample_shape is a tuple with a single value for the input feature
+ length
+ """
+
+ def __init__(self, name, nb_kernels, kernel=3, stride=1,
+ border_mode='same', cudnn_prefer='fatest',
+ use_bias=True, W_specs={'init': 'Xavier'},
+ b_specs={'init': 'Constant', 'value': 0}, pad=None,
+ input_sample_shape=None):
+ pad = None
+ if pad is not None:
+ pad = (0, pad)
+ if input_sample_shape is not None:
+ input_sample_shape = (1, 1, input_sample_shape[0])
+ super(Conv1D, self).__init__(name, nb_kernels, (1, kernel), (0,
stride),
+ border_mode, cudnn_prefer,
+ use_bias=use_bias, pad=pad,
+ W_specs=W_specs, b_specs=b_specs,
+ input_sample_shape=input_sample_shape)
+
+ def get_output_sample_shape(self):
+ shape = self.layer.GetOutputSampleShape()
+ assert len(shape) == 3, 'The output sample shape should be 3D.'\
+ 'But the length is %d' % len(shape)
+ return (shape[0], shape[2])
+
+
+class Pooling2D(Layer):
+ '''2D pooling layer providing max/avg pooling.
+
+ All args are the same as those for Conv2D, except the following one
+
+ Args:
+ mode: pooling type, model_pb2.PoolingConf.MAX or
+ model_pb2.PoolingConf.AVE
+
+ '''
+ def __init__(self, name, mode, kernel=3, stride=2, border_mode='same',
+ pad=None, data_format='NCHW', input_sample_shape=None):
+ super(Pooling2D, self).__init__(name)
+ assert data_format == 'NCHW', 'Not supported data format: %s ' \
+ 'only "NCHW" is enabled currently' % (data_format)
+ conf = self.conf.pooling_conf
+ conf = _set_kernel_stride_pad(conf, kernel, stride, border_mode, pad)
+ conf.pool = mode
+ _check_engine(engine, ['cudnn', 'singacpp'])
+ self.layer = _create_layer(engine, 'Pooling')
+ if input_sample_shape is not None:
+ self.setup(input_sample_shape)
+
+
+class MaxPooling2D(Pooling2D):
+
+ def __init__(self, name, kernel=3, stride=2, border_mode='same', pad=None,
+ data_format='NCHW', input_sample_shape=None):
+ super(MaxPooling2D, self).__init__(name, model_pb2.PoolingConf.MAX,
+ kernel, stride, border_mode,
+ pad, data_format,
input_sample_shape)
+
+
+class AvgPooling2D(Pooling2D):
+
+ def __init__(self, name, kernel=3, stride=2, border_mode='same', pad=None,
+ data_format='NCHW', input_sample_shape=None):
+ super(AvgPooling2D, self).__init__(name, model_pb2.PoolingConf.AVE,
+ kernel, stride, border_mode,
+ pad, data_format,
input_sample_shape)
+
+
+class MaxPooling1D(MaxPooling2D):
+
+ def __init__(self, name, kernel=3, stride=2, border_mode='same', pad=None,
+ data_format='NCHW', input_sample_shape=None):
+ """Max pooling for 1D feature.
+
+ Args:
+ input_sample_shape (tuple): 1D tuple for input feature length
+ """
+ pad = None
+ if pad is not None:
+ pad = (0, pad)
+ if input_sample_shape is not None:
+ assert len(input_sample_shape) == 1, \
+ 'AvgPooling1D expects input sample to be 1D'
+ input_sample_shape = (1, 1, input_sample_shape[0])
+ else:
+ input_sample_shape = None
+ super(MaxPooling1D, self).__init__(name, (1, kernel), (0, stride),
+ border_mode, pad,
+ data_format, input_sample_shape)
+
+ def get_output_sample_shape(self):
+ shape = self.layer.GetOutputSampleShape()
+ return (shape[2],)
+
+
+class AvgPooling1D(AvgPooling2D):
+
+ def __init__(self, name, kernel=3, stride=2, border_mode='same', pad=None,
+ data_format='NCHW', input_sample_shape=None):
+ """input_feature_length is a scalar value"""
+ pad2 = None
+ if pad is not None:
+ pad2 = (pad, 0)
+ if input_sample_shape is not None:
+ assert len(input_sample_shape) == 1, \
+ 'AvgPooling1D expects input sample to be 1D'
+ input_sample_shape = (1, 1, input_sample_shape[0])
+ else:
+ input_sample_shape = None
+
+ super(AvgPooling1D, self).__init__(name, (kernel, 1), (0, stride),
+ border_mode, pad2,
+ data_format, input_sample_shape)
+
+ def get_output_sample_shape(self):
+ shape = self.layer.GetOutputSampleShape()
+ return (shape[2],)
+
+
+class BatchNormalization(Layer):
+ """Batch-normalization.
+
+ Args:
+ momentum (float): for running average mean and variance.
+ beta_specs (dict): dictionary includes the fields for the beta
+ param:
+ 'name' for parameter name
+ 'lr_mult' for learning rate multiplier
+ 'decay_mult' for weight decay multiplier
+ 'init' for init method, which could be 'gaussian', 'uniform',
+ 'xavier' and ''
+ 'std', 'mean', 'high', 'low' for corresponding init methods
+ 'clamp' for gradient constraint, value is scalar
+ 'regularizer' for regularization, currently support 'l2'
+ gamma_specs (dict): similar to beta_specs, but for the gamma param.
+ name (string): layer name
+ input_sample_shape (tuple): with at least one integer
+ """
+ def __init__(self, name, momentum=0.9,
+ beta_specs=None, gamma_specs=None, input_sample_shape=None):
+ super(BatchNormalization, self).__init__(name)
+ conf = self.conf.batchnorm_conf
+ conf.factor = momentum
+ if beta_specs is None:
+ beta_specs = {'init': 'Xavier'}
+ if gamma_specs is None:
+ gamma_specs = {'init': 'Xavier'}
+ if 'name' not in beta_specs:
+ beta_specs['name'] = name + '_beta'
+ if 'name' not in gamma_specs:
+ gamma_specs['name'] = name + '_gamma'
+ mean_specs = {'init': 'constant', 'value': 0, 'name': name+'_mean'}
+ var_specs = {'init': 'constant', 'value': 1, 'name': name+'_var'}
+ self.conf.param.extend([_construct_param_specs_from_dict(gamma_specs)])
+ self.conf.param.extend([_construct_param_specs_from_dict(beta_specs)])
+ self.conf.param.extend([_construct_param_specs_from_dict(mean_specs)])
+ self.conf.param.extend([_construct_param_specs_from_dict(var_specs)])
+ self.param_specs.append(_construct_param_specs_from_dict(gamma_specs))
+ self.param_specs.append(_construct_param_specs_from_dict(beta_specs))
+ self.param_specs.append(_construct_param_specs_from_dict(mean_specs))
+ self.param_specs.append(_construct_param_specs_from_dict(var_specs))
+ _check_engine(engine, ['cudnn', 'singa', 'singacpp', 'singacuda',
+ 'singacl'])
+ self.layer = _create_layer(engine, 'BatchNorm')
+ if input_sample_shape is not None:
+ self.setup(input_sample_shape)
+
+
+class LRN(Layer):
+ """Local response normalization.
+
+ Args:
+ size (int): # of channels to be crossed
+ normalization.
+ mode (string): 'cross_channel'
+ input_sample_shape (tuple): 3d tuple, (channel, height, width)
+ """
+
+ def __init__(self, name, size=5, alpha=1, beta=0.75, mode='cross_channel',
+ k=1, input_sample_shape=None):
+ super(LRN, self).__init__(name)
+ conf = self.conf.lrn_conf
+ conf.local_size = size
+ conf.alpha = alpha
+ conf.beta = beta
+ conf.k = k
+ # TODO(wangwei) enable mode = 'within_channel'
+ assert mode == 'cross_channel', 'only support mode="across_channel"'
+ conf.norm_region = model_pb2.LRNConf.ACROSS_CHANNELS
+ _check_engine(engine, ['cudnn', 'singa', 'singacpp', 'singacuda',
+ 'singacl'])
+ self.layer = _create_layer(engine, 'LRN')
+ if input_sample_shape is not None:
+ self.setup(input_sample_shape)
+
+
+class Dense(Layer):
+ """Apply linear/affine transformation, also called inner-product or
+ fully connected layer.
+
+ Args:
+ num_output (int): output feature length.
+ use_bias (bool): add a bias vector or not to the transformed feature
+ W_specs (dict): specs for the weight matrix
+ 'name' for parameter name
+ 'lr_mult' for learning rate multiplier
+ 'decay_mult' for weight decay multiplier
+ 'init' for init method, which could be 'gaussian', 'uniform',
+ 'xavier' and ''
+ 'std', 'mean', 'high', 'low' for corresponding init methods
+ 'clamp' for gradient constraint, value is scalar
+ 'regularizer' for regularization, currently support 'l2'
+ b_specs (dict): specs for the bias vector, same fields as W_specs.
+ W_transpose (bool): if true, output=x*W.T+b;
+ input_sample_shape (tuple): input feature length
+ """
+ def __init__(self, name, num_output, use_bias=True,
+ W_specs=None, b_specs=None,
+ W_transpose=False, input_sample_shape=None):
+ """Apply linear/affine transformation, also called inner-product or
+ fully connected layer.
+
+ Args:
+ num_output (int): output feature length.
+ use_bias (bool): add a bias vector or not to the transformed
feature
+ W_specs (dict): specs for the weight matrix
+ 'name' for parameter name
+ 'lr_mult' for learning rate multiplier
+ 'decay_mult' for weight decay multiplier
+ 'init' for init method, which could be 'gaussian', 'uniform',
+ 'xavier' and ''
+ 'std', 'mean', 'high', 'low' for corresponding init methods
+ 'clamp' for gradient constraint, value is scalar
+ 'regularizer' for regularization, currently support 'l2'
+ b_specs (dict): specs for the bias vector, same fields as W_specs.
+ W_transpose (bool): if true, output=x*W.T+b;
+ input_sample_shape (tuple): input feature length
+ """
+ super(Dense, self).__init__(name)
+ conf = self.conf.dense_conf
+ conf.num_output = num_output
+ conf.bias_term = use_bias
+ conf.transpose = W_transpose
+ if W_specs is None:
+ W_specs = {'init': 'xavier'}
+ if b_specs is None:
+ b_specs = {'init': 'constant', 'value': 0}
+ if 'name' not in W_specs:
+ W_specs['name'] = name + '_weight'
+ if 'name' not in b_specs:
+ b_specs['name'] = name + '_bias'
+ wspecs = _construct_param_specs_from_dict(W_specs)
+ bspecs = _construct_param_specs_from_dict(b_specs)
+ self.conf.param.extend([wspecs, bspecs])
+ self.param_specs.extend([wspecs, bspecs])
+ # dense layer is transparent to engine.
+ if engine == 'cudnn':
+ self.layer = _create_layer('singacuda', 'Dense')
+ else:
+ self.layer = _create_layer(engine, 'Dense')
+ if input_sample_shape is not None:
+ self.setup(input_sample_shape)
+
+
+class Dropout(Layer):
+ """Droput layer.
+
+ Args:
+ p (float): probability for dropping out the element, i.e., set to 0
+ name (string): layer name
+ """
+
+ def __init__(self, name, p=0.5, input_sample_shape=None):
+ super(Dropout, self).__init__(name)
+ conf = self.conf.dropout_conf
+ conf.dropout_ratio = p
+ # 'cudnn' works for v>=5.0
+ # if engine.lower() == 'cudnn':
+ # engine = 'cuda'
+ _check_engine(engine, ['cudnn', 'singa', 'singacpp', 'singacuda',
+ 'singacl'])
+ self.layer = _create_layer(engine, 'Dropout')
+ if input_sample_shape is not None:
+ self.setup(input_sample_shape)
+
+
+class Activation(Layer):
+ """Activation layers.
+
+ Args:
+ name (string): layer name
+ mode (string): 'relu', 'sigmoid', or 'tanh'
+ input_sample_shape (tuple): shape of a single sample
+ """
+ def __init__(self, name, mode='relu', input_sample_shape=None):
+ super(Activation, self).__init__(name)
+ _check_engine(engine, ['cudnn', 'singacpp', 'singacuda', 'singacl'])
+ self.conf.type = (engine + '_' + mode).lower()
+ self.layer = _create_layer(engine, mode)
+ if input_sample_shape is not None:
+ self.setup(input_sample_shape)
+
+
+class Softmax(Layer):
+ """Apply softmax.
+
+ Args:
+ axis (int): reshape the input as a matrix with the dimension
+ [0,axis) as the row, the [axis, -1) as the column.
+ input_sample_shape (tuple): shape of a single sample
+ """
+ def __init__(self, name, axis=1, input_sample_shape=None):
+ super(Softmax, self).__init__(name)
+ # conf = self.conf.softmax_conf
+ # conf.axis = axis
+ _check_engine(engine, ['cudnn', 'singa', 'singacpp', 'singacl',
+ 'singacuda'])
+ self.layer = _create_layer(engine, 'Softmax')
+ if input_sample_shape is not None:
+ self.setup(input_sample_shape)
+
+
+class Flatten(Layer):
+ """Reshape the input tensor into a matrix.
+
+ Args:
+ axis (int): reshape the input as a matrix with the dimension
+ [0,axis) as the row, the [axis, -1) as the column.
+ input_sample_shape (tuple): shape for a single sample
+ """
+ def __init__(self, name, axis=1, input_sample_shape=None):
+ super(Flatten, self).__init__(name)
+ conf = self.conf.flatten_conf
+ conf.axis = axis
+ # fltten layer is transparent to engine
+ if engine == 'cudnn':
+ self.layer = _create_layer('singacuda', 'Flatten')
+ else:
+ self.layer = _create_layer(engine, 'Flatten')
+ if input_sample_shape is not None:
+ self.setup(input_sample_shape)
+
+
+class Merge(Layer):
+ '''Sum all input tensors.
+
+ Args:
+ input_sample_shape: sample shape of the input. The sample shape of all
+ inputs should be the same.
+ '''
+ def __init__(self, name, input_sample_shape=None):
+ self.in_shape = input_sample_shape
+ self.num_input = 1
+ super(Merge, self).__init__(name)
+
+ def setup(self, in_shape):
+ self.in_shape = in_shape
+ self.has_setup = True
+
+ def get_output_sample_shape(self):
+ return self.in_shape
+
+ def forward(self, flag, inputs):
+ assert len(inputs) > 1, 'There must be multiple input tensors'
+ self.num_input = len(inputs)
+ output = tensor.Tensor()
+ output.reset_like(inputs[0])
+ output.set_value(0)
+ for x in inputs:
+ output += x
+ return output
+
+ def backward(self, flag, grad):
+ assert isinstance(grad, tensor.Tensor), 'The input must be Tensor'
+ return [grad], [] # * self.num_input
+
+
+class Split(Layer):
+ '''Replicate the input tensor.
+
+ Args:
+ num_output (int): number of output tensors to generate.
+ input_sample_shape: includes a single integer for the input sample
+ feature size.
+ '''
+ def __init__(self, name, num_output, input_sample_shape=None):
+ self.num_output = num_output
+ self.in_shape = input_sample_shape
+ super(Split, self).__init__(name)
+
+ def setup(self, in_shape):
+ self.in_shape = in_shape
+ self.has_setup = True
+
+ def get_output_sample_shape(self):
+ return self.in_shape
+
+ def forward(self, flag, input):
+ assert isinstance(input, tensor.Tensor), 'The input must be Tensor'
+ outputs = [input] * self.num_output
+ return outputs
+
+ def backward(self, flag, grads):
+ assert len(grads) > 1, 'There must be multiple gradients'
+ dx = tensor.Tensor()
+ dx.reset_like(grads[0])
+ dx.set_value(0)
+ for g in grads:
+ dx += g
+ return dx, []
+
+
+class RNN(Layer):
+ '''Recurrent layer with 4 types of units, namely lstm, gru, tanh and relu.
+
+ Args:
+ hidden_size: hidden feature size, the same for all stacks of layers.
+ rnn_mode: decides the rnn unit, which could be one of 'lstm', 'gru',
+ 'tanh' and 'relu', refer to cudnn manual for each mode.
+ num_stacks: num of stacks of rnn layers. It is different to the
+ unrolling seqence length.
+ input_mode: 'linear' convert the input feature x by by a linear
+ transformation to get a feature vector of size hidden_size;
+ 'skip' does nothing but requires the input feature size equals
+ hidden_size
+ bidirection: True for bidirectional RNN
+ param_specs: config for initializing the RNN parameters.
+ input_sample_shape: includes a single integer for the input sample
+ feature size.
+ '''
+
+ def __init__(self, name, hidden_size, rnn_mode='lstm', dropout=0.0,
+ num_stacks=1, input_mode='linear', bidirectional=False,
+ param_specs=None, input_sample_shape=None):
+ super(RNN, self).__init__(name)
+ conf = self.conf.rnn_conf
+ assert hidden_size > 0, 'Hidden feature size must > 0'
+ conf.hidden_size = hidden_size
+ assert rnn_mode in Set(['lstm', 'gru', 'tanh', 'relu']), \
+ 'rnn mode %s is not available' % (rnn_mode)
+ conf.rnn_mode = rnn_mode
+ conf.num_stacks = num_stacks
+ conf.dropout = dropout
+ conf.input_mode = input_mode
+ conf.direction = 'unidirectional'
+ if bidirectional:
+ conf.direction = 'bidirectional'
+ # currently only has rnn layer implemented using cudnn
+ _check_engine(engine, ['cudnn'])
+ if param_specs is None:
+ param_specs = {'name': name + '-weight',
+ 'init': 'uniform', 'low': 0, 'high': 1}
+ self.conf.param.extend([_construct_param_specs_from_dict(param_specs)])
+ self.param_specs.append(_construct_param_specs_from_dict(param_specs))
+
+ self.layer = singa_wrap.CudnnRNN()
+ if input_sample_shape is not None:
+ self.setup(input_sample_shape)
+
+ def forward(self, flag, inputs):
+ '''Forward inputs through the RNN.
+
+ Args:
+ flag, kTrain or kEval.
+ inputs, <x1, x2,...xn, hx, cx>, where xi is the input tensor for
the
+ i-th position, its shape is (batch_size, input_feature_length);
+ the batch_size of xi must >= that of xi+1; hx is the initial
+ hidden state of shape (num_stacks * bidirection?2:1,
batch_size,
+ hidden_size). cx is the initial cell state tensor of the same
+ shape as hy. cx is valid for only lstm. For other RNNs there is
+ no cx. Both hx and cx could be dummy tensors without shape and
+ data.
+
+ Returns:
+ <y1, y2, ... yn, hy, cy>, where yi is the output tensor for the
i-th
+ position, its shape is (batch_size,
+ hidden_size * bidirection?2:1). hy is the final hidden state
+ tensor. cx is the final cell state tensor. cx is only used for
+ lstm.
+ '''
+ assert self.has_setup, 'Must call setup() before forward()'
+ assert len(inputs) > 1, 'The input to RNN must include at '\
+ 'least one input tensor '\
+ 'and one hidden state tensor (could be a dummy tensor)'
+ tensors = []
+ for t in inputs:
+ assert isinstance(t, tensor.Tensor), \
+ 'input must be py Tensor %s' % (type(t))
+ tensors.append(t.singa_tensor)
+ y = self.layer.Forward(flag, tensors)
+ return tensor.from_raw_tensors(y)
+
+ def backward(self, flag, grad):
+ '''Backward gradients through the RNN.
+
+ Args:
+ flag, for future use.
+ grad, <dy1, dy2,...dyn, dhy, dcy>, where dyi is the gradient for
the
+ i-th output, its shape is (batch_size,
hidden_size*bidirection?2:1);
+ dhy is the gradient for the final hidden state, its shape is
+ (num_stacks * bidirection?2:1, batch_size,
+ hidden_size). dcy is the gradient for the final cell state.
+ cx is valid only for lstm. For other RNNs there is
+ no cx. Both dhy and dcy could be dummy tensors without shape
and
+ data.
+
+ Returns:
+ <dx1, dx2, ... dxn, dhx, dcx>, where dxi is the gradient tensor for
+ the i-th input, its shape is (batch_size,
+ input_feature_length). dhx is the gradient for the initial
+ hidden state. dcx is the gradient for the initial cell state,
+ which is valid only for lstm.
+ '''
+ tensors = []
+ for t in grad:
+ assert isinstance(t, tensor.Tensor), 'grad must be py Tensor'
+ tensors.append(t.singa_tensor)
+ ret = self.layer.Backward(flag, tensors)
+ return tensor.from_raw_tensors(ret[0]), tensor.from_raw_tensors(ret[1])
+
+
+class LSTM(RNN):
+ def __init__(self, name, hidden_size, dropout=0.0, num_stacks=1,
+ input_mode='linear', bidirectional=False,
+ param_specs=None, input_sample_shape=None):
+ super(LSTM, self).__init__(name, hidden_size, 'lstm', dropout,
+ num_stacks, input_mode, bidirectional,
+ param_specs, input_sample_shape)
+
+
+class GRU(RNN):
+ def __init__(self, name, hidden_size, dropout=0.0, num_stacks=1,
+ input_mode='linear', bidirectional=False, param_specs=None,
+ input_sample_shape=None):
+ super(GRU, self).__init__(name, hidden_size, 'gru', dropout,
+ num_stacks, input_mode, bidirectional,
+ param_specs, input_sample_shape)
+
+
+def _check_engine(engine, allowed_engines):
+ assert engine.lower() in Set(allowed_engines), \
+ '%s is not a supported engine. Pls use one of %s' % \
+ (engine, ', '.join(allowed_engines))
+
+
+def _create_layer(eng, layer):
+ ''' create singa wrap layer.
+
+ Both arguments are case insensitive.
+ Args:
+ engine, implementation engine, either 'singa' or 'cudnn'
+ layer, layer type, e.g., 'convolution', 'pooling'; for activation
+ layers, use the specific activation mode, e.g. 'relu', 'tanh'.
+ '''
+ layer_type = eng + '_' + layer
+ return singa_wrap.CreateLayer(layer_type.lower())
+
+
+def _set_kernel_stride_pad(conf, kernel, stride, border_mode, pad):
+ """Private function called by Convolution2D and Pooling2D."""
+ if isinstance(kernel, tuple):
+ conf.kernel_h = kernel[0]
+ conf.kernel_w = kernel[1]
+ else:
+ conf.kernel_h = kernel
+ conf.kernel_w = kernel
+ if isinstance(stride, tuple):
+ conf.stride_h = stride[0]
+ conf.stride_w = stride[1]
+ else:
+ conf.stride_h = stride
+ conf.stride_w = stride
+ mode = border_mode.lower()
+ if pad is None:
+ # TODO(wangwei) check the border mode
+ if mode == 'same':
+ assert conf.kernel_h % 2 == 1 and conf.kernel_w % 2 == 1, \
+ 'Must use odd kernel for mode="same", kernel is (%d, %d)' % (
+ conf.kernel_h, conf.kernel_w)
+ pad = (conf.kernel_h / 2, conf.kernel_w / 2)
+ elif mode == 'valid':
+ pad = (0, 0)
+ else:
+ assert False, ('Unsupported border_mode: %s. '
+ 'Please use {"valid", "same"}' % border_mode)
+ assert isinstance(pad, tuple), 'pad should be a tuple'
+ if isinstance(pad, tuple):
+ conf.pad_h = pad[0]
+ conf.pad_w = pad[1]
+ else:
+ conf.pad_h = pad
+ conf.pad_w = pad
+ return conf
+
+
+def _construct_param_specs_from_dict(specs):
+ """Conver the param specs from a dict into ParamSpec protobuf object.
+
+ Args:
+ specs (dict): the fields inlcude
+ 'name' for parameter name
+ 'lr_mult' for learning rate multiplier;
+ 'decay_mult' for weight decay multiplier;
+ 'init' for init method, which could be 'gaussian', 'uniform',
+ 'xavier' and 'msra';
+ 'std', 'mean', 'high', 'low' are used by corresponding init
methods;
+ 'constraint' for gradient constraint, value is a float threshold
for
+ clampping the gradient.
+ 'regularizer' for regularization, currently support 'l2', value is
a
+ float for the coefficient.
+
+ Returns:
+ a ParamSpec object
+ """
+ conf = model_pb2.ParamSpec()
+ if 'name' in specs:
+ conf.name = specs['name']
+ if 'lr_mult' in specs:
+ conf.lr_mult = specs['lr_mult']
+ if 'decay_mult' in specs:
+ conf.decay_mult = specs['decay_mult']
+ if 'init' in specs:
+ filler = conf.filler
+ filler.type = specs['init'].lower()
+ if specs['init'].lower() == 'uniform':
+ assert 'low' in specs and 'high' in specs, \
+ 'low and high are required for "uniform" init method'
+ filler.min = specs['low']
+ filler.max = specs['high']
+ elif specs['init'].lower() == 'gaussian':
+ assert 'mean' in specs and 'std' in specs, \
+ 'std and mean are required for "gaussian" init method'
+ filler.mean = specs['mean']
+ filler.std = specs['std']
+ elif specs['init'].lower() == 'constant' and 'value' in specs:
+ filler.value = specs['value']
+ if 'regularizer' in specs:
+ conf.regularizer.coefficient = specs['regularizer']
+ if 'constraint' in specs:
+ conf.constraint.threshold = specs['constraint']
+ return conf
+
+
+def get_layer_list():
+ """ Return a list of strings which include the identifiers (tags) of all
+ supported layers
+ """
+ return singa_wrap.GetRegisteredLayers()
http://git-wip-us.apache.org/repos/asf/incubator-singa/blob/d76caea3/python/singa/loss.py
----------------------------------------------------------------------
diff --git a/python/singa/loss.py b/python/singa/loss.py
new file mode 100644
index 0000000..c88290b
--- /dev/null
+++ b/python/singa/loss.py
@@ -0,0 +1,141 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+# =============================================================================
+
+'''
+Loss module includes a set of training loss implmentations. Some are converted
+from C++ implementation, and the rest are implemented directly using python
+Tensor.
+
+Example usage::
+
+ from singa import tensor
+ from singa import loss
+ from singa.proto import model_pb2
+
+ x = tensor.Tensor((3, 5))
+ x.uniform(0, 1) # randomly genearte the prediction activation
+ y = tensor.from_numpy(np.array([0, 1, 3], dtype=np.int)) # set the truth
+
+ f = loss.SoftmaxCrossEntropy()
+ l = f.forward(model_pb2.kTrain, x, y) # l is tensor with 3 loss values
+ g = f.backward() # g is a tensor containing all gradients of x w.r.t l
+'''
+
+
+from . import singa_wrap as singa
+import tensor
+
+
+class Loss(object):
+ '''Base loss class.
+
+ Subclasses that wrap the C++ loss classes can use the inherited foward,
+ backward, and evaluate functions of this base class. Other subclasses need
+ to override these functions
+ '''
+
+ def __init__(self):
+ self.swig_loss = None
+
+ def forward(self, flag, x, y):
+ '''Compute the loss values.
+
+ Args:
+ flag (int): kTrain or kEval. If it is kTrain, then the backward
+ function must be called before calling forward again.
+ x (Tensor): the prediction Tensor
+ y (Tensor): the ground truch Tensor, x.shape[0] must = y.shape[0]
+
+ Returns:
+ a tensor of floats for the loss values, one per sample
+ '''
+ return tensor.from_raw_tensor(
+ self.swig_loss.Forward(flag, x.singa_tensor, y.singa_tensor))
+
+ def backward(self):
+ '''
+ Returns:
+ the grad of x w.r.t. the loss
+ '''
+ return tensor.from_raw_tensor(self.swig_loss.Backward())
+
+ def evaluate(self, flag, x, y): # TODO(wangwei) remove flag
+ '''
+ Args:
+ flag (int): must be kEval, to be removed
+ x (Tensor): the prediction Tensor
+ y (Tensor): the ground truth Tnesor
+
+ Returns:
+ the averaged loss for all samples in x.
+ '''
+ return self.swig_loss.Evaluate(flag, x.singa_tensor, y.singa_tensor)
+
+
+class SoftmaxCrossEntropy(Loss):
+ '''This loss function is a combination of SoftMax and Cross-Entropy loss.
+
+ It converts the inputs via SoftMax function and then
+ computes the cross-entropy loss against the ground truth values.
+ '''
+
+ def __init__(self):
+ self.swig_loss = singa.SoftmaxCrossEntropy()
+
+
+class SquaredError(Loss):
+ '''This loss evaluates the squared error between the prediction and the
+ truth values.
+
+ It is implemented using Python Tensor operations.
+ '''
+ def __init__(self):
+ super(Loss, SquaredError).__init__()
+ self.err = None
+
+ def forward(self, flag, x, y):
+ '''Compute the error as 0.5 * ||x-y||^2.
+
+ Args:
+ flag (int): kTrain or kEval; if kTrain, then the backward must be
+ called before calling forward again.
+ x (Tensor): the prediction Tensor
+ y (Tensor): the truth Tensor, an integer value per sample, whose
+ value is [0, x.shape[1])
+
+ Returns:
+ a Tensor with one error value per sample
+ '''
+ self.err = x - y
+ return 0.5 * tensor.squared(self.err)
+
+ def backward(self):
+ '''Compute the gradient of x w.r.t the error.
+
+ Returns:
+ x - y
+ '''
+ return self.err
+
+ def evaluate(self, flag, x, y):
+ '''Compuate the averaged error.
+
+ Returns:
+ a float value as the averaged error
+ '''
+ return tensor.sum(0.5 * tensor.squared(x - y)) / x.size()
http://git-wip-us.apache.org/repos/asf/incubator-singa/blob/d76caea3/python/singa/metric.py
----------------------------------------------------------------------
diff --git a/python/singa/metric.py b/python/singa/metric.py
new file mode 100644
index 0000000..3a5750d
--- /dev/null
+++ b/python/singa/metric.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.
+# =============================================================================
+'''This module includes a set of metric classes for evaluating the model's
+performance. The specific metric classes could be converted from C++
+implmentation or implemented directly using Python.
+
+
+Example usage::
+
+ from singa import tensor
+ from singa import metric
+
+ x = tensor.Tensor((3, 5))
+ x.uniform(0, 1) # randomly genearte the prediction activation
+ x = tensor.SoftMax(x) # normalize the prediction into probabilities
+ y = tensor.from_numpy(np.array([0, 1, 3], dtype=np.int)) # set the truth
+
+ f = metric.Accuracy()
+ acc = f.evaluate(x, y) # averaged accuracy over all 3 samples in x
+
+'''
+
+from . import singa_wrap as singa
+import tensor
+
+
+class Metric(object):
+ '''Base metric class.
+
+ Subclasses that wrap the C++ loss classes can use the inherited foward,
+ and evaluate functions of this base class. Other subclasses need
+ to override these functions. Users need to feed in the **predictions** and
+ ground truth to get the metric values.
+ '''
+
+ def __init__(self):
+ self.swig_metric = None
+
+ def forward(self, x, y):
+ '''Compute the metric for each sample.
+
+ Args:
+ x (Tensor): predictions, one row per sample
+ y (Tensor): ground truth values, one row per sample
+
+ Returns:
+ a tensor of floats, one per sample
+ '''
+ return tensor.from_raw_tensor(
+ self.swig_metric.Forward(x.singa_tensor, y.singa_tensor))
+
+ def evaluate(self, x, y):
+ '''Compute the averaged metric over all samples.
+
+ Args:
+ x (Tensor): predictions, one row per sample
+ y (Tensor): ground truth values, one row per sample
+ Returns:
+ a float value for the averaged metric
+ '''
+ return self.swig_metric.Evaluate(x.singa_tensor, y.singa_tensor)
+
+
+class Accuracy(Metric):
+ '''Compute the top one accuracy for singel label prediction tasks.
+
+ It calls the C++ functions to do the calculation.
+ '''
+ def __init__(self):
+ self.swig_metric = singa.Accuracy()
http://git-wip-us.apache.org/repos/asf/incubator-singa/blob/d76caea3/python/singa/model.py
----------------------------------------------------------------------
diff --git a/python/singa/model.py b/python/singa/model.py
new file mode 100644
index 0000000..38d9950
--- /dev/null
+++ b/python/singa/model.py
@@ -0,0 +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.
+# */
+
+class Model(Object):
+ pass
+