Commit ff353c4f authored by Evgeny Kusmenko's avatar Evgeny Kusmenko

Merge branch 'feature/loss_function' into 'master'

Feature/loss function

See merge request !16
parents 053bf612 68cd0002
Pipeline #155173 passed with stages
in 6 minutes and 40 seconds
......@@ -8,7 +8,7 @@
<groupId>de.monticore.lang.monticar</groupId>
<artifactId>cnnarch-gluon-generator</artifactId>
<version>0.2.1-SNAPSHOT</version>
<version>0.2.2-SNAPSHOT</version>
<!-- == PROJECT DEPENDENCIES ============================================= -->
......@@ -16,8 +16,8 @@
<!-- .. SE-Libraries .................................................. -->
<CNNArch.version>0.3.0-SNAPSHOT</CNNArch.version>
<CNNTrain.version>0.3.2-SNAPSHOT</CNNTrain.version>
<CNNArch2MXNet.version>0.2.15-SNAPSHOT</CNNArch2MXNet.version>
<CNNTrain.version>0.3.4-SNAPSHOT</CNNTrain.version>
<CNNArch2MXNet.version>0.2.16-SNAPSHOT</CNNArch2MXNet.version>
<embedded-montiarc-math-opt-generator>0.1.4</embedded-montiarc-math-opt-generator>
<EMADL2PythonWrapper.version>0.0.1</EMADL2PythonWrapper.version>
......
......@@ -71,10 +71,8 @@ public class CNNArch2GluonTemplateController extends CNNArchTemplateController {
if (layer.isAtomic()){
ArchitectureElementSymbol nextElement = layer.getOutputElement().get();
if (!isSoftmaxOutput(nextElement) && !isLogisticRegressionOutput(nextElement)){
String templateName = layer.getDeclaration().getName();
include(TEMPLATE_ELEMENTS_DIR_PATH, templateName, writer, netDefinitionMode);
}
String templateName = layer.getDeclaration().getName();
include(TEMPLATE_ELEMENTS_DIR_PATH, templateName, writer, netDefinitionMode);
}
else {
include(layer.getResolvedThis().get(), writer, netDefinitionMode);
......
......@@ -6,6 +6,31 @@ import os
import shutil
from mxnet import gluon, autograd, nd
class CrossEntropyLoss(gluon.loss.Loss):
def __init__(self, axis=-1, sparse_label=True, weight=None, batch_axis=0, **kwargs):
super(CrossEntropyLoss, self).__init__(weight, batch_axis, **kwargs)
self._axis = axis
self._sparse_label = sparse_label
def hybrid_forward(self, F, pred, label, sample_weight=None):
pred = F.log(pred)
if self._sparse_label:
loss = -F.pick(pred, label, axis=self._axis, keepdims=True)
else:
label = gluon.loss._reshape_like(F, label, pred)
loss = -F.sum(pred * label, axis=self._axis, keepdims=True)
loss = gluon.loss._apply_weighting(F, loss, self._weight, sample_weight)
return F.mean(loss, axis=self._batch_axis, exclude=True)
class LogCoshLoss(gluon.loss.Loss):
def __init__(self, weight=None, batch_axis=0, **kwargs):
super(LogCoshLoss, self).__init__(weight, batch_axis, **kwargs)
def hybrid_forward(self, F, pred, label, sample_weight=None):
loss = F.log(F.cosh(pred - label))
loss = gluon.loss._apply_weighting(F, loss, self._weight, sample_weight)
return F.mean(loss, axis=self._batch_axis, exclude=True)
class CNNSupervisedTrainer(object):
def __init__(self, data_loader, net_constructor, net=None):
self._data_loader = data_loader
......@@ -15,6 +40,8 @@ class CNNSupervisedTrainer(object):
def train(self, batch_size=64,
num_epoch=10,
eval_metric='acc',
loss ='softmax_cross_entropy',
loss_params={},
optimizer='adam',
optimizer_params=(('learning_rate', 0.001),),
load_checkpoint=True,
......@@ -69,15 +96,36 @@ class CNNSupervisedTrainer(object):
trainer = mx.gluon.Trainer(self._net.collect_params(), optimizer, optimizer_params)
if self._net.last_layer == 'softmax':
loss_function = mx.gluon.loss.SoftmaxCrossEntropyLoss()
elif self._net.last_layer == 'sigmoid':
margin = loss_params['margin'] if 'margin' in loss_params else 1.0
sparseLabel = loss_params['sparse_label'] if 'sparse_label' in loss_params else True
if loss == 'softmax_cross_entropy':
fromLogits = loss_params['from_logits'] if 'from_logits' in loss_params else False
loss_function = mx.gluon.loss.SoftmaxCrossEntropyLoss(from_logits=fromLogits, sparse_label=sparseLabel)
elif loss == 'sigmoid_binary_cross_entropy':
loss_function = mx.gluon.loss.SigmoidBinaryCrossEntropyLoss()
elif self._net.last_layer == 'linear':
elif loss == 'cross_entropy':
loss_function = CrossEntropyLoss(sparse_label=sparseLabel)
elif loss == 'l2':
loss_function = mx.gluon.loss.L2Loss()
else: # TODO: Change default?
elif loss == 'l1':
loss_function = mx.gluon.loss.L2Loss()
logging.warning("Invalid last_layer, defaulting to L2 loss")
elif loss == 'huber':
rho = loss_params['rho'] if 'rho' in loss_params else 1
loss_function = mx.gluon.loss.HuberLoss(rho=rho)
elif loss == 'hinge':
loss_function = mx.gluon.loss.HingeLoss(margin=margin)
elif loss == 'squared_hinge':
loss_function = mx.gluon.loss.SquaredHingeLoss(margin=margin)
elif loss == 'logistic':
labelFormat = loss_params['label_format'] if 'label_format' in loss_params else 'signed'
loss_function = mx.gluon.loss.LogisticLoss(label_format=labelFormat)
elif loss == 'kullback_leibler':
fromLogits = loss_params['from_logits'] if 'from_logits' in loss_params else True
loss_function = mx.gluon.loss.KLDivLoss(from_logits=fromLogits)
elif loss == 'log_cosh':
loss_function = LogCoshLoss()
else:
logging.error("Invalid loss parameter.")
speed_period = 50
tic = None
......
......@@ -37,6 +37,16 @@ if __name__ == "__main__":
<#if (config.evalMetric)??>
eval_metric='${config.evalMetric}',
</#if>
<#if (config.configuration.loss)??>
loss='${config.lossName}',
<#if (config.lossParams)??>
loss_params={
<#list config.lossParams?keys as param>
'${param}': ${config.lossParams[param]}<#sep>,
</#list>
},
</#if>
</#if>
<#if (config.configuration.optimizer)??>
optimizer='${config.optimizerName}',
optimizer_params={
......
......@@ -2,13 +2,7 @@
<#assign mode = definition_mode.toString()>
<#if mode == "ARCHITECTURE_DEFINITION">
<#if element.softmaxOutput>
self.last_layer = 'softmax'
<#elseif element.logisticRegressionOutput>
self.last_layer = 'sigmoid'
<#elseif element.linearRegressionOutput>
self.last_layer = 'linear'
</#if>
</#if>
<#if mode == "FORWARD_FUNCTION">
return ${input}
......
......@@ -5,8 +5,15 @@
<#else>
'use_fix_target': False,
</#if>
<#if (config.loss)??>
'loss_function': '${config.loss}',
<#if (config.configuration.loss)??>
'loss': '${config.lossName}',
<#if (config.lossParams)??>
'loss_params': {
<#list config.lossParams?keys as param>
'${param}': ${config.lossParams[param]}<#sep>,
</#list>
},
</#if>
</#if>
<#if (config.configuration.optimizer)??>
'optimizer': '${config.optimizerName}',
......
......@@ -132,7 +132,7 @@ public class GenerationTest extends AbstractSymtabTest {
Log.getFindings().clear();
String[] args = {"-m", "src/test/resources/valid_tests", "-r", "MultipleOutputs"};
CNNArch2GluonCli.main(args);
assertTrue(Log.getFindings().size() == 3);
assertTrue(Log.getFindings().size() == 1);
}
@Test
......
......@@ -193,8 +193,9 @@ class Net(gluon.HybridBlock):
self.fc8_ = gluon.nn.Dense(units=10, use_bias=True)
# fc8_, output shape: {[10,1,1]}
self.softmax8_ = Softmax()
self.last_layer = 'softmax'
def hybrid_forward(self, F, data):
......@@ -259,4 +260,5 @@ class Net(gluon.HybridBlock):
relu7_ = self.relu7_(fc7_)
dropout7_ = self.dropout7_(relu7_)
fc8_ = self.fc8_(dropout7_)
return fc8_
softmax8_ = self.softmax8_(fc8_)
return softmax8_
......@@ -344,8 +344,9 @@ class Net(gluon.HybridBlock):
self.fc32_ = gluon.nn.Dense(units=10, use_bias=True)
# fc32_, output shape: {[10,1,1]}
self.softmax32_ = Softmax()
self.last_layer = 'softmax'
def hybrid_forward(self, F, data):
......@@ -452,4 +453,5 @@ class Net(gluon.HybridBlock):
fc31_ = self.fc31_(globalpooling31_)
dropout31_ = self.dropout31_(fc31_)
fc32_ = self.fc32_(dropout31_)
return fc32_
softmax32_ = self.softmax32_(fc32_)
return softmax32_
......@@ -36,9 +36,9 @@ class ZScoreNormalization(gluon.HybridBlock):
super(ZScoreNormalization, self).__init__(**kwargs)
with self.name_scope():
self.data_mean = self.params.get('data_mean', shape=data_mean.shape,
init=mx.init.Constant(data_mean.asnumpy().tolist()), differentiable=False)
init=mx.init.Constant(data_mean.asnumpy().tolist()), differentiable=False)
self.data_std = self.params.get('data_std', shape=data_mean.shape,
init=mx.init.Constant(data_std.asnumpy().tolist()), differentiable=False)
init=mx.init.Constant(data_std.asnumpy().tolist()), differentiable=False)
def hybrid_forward(self, F, x, data_mean, data_std):
x = F.broadcast_sub(x, data_mean)
......@@ -54,9 +54,9 @@ class Padding(gluon.HybridBlock):
def hybrid_forward(self, F, x):
x = F.pad(data=x,
mode='constant',
pad_width=self.pad_width,
constant_value=0)
mode='constant',
pad_width=self.pad_width,
constant_value=0)
return x
......@@ -80,17 +80,17 @@ class Net(gluon.HybridBlock):
self.conv1_padding = Padding(padding=(0,0,0,0,1,1,1,1))
self.conv1_ = gluon.nn.Conv2D(channels=64,
kernel_size=(3,3),
strides=(1,1),
use_bias=True)
kernel_size=(3,3),
strides=(1,1),
use_bias=True)
# conv1_, output shape: {[64,224,224]}
self.relu1_ = gluon.nn.Activation(activation='relu')
self.conv2_padding = Padding(padding=(0,0,0,0,1,1,1,1))
self.conv2_ = gluon.nn.Conv2D(channels=64,
kernel_size=(3,3),
strides=(1,1),
use_bias=True)
kernel_size=(3,3),
strides=(1,1),
use_bias=True)
# conv2_, output shape: {[64,224,224]}
self.relu2_ = gluon.nn.Activation(activation='relu')
......@@ -101,17 +101,17 @@ class Net(gluon.HybridBlock):
self.conv3_padding = Padding(padding=(0,0,0,0,1,1,1,1))
self.conv3_ = gluon.nn.Conv2D(channels=128,
kernel_size=(3,3),
strides=(1,1),
use_bias=True)
kernel_size=(3,3),
strides=(1,1),
use_bias=True)
# conv3_, output shape: {[128,112,112]}
self.relu3_ = gluon.nn.Activation(activation='relu')
self.conv4_padding = Padding(padding=(0,0,0,0,1,1,1,1))
self.conv4_ = gluon.nn.Conv2D(channels=128,
kernel_size=(3,3),
strides=(1,1),
use_bias=True)
kernel_size=(3,3),
strides=(1,1),
use_bias=True)
# conv4_, output shape: {[128,112,112]}
self.relu4_ = gluon.nn.Activation(activation='relu')
......@@ -122,25 +122,25 @@ class Net(gluon.HybridBlock):
self.conv5_padding = Padding(padding=(0,0,0,0,1,1,1,1))
self.conv5_ = gluon.nn.Conv2D(channels=256,
kernel_size=(3,3),
strides=(1,1),
use_bias=True)
kernel_size=(3,3),
strides=(1,1),
use_bias=True)
# conv5_, output shape: {[256,56,56]}
self.relu5_ = gluon.nn.Activation(activation='relu')
self.conv6_padding = Padding(padding=(0,0,0,0,1,1,1,1))
self.conv6_ = gluon.nn.Conv2D(channels=256,
kernel_size=(3,3),
strides=(1,1),
use_bias=True)
kernel_size=(3,3),
strides=(1,1),
use_bias=True)
# conv6_, output shape: {[256,56,56]}
self.relu6_ = gluon.nn.Activation(activation='relu')
self.conv7_padding = Padding(padding=(0,0,0,0,1,1,1,1))
self.conv7_ = gluon.nn.Conv2D(channels=256,
kernel_size=(3,3),
strides=(1,1),
use_bias=True)
kernel_size=(3,3),
strides=(1,1),
use_bias=True)
# conv7_, output shape: {[256,56,56]}
self.relu7_ = gluon.nn.Activation(activation='relu')
......@@ -151,25 +151,25 @@ class Net(gluon.HybridBlock):
self.conv8_padding = Padding(padding=(0,0,0,0,1,1,1,1))
self.conv8_ = gluon.nn.Conv2D(channels=512,
kernel_size=(3,3),
strides=(1,1),
use_bias=True)
kernel_size=(3,3),
strides=(1,1),
use_bias=True)
# conv8_, output shape: {[512,28,28]}
self.relu8_ = gluon.nn.Activation(activation='relu')
self.conv9_padding = Padding(padding=(0,0,0,0,1,1,1,1))
self.conv9_ = gluon.nn.Conv2D(channels=512,
kernel_size=(3,3),
strides=(1,1),
use_bias=True)
kernel_size=(3,3),
strides=(1,1),
use_bias=True)
# conv9_, output shape: {[512,28,28]}
self.relu9_ = gluon.nn.Activation(activation='relu')
self.conv10_padding = Padding(padding=(0,0,0,0,1,1,1,1))
self.conv10_ = gluon.nn.Conv2D(channels=512,
kernel_size=(3,3),
strides=(1,1),
use_bias=True)
kernel_size=(3,3),
strides=(1,1),
use_bias=True)
# conv10_, output shape: {[512,28,28]}
self.relu10_ = gluon.nn.Activation(activation='relu')
......@@ -180,25 +180,25 @@ class Net(gluon.HybridBlock):
self.conv11_padding = Padding(padding=(0,0,0,0,1,1,1,1))
self.conv11_ = gluon.nn.Conv2D(channels=512,
kernel_size=(3,3),
strides=(1,1),
use_bias=True)
kernel_size=(3,3),
strides=(1,1),
use_bias=True)
# conv11_, output shape: {[512,14,14]}
self.relu11_ = gluon.nn.Activation(activation='relu')
self.conv12_padding = Padding(padding=(0,0,0,0,1,1,1,1))
self.conv12_ = gluon.nn.Conv2D(channels=512,
kernel_size=(3,3),
strides=(1,1),
use_bias=True)
kernel_size=(3,3),
strides=(1,1),
use_bias=True)
# conv12_, output shape: {[512,14,14]}
self.relu12_ = gluon.nn.Activation(activation='relu')
self.conv13_padding = Padding(padding=(0,0,0,0,1,1,1,1))
self.conv13_ = gluon.nn.Conv2D(channels=512,
kernel_size=(3,3),
strides=(1,1),
use_bias=True)
kernel_size=(3,3),
strides=(1,1),
use_bias=True)
# conv13_, output shape: {[512,14,14]}
self.relu13_ = gluon.nn.Activation(activation='relu')
......@@ -221,8 +221,9 @@ class Net(gluon.HybridBlock):
self.fc15_ = gluon.nn.Dense(units=1000, use_bias=True)
# fc15_, output shape: {[1000,1,1]}
self.softmax15_ = Softmax()
self.last_layer = 'softmax'
def hybrid_forward(self, F, data):
......@@ -279,4 +280,5 @@ class Net(gluon.HybridBlock):
relu15_ = self.relu15_(fc14_)
dropout15_ = self.dropout15_(relu15_)
fc15_ = self.fc15_(dropout15_)
return fc15_
softmax15_ = self.softmax15_(fc15_)
return softmax15_
......@@ -13,7 +13,7 @@ if __name__ == "__main__":
fullConfig_creator = CNNCreator_fullConfig.CNNCreator_fullConfig()
fullConfig_loader = CNNDataLoader_fullConfig.fullConfigDataLoader()
fullConfig_trainer = supervised_trainer.CNNSupervisedTrainer(fullConfig_loader,
fullConfig_creator)
fullConfig_creator)
fullConfig_trainer.train(
batch_size=100,
......@@ -22,6 +22,10 @@ if __name__ == "__main__":
context='gpu',
normalize=True,
eval_metric='mse',
loss='softmax_cross_entropy',
loss_params={
'sparse_label': True,
'from_logits': False},
optimizer='rmsprop',
optimizer_params={
'weight_decay': 0.01,
......
......@@ -13,11 +13,12 @@ if __name__ == "__main__":
simpleConfig_creator = CNNCreator_simpleConfig.CNNCreator_simpleConfig()
simpleConfig_loader = CNNDataLoader_simpleConfig.simpleConfigDataLoader()
simpleConfig_trainer = supervised_trainer.CNNSupervisedTrainer(simpleConfig_loader,
simpleConfig_creator)
simpleConfig_creator)
simpleConfig_trainer.train(
batch_size=100,
num_epoch=50,
loss='cross_entropy',
optimizer='adam',
optimizer_params={
'learning_rate': 0.001}
......
......@@ -86,7 +86,7 @@ if __name__ == "__main__":
'qnet':qnet_creator.net,
'use_fix_target': True,
'target_update_interval': 500,
'loss_function': 'huber_loss',
'loss': 'huber',
'optimizer': 'adam',
'optimizer_params': {
'learning_rate': 0.001 },
......
......@@ -81,7 +81,7 @@ if __name__ == "__main__":
'target_score': 185.5,
'qnet':qnet_creator.net,
'use_fix_target': False,
'loss_function': 'euclidean',
'loss': 'l2',
'optimizer': 'rmsprop',
'optimizer_params': {
'weight_decay': 0.01,
......
......@@ -87,7 +87,7 @@ if __name__ == "__main__":
'qnet':qnet_creator.net,
'use_fix_target': True,
'target_update_interval': 500,
'loss_function': 'huber_loss',
'loss': 'huber',
'optimizer': 'adam',
'optimizer_params': {
'learning_rate': 0.001 },
......
......@@ -102,7 +102,7 @@ class Net(gluon.HybridBlock):
# fc5_, output shape: {[1,1,1]}
self.last_layer = 'linear'
def hybrid_forward(self, F, state, action):
......
......@@ -102,7 +102,7 @@ class Net(gluon.HybridBlock):
# fc5_, output shape: {[1,1,1]}
self.last_layer = 'linear'
def hybrid_forward(self, F, state, action):
......
......@@ -6,6 +6,31 @@ import os
import shutil
from mxnet import gluon, autograd, nd
class CrossEntropyLoss(gluon.loss.Loss):
def __init__(self, axis=-1, sparse_label=True, weight=None, batch_axis=0, **kwargs):
super(CrossEntropyLoss, self).__init__(weight, batch_axis, **kwargs)
self._axis = axis
self._sparse_label = sparse_label
def hybrid_forward(self, F, pred, label, sample_weight=None):
pred = F.log(pred)
if self._sparse_label:
loss = -F.pick(pred, label, axis=self._axis, keepdims=True)
else:
label = gluon.loss._reshape_like(F, label, pred)
loss = -F.sum(pred * label, axis=self._axis, keepdims=True)
loss = gluon.loss._apply_weighting(F, loss, self._weight, sample_weight)
return F.mean(loss, axis=self._batch_axis, exclude=True)
class LogCoshLoss(gluon.loss.Loss):
def __init__(self, weight=None, batch_axis=0, **kwargs):
super(LogCoshLoss, self).__init__(weight, batch_axis, **kwargs)
def hybrid_forward(self, F, pred, label, sample_weight=None):
loss = F.log(F.cosh(pred - label))
loss = gluon.loss._apply_weighting(F, loss, self._weight, sample_weight)
return F.mean(loss, axis=self._batch_axis, exclude=True)
class CNNSupervisedTrainer(object):
def __init__(self, data_loader, net_constructor, net=None):
self._data_loader = data_loader
......@@ -15,6 +40,8 @@ class CNNSupervisedTrainer(object):
def train(self, batch_size=64,
num_epoch=10,
eval_metric='acc',
loss ='softmax_cross_entropy',
loss_params={},
optimizer='adam',
optimizer_params=(('learning_rate', 0.001),),
load_checkpoint=True,
......@@ -37,9 +64,9 @@ class CNNSupervisedTrainer(object):
min_learning_rate = optimizer_params['learning_rate_minimum']
del optimizer_params['learning_rate_minimum']
optimizer_params['lr_scheduler'] = mx.lr_scheduler.FactorScheduler(
optimizer_params['step_size'],
factor=optimizer_params['learning_rate_decay'],
stop_factor_lr=min_learning_rate)
optimizer_params['step_size'],
factor=optimizer_params['learning_rate_decay'],
stop_factor_lr=min_learning_rate)
del optimizer_params['step_size']
del optimizer_params['learning_rate_decay']
......@@ -69,15 +96,36 @@ class CNNSupervisedTrainer(object):
trainer = mx.gluon.Trainer(self._net.collect_params(), optimizer, optimizer_params)
if self._net.last_layer == 'softmax':
loss_function = mx.gluon.loss.SoftmaxCrossEntropyLoss()
elif self._net.last_layer == 'sigmoid':
margin = loss_params['margin'] if 'margin' in loss_params else 1.0
sparseLabel = loss_params['sparse_label'] if 'sparse_label' in loss_params else True
if loss == 'softmax_cross_entropy':
fromLogits = loss_params['from_logits'] if 'from_logits' in loss_params else False
loss_function = mx.gluon.loss.SoftmaxCrossEntropyLoss(from_logits=fromLogits, sparse_label=sparseLabel)
elif loss == 'sigmoid_binary_cross_entropy':
loss_function = mx.gluon.loss.SigmoidBinaryCrossEntropyLoss()
elif self._net.last_layer == 'linear':
elif loss == 'cross_entropy':
loss_function = CrossEntropyLoss(sparse_label=sparseLabel)
elif loss == 'l2':
loss_function = mx.gluon.loss.L2Loss()
else: # TODO: Change default?
elif loss == 'l1':
loss_function = mx.gluon.loss.L2Loss()
logging.warning("Invalid last_layer, defaulting to L2 loss")
elif loss == 'huber':
rho = loss_params['rho'] if 'rho' in loss_params else 1
loss_function = mx.gluon.loss.HuberLoss(rho=rho)
elif loss == 'hinge':
loss_function = mx.gluon.loss.HingeLoss(margin=margin)
elif loss == 'squared_hinge':
loss_function = mx.gluon.loss.SquaredHingeLoss(margin=margin)
elif loss == 'logistic':
labelFormat = loss_params['label_format'] if 'label_format' in loss_params else 'signed'
loss_function = mx.gluon.loss.LogisticLoss(label_format=labelFormat)
elif loss == 'kullback_leibler':
fromLogits = loss_params['from_logits'] if 'from_logits' in loss_params else True
loss_function = mx.gluon.loss.KLDivLoss(from_logits=fromLogits)
elif loss == 'log_cosh':
loss_function = LogCoshLoss()
else:
logging.error("Invalid loss parameter.")
speed_period = 50
tic = None
......
......@@ -3,6 +3,10 @@ configuration FullConfig{
batch_size : 100
load_checkpoint : true
eval_metric : mse
loss: softmax_cross_entropy{
sparse_label: true
from_logits: false
}
context : gpu
normalize : true
optimizer : rmsprop{
......
......@@ -24,7 +24,7 @@ configuration ReinforcementConfig1 {
use_double_dqn : true
loss : huber_loss
loss : huber
replay_memory : buffer{
memory_size : 1000000
......
......@@ -19,7 +19,7 @@ configuration ReinforcementConfig2 {
use_double_dqn : false
loss : euclidean
loss : l2
replay_memory : buffer{
memory_size : 10000
......
......@@ -23,7 +23,7 @@ configuration ReinforcementConfig3 {
use_double_dqn : true
loss : huber_loss
loss : huber
replay_memory : buffer{
memory_size : 1000000
......
configuration SimpleConfig{
num_epoch : 50
batch_size : 100
loss : cross_entropy
optimizer : adam{
learning_rate : 0.001
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment