mirror of
https://github.com/wassname/ray.git
synced 2026-09-11 12:43:20 +08:00
Clean up top level Ray dir (#5404)
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
from .cython_simple import simple_func, fib, fib_int, \
|
||||
fib_cpdef, fib_cdef, simple_class
|
||||
from .masked_log import masked_log
|
||||
|
||||
from .cython_blas import \
|
||||
compute_self_corr_for_voxel_sel, \
|
||||
compute_kernel_matrix, \
|
||||
compute_single_self_corr_syrk, \
|
||||
compute_single_self_corr_gemm, \
|
||||
compute_corr_vectors, \
|
||||
compute_single_matrix_multiplication
|
||||
|
||||
__all__ = [
|
||||
"simple_func",
|
||||
"fib",
|
||||
"fib_int",
|
||||
"fib_cpdef",
|
||||
"fib_cdef",
|
||||
"simple_class",
|
||||
"masked_log",
|
||||
"compute_self_corr_for_voxel_sel",
|
||||
"compute_kernel_matrix",
|
||||
"compute_single_self_corr_syrk",
|
||||
"compute_single_self_corr_gemm",
|
||||
"compute_corr_vectors",
|
||||
"compute_single_matrix_multiplication"
|
||||
]
|
||||
@@ -0,0 +1,571 @@
|
||||
#!python
|
||||
# cython: embedsignature=True, binding=True
|
||||
|
||||
# Copyright 2016 Intel Corporation
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# Authors: Yida Wang
|
||||
# (Intel Labs), 2016
|
||||
|
||||
cimport scipy.linalg.cython_blas as blas
|
||||
|
||||
|
||||
def compute_self_corr_for_voxel_sel(py_trans_a, py_trans_b, py_m, py_n, py_k,
|
||||
py_alpha, py_a, py_lda, int py_start_voxel,
|
||||
py_b, py_ldb, py_beta, py_c, py_ldc,
|
||||
int py_start_epoch):
|
||||
""" use blas API sgemm wrapped by scipy to compute correlation
|
||||
|
||||
This method is limited to process self-correlation.
|
||||
The blas APIs process matrices in column-major,
|
||||
but our matrices are in row-major,
|
||||
so we play the transpose trick here, i.e. A*B=(B^T*A^T)^T.
|
||||
The resulting matrix in shape [num_assigned_voxels, num_voxels]
|
||||
is stored in an alternate way to make sure that
|
||||
the correlation vectors of the same voxel stored continuously
|
||||
|
||||
Parameters
|
||||
----------
|
||||
py_trans_a: str
|
||||
do transpose or not for the first matrix A
|
||||
|
||||
py_trans_b: str
|
||||
do transpose or not for the first matrix B
|
||||
|
||||
py_m: int
|
||||
the row of the resulting matrix C
|
||||
in our case, is num_voxels
|
||||
|
||||
py_n: int
|
||||
the column of the resulting matrix C
|
||||
in our case, is num_assigned_voxels
|
||||
|
||||
py_k: int
|
||||
the collapsed dimension of the multiplying matrices
|
||||
i.e. the column of the first matrix after transpose if necessary
|
||||
the row of the second matrix after transpose if necessary
|
||||
|
||||
py_alpha: float
|
||||
the weight applied to the first matrix A
|
||||
|
||||
py_a: 2D array in shape [epoch_length, num_voxels]
|
||||
It is the activity data of an epoch, part 1 of the data to be
|
||||
correlated with. Note that py_a can point to the same location of py_b.
|
||||
|
||||
py_lda: int
|
||||
the stride of the first matrix A
|
||||
|
||||
py_start_voxel: int
|
||||
the starting voxel of assigned voxels
|
||||
used to locate the second matrix B
|
||||
|
||||
py_b: 2D array in shape [epoch_length, num_voxels]
|
||||
It is the activity data of an epoch, part 2 of the data to be
|
||||
correlated with. Note that py_a can point to the same location of py_b.
|
||||
|
||||
py_ldb: int
|
||||
the stride of the second matrix B
|
||||
|
||||
py_beta: float
|
||||
the weight applied to the resulting matrix C
|
||||
|
||||
py_c: 3D array in shape [num_selected_voxels, num_epochs, num_voxels]
|
||||
place to store the resulting correlation values
|
||||
|
||||
py_ldc: int
|
||||
the stride of the resulting matrix
|
||||
in our case, num_voxels*num_epochs
|
||||
|
||||
py_start_epoch: int
|
||||
the epoch over which the correlation is computed
|
||||
|
||||
Returns
|
||||
-------
|
||||
py_c: 3D array in shape [num_selected_voxels, num_epochs, num_voxels]
|
||||
write the resulting correlation values in an alternate way
|
||||
for the processing epoch
|
||||
"""
|
||||
cdef bytes by_trans_a=py_trans_a.encode()
|
||||
cdef bytes by_trans_b=py_trans_b.encode()
|
||||
cdef char* trans_a = by_trans_a
|
||||
cdef char* trans_b = by_trans_b
|
||||
cdef int M, N, K, lda, ldb, ldc
|
||||
M = py_m
|
||||
N = py_n
|
||||
K = py_k
|
||||
lda = py_lda
|
||||
ldb = py_ldb
|
||||
ldc = py_ldc
|
||||
cdef float alpha, beta
|
||||
alpha = py_alpha
|
||||
beta = py_beta
|
||||
cdef float[:, ::1] A
|
||||
A = py_a
|
||||
cdef float[:, ::1] B
|
||||
B = py_b
|
||||
cdef float[:, :, ::1] C
|
||||
C = py_c
|
||||
blas.sgemm(trans_a, trans_b, &M, &N, &K, &alpha, &A[0, 0], &lda,
|
||||
&B[0, py_start_voxel], &ldb, &beta,
|
||||
&C[0, py_start_epoch, 0], &ldc)
|
||||
|
||||
|
||||
def compute_kernel_matrix(py_uplo, py_trans, py_n, py_k, py_alpha, py_a,
|
||||
int py_start_voxel, py_lda,
|
||||
py_beta, py_c, py_ldc):
|
||||
""" use blas API syrk wrapped by scipy to compute kernel matrix of SVM
|
||||
|
||||
The blas APIs process matrices in column-major, but our matrices are
|
||||
in row-major, so we play the transpose trick here, i.e. A*B=(B^T*A^T)^T
|
||||
|
||||
In SVM with linear kernel, the distance of two samples
|
||||
is essentially the dot product of them.
|
||||
Therefore, the kernel matrix can be obtained by matrix multiplication.
|
||||
Since the kernel matrix is symmetric, ssyrk is used,
|
||||
the other half of the matrix is assigned later.
|
||||
In our case, the dimension of samples is much larger than
|
||||
the number samples, so we proportionally shrink the values of
|
||||
the kernel matrix for getting more robust alpha values in SVM iteration.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
py_uplo: str
|
||||
getting the upper or lower triangle of the matrix
|
||||
|
||||
py_trans: str
|
||||
do transpose or not for the input matrix A
|
||||
|
||||
py_n: int
|
||||
the row and column of the resulting matrix C
|
||||
in our case, is num_epochs
|
||||
|
||||
py_k: int
|
||||
the collapsed dimension of the multiplying matrices
|
||||
i.e. the column of the first matrix after transpose if necessary
|
||||
the row of the second matrix after transpose if necessary
|
||||
in our case, is num_voxels
|
||||
|
||||
py_alpha: float
|
||||
the weight applied to the input matrix A
|
||||
|
||||
py_a: 3D array in shape [num_assigned_voxels, num_epochs, num_voxels]
|
||||
in our case the normalized correlation values of a voxel
|
||||
|
||||
py_start_voxel: int
|
||||
the processed voxel
|
||||
used to locate the input matrix A
|
||||
|
||||
py_lda: int
|
||||
the stride of the input matrix A
|
||||
|
||||
py_beta: float
|
||||
the weight applied to the resulting matrix C
|
||||
|
||||
py_c: 2D array in shape [num_epochs, num_epochs]
|
||||
place to store the resulting kernel matrix
|
||||
|
||||
py_ldc: int
|
||||
the stride of the resulting matrix
|
||||
|
||||
Returns
|
||||
-------
|
||||
py_c: 2D array in shape [num_epochs, num_epochs]
|
||||
write the resulting kernel_matrix
|
||||
for the processing voxel
|
||||
"""
|
||||
cdef bytes by_uplo=py_uplo.encode()
|
||||
cdef bytes by_trans=py_trans.encode()
|
||||
cdef char* uplo = by_uplo
|
||||
cdef char* trans = by_trans
|
||||
cdef int N, K, lda, ldc
|
||||
N = py_n
|
||||
K = py_k
|
||||
lda = py_lda
|
||||
ldc = py_ldc
|
||||
cdef float alpha, beta
|
||||
alpha = py_alpha
|
||||
beta = py_beta
|
||||
cdef float[:, :, ::1] A
|
||||
A = py_a
|
||||
cdef float[:, ::1] C
|
||||
C = py_c
|
||||
blas.ssyrk(uplo, trans, &N, &K, &alpha, &A[py_start_voxel, 0, 0], &lda,
|
||||
&beta, &C[0, 0], &ldc)
|
||||
# complete the other half of the kernel matrix
|
||||
if py_uplo == 'L':
|
||||
for j in range(py_c.shape[0]):
|
||||
for k in range(j):
|
||||
py_c[j, k] = py_c[k, j]
|
||||
else:
|
||||
for j in range(py_c.shape[0]):
|
||||
for k in range(j):
|
||||
py_c[k, j] = py_c[j, k]
|
||||
|
||||
|
||||
def compute_single_self_corr_syrk(py_uplo, py_trans, py_n, py_k,
|
||||
py_alpha, py_a, py_lda,
|
||||
py_beta, py_c, py_ldc,
|
||||
int py_start_sample):
|
||||
""" use blas API syrk wrapped by scipy to compute correlation matrix
|
||||
|
||||
This is to compute the correlation between selected voxels for
|
||||
final training and classification. Since the resulting correlation
|
||||
matrix is symmetric, syrk is used. However, it looks like that in most
|
||||
cases, syrk performs much worse than gemm (the next function).
|
||||
Here we assume that the resulting matrix is stored in a compact way,
|
||||
i.e. py_ldc == py_n.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
py_uplo: str
|
||||
getting the upper or lower triangle of the matrix
|
||||
|
||||
py_trans: str
|
||||
do transpose or not for the input matrix A
|
||||
|
||||
py_n: int
|
||||
the row and column of the resulting matrix C
|
||||
in our case, is num_selected_voxels
|
||||
|
||||
py_k: int
|
||||
the collapsed dimension of the multiplying matrices
|
||||
i.e. the column of the first matrix after transpose if necessary
|
||||
the row of the second matrix after transpose if necessary
|
||||
in our case, is num_TRs
|
||||
|
||||
py_alpha: float
|
||||
the weight applied to the input matrix A
|
||||
|
||||
py_a: 2D array in shape [num_TRs, num_selected_voxels]
|
||||
in our case the normalized activity values
|
||||
|
||||
py_lda: int
|
||||
the stride of the input matrix A
|
||||
|
||||
py_beta: float
|
||||
the weight applied to the resulting matrix C
|
||||
|
||||
py_c: 3D array
|
||||
in shape [num_samples, num_selected_voxels, num_selected_voxels]
|
||||
place to store the resulting kernel matrix
|
||||
|
||||
py_ldc: int
|
||||
the stride of the resulting matrix
|
||||
|
||||
py_start_sample: int
|
||||
the processed sample
|
||||
used to locate the resulting matrix C
|
||||
|
||||
Returns
|
||||
-------
|
||||
py_c: 3D array
|
||||
in shape [num_samples, num_selected_voxels, num_selected_voxels]
|
||||
write the resulting correlation matrices
|
||||
for the processed sample
|
||||
"""
|
||||
cdef bytes by_uplo=py_uplo.encode()
|
||||
cdef bytes by_trans=py_trans.encode()
|
||||
cdef char* uplo = by_uplo
|
||||
cdef char* trans = by_trans
|
||||
cdef int N, K, lda, ldc
|
||||
N = py_n
|
||||
K = py_k
|
||||
lda = py_lda
|
||||
ldc = py_ldc
|
||||
cdef float alpha, beta
|
||||
alpha = py_alpha
|
||||
beta = py_beta
|
||||
cdef float[:, ::1] A
|
||||
A = py_a
|
||||
cdef float[:, :, ::1] C
|
||||
C = py_c
|
||||
blas.ssyrk(uplo, trans, &N, &K, &alpha, &A[0, 0], &lda,
|
||||
&beta, &C[py_start_sample, 0, 0], &ldc)
|
||||
# complete the other half of the kernel matrix
|
||||
if py_uplo == 'L':
|
||||
for j in range(py_c.shape[1]):
|
||||
for k in range(j):
|
||||
py_c[py_start_sample, j, k] = py_c[py_start_sample, k, j]
|
||||
else:
|
||||
for j in range(py_c.shape[1]):
|
||||
for k in range(j):
|
||||
py_c[py_start_sample, k, j] = py_c[py_start_sample, j, k]
|
||||
|
||||
|
||||
def compute_single_self_corr_gemm(py_trans_a, py_trans_b, py_m, py_n,
|
||||
py_k, py_alpha, py_a, py_lda,
|
||||
py_ldb, py_beta, py_c, py_ldc,
|
||||
int py_start_sample):
|
||||
""" use blas API gemm wrapped by scipy to compute correlation matrix
|
||||
|
||||
This is to compute the correlation between selected voxels for
|
||||
final training and classification. Although the resulting correlation
|
||||
matrix is symmetric, in most cases, gemm performs better than syrk.
|
||||
Here we assume that the resulting matrix is stored in a compact way,
|
||||
i.e. py_ldc == py_n.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
py_trans_a: str
|
||||
do transpose or not for the first matrix A
|
||||
|
||||
py_trans_b: str
|
||||
do transpose or not for the first matrix B
|
||||
|
||||
py_m: int
|
||||
the row of the resulting matrix C
|
||||
in our case, is num_selected_voxels
|
||||
|
||||
py_n: int
|
||||
the column of the resulting matrix C
|
||||
in our case, is num_selected_voxels
|
||||
|
||||
py_k: int
|
||||
the collapsed dimension of the multiplying matrices
|
||||
i.e. the column of the first matrix after transpose if necessary
|
||||
the row of the second matrix after transpose if necessary
|
||||
in our case, is num_TRs
|
||||
|
||||
py_alpha: float
|
||||
the weight applied to the input matrix A
|
||||
|
||||
py_a: 2D array in shape [num_TRs, num_selected_voxels]
|
||||
in our case the normalized activity values
|
||||
both multipliers are specified here as the same one
|
||||
|
||||
py_lda: int
|
||||
the stride of the input matrix A
|
||||
|
||||
py_ldb: int
|
||||
the stride of the input matrix B
|
||||
in our case, the same as py_lda
|
||||
|
||||
py_beta: float
|
||||
the weight applied to the resulting matrix C
|
||||
|
||||
py_c: 3D array
|
||||
in shape [num_samples, num_selected_voxels, num_selected_voxels]
|
||||
place to store the resulting kernel matrix
|
||||
|
||||
py_ldc: int
|
||||
the stride of the resulting matrix
|
||||
|
||||
py_start_sample: int
|
||||
the processed sample
|
||||
used to locate the resulting matrix C
|
||||
|
||||
Returns
|
||||
-------
|
||||
py_c: 3D array
|
||||
in shape [num_samples, num_selected_voxels, num_selected_voxels]
|
||||
write the resulting correlation matrices
|
||||
for the processed sample
|
||||
"""
|
||||
cdef bytes by_trans_a=py_trans_a.encode()
|
||||
cdef bytes by_trans_b=py_trans_b.encode()
|
||||
cdef char* trans_a = by_trans_a
|
||||
cdef char* trans_b = by_trans_b
|
||||
cdef int M, N, K, lda, ldb, ldc
|
||||
M = py_m
|
||||
N = py_n
|
||||
K = py_k
|
||||
lda = py_lda
|
||||
ldb = py_ldb
|
||||
ldc = py_ldc
|
||||
cdef float alpha, beta
|
||||
alpha = py_alpha
|
||||
beta = py_beta
|
||||
cdef float[:, ::1] A
|
||||
A = py_a
|
||||
cdef float[:, :, ::1] C
|
||||
C = py_c
|
||||
blas.sgemm(trans_a, trans_b, &M, &N, &K, &alpha, &A[0, 0], &lda,
|
||||
&A[0, 0], &ldb, &beta, &C[py_start_sample, 0, 0], &ldc)
|
||||
|
||||
|
||||
def compute_corr_vectors(py_trans_a, py_trans_b, py_m, py_n,
|
||||
py_k, py_alpha, py_a, py_lda,
|
||||
py_b, py_ldb, py_beta, py_c, py_ldc,
|
||||
int py_start_voxel,
|
||||
int py_start_sample):
|
||||
""" use blas API gemm wrapped by scipy to construct a correlation vector
|
||||
|
||||
The correlation vector is essentially correlation matrices computed
|
||||
from two activity matrices. It will be placed in the corresponding place
|
||||
of the resulting correlation data set.
|
||||
The blas APIs process matrices in column-major,
|
||||
but our matrices are in row-major, so we play the transpose trick here,
|
||||
i.e. A*B=(B^T*A^T)^T
|
||||
|
||||
py_trans_a: str
|
||||
do transpose or not for the first matrix A
|
||||
|
||||
py_trans_b: str
|
||||
do transpose or not for the first matrix B
|
||||
|
||||
py_m: int
|
||||
the row of the resulting matrix C
|
||||
|
||||
py_n: int
|
||||
the column of the resulting matrix C
|
||||
|
||||
py_k: int
|
||||
the collapsed dimension of the multiplying matrices
|
||||
i.e. the column of the first matrix after transpose if necessary
|
||||
the row of the second matrix after transpose if necessary
|
||||
|
||||
py_alpha: float
|
||||
the weight applied to the input matrix A
|
||||
|
||||
py_a: 2D array
|
||||
|
||||
py_lda: int
|
||||
the stride of the input matrix A
|
||||
|
||||
py_b: 2D array
|
||||
|
||||
py_ldb: int
|
||||
the stride of the input matrix B
|
||||
|
||||
py_beta: float
|
||||
the weight applied to the resulting matrix C
|
||||
|
||||
py_c: 2D array
|
||||
in shape [py_m, py_n] of column-major
|
||||
in fact it is
|
||||
in shape [py_n, py_m] of row-major
|
||||
|
||||
py_ldc: int
|
||||
the stride of the resulting matrix
|
||||
|
||||
py_start_voxel: int
|
||||
the starting voxel of assigned voxels
|
||||
used to locate the second matrix B
|
||||
|
||||
py_start_sample: int
|
||||
the processed sample
|
||||
used to locate the resulting matrix C
|
||||
|
||||
Returns
|
||||
-------
|
||||
py_c: 2D array
|
||||
in shape [py_m, py_n] of column-major
|
||||
write the resulting matrix to the place indicated by py_start_sample
|
||||
"""
|
||||
cdef bytes by_trans_a=py_trans_a.encode()
|
||||
cdef bytes by_trans_b=py_trans_b.encode()
|
||||
cdef char* trans_a = by_trans_a
|
||||
cdef char* trans_b = by_trans_b
|
||||
cdef int M, N, K, lda, ldb, ldc
|
||||
M = py_m
|
||||
N = py_n
|
||||
K = py_k
|
||||
lda = py_lda
|
||||
ldb = py_ldb
|
||||
ldc = py_ldc
|
||||
cdef float alpha, beta
|
||||
alpha = py_alpha
|
||||
beta = py_beta
|
||||
cdef float[:, ::1] A
|
||||
A = py_a
|
||||
cdef float[:, ::1] B
|
||||
B = py_b
|
||||
cdef float[:, :, ::1] C
|
||||
C = py_c
|
||||
blas.sgemm(trans_a, trans_b, &M, &N, &K, &alpha, &A[0, 0], &lda,
|
||||
&B[0, py_start_voxel], &ldb, &beta,
|
||||
&C[py_start_sample, 0, 0], &ldc)
|
||||
|
||||
|
||||
def compute_single_matrix_multiplication(py_trans_a, py_trans_b, py_m, py_n,
|
||||
py_k, py_alpha, py_a, py_lda,
|
||||
py_b, py_ldb, py_beta, py_c, py_ldc):
|
||||
""" use blas API gemm wrapped by scipy to do matrix multiplication
|
||||
|
||||
This is to compute the matrix multiplication.
|
||||
The blas APIs process matrices in column-major,
|
||||
but our matrices are in row-major, so we play the transpose trick here,
|
||||
i.e. A*B=(B^T*A^T)^T
|
||||
|
||||
Parameters
|
||||
----------
|
||||
py_trans_a: str
|
||||
do transpose or not for the first matrix A
|
||||
|
||||
py_trans_b: str
|
||||
do transpose or not for the first matrix B
|
||||
|
||||
py_m: int
|
||||
the row of the resulting matrix C
|
||||
|
||||
py_n: int
|
||||
the column of the resulting matrix C
|
||||
|
||||
py_k: int
|
||||
the collapsed dimension of the multiplying matrices
|
||||
i.e. the column of the first matrix after transpose if necessary
|
||||
the row of the second matrix after transpose if necessary
|
||||
|
||||
py_alpha: float
|
||||
the weight applied to the input matrix A
|
||||
|
||||
py_a: 2D array
|
||||
|
||||
py_lda: int
|
||||
the stride of the input matrix A
|
||||
|
||||
py_b: 2D array
|
||||
|
||||
py_ldb: int
|
||||
the stride of the input matrix B
|
||||
|
||||
py_beta: float
|
||||
the weight applied to the resulting matrix C
|
||||
|
||||
py_c: 2D array
|
||||
in shape [py_m, py_n] of column-major
|
||||
in fact it is
|
||||
in shape [py_n, py_m] of row-major
|
||||
|
||||
py_ldc: int
|
||||
the stride of the resulting matrix
|
||||
|
||||
Returns
|
||||
-------
|
||||
py_c: 2D array
|
||||
in shape [py_m, py_n] of column-major
|
||||
write the resulting matrix
|
||||
"""
|
||||
cdef bytes by_trans_a=py_trans_a.encode()
|
||||
cdef bytes by_trans_b=py_trans_b.encode()
|
||||
cdef char* trans_a = by_trans_a
|
||||
cdef char* trans_b = by_trans_b
|
||||
cdef int M, N, K, lda, ldb, ldc
|
||||
M = py_m
|
||||
N = py_n
|
||||
K = py_k
|
||||
lda = py_lda
|
||||
ldb = py_ldb
|
||||
ldc = py_ldc
|
||||
cdef float alpha, beta
|
||||
alpha = py_alpha
|
||||
beta = py_beta
|
||||
cdef float[:, ::1] A
|
||||
A = py_a
|
||||
cdef float[:, ::1] B
|
||||
B = py_b
|
||||
cdef float[:, ::1] C
|
||||
C = py_c
|
||||
blas.sgemm(trans_a, trans_b, &M, &N, &K, &alpha, &A[0, 0], &lda,
|
||||
&B[0, 0], &ldb, &beta, &C[0, 0], &ldc)
|
||||
@@ -0,0 +1,48 @@
|
||||
#!python
|
||||
# cython: embedsignature=True, binding=True
|
||||
|
||||
|
||||
def simple_func(x, y, z):
|
||||
return x + y + z
|
||||
|
||||
|
||||
# Cython code directly callable from Python
|
||||
def fib(n):
|
||||
if n < 2:
|
||||
return n
|
||||
return fib(n-2) + fib(n-1)
|
||||
|
||||
|
||||
# Typed Cython code
|
||||
def fib_int(int n):
|
||||
if n < 2:
|
||||
return n
|
||||
return fib_int(n-2) + fib_int(n-1)
|
||||
|
||||
|
||||
# Cython-Python code
|
||||
cpdef fib_cpdef(int n):
|
||||
if n < 2:
|
||||
return n
|
||||
return fib_cpdef(n-2) + fib_cpdef(n-1)
|
||||
|
||||
|
||||
# C code
|
||||
def fib_cdef(int n):
|
||||
return fib_in_c(n)
|
||||
|
||||
|
||||
cdef int fib_in_c(int n):
|
||||
if n < 2:
|
||||
return n
|
||||
return fib_in_c(n-2) + fib_in_c(n-1)
|
||||
|
||||
|
||||
# Simple class
|
||||
class simple_class(object):
|
||||
def __init__(self):
|
||||
self.value = 0
|
||||
|
||||
def increment(self):
|
||||
self.value += 1
|
||||
return self.value
|
||||
@@ -0,0 +1,48 @@
|
||||
#!python
|
||||
# cython: embedsignature=True, binding=True
|
||||
|
||||
# Copyright 2016 Intel Corporation
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
|
||||
from libc.math cimport log
|
||||
|
||||
import numpy as np
|
||||
cimport numpy as np
|
||||
|
||||
|
||||
def masked_log(x):
|
||||
"""Compute natural logarithm while accepting nonpositive input
|
||||
|
||||
For nonpositive elements, return -inf.
|
||||
|
||||
Modified slightly from the original BrainIAK code to support
|
||||
Python 2.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
x: ndarray[T]
|
||||
|
||||
Returns
|
||||
-------
|
||||
ndarray[Union[T, np.float64]]
|
||||
"""
|
||||
y = np.empty(x.shape, dtype=np.float64)
|
||||
lim = x.shape[0]
|
||||
for i in range(lim):
|
||||
if x[i] <= 0:
|
||||
y[i] = float("-inf")
|
||||
else:
|
||||
y[i] = log(x[i])
|
||||
return y
|
||||
@@ -0,0 +1,120 @@
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
import ray
|
||||
import click
|
||||
import inspect
|
||||
|
||||
import numpy as np
|
||||
import cython_examples as cyth
|
||||
|
||||
|
||||
def run_func(func, *args, **kwargs):
|
||||
"""Helper function for running examples"""
|
||||
ray.init()
|
||||
|
||||
func = ray.remote(func)
|
||||
|
||||
# NOTE: kwargs not allowed for now
|
||||
result = ray.get(func.remote(*args))
|
||||
|
||||
# Inspect the stack to get calling example
|
||||
caller = inspect.stack()[1][3]
|
||||
print("%s: %s" % (caller, str(result)))
|
||||
|
||||
return result
|
||||
|
||||
|
||||
@click.group(context_settings={"help_option_names": ["-h", "--help"]})
|
||||
def cli():
|
||||
"""Working with Cython actors and functions in Ray"""
|
||||
|
||||
|
||||
@cli.command()
|
||||
def example1():
|
||||
"""Cython def function"""
|
||||
|
||||
run_func(cyth.simple_func, 1, 2, 3)
|
||||
|
||||
|
||||
@cli.command()
|
||||
def example2():
|
||||
"""Cython def function, recursive"""
|
||||
|
||||
run_func(cyth.fib, 10)
|
||||
|
||||
|
||||
@cli.command()
|
||||
def example3():
|
||||
"""Cython def function, built-in typed parameter"""
|
||||
|
||||
# NOTE: Cython will attempt to cast argument to correct type
|
||||
# NOTE: Floats will be cast to int, but string, for example will error
|
||||
run_func(cyth.fib_int, 10)
|
||||
|
||||
|
||||
@cli.command()
|
||||
def example4():
|
||||
"""Cython cpdef function"""
|
||||
|
||||
run_func(cyth.fib_cpdef, 10)
|
||||
|
||||
|
||||
@cli.command()
|
||||
def example5():
|
||||
"""Cython wrapped cdef function"""
|
||||
|
||||
# NOTE: cdef functions are not exposed to Python
|
||||
run_func(cyth.fib_cdef, 10)
|
||||
|
||||
|
||||
@cli.command()
|
||||
def example6():
|
||||
"""Cython simple class"""
|
||||
|
||||
ray.init()
|
||||
|
||||
cls = ray.remote(cyth.simple_class)
|
||||
a1 = cls.remote()
|
||||
a2 = cls.remote()
|
||||
|
||||
result1 = ray.get(a1.increment.remote())
|
||||
result2 = ray.get(a2.increment.remote())
|
||||
|
||||
print(result1, result2)
|
||||
|
||||
|
||||
@cli.command()
|
||||
def example7():
|
||||
"""Cython with function from BrainIAK (masked log)"""
|
||||
|
||||
run_func(cyth.masked_log, np.array([-1.0, 0.0, 1.0, 2.0]))
|
||||
|
||||
|
||||
@cli.command()
|
||||
def example8():
|
||||
"""Cython with blas. NOTE: requires scipy"""
|
||||
|
||||
# See cython_blas.pyx for argument documentation
|
||||
mat = np.array([[[2.0, 2.0], [2.0, 2.0]], [[2.0, 2.0], [2.0, 2.0]]],
|
||||
dtype=np.float32)
|
||||
result = np.zeros((2, 2), np.float32, order="C")
|
||||
|
||||
run_func(cyth.compute_kernel_matrix,
|
||||
"L",
|
||||
"T",
|
||||
2,
|
||||
2,
|
||||
1.0,
|
||||
mat,
|
||||
0,
|
||||
2,
|
||||
1.0,
|
||||
result,
|
||||
2
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
cli()
|
||||
@@ -0,0 +1,35 @@
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
import os
|
||||
from setuptools import setup
|
||||
from Cython.Build import cythonize
|
||||
|
||||
import numpy
|
||||
|
||||
pkg_dir = "cython_examples"
|
||||
modules = ["cython_simple.pyx", "masked_log.pyx"]
|
||||
install_requires = ["cython", "numpy"]
|
||||
include_dirs = [numpy.get_include()]
|
||||
|
||||
# TODO: Need scipy to run BrainIAK example, but don't want to add additional
|
||||
# dependencies
|
||||
try:
|
||||
import scipy # noqa
|
||||
modules.append("cython_blas.pyx")
|
||||
install_requires.append("scipy")
|
||||
except ImportError as e: # noqa
|
||||
pass
|
||||
|
||||
modules = [os.path.join(pkg_dir, module) for module in modules]
|
||||
|
||||
setup(
|
||||
name=pkg_dir,
|
||||
version="0.0.1",
|
||||
description="Cython examples for Ray",
|
||||
packages=[pkg_dir],
|
||||
ext_modules=cythonize(modules),
|
||||
install_requires=install_requires,
|
||||
include_dirs=include_dirs
|
||||
)
|
||||
@@ -0,0 +1,154 @@
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
import argparse
|
||||
from collections import defaultdict
|
||||
import numpy as np
|
||||
import ray
|
||||
|
||||
from tensorflow.examples.tutorials.mnist import input_data
|
||||
|
||||
import objective
|
||||
|
||||
parser = argparse.ArgumentParser(description="Run the hyperparameter "
|
||||
"optimization example.")
|
||||
parser.add_argument("--num-starting-segments", default=5, type=int,
|
||||
help="The number of training segments to start in "
|
||||
"parallel.")
|
||||
parser.add_argument("--num-segments", default=10, type=int,
|
||||
help="The number of additional training segments to "
|
||||
"perform.")
|
||||
parser.add_argument("--steps-per-segment", default=20, type=int,
|
||||
help="The number of steps of training to do per training "
|
||||
"segment.")
|
||||
parser.add_argument("--redis-address", default=None, type=str,
|
||||
help="The Redis address of the cluster.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
args = parser.parse_args()
|
||||
|
||||
ray.init(redis_address=args.redis_address)
|
||||
|
||||
# The number of training passes over the dataset to use for network.
|
||||
steps = args.steps_per_segment
|
||||
|
||||
# Load the mnist data and turn the data into remote objects.
|
||||
print("Downloading the MNIST dataset. This may take a minute.")
|
||||
mnist = input_data.read_data_sets("MNIST_data", one_hot=True)
|
||||
train_images = ray.put(mnist.train.images)
|
||||
train_labels = ray.put(mnist.train.labels)
|
||||
validation_images = ray.put(mnist.validation.images)
|
||||
validation_labels = ray.put(mnist.validation.labels)
|
||||
|
||||
# Keep track of the accuracies that we've seen at different numbers of
|
||||
# iterations.
|
||||
accuracies_by_num_steps = defaultdict(lambda: [])
|
||||
|
||||
# Define a method to determine if an experiment looks promising or not.
|
||||
def is_promising(experiment_info):
|
||||
accuracies = experiment_info["accuracies"]
|
||||
total_num_steps = experiment_info["total_num_steps"]
|
||||
comparable_accuracies = accuracies_by_num_steps[total_num_steps]
|
||||
if len(comparable_accuracies) == 0:
|
||||
if len(accuracies) == 1:
|
||||
# This means that we haven't seen anything finish yet, so keep
|
||||
# running this experiment.
|
||||
return True
|
||||
else:
|
||||
# The experiment is promising if the second half of the
|
||||
# accuracies are better than the first half of the accuracies.
|
||||
return (np.mean(accuracies[:len(accuracies) // 2]) <
|
||||
np.mean(accuracies[len(accuracies) // 2:]))
|
||||
# Otherwise, continue running the experiment if it is in the top half
|
||||
# of experiments we've seen so far at this point in time.
|
||||
return np.mean(accuracy > np.array(comparable_accuracies)) > 0.5
|
||||
|
||||
# Keep track of all of the experiment segments that we're running. This
|
||||
# dictionary uses the object ID of the experiment as the key.
|
||||
experiment_info = {}
|
||||
# Keep track of the curently running experiment IDs.
|
||||
remaining_ids = []
|
||||
|
||||
# Keep track of the best hyperparameters and the best accuracy.
|
||||
best_hyperparameters = None
|
||||
best_accuracy = 0
|
||||
|
||||
# A function for generating random hyperparameters.
|
||||
def generate_hyperparameters():
|
||||
return {"learning_rate": 10 ** np.random.uniform(-5, 5),
|
||||
"batch_size": np.random.randint(1, 100),
|
||||
"dropout": np.random.uniform(0, 1),
|
||||
"stddev": 10 ** np.random.uniform(-5, 5)}
|
||||
|
||||
# Launch some initial experiments.
|
||||
for _ in range(args.num_starting_segments):
|
||||
hyperparameters = generate_hyperparameters()
|
||||
experiment_id = objective.train_cnn_and_compute_accuracy.remote(
|
||||
hyperparameters, steps, train_images, train_labels,
|
||||
validation_images, validation_labels)
|
||||
experiment_info[experiment_id] = {"hyperparameters": hyperparameters,
|
||||
"total_num_steps": steps,
|
||||
"accuracies": []}
|
||||
remaining_ids.append(experiment_id)
|
||||
|
||||
for _ in range(args.num_segments):
|
||||
# Wait for a segment of an experiment to finish.
|
||||
ready_ids, remaining_ids = ray.wait(remaining_ids, num_returns=1)
|
||||
experiment_id = ready_ids[0]
|
||||
# Get the accuracy and the weights.
|
||||
accuracy, weights = ray.get(experiment_id)
|
||||
# Update the experiment info.
|
||||
previous_info = experiment_info[experiment_id]
|
||||
previous_info["accuracies"].append(accuracy)
|
||||
|
||||
# Update the best accuracy and best hyperparameters.
|
||||
if accuracy > best_accuracy:
|
||||
best_hyperparameters = previous_info["hyperparameters"]
|
||||
best_accuracy = accuracy
|
||||
|
||||
if is_promising(previous_info):
|
||||
# If the experiment still looks promising, then continue running
|
||||
# it.
|
||||
print("Continuing to run the experiment with hyperparameters {}."
|
||||
.format(previous_info["hyperparameters"]))
|
||||
new_hyperparameters = previous_info["hyperparameters"]
|
||||
new_info = {"hyperparameters": new_hyperparameters,
|
||||
"total_num_steps": (previous_info["total_num_steps"] +
|
||||
steps),
|
||||
"accuracies": previous_info["accuracies"][:]}
|
||||
starting_weights = weights
|
||||
else:
|
||||
# If the experiment does not look promising, start a new
|
||||
# experiment.
|
||||
print("Ending the experiment with hyperparameters {}."
|
||||
.format(previous_info["hyperparameters"]))
|
||||
new_hyperparameters = generate_hyperparameters()
|
||||
new_info = {"hyperparameters": new_hyperparameters,
|
||||
"total_num_steps": steps,
|
||||
"accuracies": []}
|
||||
starting_weights = None
|
||||
|
||||
# Start running the next segment.
|
||||
new_experiment_id = objective.train_cnn_and_compute_accuracy.remote(
|
||||
new_hyperparameters, steps, train_images, train_labels,
|
||||
validation_images, validation_labels, weights=starting_weights)
|
||||
experiment_info[new_experiment_id] = new_info
|
||||
remaining_ids.append(new_experiment_id)
|
||||
|
||||
# Update the set of all accuracies that we've seen.
|
||||
accuracies_by_num_steps[previous_info["total_num_steps"]].append(
|
||||
accuracy)
|
||||
|
||||
# Record the best performing set of hyperparameters.
|
||||
print("""Best accuracy was {:.3} with
|
||||
learning_rate: {:.2}
|
||||
batch_size: {}
|
||||
dropout: {:.2}
|
||||
stddev: {:.2}
|
||||
""".format(100 * best_accuracy,
|
||||
best_hyperparameters["learning_rate"],
|
||||
best_hyperparameters["batch_size"],
|
||||
best_hyperparameters["dropout"],
|
||||
best_hyperparameters["stddev"]))
|
||||
@@ -0,0 +1,100 @@
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
import numpy as np
|
||||
import ray
|
||||
import argparse
|
||||
|
||||
from tensorflow.examples.tutorials.mnist import input_data
|
||||
|
||||
import objective
|
||||
|
||||
parser = argparse.ArgumentParser(description="Run the hyperparameter "
|
||||
"optimization example.")
|
||||
parser.add_argument("--trials", default=2, type=int,
|
||||
help="The number of random trials to do.")
|
||||
parser.add_argument("--steps", default=10, type=int,
|
||||
help="The number of steps of training to do per network.")
|
||||
parser.add_argument("--redis-address", default=None, type=str,
|
||||
help="The Redis address of the cluster.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
args = parser.parse_args()
|
||||
|
||||
ray.init(redis_address=args.redis_address)
|
||||
|
||||
# The number of sets of random hyperparameters to try.
|
||||
trials = args.trials
|
||||
# The number of training passes over the dataset to use for network.
|
||||
steps = args.steps
|
||||
|
||||
# Load the mnist data and turn the data into remote objects.
|
||||
print("Downloading the MNIST dataset. This may take a minute.")
|
||||
mnist = input_data.read_data_sets("MNIST_data", one_hot=True)
|
||||
train_images = ray.put(mnist.train.images)
|
||||
train_labels = ray.put(mnist.train.labels)
|
||||
validation_images = ray.put(mnist.validation.images)
|
||||
validation_labels = ray.put(mnist.validation.labels)
|
||||
|
||||
# Keep track of the best hyperparameters and the best accuracy.
|
||||
best_hyperparameters = None
|
||||
best_accuracy = 0
|
||||
# This list holds the object IDs for all of the experiments that we have
|
||||
# launched and that have not yet been processed.
|
||||
remaining_ids = []
|
||||
# This is a dictionary mapping the object ID of an experiment to the
|
||||
# hyerparameters used for that experiment.
|
||||
hyperparameters_mapping = {}
|
||||
|
||||
# A function for generating random hyperparameters.
|
||||
def generate_hyperparameters():
|
||||
return {"learning_rate": 10 ** np.random.uniform(-5, 5),
|
||||
"batch_size": np.random.randint(1, 100),
|
||||
"dropout": np.random.uniform(0, 1),
|
||||
"stddev": 10 ** np.random.uniform(-5, 5)}
|
||||
|
||||
# Randomly generate some hyperparameters, and launch a task for each set.
|
||||
for i in range(trials):
|
||||
hyperparameters = generate_hyperparameters()
|
||||
accuracy_id = objective.train_cnn_and_compute_accuracy.remote(
|
||||
hyperparameters, steps, train_images, train_labels,
|
||||
validation_images, validation_labels)
|
||||
remaining_ids.append(accuracy_id)
|
||||
# Keep track of which hyperparameters correspond to this experiment.
|
||||
hyperparameters_mapping[accuracy_id] = hyperparameters
|
||||
|
||||
# Fetch and print the results of the tasks in the order that they complete.
|
||||
for i in range(trials):
|
||||
# Use ray.wait to get the object ID of the first task that completes.
|
||||
ready_ids, remaining_ids = ray.wait(remaining_ids)
|
||||
# Process the output of this task.
|
||||
result_id = ready_ids[0]
|
||||
hyperparameters = hyperparameters_mapping[result_id]
|
||||
accuracy, _ = ray.get(result_id)
|
||||
print("""We achieve accuracy {:.3}% with
|
||||
learning_rate: {:.2}
|
||||
batch_size: {}
|
||||
dropout: {:.2}
|
||||
stddev: {:.2}
|
||||
""".format(100 * accuracy,
|
||||
hyperparameters["learning_rate"],
|
||||
hyperparameters["batch_size"],
|
||||
hyperparameters["dropout"],
|
||||
hyperparameters["stddev"]))
|
||||
if accuracy > best_accuracy:
|
||||
best_hyperparameters = hyperparameters
|
||||
best_accuracy = accuracy
|
||||
|
||||
# Record the best performing set of hyperparameters.
|
||||
print("""Best accuracy over {} trials was {:.3} with
|
||||
learning_rate: {:.2}
|
||||
batch_size: {}
|
||||
dropout: {:.2}
|
||||
stddev: {:.2}
|
||||
""".format(trials, 100 * best_accuracy,
|
||||
best_hyperparameters["learning_rate"],
|
||||
best_hyperparameters["batch_size"],
|
||||
best_hyperparameters["dropout"],
|
||||
best_hyperparameters["stddev"]))
|
||||
@@ -0,0 +1,127 @@
|
||||
# Most of the tensorflow code is adapted from Tensorflow's tutorial on using
|
||||
# CNNs to train MNIST
|
||||
# https://www.tensorflow.org/versions/r0.9/tutorials/mnist/pros/index.html#build-a-multilayer-convolutional-network. # noqa: E501
|
||||
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
import tensorflow as tf
|
||||
|
||||
import ray
|
||||
import ray.experimental.tf_utils
|
||||
|
||||
|
||||
def get_batch(data, batch_index, batch_size):
|
||||
# This method currently drops data when num_data is not divisible by
|
||||
# batch_size.
|
||||
num_data = data.shape[0]
|
||||
num_batches = num_data // batch_size
|
||||
batch_index %= num_batches
|
||||
return data[(batch_index * batch_size):((batch_index + 1) * batch_size)]
|
||||
|
||||
|
||||
def weight(shape, stddev):
|
||||
initial = tf.truncated_normal(shape, stddev=stddev)
|
||||
return tf.Variable(initial)
|
||||
|
||||
|
||||
def bias(shape):
|
||||
initial = tf.constant(0.1, shape=shape)
|
||||
return tf.Variable(initial)
|
||||
|
||||
|
||||
def conv2d(x, W):
|
||||
return tf.nn.conv2d(x, W, strides=[1, 1, 1, 1], padding="SAME")
|
||||
|
||||
|
||||
def max_pool_2x2(x):
|
||||
return tf.nn.max_pool(
|
||||
x, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding="SAME")
|
||||
|
||||
|
||||
def cnn_setup(x, y, keep_prob, lr, stddev):
|
||||
first_hidden = 32
|
||||
second_hidden = 64
|
||||
fc_hidden = 1024
|
||||
W_conv1 = weight([5, 5, 1, first_hidden], stddev)
|
||||
B_conv1 = bias([first_hidden])
|
||||
x_image = tf.reshape(x, [-1, 28, 28, 1])
|
||||
h_conv1 = tf.nn.relu(conv2d(x_image, W_conv1) + B_conv1)
|
||||
h_pool1 = max_pool_2x2(h_conv1)
|
||||
W_conv2 = weight([5, 5, first_hidden, second_hidden], stddev)
|
||||
b_conv2 = bias([second_hidden])
|
||||
h_conv2 = tf.nn.relu(conv2d(h_pool1, W_conv2) + b_conv2)
|
||||
h_pool2 = max_pool_2x2(h_conv2)
|
||||
W_fc1 = weight([7 * 7 * second_hidden, fc_hidden], stddev)
|
||||
b_fc1 = bias([fc_hidden])
|
||||
h_pool2_flat = tf.reshape(h_pool2, [-1, 7 * 7 * second_hidden])
|
||||
h_fc1 = tf.nn.relu(tf.matmul(h_pool2_flat, W_fc1) + b_fc1)
|
||||
h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob)
|
||||
W_fc2 = weight([fc_hidden, 10], stddev)
|
||||
b_fc2 = bias([10])
|
||||
y_conv = tf.nn.softmax(tf.matmul(h_fc1_drop, W_fc2) + b_fc2)
|
||||
cross_entropy = tf.reduce_mean(
|
||||
-tf.reduce_sum(y * tf.log(y_conv), reduction_indices=[1]))
|
||||
correct_pred = tf.equal(tf.argmax(y_conv, 1), tf.argmax(y, 1))
|
||||
return (tf.train.AdamOptimizer(lr).minimize(cross_entropy),
|
||||
tf.reduce_mean(tf.cast(correct_pred, tf.float32)), cross_entropy)
|
||||
|
||||
|
||||
# Define a remote function that takes a set of hyperparameters as well as the
|
||||
# data, consructs and trains a network, and returns the validation accuracy.
|
||||
@ray.remote
|
||||
def train_cnn_and_compute_accuracy(params,
|
||||
steps,
|
||||
train_images,
|
||||
train_labels,
|
||||
validation_images,
|
||||
validation_labels,
|
||||
weights=None):
|
||||
# Extract the hyperparameters from the params dictionary.
|
||||
learning_rate = params["learning_rate"]
|
||||
batch_size = params["batch_size"]
|
||||
keep = 1 - params["dropout"]
|
||||
stddev = params["stddev"]
|
||||
# Create the network and related variables.
|
||||
with tf.Graph().as_default():
|
||||
# Create the input placeholders for the network.
|
||||
x = tf.placeholder(tf.float32, shape=[None, 784])
|
||||
y = tf.placeholder(tf.float32, shape=[None, 10])
|
||||
keep_prob = tf.placeholder(tf.float32)
|
||||
# Create the network.
|
||||
train_step, accuracy, loss = cnn_setup(x, y, keep_prob, learning_rate,
|
||||
stddev)
|
||||
# Do the training and evaluation.
|
||||
with tf.Session() as sess:
|
||||
# Use the TensorFlowVariables utility. This is only necessary if we
|
||||
# want to set and get the weights.
|
||||
variables = ray.experimental.tf_utils.TensorFlowVariables(
|
||||
loss, sess)
|
||||
# Initialize the network weights.
|
||||
sess.run(tf.global_variables_initializer())
|
||||
# If some network weights were passed in, set those.
|
||||
if weights is not None:
|
||||
variables.set_weights(weights)
|
||||
# Do some steps of training.
|
||||
for i in range(1, steps + 1):
|
||||
# Fetch the next batch of data.
|
||||
image_batch = get_batch(train_images, i, batch_size)
|
||||
label_batch = get_batch(train_labels, i, batch_size)
|
||||
# Do one step of training.
|
||||
sess.run(
|
||||
train_step,
|
||||
feed_dict={
|
||||
x: image_batch,
|
||||
y: label_batch,
|
||||
keep_prob: keep
|
||||
})
|
||||
# Training is done, so compute the validation accuracy and the
|
||||
# current weights and return.
|
||||
totalacc = accuracy.eval(feed_dict={
|
||||
x: validation_images,
|
||||
y: validation_labels,
|
||||
keep_prob: 1.0
|
||||
})
|
||||
new_weights = variables.get_weights()
|
||||
return float(totalacc), new_weights
|
||||
@@ -0,0 +1,150 @@
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
import numpy as np
|
||||
import os
|
||||
import scipy.optimize
|
||||
|
||||
import tensorflow as tf
|
||||
from tensorflow.examples.tutorials.mnist import input_data
|
||||
|
||||
import ray
|
||||
import ray.experimental.tf_utils
|
||||
|
||||
|
||||
class LinearModel(object):
|
||||
"""Simple class for a one layer neural network.
|
||||
|
||||
Note that this code does not initialize the network weights. Instead
|
||||
weights are set via self.variables.set_weights.
|
||||
|
||||
Example:
|
||||
net = LinearModel([10, 10])
|
||||
weights = [np.random.normal(size=[10, 10]),
|
||||
np.random.normal(size=[10])]
|
||||
variable_names = [v.name for v in net.variables]
|
||||
net.variables.set_weights(dict(zip(variable_names, weights)))
|
||||
|
||||
Attributes:
|
||||
x (tf.placeholder): Input vector.
|
||||
w (tf.Variable): Weight matrix.
|
||||
b (tf.Variable): Bias vector.
|
||||
y_ (tf.placeholder): Input result vector.
|
||||
cross_entropy (tf.Operation): Final layer of network.
|
||||
cross_entropy_grads (tf.Operation): Gradient computation.
|
||||
sess (tf.Session): Session used for training.
|
||||
variables (TensorFlowVariables): Extracted variables and methods to
|
||||
manipulate them.
|
||||
"""
|
||||
|
||||
def __init__(self, shape):
|
||||
"""Creates a LinearModel object."""
|
||||
x = tf.placeholder(tf.float32, [None, shape[0]])
|
||||
w = tf.Variable(tf.zeros(shape))
|
||||
b = tf.Variable(tf.zeros(shape[1]))
|
||||
self.x = x
|
||||
self.w = w
|
||||
self.b = b
|
||||
y = tf.nn.softmax(tf.matmul(x, w) + b)
|
||||
y_ = tf.placeholder(tf.float32, [None, shape[1]])
|
||||
self.y_ = y_
|
||||
cross_entropy = tf.reduce_mean(
|
||||
-tf.reduce_sum(y_ * tf.log(y), reduction_indices=[1]))
|
||||
self.cross_entropy = cross_entropy
|
||||
self.cross_entropy_grads = tf.gradients(cross_entropy, [w, b])
|
||||
self.sess = tf.Session()
|
||||
# In order to get and set the weights, we pass in the loss function to
|
||||
# Ray's TensorFlowVariables to automatically create methods to modify
|
||||
# the weights.
|
||||
self.variables = ray.experimental.tf_utils.TensorFlowVariables(
|
||||
cross_entropy, self.sess)
|
||||
|
||||
def loss(self, xs, ys):
|
||||
"""Computes the loss of the network."""
|
||||
return float(
|
||||
self.sess.run(
|
||||
self.cross_entropy, feed_dict={
|
||||
self.x: xs,
|
||||
self.y_: ys
|
||||
}))
|
||||
|
||||
def grad(self, xs, ys):
|
||||
"""Computes the gradients of the network."""
|
||||
return self.sess.run(
|
||||
self.cross_entropy_grads, feed_dict={
|
||||
self.x: xs,
|
||||
self.y_: ys
|
||||
})
|
||||
|
||||
|
||||
@ray.remote
|
||||
class NetActor(object):
|
||||
def __init__(self, xs, ys):
|
||||
os.environ["CUDA_VISIBLE_DEVICES"] = ""
|
||||
with tf.device("/cpu:0"):
|
||||
self.net = LinearModel([784, 10])
|
||||
self.xs = xs
|
||||
self.ys = ys
|
||||
|
||||
# Compute the loss on a batch of data.
|
||||
def loss(self, theta):
|
||||
net = self.net
|
||||
net.variables.set_flat(theta)
|
||||
return net.loss(self.xs, self.ys)
|
||||
|
||||
# Compute the gradient of the loss on a batch of data.
|
||||
def grad(self, theta):
|
||||
net = self.net
|
||||
net.variables.set_flat(theta)
|
||||
gradients = net.grad(self.xs, self.ys)
|
||||
return np.concatenate([g.flatten() for g in gradients])
|
||||
|
||||
def get_flat_size(self):
|
||||
return self.net.variables.get_flat_size()
|
||||
|
||||
|
||||
# Compute the loss on the entire dataset.
|
||||
def full_loss(theta):
|
||||
theta_id = ray.put(theta)
|
||||
loss_ids = [actor.loss.remote(theta_id) for actor in actors]
|
||||
return sum(ray.get(loss_ids))
|
||||
|
||||
|
||||
# Compute the gradient of the loss on the entire dataset.
|
||||
def full_grad(theta):
|
||||
theta_id = ray.put(theta)
|
||||
grad_ids = [actor.grad.remote(theta_id) for actor in actors]
|
||||
# The float64 conversion is necessary for use with fmin_l_bfgs_b.
|
||||
return sum(ray.get(grad_ids)).astype("float64")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
ray.init()
|
||||
|
||||
# From the perspective of scipy.optimize.fmin_l_bfgs_b, full_loss is simply
|
||||
# a function which takes some parameters theta, and computes a loss.
|
||||
# Similarly, full_grad is a function which takes some parameters theta, and
|
||||
# computes the gradient of the loss. Internally, these functions use Ray to
|
||||
# distribute the computation of the loss and the gradient over the data
|
||||
# that is represented by the remote object IDs x_batches and y_batches and
|
||||
# which is potentially distributed over a cluster. However, these details
|
||||
# are hidden from scipy.optimize.fmin_l_bfgs_b, which simply uses it to run
|
||||
# the L-BFGS algorithm.
|
||||
|
||||
# Load the mnist data and turn the data into remote objects.
|
||||
print("Downloading the MNIST dataset. This may take a minute.")
|
||||
mnist = input_data.read_data_sets("MNIST_data", one_hot=True)
|
||||
num_batches = 10
|
||||
batch_size = mnist.train.num_examples // num_batches
|
||||
batches = [mnist.train.next_batch(batch_size) for _ in range(num_batches)]
|
||||
print("Putting MNIST in the object store.")
|
||||
actors = [NetActor.remote(xs, ys) for (xs, ys) in batches]
|
||||
# Initialize the weights for the network to the vector of all zeros.
|
||||
dim = ray.get(actors[0].get_flat_size.remote())
|
||||
theta_init = 1e-2 * np.random.normal(size=dim)
|
||||
|
||||
# Use L-BFGS to minimize the loss function.
|
||||
print("Running L-BFGS.")
|
||||
result = scipy.optimize.fmin_l_bfgs_b(
|
||||
full_loss, theta_init, maxiter=10, fprime=full_grad, disp=True)
|
||||
@@ -0,0 +1,85 @@
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
import atoma
|
||||
from flask import Flask, jsonify, request
|
||||
from flask_cors import CORS
|
||||
import requests
|
||||
import sqlite3
|
||||
|
||||
import ray
|
||||
|
||||
|
||||
@ray.remote
|
||||
class NewsServer(object):
|
||||
|
||||
def __init__(self):
|
||||
self.conn = sqlite3.connect("newsreader.db")
|
||||
c = self.conn.cursor()
|
||||
c.execute("""CREATE TABLE IF NOT EXISTS news
|
||||
(title text, link text,
|
||||
description text, published timestamp,
|
||||
feed url, liked bool)""")
|
||||
self.conn.commit()
|
||||
|
||||
def retrieve_feed(self, url):
|
||||
response = requests.get(url)
|
||||
feed = atoma.parse_rss_bytes(response.content)
|
||||
items = []
|
||||
c = self.conn.cursor()
|
||||
for item in feed.items:
|
||||
items.append({"title": item.title,
|
||||
"link": item.link,
|
||||
"description": item.description,
|
||||
"description_text": item.description,
|
||||
"pubDate": str(item.pub_date)})
|
||||
c.execute("""INSERT INTO news (title, link, description,
|
||||
published, feed, liked) values
|
||||
(?, ?, ?, ?, ?, ?)""", (
|
||||
item.title, item.link, item.description,
|
||||
item.pub_date, feed.link, False))
|
||||
self.conn.commit()
|
||||
|
||||
return {"channel": {"title": feed.title,
|
||||
"link": feed.link,
|
||||
"url": feed.link},
|
||||
"items": items}
|
||||
|
||||
def like_item(self, url, is_faved):
|
||||
c = self.conn.cursor()
|
||||
if is_faved:
|
||||
c.execute("UPDATE news SET liked = 1 WHERE link = ?", (url,))
|
||||
else:
|
||||
c.execute("UPDATE news SET liked = 0 WHERE link = ?", (url,))
|
||||
self.conn.commit()
|
||||
|
||||
|
||||
# instantiate the app
|
||||
app = Flask(__name__)
|
||||
app.config.from_object(__name__)
|
||||
|
||||
# enable CORS
|
||||
CORS(app)
|
||||
|
||||
|
||||
@app.route("/api", methods=["POST"])
|
||||
def dispatcher():
|
||||
req = request.get_json()
|
||||
method_name = req["method_name"]
|
||||
method_args = req["method_args"]
|
||||
if hasattr(dispatcher.server, method_name):
|
||||
method = getattr(dispatcher.server, method_name)
|
||||
# Doing a blocking ray.get right after submitting the task
|
||||
# might be bad for performance if the task is expensive.
|
||||
result = ray.get(method.remote(*method_args))
|
||||
return jsonify(result)
|
||||
else:
|
||||
return jsonify(
|
||||
{"error": "method_name '" + method_name + "' not found"})
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
ray.init(num_cpus=2)
|
||||
dispatcher.server = NewsServer.remote()
|
||||
app.run()
|
||||
@@ -0,0 +1,80 @@
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
import argparse
|
||||
import time
|
||||
|
||||
import ray
|
||||
import model
|
||||
|
||||
parser = argparse.ArgumentParser(description="Run the asynchronous parameter "
|
||||
"server example.")
|
||||
parser.add_argument("--num-workers", default=4, type=int,
|
||||
help="The number of workers to use.")
|
||||
parser.add_argument("--redis-address", default=None, type=str,
|
||||
help="The Redis address of the cluster.")
|
||||
|
||||
|
||||
@ray.remote
|
||||
class ParameterServer(object):
|
||||
def __init__(self, keys, values):
|
||||
# These values will be mutated, so we must create a copy that is not
|
||||
# backed by the object store.
|
||||
values = [value.copy() for value in values]
|
||||
self.weights = dict(zip(keys, values))
|
||||
|
||||
def push(self, keys, values):
|
||||
for key, value in zip(keys, values):
|
||||
self.weights[key] += value
|
||||
|
||||
def pull(self, keys):
|
||||
return [self.weights[key] for key in keys]
|
||||
|
||||
|
||||
@ray.remote
|
||||
def worker_task(ps, worker_index, batch_size=50):
|
||||
# Download MNIST.
|
||||
mnist = model.download_mnist_retry(seed=worker_index)
|
||||
|
||||
# Initialize the model.
|
||||
net = model.SimpleCNN()
|
||||
keys = net.get_weights()[0]
|
||||
|
||||
while True:
|
||||
# Get the current weights from the parameter server.
|
||||
weights = ray.get(ps.pull.remote(keys))
|
||||
net.set_weights(keys, weights)
|
||||
|
||||
# Compute an update and push it to the parameter server.
|
||||
xs, ys = mnist.train.next_batch(batch_size)
|
||||
gradients = net.compute_update(xs, ys)
|
||||
ps.push.remote(keys, gradients)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
args = parser.parse_args()
|
||||
|
||||
ray.init(redis_address=args.redis_address)
|
||||
|
||||
# Create a parameter server with some random weights.
|
||||
net = model.SimpleCNN()
|
||||
all_keys, all_values = net.get_weights()
|
||||
ps = ParameterServer.remote(all_keys, all_values)
|
||||
|
||||
# Start some training tasks.
|
||||
worker_tasks = [worker_task.remote(ps, i) for i in range(args.num_workers)]
|
||||
|
||||
# Download MNIST.
|
||||
mnist = model.download_mnist_retry()
|
||||
|
||||
i = 0
|
||||
while True:
|
||||
# Get and evaluate the current model.
|
||||
current_weights = ray.get(ps.pull.remote(all_keys))
|
||||
net.set_weights(all_keys, current_weights)
|
||||
test_xs, test_ys = mnist.test.next_batch(1000)
|
||||
accuracy = net.compute_accuracy(test_xs, test_ys)
|
||||
print("Iteration {}: accuracy is {}".format(i, accuracy))
|
||||
i += 1
|
||||
time.sleep(1)
|
||||
@@ -0,0 +1,203 @@
|
||||
# Most of the tensorflow code is adapted from Tensorflow's tutorial on using
|
||||
# CNNs to train MNIST
|
||||
# https://www.tensorflow.org/get_started/mnist/pros#build-a-multilayer-convolutional-network. # noqa: E501
|
||||
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
import time
|
||||
|
||||
import tensorflow as tf
|
||||
from tensorflow.examples.tutorials.mnist import input_data
|
||||
|
||||
import ray
|
||||
import ray.experimental.tf_utils
|
||||
|
||||
|
||||
def download_mnist_retry(seed=0, max_num_retries=20):
|
||||
for _ in range(max_num_retries):
|
||||
try:
|
||||
return input_data.read_data_sets(
|
||||
"MNIST_data", one_hot=True, seed=seed)
|
||||
except tf.errors.AlreadyExistsError:
|
||||
time.sleep(1)
|
||||
raise Exception("Failed to download MNIST.")
|
||||
|
||||
|
||||
class SimpleCNN(object):
|
||||
def __init__(self, learning_rate=1e-4):
|
||||
with tf.Graph().as_default():
|
||||
|
||||
# Create the model
|
||||
self.x = tf.placeholder(tf.float32, [None, 784])
|
||||
|
||||
# Define loss and optimizer
|
||||
self.y_ = tf.placeholder(tf.float32, [None, 10])
|
||||
|
||||
# Build the graph for the deep net
|
||||
self.y_conv, self.keep_prob = deepnn(self.x)
|
||||
|
||||
with tf.name_scope("loss"):
|
||||
cross_entropy = tf.nn.softmax_cross_entropy_with_logits(
|
||||
labels=self.y_, logits=self.y_conv)
|
||||
self.cross_entropy = tf.reduce_mean(cross_entropy)
|
||||
|
||||
with tf.name_scope("adam_optimizer"):
|
||||
self.optimizer = tf.train.AdamOptimizer(learning_rate)
|
||||
self.train_step = self.optimizer.minimize(self.cross_entropy)
|
||||
|
||||
with tf.name_scope("accuracy"):
|
||||
correct_prediction = tf.equal(
|
||||
tf.argmax(self.y_conv, 1), tf.argmax(self.y_, 1))
|
||||
correct_prediction = tf.cast(correct_prediction, tf.float32)
|
||||
self.accuracy = tf.reduce_mean(correct_prediction)
|
||||
|
||||
self.sess = tf.Session(
|
||||
config=tf.ConfigProto(
|
||||
intra_op_parallelism_threads=1,
|
||||
inter_op_parallelism_threads=1))
|
||||
self.sess.run(tf.global_variables_initializer())
|
||||
|
||||
# Helper values.
|
||||
|
||||
self.variables = ray.experimental.tf_utils.TensorFlowVariables(
|
||||
self.cross_entropy, self.sess)
|
||||
|
||||
self.grads = self.optimizer.compute_gradients(self.cross_entropy)
|
||||
self.grads_placeholder = [(tf.placeholder(
|
||||
"float", shape=grad[1].get_shape()), grad[1])
|
||||
for grad in self.grads]
|
||||
self.apply_grads_placeholder = self.optimizer.apply_gradients(
|
||||
self.grads_placeholder)
|
||||
|
||||
def compute_update(self, x, y):
|
||||
# TODO(rkn): Computing the weights before and after the training step
|
||||
# and taking the diff is awful.
|
||||
weights = self.get_weights()[1]
|
||||
self.sess.run(
|
||||
self.train_step,
|
||||
feed_dict={
|
||||
self.x: x,
|
||||
self.y_: y,
|
||||
self.keep_prob: 0.5
|
||||
})
|
||||
new_weights = self.get_weights()[1]
|
||||
return [x - y for x, y in zip(new_weights, weights)]
|
||||
|
||||
def compute_gradients(self, x, y):
|
||||
return self.sess.run(
|
||||
[grad[0] for grad in self.grads],
|
||||
feed_dict={
|
||||
self.x: x,
|
||||
self.y_: y,
|
||||
self.keep_prob: 0.5
|
||||
})
|
||||
|
||||
def apply_gradients(self, gradients):
|
||||
feed_dict = {}
|
||||
for i in range(len(self.grads_placeholder)):
|
||||
feed_dict[self.grads_placeholder[i][0]] = gradients[i]
|
||||
self.sess.run(self.apply_grads_placeholder, feed_dict=feed_dict)
|
||||
|
||||
def compute_accuracy(self, x, y):
|
||||
return self.sess.run(
|
||||
self.accuracy,
|
||||
feed_dict={
|
||||
self.x: x,
|
||||
self.y_: y,
|
||||
self.keep_prob: 1.0
|
||||
})
|
||||
|
||||
def set_weights(self, variable_names, weights):
|
||||
self.variables.set_weights(dict(zip(variable_names, weights)))
|
||||
|
||||
def get_weights(self):
|
||||
weights = self.variables.get_weights()
|
||||
return list(weights.keys()), list(weights.values())
|
||||
|
||||
|
||||
def deepnn(x):
|
||||
"""deepnn builds the graph for a deep net for classifying digits.
|
||||
|
||||
Args:
|
||||
x: an input tensor with the dimensions (N_examples, 784), where 784 is
|
||||
the number of pixels in a standard MNIST image.
|
||||
|
||||
Returns:
|
||||
A tuple (y, keep_prob). y is a tensor of shape (N_examples, 10), with
|
||||
values equal to the logits of classifying the digit into one of 10
|
||||
classes (the digits 0-9). keep_prob is a scalar placeholder for the
|
||||
probability of dropout.
|
||||
"""
|
||||
# Reshape to use within a convolutional neural net.
|
||||
# Last dimension is for "features" - there is only one here, since images
|
||||
# are grayscale -- it would be 3 for an RGB image, 4 for RGBA, etc.
|
||||
with tf.name_scope("reshape"):
|
||||
x_image = tf.reshape(x, [-1, 28, 28, 1])
|
||||
|
||||
# First convolutional layer - maps one grayscale image to 32 feature maps.
|
||||
with tf.name_scope("conv1"):
|
||||
W_conv1 = weight_variable([5, 5, 1, 32])
|
||||
b_conv1 = bias_variable([32])
|
||||
h_conv1 = tf.nn.relu(conv2d(x_image, W_conv1) + b_conv1)
|
||||
|
||||
# Pooling layer - downsamples by 2X.
|
||||
with tf.name_scope("pool1"):
|
||||
h_pool1 = max_pool_2x2(h_conv1)
|
||||
|
||||
# Second convolutional layer -- maps 32 feature maps to 64.
|
||||
with tf.name_scope("conv2"):
|
||||
W_conv2 = weight_variable([5, 5, 32, 64])
|
||||
b_conv2 = bias_variable([64])
|
||||
h_conv2 = tf.nn.relu(conv2d(h_pool1, W_conv2) + b_conv2)
|
||||
|
||||
# Second pooling layer.
|
||||
with tf.name_scope("pool2"):
|
||||
h_pool2 = max_pool_2x2(h_conv2)
|
||||
|
||||
# Fully connected layer 1 -- after 2 round of downsampling, our 28x28 image
|
||||
# is down to 7x7x64 feature maps -- maps this to 1024 features.
|
||||
with tf.name_scope("fc1"):
|
||||
W_fc1 = weight_variable([7 * 7 * 64, 1024])
|
||||
b_fc1 = bias_variable([1024])
|
||||
|
||||
h_pool2_flat = tf.reshape(h_pool2, [-1, 7 * 7 * 64])
|
||||
h_fc1 = tf.nn.relu(tf.matmul(h_pool2_flat, W_fc1) + b_fc1)
|
||||
|
||||
# Dropout - controls the complexity of the model, prevents co-adaptation of
|
||||
# features.
|
||||
with tf.name_scope("dropout"):
|
||||
keep_prob = tf.placeholder(tf.float32)
|
||||
h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob)
|
||||
|
||||
# Map the 1024 features to 10 classes, one for each digit
|
||||
with tf.name_scope("fc2"):
|
||||
W_fc2 = weight_variable([1024, 10])
|
||||
b_fc2 = bias_variable([10])
|
||||
|
||||
y_conv = tf.matmul(h_fc1_drop, W_fc2) + b_fc2
|
||||
return y_conv, keep_prob
|
||||
|
||||
|
||||
def conv2d(x, W):
|
||||
"""conv2d returns a 2d convolution layer with full stride."""
|
||||
return tf.nn.conv2d(x, W, strides=[1, 1, 1, 1], padding="SAME")
|
||||
|
||||
|
||||
def max_pool_2x2(x):
|
||||
"""max_pool_2x2 downsamples a feature map by 2X."""
|
||||
return tf.nn.max_pool(
|
||||
x, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding="SAME")
|
||||
|
||||
|
||||
def weight_variable(shape):
|
||||
"""weight_variable generates a weight variable of a given shape."""
|
||||
initial = tf.truncated_normal(shape, stddev=0.1)
|
||||
return tf.Variable(initial)
|
||||
|
||||
|
||||
def bias_variable(shape):
|
||||
"""bias_variable generates a bias variable of a given shape."""
|
||||
initial = tf.constant(0.1, shape=shape)
|
||||
return tf.Variable(initial)
|
||||
@@ -0,0 +1,76 @@
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
import argparse
|
||||
import numpy as np
|
||||
|
||||
import ray
|
||||
import model
|
||||
|
||||
parser = argparse.ArgumentParser(description="Run the synchronous parameter "
|
||||
"server example.")
|
||||
parser.add_argument("--num-workers", default=4, type=int,
|
||||
help="The number of workers to use.")
|
||||
parser.add_argument("--redis-address", default=None, type=str,
|
||||
help="The Redis address of the cluster.")
|
||||
|
||||
|
||||
@ray.remote
|
||||
class ParameterServer(object):
|
||||
def __init__(self, learning_rate):
|
||||
self.net = model.SimpleCNN(learning_rate=learning_rate)
|
||||
|
||||
def apply_gradients(self, *gradients):
|
||||
self.net.apply_gradients(np.mean(gradients, axis=0))
|
||||
return self.net.variables.get_flat()
|
||||
|
||||
def get_weights(self):
|
||||
return self.net.variables.get_flat()
|
||||
|
||||
|
||||
@ray.remote
|
||||
class Worker(object):
|
||||
def __init__(self, worker_index, batch_size=50):
|
||||
self.worker_index = worker_index
|
||||
self.batch_size = batch_size
|
||||
self.mnist = model.download_mnist_retry(seed=worker_index)
|
||||
self.net = model.SimpleCNN()
|
||||
|
||||
def compute_gradients(self, weights):
|
||||
self.net.variables.set_flat(weights)
|
||||
xs, ys = self.mnist.train.next_batch(self.batch_size)
|
||||
return self.net.compute_gradients(xs, ys)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
args = parser.parse_args()
|
||||
|
||||
ray.init(redis_address=args.redis_address)
|
||||
|
||||
# Create a parameter server.
|
||||
net = model.SimpleCNN()
|
||||
ps = ParameterServer.remote(1e-4 * args.num_workers)
|
||||
|
||||
# Create workers.
|
||||
workers = [Worker.remote(worker_index)
|
||||
for worker_index in range(args.num_workers)]
|
||||
|
||||
# Download MNIST.
|
||||
mnist = model.download_mnist_retry()
|
||||
|
||||
i = 0
|
||||
current_weights = ps.get_weights.remote()
|
||||
while True:
|
||||
# Compute and apply gradients.
|
||||
gradients = [worker.compute_gradients.remote(current_weights)
|
||||
for worker in workers]
|
||||
current_weights = ps.apply_gradients.remote(*gradients)
|
||||
|
||||
if i % 10 == 0:
|
||||
# Evaluate the current model.
|
||||
net.variables.set_flat(ray.get(current_weights))
|
||||
test_xs, test_ys = mnist.test.next_batch(1000)
|
||||
accuracy = net.compute_accuracy(test_xs, test_ys)
|
||||
print("Iteration {}: accuracy is {}".format(i, accuracy))
|
||||
i += 1
|
||||
@@ -0,0 +1,116 @@
|
||||
"""CIFAR dataset input module, with the majority taken from
|
||||
https://github.com/tensorflow/models/tree/master/resnet.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
import tensorflow as tf
|
||||
|
||||
|
||||
def build_data(data_path, size, dataset):
|
||||
"""Creates the queue and preprocessing operations for the dataset.
|
||||
|
||||
Args:
|
||||
data_path: Filename for cifar10 data.
|
||||
size: The number of images in the dataset.
|
||||
dataset: The dataset we are using.
|
||||
|
||||
Returns:
|
||||
queue: A Tensorflow queue for extracting the images and labels.
|
||||
"""
|
||||
image_size = 32
|
||||
if dataset == "cifar10":
|
||||
label_bytes = 1
|
||||
label_offset = 0
|
||||
elif dataset == "cifar100":
|
||||
label_bytes = 1
|
||||
label_offset = 1
|
||||
depth = 3
|
||||
image_bytes = image_size * image_size * depth
|
||||
record_bytes = label_bytes + label_offset + image_bytes
|
||||
|
||||
def load_transform(value):
|
||||
# Convert these examples to dense labels and processed images.
|
||||
record = tf.reshape(tf.decode_raw(value, tf.uint8), [record_bytes])
|
||||
label = tf.cast(tf.slice(record, [label_offset], [label_bytes]),
|
||||
tf.int32)
|
||||
# Convert from string to [depth * height * width] to
|
||||
# [depth, height, width].
|
||||
depth_major = tf.reshape(
|
||||
tf.slice(record, [label_bytes], [image_bytes]),
|
||||
[depth, image_size, image_size])
|
||||
# Convert from [depth, height, width] to [height, width, depth].
|
||||
image = tf.cast(tf.transpose(depth_major, [1, 2, 0]), tf.float32)
|
||||
return (image, label)
|
||||
# Read examples from files in the filename queue.
|
||||
data_files = tf.gfile.Glob(data_path)
|
||||
data = tf.contrib.data.FixedLengthRecordDataset(data_files,
|
||||
record_bytes=record_bytes)
|
||||
data = data.map(load_transform)
|
||||
data = data.batch(size)
|
||||
iterator = data.make_one_shot_iterator()
|
||||
return iterator.get_next()
|
||||
|
||||
|
||||
def build_input(data, batch_size, dataset, train):
|
||||
"""Build CIFAR image and labels.
|
||||
|
||||
Args:
|
||||
data_path: Filename for cifar10 data.
|
||||
batch_size: Input batch size.
|
||||
train: True if we are training and false if we are testing.
|
||||
|
||||
Returns:
|
||||
images: Batches of images of size
|
||||
[batch_size, image_size, image_size, 3].
|
||||
labels: Batches of labels of size [batch_size, num_classes].
|
||||
|
||||
Raises:
|
||||
ValueError: When the specified dataset is not supported.
|
||||
"""
|
||||
image_size = 32
|
||||
depth = 3
|
||||
num_classes = 10 if dataset == "cifar10" else 100
|
||||
images, labels = data
|
||||
num_samples = images.shape[0] - images.shape[0] % batch_size
|
||||
dataset = tf.contrib.data.Dataset.from_tensor_slices(
|
||||
(images[:num_samples], labels[:num_samples]))
|
||||
|
||||
def map_train(image, label):
|
||||
image = tf.image.resize_image_with_crop_or_pad(image, image_size + 4,
|
||||
image_size + 4)
|
||||
image = tf.random_crop(image, [image_size, image_size, 3])
|
||||
image = tf.image.random_flip_left_right(image)
|
||||
image = tf.image.per_image_standardization(image)
|
||||
return (image, label)
|
||||
|
||||
def map_test(image, label):
|
||||
image = tf.image.resize_image_with_crop_or_pad(image, image_size,
|
||||
image_size)
|
||||
image = tf.image.per_image_standardization(image)
|
||||
return (image, label)
|
||||
|
||||
dataset = dataset.map(map_train if train else map_test)
|
||||
dataset = dataset.batch(batch_size)
|
||||
dataset = dataset.repeat()
|
||||
if train:
|
||||
dataset = dataset.shuffle(buffer_size=16 * batch_size)
|
||||
images, labels = dataset.make_one_shot_iterator().get_next()
|
||||
images = tf.reshape(images, [batch_size, image_size, image_size, depth])
|
||||
labels = tf.reshape(labels, [batch_size, 1])
|
||||
indices = tf.reshape(tf.range(0, batch_size, 1), [batch_size, 1])
|
||||
labels = tf.sparse_to_dense(
|
||||
tf.concat([indices, labels], 1),
|
||||
[batch_size, num_classes], 1.0, 0.0)
|
||||
|
||||
assert len(images.get_shape()) == 4
|
||||
assert images.get_shape()[0] == batch_size
|
||||
assert images.get_shape()[-1] == 3
|
||||
assert len(labels.get_shape()) == 2
|
||||
assert labels.get_shape()[0] == batch_size
|
||||
assert labels.get_shape()[1] == num_classes
|
||||
if not train:
|
||||
tf.summary.image("images", images)
|
||||
return images, labels
|
||||
@@ -0,0 +1,257 @@
|
||||
"""ResNet training script, with some code from
|
||||
https://github.com/tensorflow/models/tree/master/resnet.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import numpy as np
|
||||
import ray
|
||||
import tensorflow as tf
|
||||
|
||||
import cifar_input
|
||||
import resnet_model
|
||||
|
||||
# Tensorflow must be at least version 1.2.0 for the example to work.
|
||||
tf_major = int(tf.__version__.split(".")[0])
|
||||
tf_minor = int(tf.__version__.split(".")[1])
|
||||
if (tf_major < 1) or (tf_major == 1 and tf_minor < 2):
|
||||
raise Exception("Your Tensorflow version is less than 1.2.0. Please "
|
||||
"update Tensorflow to the latest version.")
|
||||
|
||||
parser = argparse.ArgumentParser(description="Run the ResNet example.")
|
||||
parser.add_argument(
|
||||
"--dataset",
|
||||
default="cifar10",
|
||||
type=str,
|
||||
help="Dataset to use: cifar10 or cifar100.")
|
||||
parser.add_argument(
|
||||
"--train_data_path",
|
||||
default="cifar-10-batches-bin/data_batch*",
|
||||
type=str,
|
||||
help="Data path for the training data.")
|
||||
parser.add_argument(
|
||||
"--eval_data_path",
|
||||
default="cifar-10-batches-bin/test_batch.bin",
|
||||
type=str,
|
||||
help="Data path for the testing data.")
|
||||
parser.add_argument(
|
||||
"--eval_dir",
|
||||
default="/tmp/resnet-model/eval",
|
||||
type=str,
|
||||
help="Data path for the tensorboard logs.")
|
||||
parser.add_argument(
|
||||
"--eval_batch_count",
|
||||
default=50,
|
||||
type=int,
|
||||
help="Number of batches to evaluate over.")
|
||||
parser.add_argument(
|
||||
"--num_gpus",
|
||||
default=0,
|
||||
type=int,
|
||||
help="Number of GPUs to use for training.")
|
||||
parser.add_argument(
|
||||
"--redis-address",
|
||||
default=None,
|
||||
type=str,
|
||||
help="The Redis address of the cluster.")
|
||||
|
||||
FLAGS = parser.parse_args()
|
||||
|
||||
# Determines if the actors require a gpu or not.
|
||||
use_gpu = 1 if int(FLAGS.num_gpus) > 0 else 0
|
||||
|
||||
|
||||
@ray.remote
|
||||
def get_data(path, size, dataset):
|
||||
# Retrieves all preprocessed images and labels using a tensorflow queue.
|
||||
# This only uses the cpu.
|
||||
os.environ["CUDA_VISIBLE_DEVICES"] = ""
|
||||
with tf.device("/cpu:0"):
|
||||
dataset = cifar_input.build_data(path, size, dataset)
|
||||
sess = tf.Session()
|
||||
images, labels = sess.run(dataset)
|
||||
sess.close()
|
||||
return images, labels
|
||||
|
||||
|
||||
@ray.remote(num_gpus=use_gpu)
|
||||
class ResNetTrainActor(object):
|
||||
def __init__(self, data, dataset, num_gpus):
|
||||
if num_gpus > 0:
|
||||
os.environ["CUDA_VISIBLE_DEVICES"] = ",".join(
|
||||
[str(i) for i in ray.get_gpu_ids()])
|
||||
hps = resnet_model.HParams(
|
||||
batch_size=128,
|
||||
num_classes=100 if dataset == "cifar100" else 10,
|
||||
min_lrn_rate=0.0001,
|
||||
lrn_rate=0.1,
|
||||
num_residual_units=5,
|
||||
use_bottleneck=False,
|
||||
weight_decay_rate=0.0002,
|
||||
relu_leakiness=0.1,
|
||||
optimizer="mom",
|
||||
num_gpus=num_gpus)
|
||||
|
||||
# We seed each actor differently so that each actor operates on a
|
||||
# different subset of data.
|
||||
if num_gpus > 0:
|
||||
tf.set_random_seed(ray.get_gpu_ids()[0] + 1)
|
||||
else:
|
||||
# Only a single actor in this case.
|
||||
tf.set_random_seed(1)
|
||||
|
||||
with tf.device("/gpu:0" if num_gpus > 0 else "/cpu:0"):
|
||||
# Build the model.
|
||||
images, labels = cifar_input.build_input(data, hps.batch_size,
|
||||
dataset, True)
|
||||
self.model = resnet_model.ResNet(hps, images, labels, "train")
|
||||
self.model.build_graph()
|
||||
config = tf.ConfigProto(allow_soft_placement=True)
|
||||
config.gpu_options.allow_growth = True
|
||||
sess = tf.Session(config=config)
|
||||
self.model.variables.set_session(sess)
|
||||
init = tf.global_variables_initializer()
|
||||
sess.run(init)
|
||||
self.steps = 10
|
||||
|
||||
def compute_steps(self, weights):
|
||||
# This method sets the weights in the network, trains the network
|
||||
# self.steps times, and returns the new weights.
|
||||
self.model.variables.set_weights(weights)
|
||||
for i in range(self.steps):
|
||||
self.model.variables.sess.run(self.model.train_op)
|
||||
return self.model.variables.get_weights()
|
||||
|
||||
def get_weights(self):
|
||||
# Note that the driver cannot directly access fields of the class,
|
||||
# so helper methods must be created.
|
||||
return self.model.variables.get_weights()
|
||||
|
||||
|
||||
@ray.remote
|
||||
class ResNetTestActor(object):
|
||||
def __init__(self, data, dataset, eval_batch_count, eval_dir):
|
||||
os.environ["CUDA_VISIBLE_DEVICES"] = ""
|
||||
hps = resnet_model.HParams(
|
||||
batch_size=100,
|
||||
num_classes=100 if dataset == "cifar100" else 10,
|
||||
min_lrn_rate=0.0001,
|
||||
lrn_rate=0.1,
|
||||
num_residual_units=5,
|
||||
use_bottleneck=False,
|
||||
weight_decay_rate=0.0002,
|
||||
relu_leakiness=0.1,
|
||||
optimizer="mom",
|
||||
num_gpus=0)
|
||||
with tf.device("/cpu:0"):
|
||||
# Builds the testing network.
|
||||
images, labels = cifar_input.build_input(data, hps.batch_size,
|
||||
dataset, False)
|
||||
self.model = resnet_model.ResNet(hps, images, labels, "eval")
|
||||
self.model.build_graph()
|
||||
config = tf.ConfigProto(allow_soft_placement=True)
|
||||
config.gpu_options.allow_growth = True
|
||||
sess = tf.Session(config=config)
|
||||
self.model.variables.set_session(sess)
|
||||
init = tf.global_variables_initializer()
|
||||
sess.run(init)
|
||||
|
||||
# Initializing parameters for tensorboard.
|
||||
self.best_precision = 0.0
|
||||
self.eval_batch_count = eval_batch_count
|
||||
self.summary_writer = tf.summary.FileWriter(eval_dir, sess.graph)
|
||||
# The IP address where tensorboard logs will be on.
|
||||
self.ip_addr = ray.services.get_node_ip_address()
|
||||
|
||||
def accuracy(self, weights, train_step):
|
||||
# Sets the weights, computes the accuracy and other metrics
|
||||
# over eval_batches, and outputs to tensorboard.
|
||||
self.model.variables.set_weights(weights)
|
||||
total_prediction, correct_prediction = 0, 0
|
||||
model = self.model
|
||||
sess = self.model.variables.sess
|
||||
for _ in range(self.eval_batch_count):
|
||||
summaries, loss, predictions, truth = sess.run(
|
||||
[model.summaries, model.cost, model.predictions, model.labels])
|
||||
|
||||
truth = np.argmax(truth, axis=1)
|
||||
predictions = np.argmax(predictions, axis=1)
|
||||
correct_prediction += np.sum(truth == predictions)
|
||||
total_prediction += predictions.shape[0]
|
||||
|
||||
precision = 1.0 * correct_prediction / total_prediction
|
||||
self.best_precision = max(precision, self.best_precision)
|
||||
precision_summ = tf.Summary()
|
||||
precision_summ.value.add(tag="Precision", simple_value=precision)
|
||||
self.summary_writer.add_summary(precision_summ, train_step)
|
||||
best_precision_summ = tf.Summary()
|
||||
best_precision_summ.value.add(
|
||||
tag="Best Precision", simple_value=self.best_precision)
|
||||
self.summary_writer.add_summary(best_precision_summ, train_step)
|
||||
self.summary_writer.add_summary(summaries, train_step)
|
||||
tf.logging.info("loss: %.3f, precision: %.3f, best precision: %.3f" %
|
||||
(loss, precision, self.best_precision))
|
||||
self.summary_writer.flush()
|
||||
return precision
|
||||
|
||||
def get_ip_addr(self):
|
||||
# As above, a helper method must be created to access the field from
|
||||
# the driver.
|
||||
return self.ip_addr
|
||||
|
||||
|
||||
def train():
|
||||
num_gpus = FLAGS.num_gpus
|
||||
if FLAGS.redis_address is None:
|
||||
ray.init(num_gpus=num_gpus)
|
||||
else:
|
||||
ray.init(redis_address=FLAGS.redis_address)
|
||||
train_data = get_data.remote(FLAGS.train_data_path, 50000, FLAGS.dataset)
|
||||
test_data = get_data.remote(FLAGS.eval_data_path, 10000, FLAGS.dataset)
|
||||
# Creates an actor for each gpu, or one if only using the cpu. Each actor
|
||||
# has access to the dataset.
|
||||
if FLAGS.num_gpus > 0:
|
||||
train_actors = [
|
||||
ResNetTrainActor.remote(train_data, FLAGS.dataset, num_gpus)
|
||||
for _ in range(num_gpus)
|
||||
]
|
||||
else:
|
||||
train_actors = [ResNetTrainActor.remote(train_data, FLAGS.dataset, 0)]
|
||||
test_actor = ResNetTestActor.remote(test_data, FLAGS.dataset,
|
||||
FLAGS.eval_batch_count, FLAGS.eval_dir)
|
||||
print("The log files for tensorboard are stored at ip {}.".format(
|
||||
ray.get(test_actor.get_ip_addr.remote())))
|
||||
step = 0
|
||||
weight_id = train_actors[0].get_weights.remote()
|
||||
acc_id = test_actor.accuracy.remote(weight_id, step)
|
||||
# Correction for dividing the weights by the number of gpus.
|
||||
if num_gpus == 0:
|
||||
num_gpus = 1
|
||||
print("Starting training loop. Use Ctrl-C to exit.")
|
||||
try:
|
||||
while True:
|
||||
all_weights = ray.get([
|
||||
actor.compute_steps.remote(weight_id) for actor in train_actors
|
||||
])
|
||||
mean_weights = {
|
||||
k: (sum(weights[k] for weights in all_weights) / num_gpus)
|
||||
for k in all_weights[0]
|
||||
}
|
||||
weight_id = ray.put(mean_weights)
|
||||
step += 10
|
||||
if step % 200 == 0:
|
||||
# Retrieves the previously computed accuracy and launches a new
|
||||
# testing task with the current weights every 200 steps.
|
||||
acc = ray.get(acc_id)
|
||||
acc_id = test_actor.accuracy.remote(weight_id, step)
|
||||
print("Step {}: {:.6f}".format(step - 200, acc))
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
train()
|
||||
@@ -0,0 +1,317 @@
|
||||
"""ResNet model with most of the code taken from
|
||||
https://github.com/tensorflow/models/tree/master/resnet.
|
||||
|
||||
Related papers:
|
||||
https://arxiv.org/pdf/1603.05027v2.pdf
|
||||
https://arxiv.org/pdf/1512.03385v1.pdf
|
||||
https://arxiv.org/pdf/1605.07146v1.pdf
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
from collections import namedtuple
|
||||
import numpy as np
|
||||
|
||||
import tensorflow as tf
|
||||
from tensorflow.python.training import moving_averages
|
||||
|
||||
import ray
|
||||
import ray.experimental.tf_utils
|
||||
|
||||
HParams = namedtuple(
|
||||
"HParams", "batch_size, num_classes, min_lrn_rate, lrn_rate, "
|
||||
"num_residual_units, use_bottleneck, weight_decay_rate, "
|
||||
"relu_leakiness, optimizer, num_gpus")
|
||||
|
||||
|
||||
class ResNet(object):
|
||||
"""ResNet model."""
|
||||
|
||||
def __init__(self, hps, images, labels, mode):
|
||||
"""ResNet constructor.
|
||||
|
||||
Args:
|
||||
hps: Hyperparameters.
|
||||
images: Batches of images of size [batch_size, image_size,
|
||||
image_size, 3].
|
||||
labels: Batches of labels of size [batch_size, num_classes].
|
||||
mode: One of 'train' and 'eval'.
|
||||
"""
|
||||
self.hps = hps
|
||||
self._images = images
|
||||
self.labels = labels
|
||||
self.mode = mode
|
||||
|
||||
self._extra_train_ops = []
|
||||
|
||||
def build_graph(self):
|
||||
"""Build a whole graph for the model."""
|
||||
self.global_step = tf.Variable(0, trainable=False)
|
||||
self._build_model()
|
||||
if self.mode == "train":
|
||||
self._build_train_op()
|
||||
else:
|
||||
# Additional initialization for the test network.
|
||||
self.variables = ray.experimental.tf_utils.TensorFlowVariables(
|
||||
self.cost)
|
||||
self.summaries = tf.summary.merge_all()
|
||||
|
||||
def _stride_arr(self, stride):
|
||||
"""Map a stride scalar to the stride array for tf.nn.conv2d."""
|
||||
return [1, stride, stride, 1]
|
||||
|
||||
def _build_model(self):
|
||||
"""Build the core model within the graph."""
|
||||
|
||||
with tf.variable_scope("init"):
|
||||
x = self._conv("init_conv", self._images, 3, 3, 16,
|
||||
self._stride_arr(1))
|
||||
|
||||
strides = [1, 2, 2]
|
||||
activate_before_residual = [True, False, False]
|
||||
if self.hps.use_bottleneck:
|
||||
res_func = self._bottleneck_residual
|
||||
filters = [16, 64, 128, 256]
|
||||
else:
|
||||
res_func = self._residual
|
||||
filters = [16, 16, 32, 64]
|
||||
|
||||
with tf.variable_scope("unit_1_0"):
|
||||
x = res_func(x, filters[0], filters[1], self._stride_arr(
|
||||
strides[0]), activate_before_residual[0])
|
||||
for i in range(1, self.hps.num_residual_units):
|
||||
with tf.variable_scope("unit_1_%d" % i):
|
||||
x = res_func(x, filters[1], filters[1], self._stride_arr(1),
|
||||
False)
|
||||
|
||||
with tf.variable_scope("unit_2_0"):
|
||||
x = res_func(x, filters[1], filters[2], self._stride_arr(
|
||||
strides[1]), activate_before_residual[1])
|
||||
for i in range(1, self.hps.num_residual_units):
|
||||
with tf.variable_scope("unit_2_%d" % i):
|
||||
x = res_func(x, filters[2], filters[2], self._stride_arr(1),
|
||||
False)
|
||||
|
||||
with tf.variable_scope("unit_3_0"):
|
||||
x = res_func(x, filters[2], filters[3], self._stride_arr(
|
||||
strides[2]), activate_before_residual[2])
|
||||
for i in range(1, self.hps.num_residual_units):
|
||||
with tf.variable_scope("unit_3_%d" % i):
|
||||
x = res_func(x, filters[3], filters[3], self._stride_arr(1),
|
||||
False)
|
||||
with tf.variable_scope("unit_last"):
|
||||
x = self._batch_norm("final_bn", x)
|
||||
x = self._relu(x, self.hps.relu_leakiness)
|
||||
x = self._global_avg_pool(x)
|
||||
|
||||
with tf.variable_scope("logit"):
|
||||
logits = self._fully_connected(x, self.hps.num_classes)
|
||||
self.predictions = tf.nn.softmax(logits)
|
||||
|
||||
with tf.variable_scope("costs"):
|
||||
xent = tf.nn.softmax_cross_entropy_with_logits(
|
||||
logits=logits, labels=self.labels)
|
||||
self.cost = tf.reduce_mean(xent, name="xent")
|
||||
self.cost += self._decay()
|
||||
|
||||
if self.mode == "eval":
|
||||
tf.summary.scalar("cost", self.cost)
|
||||
|
||||
def _build_train_op(self):
|
||||
"""Build training specific ops for the graph."""
|
||||
num_gpus = self.hps.num_gpus if self.hps.num_gpus != 0 else 1
|
||||
# The learning rate schedule is dependent on the number of gpus.
|
||||
boundaries = [int(20000 * i / np.sqrt(num_gpus)) for i in range(2, 5)]
|
||||
values = [0.1, 0.01, 0.001, 0.0001]
|
||||
self.lrn_rate = tf.train.piecewise_constant(self.global_step,
|
||||
boundaries, values)
|
||||
tf.summary.scalar("learning rate", self.lrn_rate)
|
||||
|
||||
if self.hps.optimizer == "sgd":
|
||||
optimizer = tf.train.GradientDescentOptimizer(self.lrn_rate)
|
||||
elif self.hps.optimizer == "mom":
|
||||
optimizer = tf.train.MomentumOptimizer(self.lrn_rate, 0.9)
|
||||
|
||||
apply_op = optimizer.minimize(self.cost, global_step=self.global_step)
|
||||
train_ops = [apply_op] + self._extra_train_ops
|
||||
self.train_op = tf.group(*train_ops)
|
||||
self.variables = ray.experimental.tf_utils.TensorFlowVariables(
|
||||
self.train_op)
|
||||
|
||||
def _batch_norm(self, name, x):
|
||||
"""Batch normalization."""
|
||||
with tf.variable_scope(name):
|
||||
params_shape = [x.get_shape()[-1]]
|
||||
|
||||
beta = tf.get_variable(
|
||||
"beta",
|
||||
params_shape,
|
||||
tf.float32,
|
||||
initializer=tf.constant_initializer(0.0, tf.float32))
|
||||
gamma = tf.get_variable(
|
||||
"gamma",
|
||||
params_shape,
|
||||
tf.float32,
|
||||
initializer=tf.constant_initializer(1.0, tf.float32))
|
||||
|
||||
if self.mode == "train":
|
||||
mean, variance = tf.nn.moments(x, [0, 1, 2], name="moments")
|
||||
|
||||
moving_mean = tf.get_variable(
|
||||
"moving_mean",
|
||||
params_shape,
|
||||
tf.float32,
|
||||
initializer=tf.constant_initializer(0.0, tf.float32),
|
||||
trainable=False)
|
||||
moving_variance = tf.get_variable(
|
||||
"moving_variance",
|
||||
params_shape,
|
||||
tf.float32,
|
||||
initializer=tf.constant_initializer(1.0, tf.float32),
|
||||
trainable=False)
|
||||
|
||||
self._extra_train_ops.append(
|
||||
moving_averages.assign_moving_average(
|
||||
moving_mean, mean, 0.9))
|
||||
self._extra_train_ops.append(
|
||||
moving_averages.assign_moving_average(
|
||||
moving_variance, variance, 0.9))
|
||||
else:
|
||||
mean = tf.get_variable(
|
||||
"moving_mean",
|
||||
params_shape,
|
||||
tf.float32,
|
||||
initializer=tf.constant_initializer(0.0, tf.float32),
|
||||
trainable=False)
|
||||
variance = tf.get_variable(
|
||||
"moving_variance",
|
||||
params_shape,
|
||||
tf.float32,
|
||||
initializer=tf.constant_initializer(1.0, tf.float32),
|
||||
trainable=False)
|
||||
tf.summary.histogram(mean.op.name, mean)
|
||||
tf.summary.histogram(variance.op.name, variance)
|
||||
# elipson used to be 1e-5. Maybe 0.001 solves NaN problem in deeper
|
||||
# net.
|
||||
y = tf.nn.batch_normalization(x, mean, variance, beta, gamma,
|
||||
0.001)
|
||||
y.set_shape(x.get_shape())
|
||||
return y
|
||||
|
||||
def _residual(self,
|
||||
x,
|
||||
in_filter,
|
||||
out_filter,
|
||||
stride,
|
||||
activate_before_residual=False):
|
||||
"""Residual unit with 2 sub layers."""
|
||||
if activate_before_residual:
|
||||
with tf.variable_scope("shared_activation"):
|
||||
x = self._batch_norm("init_bn", x)
|
||||
x = self._relu(x, self.hps.relu_leakiness)
|
||||
orig_x = x
|
||||
else:
|
||||
with tf.variable_scope("residual_only_activation"):
|
||||
orig_x = x
|
||||
x = self._batch_norm("init_bn", x)
|
||||
x = self._relu(x, self.hps.relu_leakiness)
|
||||
|
||||
with tf.variable_scope("sub1"):
|
||||
x = self._conv("conv1", x, 3, in_filter, out_filter, stride)
|
||||
|
||||
with tf.variable_scope("sub2"):
|
||||
x = self._batch_norm("bn2", x)
|
||||
x = self._relu(x, self.hps.relu_leakiness)
|
||||
x = self._conv("conv2", x, 3, out_filter, out_filter, [1, 1, 1, 1])
|
||||
|
||||
with tf.variable_scope("sub_add"):
|
||||
if in_filter != out_filter:
|
||||
orig_x = tf.nn.avg_pool(orig_x, stride, stride, "VALID")
|
||||
orig_x = tf.pad(
|
||||
orig_x,
|
||||
[[0, 0], [0, 0], [0, 0], [(out_filter - in_filter) // 2,
|
||||
(out_filter - in_filter) // 2]])
|
||||
x += orig_x
|
||||
|
||||
return x
|
||||
|
||||
def _bottleneck_residual(self,
|
||||
x,
|
||||
in_filter,
|
||||
out_filter,
|
||||
stride,
|
||||
activate_before_residual=False):
|
||||
"""Bottleneck residual unit with 3 sub layers."""
|
||||
if activate_before_residual:
|
||||
with tf.variable_scope("common_bn_relu"):
|
||||
x = self._batch_norm("init_bn", x)
|
||||
x = self._relu(x, self.hps.relu_leakiness)
|
||||
orig_x = x
|
||||
else:
|
||||
with tf.variable_scope("residual_bn_relu"):
|
||||
orig_x = x
|
||||
x = self._batch_norm("init_bn", x)
|
||||
x = self._relu(x, self.hps.relu_leakiness)
|
||||
|
||||
with tf.variable_scope("sub1"):
|
||||
x = self._conv("conv1", x, 1, in_filter, out_filter / 4, stride)
|
||||
|
||||
with tf.variable_scope("sub2"):
|
||||
x = self._batch_norm("bn2", x)
|
||||
x = self._relu(x, self.hps.relu_leakiness)
|
||||
x = self._conv("conv2", x, 3, out_filter / 4, out_filter / 4,
|
||||
[1, 1, 1, 1])
|
||||
|
||||
with tf.variable_scope("sub3"):
|
||||
x = self._batch_norm("bn3", x)
|
||||
x = self._relu(x, self.hps.relu_leakiness)
|
||||
x = self._conv("conv3", x, 1, out_filter / 4, out_filter,
|
||||
[1, 1, 1, 1])
|
||||
|
||||
with tf.variable_scope("sub_add"):
|
||||
if in_filter != out_filter:
|
||||
orig_x = self._conv("project", orig_x, 1, in_filter,
|
||||
out_filter, stride)
|
||||
x += orig_x
|
||||
|
||||
return x
|
||||
|
||||
def _decay(self):
|
||||
"""L2 weight decay loss."""
|
||||
costs = []
|
||||
for var in tf.trainable_variables():
|
||||
if var.op.name.find(r"DW") > 0:
|
||||
costs.append(tf.nn.l2_loss(var))
|
||||
|
||||
return tf.multiply(self.hps.weight_decay_rate, tf.add_n(costs))
|
||||
|
||||
def _conv(self, name, x, filter_size, in_filters, out_filters, strides):
|
||||
"""Convolution."""
|
||||
with tf.variable_scope(name):
|
||||
n = filter_size * filter_size * out_filters
|
||||
kernel = tf.get_variable(
|
||||
"DW", [filter_size, filter_size, in_filters, out_filters],
|
||||
tf.float32,
|
||||
initializer=tf.random_normal_initializer(
|
||||
stddev=np.sqrt(2.0 / n)))
|
||||
return tf.nn.conv2d(x, kernel, strides, padding="SAME")
|
||||
|
||||
def _relu(self, x, leakiness=0.0):
|
||||
"""Relu, with optional leaky support."""
|
||||
return tf.where(tf.less(x, 0.0), leakiness * x, x, name="leaky_relu")
|
||||
|
||||
def _fully_connected(self, x, out_dim):
|
||||
"""FullyConnected layer for final output."""
|
||||
x = tf.reshape(x, [self.hps.batch_size, -1])
|
||||
w = tf.get_variable(
|
||||
"DW", [x.get_shape()[1], out_dim],
|
||||
initializer=tf.uniform_unit_scaling_initializer(factor=1.0))
|
||||
b = tf.get_variable(
|
||||
"biases", [out_dim], initializer=tf.constant_initializer())
|
||||
return tf.nn.xw_plus_b(x, w, b)
|
||||
|
||||
def _global_avg_pool(self, x):
|
||||
assert x.get_shape().ndims == 4
|
||||
return tf.reduce_mean(x, [1, 2])
|
||||
@@ -0,0 +1,213 @@
|
||||
# This code is copied and adapted from Andrej Karpathy's code for learning to
|
||||
# play Pong https://gist.github.com/karpathy/a4166c7fe253700972fcbc77e4ea32c5.
|
||||
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
import argparse
|
||||
import numpy as np
|
||||
import os
|
||||
import ray
|
||||
import time
|
||||
|
||||
import gym
|
||||
|
||||
# Define some hyperparameters.
|
||||
|
||||
# The number of hidden layer neurons.
|
||||
H = 200
|
||||
learning_rate = 1e-4
|
||||
# Discount factor for reward.
|
||||
gamma = 0.99
|
||||
# The decay factor for RMSProp leaky sum of grad^2.
|
||||
decay_rate = 0.99
|
||||
|
||||
# The input dimensionality: 80x80 grid.
|
||||
D = 80 * 80
|
||||
|
||||
|
||||
def sigmoid(x):
|
||||
# Sigmoid "squashing" function to interval [0, 1].
|
||||
return 1.0 / (1.0 + np.exp(-x))
|
||||
|
||||
|
||||
def preprocess(img):
|
||||
"""Preprocess 210x160x3 uint8 frame into 6400 (80x80) 1D float vector."""
|
||||
# Crop the image.
|
||||
img = img[35:195]
|
||||
# Downsample by factor of 2.
|
||||
img = img[::2, ::2, 0]
|
||||
# Erase background (background type 1).
|
||||
img[img == 144] = 0
|
||||
# Erase background (background type 2).
|
||||
img[img == 109] = 0
|
||||
# Set everything else (paddles, ball) to 1.
|
||||
img[img != 0] = 1
|
||||
return img.astype(np.float).ravel()
|
||||
|
||||
|
||||
def discount_rewards(r):
|
||||
"""take 1D float array of rewards and compute discounted reward"""
|
||||
discounted_r = np.zeros_like(r)
|
||||
running_add = 0
|
||||
for t in reversed(range(0, r.size)):
|
||||
# Reset the sum, since this was a game boundary (pong specific!).
|
||||
if r[t] != 0:
|
||||
running_add = 0
|
||||
running_add = running_add * gamma + r[t]
|
||||
discounted_r[t] = running_add
|
||||
return discounted_r
|
||||
|
||||
|
||||
def policy_forward(x, model):
|
||||
h = np.dot(model["W1"], x)
|
||||
h[h < 0] = 0 # ReLU nonlinearity.
|
||||
logp = np.dot(model["W2"], h)
|
||||
p = sigmoid(logp)
|
||||
# Return probability of taking action 2, and hidden state.
|
||||
return p, h
|
||||
|
||||
|
||||
def policy_backward(eph, epx, epdlogp, model):
|
||||
"""backward pass. (eph is array of intermediate hidden states)"""
|
||||
dW2 = np.dot(eph.T, epdlogp).ravel()
|
||||
dh = np.outer(epdlogp, model["W2"])
|
||||
# Backprop relu.
|
||||
dh[eph <= 0] = 0
|
||||
dW1 = np.dot(dh.T, epx)
|
||||
return {"W1": dW1, "W2": dW2}
|
||||
|
||||
|
||||
@ray.remote
|
||||
class PongEnv(object):
|
||||
def __init__(self):
|
||||
# Tell numpy to only use one core. If we don't do this, each actor may
|
||||
# try to use all of the cores and the resulting contention may result
|
||||
# in no speedup over the serial version. Note that if numpy is using
|
||||
# OpenBLAS, then you need to set OPENBLAS_NUM_THREADS=1, and you
|
||||
# probably need to do it from the command line (so it happens before
|
||||
# numpy is imported).
|
||||
os.environ["MKL_NUM_THREADS"] = "1"
|
||||
self.env = gym.make("Pong-v0")
|
||||
|
||||
def compute_gradient(self, model):
|
||||
# Reset the game.
|
||||
observation = self.env.reset()
|
||||
# Note that prev_x is used in computing the difference frame.
|
||||
prev_x = None
|
||||
xs, hs, dlogps, drs = [], [], [], []
|
||||
reward_sum = 0
|
||||
done = False
|
||||
while not done:
|
||||
cur_x = preprocess(observation)
|
||||
x = cur_x - prev_x if prev_x is not None else np.zeros(D)
|
||||
prev_x = cur_x
|
||||
|
||||
aprob, h = policy_forward(x, model)
|
||||
# Sample an action.
|
||||
action = 2 if np.random.uniform() < aprob else 3
|
||||
|
||||
# The observation.
|
||||
xs.append(x)
|
||||
# The hidden state.
|
||||
hs.append(h)
|
||||
y = 1 if action == 2 else 0 # A "fake label".
|
||||
# The gradient that encourages the action that was taken to be
|
||||
# taken (see http://cs231n.github.io/neural-networks-2/#losses if
|
||||
# confused).
|
||||
dlogps.append(y - aprob)
|
||||
|
||||
observation, reward, done, info = self.env.step(action)
|
||||
reward_sum += reward
|
||||
|
||||
# Record reward (has to be done after we call step() to get reward
|
||||
# for previous action).
|
||||
drs.append(reward)
|
||||
|
||||
epx = np.vstack(xs)
|
||||
eph = np.vstack(hs)
|
||||
epdlogp = np.vstack(dlogps)
|
||||
epr = np.vstack(drs)
|
||||
# Reset the array memory.
|
||||
xs, hs, dlogps, drs = [], [], [], []
|
||||
|
||||
# Compute the discounted reward backward through time.
|
||||
discounted_epr = discount_rewards(epr)
|
||||
# Standardize the rewards to be unit normal (helps control the gradient
|
||||
# estimator variance).
|
||||
discounted_epr -= np.mean(discounted_epr)
|
||||
discounted_epr /= np.std(discounted_epr)
|
||||
# Modulate the gradient with advantage (the policy gradient magic
|
||||
# happens right here).
|
||||
epdlogp *= discounted_epr
|
||||
return policy_backward(eph, epx, epdlogp, model), reward_sum
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description="Train an RL agent on Pong.")
|
||||
parser.add_argument(
|
||||
"--batch-size",
|
||||
default=10,
|
||||
type=int,
|
||||
help="The number of rollouts to do per batch.")
|
||||
parser.add_argument(
|
||||
"--redis-address",
|
||||
default=None,
|
||||
type=str,
|
||||
help="The Redis address of the cluster.")
|
||||
parser.add_argument(
|
||||
"--iterations",
|
||||
default=-1,
|
||||
type=int,
|
||||
help="The number of model updates to perform. By "
|
||||
"default, training will not terminate.")
|
||||
args = parser.parse_args()
|
||||
batch_size = args.batch_size
|
||||
|
||||
ray.init(redis_address=args.redis_address)
|
||||
|
||||
# Run the reinforcement learning.
|
||||
|
||||
running_reward = None
|
||||
batch_num = 1
|
||||
model = {}
|
||||
# "Xavier" initialization.
|
||||
model["W1"] = np.random.randn(H, D) / np.sqrt(D)
|
||||
model["W2"] = np.random.randn(H) / np.sqrt(H)
|
||||
# Update buffers that add up gradients over a batch.
|
||||
grad_buffer = {k: np.zeros_like(v) for k, v in model.items()}
|
||||
# Update the rmsprop memory.
|
||||
rmsprop_cache = {k: np.zeros_like(v) for k, v in model.items()}
|
||||
actors = [PongEnv.remote() for _ in range(batch_size)]
|
||||
iteration = 0
|
||||
while iteration != args.iterations:
|
||||
iteration += 1
|
||||
model_id = ray.put(model)
|
||||
actions = []
|
||||
# Launch tasks to compute gradients from multiple rollouts in parallel.
|
||||
start_time = time.time()
|
||||
for i in range(batch_size):
|
||||
action_id = actors[i].compute_gradient.remote(model_id)
|
||||
actions.append(action_id)
|
||||
for i in range(batch_size):
|
||||
action_id, actions = ray.wait(actions)
|
||||
grad, reward_sum = ray.get(action_id[0])
|
||||
# Accumulate the gradient over batch.
|
||||
for k in model:
|
||||
grad_buffer[k] += grad[k]
|
||||
running_reward = (reward_sum if running_reward is None else
|
||||
running_reward * 0.99 + reward_sum * 0.01)
|
||||
end_time = time.time()
|
||||
print("Batch {} computed {} rollouts in {} seconds, "
|
||||
"running mean is {}".format(batch_num, batch_size,
|
||||
end_time - start_time,
|
||||
running_reward))
|
||||
for k, v in model.items():
|
||||
g = grad_buffer[k]
|
||||
rmsprop_cache[k] = (
|
||||
decay_rate * rmsprop_cache[k] + (1 - decay_rate) * g**2)
|
||||
model[k] += learning_rate * g / (np.sqrt(rmsprop_cache[k]) + 1e-5)
|
||||
# Reset the batch gradient buffer.
|
||||
grad_buffer[k] = np.zeros_like(v)
|
||||
batch_num += 1
|
||||
@@ -0,0 +1,8 @@
|
||||
New York City
|
||||
Berlin
|
||||
London
|
||||
Paris
|
||||
United States
|
||||
Germany
|
||||
France
|
||||
United Kingdom
|
||||
@@ -0,0 +1,104 @@
|
||||
import argparse
|
||||
from collections import Counter, defaultdict
|
||||
import heapq
|
||||
import numpy as np
|
||||
import os
|
||||
import ray
|
||||
import wikipedia
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--num-mappers",
|
||||
help="number of mapper actors used", default=3)
|
||||
parser.add_argument("--num-reducers",
|
||||
help="number of reducer actors used", default=4)
|
||||
|
||||
|
||||
@ray.remote
|
||||
class Mapper(object):
|
||||
def __init__(self, title_stream):
|
||||
self.title_stream = title_stream
|
||||
self.num_articles_processed = 0
|
||||
self.articles = []
|
||||
self.word_counts = []
|
||||
|
||||
def get_new_article(self):
|
||||
# Get the next wikipedia article.
|
||||
article = wikipedia.page(self.title_stream.next()).content
|
||||
# Count the words and store the result.
|
||||
self.word_counts.append(Counter(article.split(" ")))
|
||||
self.num_articles_processed += 1
|
||||
|
||||
def get_range(self, article_index, keys):
|
||||
# Process more articles if this Mapper hasn't processed enough yet.
|
||||
while self.num_articles_processed < article_index + 1:
|
||||
self.get_new_article()
|
||||
# Return the word counts from within a given character range.
|
||||
return [(k, v) for k, v in self.word_counts[article_index].items()
|
||||
if len(k) >= 1 and k[0] >= keys[0] and k[0] <= keys[1]]
|
||||
|
||||
|
||||
@ray.remote
|
||||
class Reducer(object):
|
||||
def __init__(self, keys, *mappers):
|
||||
self.mappers = mappers
|
||||
self.keys = keys
|
||||
|
||||
def next_reduce_result(self, article_index):
|
||||
word_count_sum = defaultdict(lambda: 0)
|
||||
# Get the word counts for this Reducer's keys from all of the Mappers
|
||||
# and aggregate the results.
|
||||
count_ids = [mapper.get_range.remote(article_index, self.keys)
|
||||
for mapper in self.mappers]
|
||||
# TODO(rkn): We should process these out of order using ray.wait.
|
||||
for count_id in count_ids:
|
||||
for k, v in ray.get(count_id):
|
||||
word_count_sum[k] += v
|
||||
return word_count_sum
|
||||
|
||||
|
||||
class Stream(object):
|
||||
def __init__(self, elements):
|
||||
self.elements = elements
|
||||
|
||||
def next(self):
|
||||
i = np.random.randint(0, len(self.elements))
|
||||
return self.elements[i]
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
args = parser.parse_args()
|
||||
|
||||
ray.init()
|
||||
|
||||
# Create one streaming source of articles per mapper.
|
||||
directory = os.path.dirname(os.path.realpath(__file__))
|
||||
streams = []
|
||||
for _ in range(args.num_mappers):
|
||||
with open(os.path.join(directory, "articles.txt")) as f:
|
||||
streams.append(Stream([line.strip() for line in f.readlines()]))
|
||||
|
||||
# Partition the keys among the reducers.
|
||||
chunks = np.array_split([chr(i) for i in range(ord("a"), ord("z") + 1)],
|
||||
args.num_reducers)
|
||||
keys = [[chunk[0], chunk[-1]] for chunk in chunks]
|
||||
|
||||
# Create a number of mappers.
|
||||
mappers = [Mapper.remote(stream) for stream in streams]
|
||||
|
||||
# Create a number of reduces, each responsible for a different range of
|
||||
# keys. This gives each Reducer actor a handle to each Mapper actor.
|
||||
reducers = [Reducer.remote(key, *mappers) for key in keys]
|
||||
|
||||
article_index = 0
|
||||
while True:
|
||||
print("article index = {}".format(article_index))
|
||||
wordcounts = {}
|
||||
counts = ray.get([reducer.next_reduce_result.remote(article_index)
|
||||
for reducer in reducers])
|
||||
for count in counts:
|
||||
wordcounts.update(count)
|
||||
most_frequent_words = heapq.nlargest(10, wordcounts,
|
||||
key=wordcounts.get)
|
||||
for word in most_frequent_words:
|
||||
print(" ", word, wordcounts[word])
|
||||
article_index += 1
|
||||
Reference in New Issue
Block a user