com.simiacryptus.mindseye.layers.aparapi.ConvolutionLayer Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of mindseye-aparapi Show documentation
Show all versions of mindseye-aparapi Show documentation
OpenCL Neural Network Components Implemented Using Aparapi
/*
* Copyright (c) 2019 by Andrew Charneski.
*
* The author licenses this file to you under the
* Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance
* with the License. You may obtain a copy
* of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.simiacryptus.mindseye.layers.aparapi;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.simiacryptus.mindseye.lang.*;
import com.simiacryptus.util.Util;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.function.DoubleSupplier;
import java.util.function.ToDoubleFunction;
import java.util.stream.IntStream;
/**
* This convolution key is often used as the reference implementation for other convolution implementation. It uses
* OpenCL via Aparapi to compile Java into GPU-accellerated kernels. Due to its simple implementation and limitations of
* Aparapi, it is not as fast as CudaSystem-powered layers.
*/
@SuppressWarnings("serial")
public class ConvolutionLayer extends LayerBase {
/**
* The Kernel.
*/
@Nullable
public final Tensor kernel;
@Nullable
private Integer paddingX = null;
@Nullable
private Integer paddingY = null;
/**
* Instantiates a new Convolution key.
*/
protected ConvolutionLayer() {
this(null, true);
}
/**
* Instantiates a new Convolution key.
*
* @param width the width
* @param height the height
* @param bands the bands
*/
public ConvolutionLayer(final int width, final int height, final int bands) {
this(width, height, bands, true);
}
/**
* Instantiates a new Convolution key.
*
* @param width the width
* @param height the height
* @param bands the bands
* @param simple the simple
*/
public ConvolutionLayer(final int width, final int height, final int bands, final boolean simple) {
this(new Tensor(width, height, bands), simple);
assert !simple || 0 == (width - 1) % 2 : "Simple kernels must have odd width";
assert !simple || 0 == (height - 1) % 2 : "Simple kernels must have odd height";
}
/**
* Instantiates a new Convolution key.
*
* @param width the width
* @param height the height
* @param inputBands the input bands
* @param outputBands the output bands
*/
public ConvolutionLayer(final int width, final int height, final int inputBands, final int outputBands) {
this(width, height, inputBands * outputBands);
}
/**
* Instantiates a new Convolution key.
*
* @param width the width
* @param height the height
* @param inputBands the input bands
* @param outputBands the output bands
* @param simple the simple
*/
public ConvolutionLayer(final int width, final int height, final int inputBands, final int outputBands, final boolean simple) {
this(width, height, inputBands * outputBands, simple);
}
/**
* Instantiates a new Convolution key.
*
* @param json the json
* @param resources the resources
*/
protected ConvolutionLayer(@Nonnull final JsonObject json, Map resources) {
super(json);
kernel = Tensor.fromJson(json.get("filter"), resources);
JsonElement paddingX = json.get("paddingX");
if (null != paddingX && paddingX.isJsonPrimitive()) this.setPaddingX((paddingX.getAsInt()));
JsonElement paddingY = json.get("paddingY");
if (null != paddingY && paddingY.isJsonPrimitive()) this.setPaddingY((paddingY.getAsInt()));
}
/**
* Instantiates a new Convolution key.
*
* @param kernel the kernel
* @param simple the simple
*/
protected ConvolutionLayer(@Nonnull final Tensor kernel, final boolean simple) {
super();
this.paddingX = simple ? null : 0;
this.paddingY = simple ? null : 0;
@Nonnull int[] dimensions = kernel.getDimensions();
if (dimensions.length != 3) throw new IllegalArgumentException(Arrays.toString(dimensions));
if (dimensions[0] <= 0) throw new IllegalArgumentException(Arrays.toString(dimensions));
if (dimensions[1] <= 0) throw new IllegalArgumentException(Arrays.toString(dimensions));
if (dimensions[2] <= 0) throw new IllegalArgumentException(Arrays.toString(dimensions));
if (dimensions[2] <= 0) throw new IllegalArgumentException(Arrays.toString(dimensions));
this.kernel = kernel;
}
/**
* From json convolution key.
*
* @param json the json
* @param rs the rs
* @return the convolution key
*/
public static ConvolutionLayer fromJson(@Nonnull final JsonObject json, Map rs) {
return new ConvolutionLayer(json, rs);
}
/**
* Add weights convolution key.
*
* @param f the f
* @return the convolution key
*/
@Nonnull
public ConvolutionLayer addWeights(@Nonnull final DoubleSupplier f) {
Util.add(f, kernel.getData());
return this;
}
@Nonnull
@Override
public Result eval(@Nonnull final Result... inObj) {
Arrays.stream(inObj).forEach(nnResult -> nnResult.addRef());
final Result input = inObj[0];
final TensorList batch = input.getData();
batch.addRef();
@Nonnull final int[] inputDims = batch.get(0).getDimensions();
@Nonnull final int[] kernelDims = kernel.getDimensions();
@Nullable final double[] kernelData = ConvolutionLayer.this.kernel.getData();
@Nonnull final ConvolutionController convolutionController = new ConvolutionController(inputDims, kernelDims, paddingX, paddingY);
final Tensor[] output = IntStream.range(0, batch.length())
.mapToObj(dataIndex -> new Tensor(convolutionController.getOutputDims()))
.toArray(i -> new Tensor[i]);
try {
final double[][] inputBuffers = batch.stream().map(x -> {
@Nullable double[] data = x.getData();
x.detach();
return data;
}).toArray(i -> new double[i][]);
final double[][] outputBuffers = Arrays.stream(output).map(x -> x.getData()).toArray(i -> new double[i][]);
convolutionController.convolve(inputBuffers, kernelData, outputBuffers);
} catch (@Nonnull final Throwable e) {
throw new RuntimeException("Error mapCoords png res " + Arrays.toString(inputDims), e);
}
int outputLength = output.length;
return new Result(TensorArray.wrap(output), (@Nonnull final DeltaSet buffer, @Nonnull final TensorList error) -> {
if (!isFrozen()) {
final double[][] inputBuffers = batch.stream().map(x -> {
@Nullable double[] data = x.getData();
x.freeRef();
return data;
}).toArray(i -> new double[i][]);
final double[][] outputBuffers = error.stream().map(x -> {
@Nullable double[] data = x.getData();
x.freeRef();
return data;
}).toArray(i -> new double[i][]);
@Nonnull final Tensor weightGradient = new Tensor(kernelDims);
convolutionController.gradient(inputBuffers, weightGradient.getData(), outputBuffers);
buffer.get(ConvolutionLayer.this.getId(), kernelData).addInPlace(weightGradient.getData()).freeRef();
weightGradient.freeRef();
}
if (input.isAlive()) {
final Tensor[] inputBufferTensors = IntStream.range(0, outputLength).mapToObj(dataIndex -> new Tensor(inputDims)).toArray(i -> new Tensor[i]);
final double[][] inputBuffers = Arrays.stream(inputBufferTensors).map(x -> {
@Nullable double[] data = x.getData();
return data;
}).toArray(i -> new double[i][]);
final double[][] outputBuffers = error.stream().map(x -> {
@Nullable double[] data = x.getData();
x.freeRef();
return data;
}).toArray(i -> new double[i][]);
convolutionController.backprop(inputBuffers, kernelData, outputBuffers);
@Nonnull TensorArray tensorArray = TensorArray.wrap(inputBufferTensors);
input.accumulate(buffer, tensorArray);
}
}) {
@Override
protected void _free() {
Arrays.stream(inObj).forEach(nnResult -> nnResult.freeRef());
batch.freeRef();
}
@Override
public boolean isAlive() {
return input.isAlive() || !isFrozen();
}
};
}
@Nonnull
@Override
public JsonObject getJson(Map resources, @Nonnull DataSerializer dataSerializer) {
@Nonnull final JsonObject json = super.getJsonStub();
json.add("filter", kernel.toJson(resources, dataSerializer));
JsonElement paddingX = json.get("paddingX");
if (null != paddingX && paddingX.isJsonPrimitive()) this.setPaddingX((paddingX.getAsInt()));
JsonElement paddingY = json.get("paddingY");
if (null != paddingY && paddingY.isJsonPrimitive()) this.setPaddingY((paddingY.getAsInt()));
return json;
}
/**
* Sets weights.
*
* @param f the f
* @return the weights
*/
@Nonnull
public ConvolutionLayer setWeights(@Nonnull final DoubleSupplier f) {
kernel.coordStream(true).forEach(c -> {
kernel.set(c, f.getAsDouble());
});
return this;
}
/**
* Sets weights.
*
* @param f the f
* @return the weights
*/
@Nonnull
public ConvolutionLayer setWeights(@Nonnull final ToDoubleFunction f) {
kernel.coordStream(true).forEach(c -> {
kernel.set(c, f.applyAsDouble(c));
});
return this;
}
@Nonnull
@Override
public List state() {
return Arrays.asList(kernel.getData());
}
/**
* Gets padding x.
*
* @return the padding x
*/
@Nullable
public Integer getPaddingX() {
return paddingX;
}
/**
* Sets padding x.
*
* @param paddingX the padding x
* @return the padding x
*/
@Nonnull
public ConvolutionLayer setPaddingX(Integer paddingX) {
this.paddingX = paddingX;
return this;
}
/**
* Gets padding y.
*
* @return the padding y
*/
@Nullable
public Integer getPaddingY() {
return paddingY;
}
/**
* Sets padding y.
*
* @param paddingY the padding y
* @return the padding y
*/
@Nonnull
public ConvolutionLayer setPaddingY(Integer paddingY) {
this.paddingY = paddingY;
return this;
}
@Override
protected void _free() {
this.kernel.freeRef();
super._free();
}
}