Skip to content

[WIP] Add part of the Keras API #5

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions build.sbt
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import sbtcrossproject.CrossPlugin.autoImport.{crossProject, CrossType}
import scala.sys.process._

organization in ThisBuild := "me.shadaj"

scalaVersion in ThisBuild := "2.12.8"
scalaVersion in ThisBuild := "2.12.10"

addCommandAlias(
"publishSignedAll",
Expand All @@ -27,13 +28,14 @@ lazy val scalaPyTensorFlowCross = crossProject(JVMPlatform, NativePlatform)
.in(file("."))
.settings(
name := "scalapy-tensorflow",
libraryDependencies += "me.shadaj" %%% "scalapy-core" % "0.3.0",
libraryDependencies += "me.shadaj" %%% "scalapy-numpy" % "0.1.0+3-046d1d67",
libraryDependencies += "me.shadaj" %%% "scalapy-core" % "0.3.0+17-2bfe86de",
// TODO: update to released scalapy-numpy
libraryDependencies += "me.shadaj" %%% "scalapy-numpy" % "0.1.0+5-ad550211+20200401-1343",
libraryDependencies += "org.scalatest" %%% "scalatest" % "3.1.0-SNAP8" % Test
).jvmSettings(
libraryDependencies += "org.scalacheck" %% "scalacheck" % "1.14.0" % Test,
fork in Test := true,
javaOptions in Test += s"-Djava.library.path=${sys.env.getOrElse("JEP_PATH", "/usr/local/lib/python3.7/site-packages/jep")}"
javaOptions in Test += s"-Djna.library.path=${"python3-config --prefix".!!.trim}/lib"
).nativeSettings(
scalaVersion := "2.11.12",
libraryDependencies += "com.github.lolgab" %%% "scalacheck" % "1.14.1" % Test,
Expand Down
2 changes: 1 addition & 1 deletion project/build.properties
Original file line number Diff line number Diff line change
@@ -1 +1 @@
sbt.version=1.2.8
sbt.version=1.3.3
21 changes: 21 additions & 0 deletions src/main/scala/me/shadaj/scalapy/tensorflow/keras/Keras.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package me.shadaj.scalapy.tensorflow.keras

import me.shadaj.scalapy.py
import me.shadaj.scalapy.tensorflow.keras.backend.Backend
import me.shadaj.scalapy.tensorflow.keras.datasets.Datasets
import me.shadaj.scalapy.tensorflow.keras.models.Models
import me.shadaj.scalapy.tensorflow.keras.optimizers.Optimizers
import me.shadaj.scalapy.tensorflow.keras.utils.Utils
import me.shadaj.scalapy.tensorflow.keras.losses.Losses
import me.shadaj.scalapy.tensorflow.keras.layers.Layers


@py.native trait Keras extends py.Object {
def models: Models = py.native
def datasets: Datasets = py.native
def backend: Backend = py.native
def utils: Utils = py.native
def optimizers: Optimizers = py.native
def losses: Losses = py.native
def layers: Layers = py.native
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package me.shadaj.scalapy.tensorflow.keras.backend

import me.shadaj.scalapy.py

@py.native trait Backend extends py.Object {
def image_data_format(): String = py.native
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package me.shadaj.scalapy.tensorflow.keras.datasets

import me.shadaj.scalapy.py

@py.native trait Datasets extends py.Object {
def mnist: Mnist = py.native
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package me.shadaj.scalapy.tensorflow.keras.datasets

import me.shadaj.scalapy.py
import me.shadaj.scalapy.numpy.NDArray

@py.native trait Mnist extends py.Object {
def load_data(path: String = "mnist.npz"): ((NDArray[Long], NDArray[Long]), (NDArray[Long], NDArray[Long])) = py.native
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package me.shadaj.scalapy.tensorflow.keras
package layers

import me.shadaj.scalapy.py

@py.native trait Conv2D extends Layer {
def filters: Int = py.native
def kernel_size: py.|[Int, (Int, Int)] = py.native
def strides: py.|[Int, (Int, Int)] = py.native
def padding: String = py.native
def data_format: py.NoneOr[String] = py.native
def dilation_rate: py.|[Int, (Int, Int)] = py.native
def activation: py.NoneOr[String] = py.native
def use_bias: Boolean = py.native
def kernel_initializer: String = py.native
def bias_initializer: String = py.native
def kernel_regularizer: py.NoneOr[String] = py.native
def bias_regularizer: py.NoneOr[String] = py.native
def activity_regularizer: py.NoneOr[String] = py.native
def kernel_constraint: py.NoneOr[String] = py.native
def bias_constraint: py.NoneOr[String] = py.native
def input_shape: py.NoneOr[(Int, Int, Int)] = py.native
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package me.shadaj.scalapy.tensorflow.keras
package layers

import me.shadaj.scalapy.py

@py.native trait Dense extends Layer {
def units: Int = py.native
def activation: py.NoneOr[String] = py.native
def use_bias: Boolean = py.native
def kernel_initializer: String = py.native
def bias_initializer: String = py.native
def kernel_regularizer: py.NoneOr[String] = py.native
def bias_regularizer: py.NoneOr[String] = py.native
def activity_regularizer: py.NoneOr[String] = py.native
def kernel_constraint: py.NoneOr[String] = py.native
def bias_constraint: py.NoneOr[String] = py.native
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package me.shadaj.scalapy.tensorflow.keras
package layers

import me.shadaj.scalapy.py
import me.shadaj.scalapy.tensorflow.Tensor

@py.native trait Dropout extends Layer {
def rate: Double = py.native
def noise_shape: py.NoneOr[Tensor] = py.native
def seed: py.NoneOr[Int] = py.native
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package me.shadaj.scalapy.tensorflow.keras.layers

import me.shadaj.scalapy.py

@py.native trait Flatten extends Layer {
def data_format: py.NoneOr[String] = py.native
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package me.shadaj.scalapy.tensorflow.keras.layers

import me.shadaj.scalapy.py

@py.native trait Layer extends py.Object
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package me.shadaj.scalapy.tensorflow.keras.layers

import me.shadaj.scalapy.py
import me.shadaj.scalapy.tensorflow.Tensor

@py.native trait Layers extends py.Object {
def Conv2D(filters: Int,
kernel_size: py.|[Int, (Int, Int)],
strides: py.|[Int, (Int, Int)] = (1, 1),
padding: String = "valid",
data_format: py.NoneOr[String] = py.None,
dilation_rate: py.|[Int, (Int, Int)] = (1, 1),
activation: py.NoneOr[String] = py.None,
use_bias: Boolean = true,
kernel_initializer: String = "glorot_uniform",
bias_initializer: String = "zeros",
kernel_regularizer: py.NoneOr[String] = py.None,
bias_regularizer: py.NoneOr[String] = py.None,
activity_regularizer: py.NoneOr[String] = py.None,
kernel_constraint: py.NoneOr[String] = py.None,
bias_constraint: py.NoneOr[String] = py.None,
input_shape: py.NoneOr[(Int, Int, Int)] = py.None
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

input_shape should be provided via **kwargs (https://www.tensorflow.org/api_docs/python/tf/keras/layers/Conv2D) but I didn't know how to do it with scalapy

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, this is something I need to work on supporting, probably with an annotation. In the meantime, you should be able to handle this by calling this.as[py.Dynamic].Conv2D(..., input_shape=input_shape, ...) since the dynamic API does support kwargs.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried the following in dynamic API:

val models2 = py.module("tensorflow").keras.models
val layers2 = py.module("tensorflow").keras.layers

val model = models2.Sequential() // kerasA.models.as[py.Dynamic].Sequential() // kerasA.models.Sequential()
model.add(layers2.Conv2D(filters = 32, kernel_size = (3, 3), activation = "relu", input_shape = (28, 28, 1)))

And it fails with:

[error] Exception in thread "main" me.shadaj.scalapy.py.PythonException: <class 'TypeError'> Dimension value must be integer or None or have an __index__ method, got 32.0
[error] 	at me.shadaj.scalapy.py.CPythonInterpreter$.$anonfun$throwErrorIfOccured$1(CPythonInterpreter.scala:110)
[error] 	at me.shadaj.scalapy.py.Platform$.Zone(Platform.scala:10)
[error] 	at me.shadaj.scalapy.py.CPythonInterpreter$.throwErrorIfOccured(CPythonInterpreter.scala:96)
[error] 	at me.shadaj.scalapy.py.CPythonInterpreter$.$anonfun$load$1(CPythonInterpreter.scala:119)
[error] 	at me.shadaj.scalapy.py.Platform$.Zone(Platform.scala:10)
[error] 	at me.shadaj.scalapy.py.CPythonInterpreter$.load(CPythonInterpreter.scala:116)
[error] 	at me.shadaj.scalapy.py.package$.eval(package.scala:84)
[error] 	at me.shadaj.scalapy.py.AnyDynamics.applyDynamicNamed(Dynamic.scala:17)
[error] 	at me.shadaj.scalapy.py.AnyDynamics.applyDynamicNamed$(Dynamic.scala:14)
[error] 	at me.shadaj.scalapy.py.package$$anon$11$$anon$12.applyDynamicNamed(package.scala:84)
[error] 	at me.shadaj.scalapy.py.AnyDynamics.applyDynamic(Dynamic.scala:11)
[error] 	at me.shadaj.scalapy.py.AnyDynamics.applyDynamic$(Dynamic.scala:8)
[error] 	at me.shadaj.scalapy.py.package$$anon$11$$anon$12.applyDynamic(package.scala:84)
[error] 	at me.shadaj.scalapy.tensorflow.MnistExample$.delayedEndpoint$me$shadaj$scalapy$tensorflow$MnistExample$1(MnistExample.scala:56)
[error] 	at me.shadaj.scalapy.tensorflow.MnistExample$delayedInit$body.apply(MnistExample.scala:10)
[error] 	at scala.Function0.apply$mcV$sp(Function0.scala:39)
[error] 	at scala.Function0.apply$mcV$sp$(Function0.scala:39)
[error] 	at scala.runtime.AbstractFunction0.apply$mcV$sp(AbstractFunction0.scala:17)
[error] 	at scala.App.$anonfun$main$1$adapted(App.scala:80)
[error] 	at scala.collection.immutable.List.foreach(List.scala:392)
[error] 	at scala.App.main(App.scala:80)
[error] 	at scala.App.main$(App.scala:78)
[error] 	at me.shadaj.scalapy.tensorflow.MnistExample$.main(MnistExample.scala:10)
[error] 	at me.shadaj.scalapy.tensorflow.MnistExample.main(MnistExample.scala)

It casts 32 to Double somewhere. I didn't have this issue in previous versions (with JEP) so perhaps there is a bug on master?

): Conv2D = py.nativeNamed

def Dropout(rate: Double,
noise_shape: py.NoneOr[Tensor] = py.None,
seed: py.NoneOr[Int] = py.None
): Dropout = py.nativeNamed

def MaxPooling2D(pool_size: (Int, Int),
strides: py.NoneOr[py.|[Int, (Int, Int)]] = py.None,
padding: String = "valid",
data_format: py.NoneOr[String] = py.None
): MaxPooling2D = py.nativeNamed

def Flatten(data_format: py.NoneOr[String] = py.None): Flatten = py.nativeNamed

def Dense(units: Int,
activation: py.NoneOr[String] = py.None,
use_bias: Boolean = true,
kernel_initializer: String = "glorot_uniform",
bias_initializer: String = "zeros",
kernel_regularizer: py.NoneOr[String] = py.None,
bias_regularizer: py.NoneOr[String] = py.None,
activity_regularizer: py.NoneOr[String] = py.None,
kernel_constraint: py.NoneOr[String] = py.None,
bias_constraint: py.NoneOr[String] = py.None
): Dense = py.nativeNamed
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package me.shadaj.scalapy.tensorflow.keras.layers

import me.shadaj.scalapy.tensorflow.Tensor
import me.shadaj.scalapy.py

@py.native trait MaxPooling2D extends Layer {
def pool_size: (Int, Int) = py.native
def strides: py.NoneOr[py.|[Int, (Int, Int)]] = py.native
def padding: String = py.native
def data_format: py.NoneOr[String] = py.native
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package me.shadaj.scalapy.tensorflow.keras

import scala.util.control.NonFatal
import me.shadaj.scalapy.py.{PyValue, Reader}
import me.shadaj.scalapy.py

package object layers {
implicit val tupleReader: Reader[py.|[Int, (Int, Int)]] = new Reader[py.|[Int, (Int, Int)]] {
override def read(v: PyValue): py.|[Int, (Int, Int)] = {
try {
v.getLong.toInt
} catch {
case NonFatal(_) =>
val tuple = v.getTuple
(tuple(0).getLong.toInt, tuple(1).getLong.toInt)
}
}
}

implicit def noneReader[T](implicit reader: Reader[T]): Reader[py.NoneOr[T]] = new Reader[py.NoneOr[T]] {
override def read(v: PyValue): py.NoneOr[T] = {
try {
reader.read(v)
} catch {
// TODO: read None
case NonFatal(_) => py.None
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package me.shadaj.scalapy.tensorflow.keras.losses

import me.shadaj.scalapy.py
import me.shadaj.scalapy.py.PyFunction

@py.native trait Losses extends py.Object {
def categorical_crossentropy: PyFunction = py.native
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package me.shadaj.scalapy.tensorflow.keras.models

import me.shadaj.scalapy.py

@py.native trait Models extends py.Object {
def Sequential(): Sequential = py.native
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package me.shadaj.scalapy.tensorflow.keras
package models

import me.shadaj.scalapy.py
import me.shadaj.scalapy.tensorflow.keras.layers.Layer
import me.shadaj.scalapy.numpy.NDArray
import me.shadaj.scalapy.py.PyFunction
import me.shadaj.scalapy.tensorflow.keras.optimizers.Optimizer

@py.native trait Sequential extends py.Object {
private val origDynamic = this.as[py.Dynamic]

def add(layer: Layer): Unit = origDynamic.add(layer = layer).as[Unit]

def compile(optimizer: py.|[String, Optimizer] = "rmsprop",
loss: py.NoneOr[PyFunction] = py.None,
metrics: Seq[String] = Seq.empty,
loss_weights: Seq[(Double, Double)] = Seq.empty,
sample_weight_mode: py.NoneOr[String] = py.None,
weighted_metrics: Seq[String] = Seq.empty,
target_tensors: py.NoneOr[String] = py.None
) = origDynamic.compile(optimizer = optimizer, loss = loss, metrics = metrics, loss_weights = loss_weights, sample_weight_mode = sample_weight_mode, weighted_metrics = weighted_metrics, target_tensors = target_tensors).as[Unit]

def fit(x: NDArray[Long],
y: NDArray[Long],
batch_size: py.NoneOr[Int] = py.None,
epochs: Int = 1,
verbose: Int = 1,
validation_data: py.NoneOr[(NDArray[Long], NDArray[Long])] = py.None
): Unit = origDynamic.fit(x = x, y = y, batch_size = batch_size, epochs = epochs, verbose = verbose, validation_data = validation_data).as[Unit]

def evaluate(x: NDArray[Long],
y: NDArray[Long],
batch_size: py.NoneOr[Int] = py.None,
verbose: Int = 1
): Seq[Double] = origDynamic.evaluate(x = x, y = y, batch_size = batch_size, verbose = verbose).as[Seq[Double]]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package me.shadaj.scalapy.tensorflow.keras.optimizers

import me.shadaj.scalapy.py
import me.shadaj.scalapy.py.{PyValue, Reader}

@py.native trait Optimizers extends py.Object {
def Adadelta(): Adadelta = py.native
}

@py.native trait Optimizer extends py.Object

@py.native class Adadelta(val value: PyValue) extends Optimizer

object Adadelta {
implicit val reader: Reader[Adadelta] = new Reader[Adadelta] {
override def read(v: PyValue): Adadelta = new Adadelta(v)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package me.shadaj.scalapy.tensorflow.keras.utils

import me.shadaj.scalapy.py
import me.shadaj.scalapy.numpy.NDArray

@py.native trait Utils extends py.Object {
def to_categorical(y: NDArray[Long], num_classes: py.NoneOr[Long] = py.None, dtype: String = "float32"): NDArray[Long] = py.native
}
Loading