commit
15a6252f8e
@ -0,0 +1,205 @@
|
||||
"""
|
||||
A very basic example for how to use current Raw SWIG API to train mnist network.
|
||||
|
||||
Current implementation uses Raw SWIG, which means the API call is directly \
|
||||
passed to C++ side of Paddle.
|
||||
|
||||
The user api could be simpler and carefully designed.
|
||||
"""
|
||||
import py_paddle.swig_paddle as api
|
||||
from py_paddle import DataProviderConverter
|
||||
import paddle.trainer.PyDataProvider2 as dp
|
||||
import numpy as np
|
||||
import random
|
||||
from mnist_util import read_from_mnist
|
||||
from paddle.trainer_config_helpers import *
|
||||
|
||||
|
||||
def optimizer_config():
|
||||
settings(
|
||||
learning_rate=1e-4,
|
||||
learning_method=AdamOptimizer(),
|
||||
batch_size=1000,
|
||||
model_average=ModelAverage(average_window=0.5),
|
||||
regularization=L2Regularization(rate=0.5))
|
||||
|
||||
|
||||
def network_config():
|
||||
imgs = data_layer(name='pixel', size=784)
|
||||
hidden1 = fc_layer(input=imgs, size=200)
|
||||
hidden2 = fc_layer(input=hidden1, size=200)
|
||||
inference = fc_layer(input=hidden2, size=10, act=SoftmaxActivation())
|
||||
cost = classification_cost(
|
||||
input=inference, label=data_layer(
|
||||
name='label', size=10))
|
||||
outputs(cost)
|
||||
|
||||
|
||||
def init_parameter(network):
|
||||
assert isinstance(network, api.GradientMachine)
|
||||
for each_param in network.getParameters():
|
||||
assert isinstance(each_param, api.Parameter)
|
||||
array_size = len(each_param)
|
||||
array = np.random.uniform(-1.0, 1.0, array_size).astype('float32')
|
||||
each_param.getBuf(api.PARAMETER_VALUE).copyFromNumpyArray(array)
|
||||
|
||||
|
||||
def generator_to_batch(generator, batch_size):
|
||||
ret_val = list()
|
||||
for each_item in generator:
|
||||
ret_val.append(each_item)
|
||||
if len(ret_val) == batch_size:
|
||||
yield ret_val
|
||||
ret_val = list()
|
||||
if len(ret_val) != 0:
|
||||
yield ret_val
|
||||
|
||||
|
||||
class BatchPool(object):
|
||||
def __init__(self, generator, batch_size):
|
||||
self.data = list(generator)
|
||||
self.batch_size = batch_size
|
||||
|
||||
def __call__(self):
|
||||
random.shuffle(self.data)
|
||||
for offset in xrange(0, len(self.data), self.batch_size):
|
||||
limit = min(offset + self.batch_size, len(self.data))
|
||||
yield self.data[offset:limit]
|
||||
|
||||
|
||||
def input_order_converter(generator):
|
||||
for each_item in generator:
|
||||
yield each_item['pixel'], each_item['label']
|
||||
|
||||
|
||||
def main():
|
||||
api.initPaddle("-use_gpu=false", "-trainer_count=4") # use 4 cpu cores
|
||||
|
||||
# get enable_types for each optimizer.
|
||||
# enable_types = [value, gradient, momentum, etc]
|
||||
# For each optimizer(SGD, Adam), GradientMachine should enable different
|
||||
# buffers.
|
||||
opt_config_proto = parse_optimizer_config(optimizer_config)
|
||||
opt_config = api.OptimizationConfig.createFromProto(opt_config_proto)
|
||||
_temp_optimizer_ = api.ParameterOptimizer.create(opt_config)
|
||||
enable_types = _temp_optimizer_.getParameterTypes()
|
||||
|
||||
# Create Simple Gradient Machine.
|
||||
model_config = parse_network_config(network_config)
|
||||
m = api.GradientMachine.createFromConfigProto(
|
||||
model_config, api.CREATE_MODE_NORMAL, enable_types)
|
||||
|
||||
# This type check is not useful. Only enable type hint in IDE.
|
||||
# Such as PyCharm
|
||||
assert isinstance(m, api.GradientMachine)
|
||||
|
||||
# Initialize Parameter by numpy.
|
||||
init_parameter(network=m)
|
||||
|
||||
# Create Local Updater. Local means not run in cluster.
|
||||
# For a cluster training, here we can change to createRemoteUpdater
|
||||
# in future.
|
||||
updater = api.ParameterUpdater.createLocalUpdater(opt_config)
|
||||
assert isinstance(updater, api.ParameterUpdater)
|
||||
|
||||
# Initialize ParameterUpdater.
|
||||
updater.init(m)
|
||||
|
||||
# DataProvider Converter is a utility convert Python Object to Paddle C++
|
||||
# Input. The input format is as same as Paddle's DataProvider.
|
||||
converter = DataProviderConverter(
|
||||
input_types=[dp.dense_vector(784), dp.integer_value(10)])
|
||||
|
||||
train_file = './data/raw_data/train'
|
||||
test_file = './data/raw_data/t10k'
|
||||
|
||||
# start gradient machine.
|
||||
# the gradient machine must be started before invoke forward/backward.
|
||||
# not just for training, but also for inference.
|
||||
m.start()
|
||||
|
||||
# evaluator can print error rate, etc. It is a C++ class.
|
||||
batch_evaluator = m.makeEvaluator()
|
||||
test_evaluator = m.makeEvaluator()
|
||||
|
||||
# Get Train Data.
|
||||
# TrainData will stored in a data pool. Currently implementation is not care
|
||||
# about memory, speed. Just a very naive implementation.
|
||||
train_data_generator = input_order_converter(read_from_mnist(train_file))
|
||||
train_data = BatchPool(train_data_generator, 512)
|
||||
|
||||
# outArgs is Neural Network forward result. Here is not useful, just passed
|
||||
# to gradient_machine.forward
|
||||
outArgs = api.Arguments.createArguments(0)
|
||||
|
||||
for pass_id in xrange(2): # we train 2 passes.
|
||||
updater.startPass()
|
||||
|
||||
for batch_id, data_batch in enumerate(train_data()):
|
||||
# data_batch is input images.
|
||||
# here, for online learning, we could get data_batch from network.
|
||||
|
||||
# Start update one batch.
|
||||
pass_type = updater.startBatch(len(data_batch))
|
||||
|
||||
# Start BatchEvaluator.
|
||||
# batch_evaluator can be used between start/finish.
|
||||
batch_evaluator.start()
|
||||
|
||||
# forwardBackward is a shortcut for forward and backward.
|
||||
# It is sometimes faster than invoke forward/backward separately,
|
||||
# because in GradientMachine, it may be async.
|
||||
m.forwardBackward(converter(data_batch), outArgs, pass_type)
|
||||
|
||||
for each_param in m.getParameters():
|
||||
updater.update(each_param)
|
||||
|
||||
# Get cost. We use numpy to calculate total cost for this batch.
|
||||
cost_vec = outArgs.getSlotValue(0)
|
||||
cost_vec = cost_vec.copyToNumpyMat()
|
||||
cost = cost_vec.sum() / len(data_batch)
|
||||
|
||||
# Make evaluator works.
|
||||
m.eval(batch_evaluator)
|
||||
|
||||
# Print logs.
|
||||
print 'Pass id', pass_id, 'Batch id', batch_id, 'with cost=', \
|
||||
cost, batch_evaluator
|
||||
|
||||
batch_evaluator.finish()
|
||||
# Finish batch.
|
||||
# * will clear gradient.
|
||||
# * ensure all values should be updated.
|
||||
updater.finishBatch(cost)
|
||||
|
||||
# testing stage. use test data set to test current network.
|
||||
updater.apply()
|
||||
test_evaluator.start()
|
||||
test_data_generator = input_order_converter(read_from_mnist(test_file))
|
||||
for data_batch in generator_to_batch(test_data_generator, 512):
|
||||
# in testing stage, only forward is needed.
|
||||
m.forward(converter(data_batch), outArgs, api.PASS_TEST)
|
||||
m.eval(test_evaluator)
|
||||
|
||||
# print error rate for test data set
|
||||
print 'Pass', pass_id, ' test evaluator: ', test_evaluator
|
||||
test_evaluator.finish()
|
||||
updater.restore()
|
||||
|
||||
updater.catchUpWith()
|
||||
params = m.getParameters()
|
||||
for each_param in params:
|
||||
assert isinstance(each_param, api.Parameter)
|
||||
value = each_param.getBuf(api.PARAMETER_VALUE)
|
||||
value = value.copyToNumpyArray()
|
||||
|
||||
# Here, we could save parameter to every where you want
|
||||
print each_param.getName(), value
|
||||
|
||||
updater.finishPass()
|
||||
|
||||
m.finish()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
@ -0,0 +1,30 @@
|
||||
import numpy
|
||||
|
||||
__all__ = ['read_from_mnist']
|
||||
|
||||
|
||||
def read_from_mnist(filename):
|
||||
imgf = filename + "-images-idx3-ubyte"
|
||||
labelf = filename + "-labels-idx1-ubyte"
|
||||
f = open(imgf, "rb")
|
||||
l = open(labelf, "rb")
|
||||
|
||||
f.read(16)
|
||||
l.read(8)
|
||||
|
||||
# Define number of samples for train/test
|
||||
if "train" in filename:
|
||||
n = 60000
|
||||
else:
|
||||
n = 10000
|
||||
|
||||
images = numpy.fromfile(
|
||||
f, 'ubyte', count=n * 28 * 28).reshape((n, 28 * 28)).astype('float32')
|
||||
images = images / 255.0 * 2.0 - 1.0
|
||||
labels = numpy.fromfile(l, 'ubyte', count=n).astype("int")
|
||||
|
||||
for i in xrange(n):
|
||||
yield {"pixel": images[i, :], 'label': labels[i]}
|
||||
|
||||
f.close()
|
||||
l.close()
|
@ -0,0 +1,44 @@
|
||||
#!/bin/bash
|
||||
# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved
|
||||
#
|
||||
# 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.
|
||||
set -e
|
||||
|
||||
# Should run pserver.sh before run this script.
|
||||
bin_dir=$(cd `dirname $0`; pwd)
|
||||
home_dir=$(cd "${bin_dir}/.."; pwd)
|
||||
source "$bin_dir/env.sh"
|
||||
|
||||
model_dir="$bin_dir/output"
|
||||
log_file="$bin_dir/train.log"
|
||||
|
||||
pushd "$home_dir"
|
||||
cfg=trainer_config.lr.py
|
||||
paddle train \
|
||||
--config=$cfg \
|
||||
--save_dir=${model_dir} \
|
||||
--trainer_count=4 \
|
||||
--local=0 \
|
||||
--log_period=100 \
|
||||
--num_passes=15 \
|
||||
--use_gpu=false \
|
||||
--show_parameter_stats_period=100 \
|
||||
--test_all_data_in_one_period=1 \
|
||||
--num_gradient_servers=1 \
|
||||
--nics=`get_nics` \
|
||||
--port=7164 \
|
||||
--ports_num=1 \
|
||||
--pservers="127.0.0.1" \
|
||||
--comment="paddle_trainer" \
|
||||
2>&1 | tee "$log_file"
|
||||
popd
|
@ -0,0 +1,28 @@
|
||||
#!/bin/bash
|
||||
# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved
|
||||
#
|
||||
# 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.
|
||||
set -e
|
||||
|
||||
function get_nics() {
|
||||
machine=`uname -s`
|
||||
local nics=""
|
||||
if [ "$machine" == "Linux" ]; then
|
||||
nics="lo"
|
||||
elif [ "$machine" == "Darwin" ]; then
|
||||
nics="lo0"
|
||||
else
|
||||
nics="unsupport"
|
||||
fi
|
||||
echo $nics
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
#!/bin/bash
|
||||
# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved
|
||||
#
|
||||
# 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.
|
||||
set -e
|
||||
bin_dir=$(cd `dirname $0`; pwd)
|
||||
source "$bin_dir/env.sh"
|
||||
|
||||
paddle pserver \
|
||||
--nics=`get_nics` \
|
||||
--port=7164 \
|
||||
--ports_num=1 \
|
||||
--ports_num_for_sparse=1 \
|
||||
--num_gradient_servers=1 \
|
||||
--comment="paddle_pserver" \
|
||||
2>&1 | tee 'pserver.log'
|
After Width: | Height: | Size: 32 KiB |
@ -0,0 +1,143 @@
|
||||
# Generative Adversarial Networks (GAN)
|
||||
|
||||
This demo implements GAN training described in the original [GAN paper](https://arxiv.org/abs/1406.2661) and deep convolutional generative adversarial networks [DCGAN paper](https://arxiv.org/abs/1511.06434).
|
||||
|
||||
The high-level structure of GAN is shown in Figure. 1 below. It is composed of two major parts: a generator and a discriminator, both of which are based on neural networks. The generator takes in some kind of noise with a known distribution and transforms it into an image. The discriminator takes in an image and determines whether it is artificially generated by the generator or a real image. So the generator and the discriminator are in a competitive game in which generator is trying to generate image to look as real as possible to fool the discriminator, while the discriminator is trying to distinguish between real and fake images.
|
||||
|
||||
<p align="center">
|
||||
<img src="./gan.png" width="500" height="300">
|
||||
</p>
|
||||
<p align="center">
|
||||
Figure 1. GAN-Model-Structure
|
||||
<a href="https://ishmaelbelghazi.github.io/ALI/">figure credit</a>
|
||||
</p>
|
||||
|
||||
The generator and discriminator take turn to be trained using SGD. The objective function of the generator is for its generated images being classified as real by the discriminator, and the objective function of the discriminator is to correctly classify real and fake images. When the GAN model is trained to converge to the equilibrium state, the generator will transform the given noise distribution to the distribution of real images, and the discriminator will not be able to distinguish between real and fake images at all.
|
||||
|
||||
## Implementation of GAN Model Structure
|
||||
Since GAN model involves multiple neural networks, it requires to use paddle python API. So the code walk-through below can also partially serve as an introduction to the usage of Paddle Python API.
|
||||
|
||||
There are three networks defined in gan_conf.py, namely **generator_training**, **discriminator_training** and **generator**. The relationship to the model structure we defined above is that **discriminator_training** is the discriminator, **generator** is the generator, and the **generator_training** combined the generator and discriminator since training generator would require the discriminator to provide loss function. This relationship is described in the following code:
|
||||
```python
|
||||
if is_generator_training:
|
||||
noise = data_layer(name="noise", size=noise_dim)
|
||||
sample = generator(noise)
|
||||
|
||||
if is_discriminator_training:
|
||||
sample = data_layer(name="sample", size=sample_dim)
|
||||
|
||||
if is_generator_training or is_discriminator_training:
|
||||
label = data_layer(name="label", size=1)
|
||||
prob = discriminator(sample)
|
||||
cost = cross_entropy(input=prob, label=label)
|
||||
classification_error_evaluator(
|
||||
input=prob, label=label, name=mode + '_error')
|
||||
outputs(cost)
|
||||
|
||||
if is_generator:
|
||||
noise = data_layer(name="noise", size=noise_dim)
|
||||
outputs(generator(noise))
|
||||
```
|
||||
|
||||
In order to train the networks defined in gan_conf.py, one first needs to initialize a Paddle environment, parse the config, create GradientMachine from the config and create trainer from GradientMachine as done in the code chunk below:
|
||||
```python
|
||||
import py_paddle.swig_paddle as api
|
||||
# init paddle environment
|
||||
api.initPaddle('--use_gpu=' + use_gpu, '--dot_period=10',
|
||||
'--log_period=100', '--gpu_id=' + args.gpu_id,
|
||||
'--save_dir=' + "./%s_params/" % data_source)
|
||||
|
||||
# Parse config
|
||||
gen_conf = parse_config(conf, "mode=generator_training,data=" + data_source)
|
||||
dis_conf = parse_config(conf, "mode=discriminator_training,data=" + data_source)
|
||||
generator_conf = parse_config(conf, "mode=generator,data=" + data_source)
|
||||
|
||||
# Create GradientMachine
|
||||
dis_training_machine = api.GradientMachine.createFromConfigProto(
|
||||
dis_conf.model_config)
|
||||
gen_training_machine = api.GradientMachine.createFromConfigProto(
|
||||
gen_conf.model_config)
|
||||
generator_machine = api.GradientMachine.createFromConfigProto(
|
||||
generator_conf.model_config)
|
||||
|
||||
# Create trainer
|
||||
dis_trainer = api.Trainer.create(dis_conf, dis_training_machine)
|
||||
gen_trainer = api.Trainer.create(gen_conf, gen_training_machine)
|
||||
```
|
||||
|
||||
In order to balance the strength between generator and discriminator, we schedule to train whichever one is performing worse by comparing their loss function value. The loss function value can be calculated by a forward pass through the GradientMachine.
|
||||
```python
|
||||
def get_training_loss(training_machine, inputs):
|
||||
outputs = api.Arguments.createArguments(0)
|
||||
training_machine.forward(inputs, outputs, api.PASS_TEST)
|
||||
loss = outputs.getSlotValue(0).copyToNumpyMat()
|
||||
return numpy.mean(loss)
|
||||
```
|
||||
|
||||
After training one network, one needs to sync the new parameters to the other networks. The code below demonstrates one example of such use case:
|
||||
```python
|
||||
# Train the gen_training
|
||||
gen_trainer.trainOneDataBatch(batch_size, data_batch_gen)
|
||||
|
||||
# Copy the parameters from gen_training to dis_training and generator
|
||||
copy_shared_parameters(gen_training_machine,
|
||||
dis_training_machine)
|
||||
copy_shared_parameters(gen_training_machine, generator_machine)
|
||||
```
|
||||
|
||||
|
||||
## A Toy Example
|
||||
With the infrastructure explained above, we can now walk you through a toy example of generating two dimensional uniform distribution using 10 dimensional Gaussian noise.
|
||||
|
||||
The Gaussian noises are generated using the code below:
|
||||
```python
|
||||
def get_noise(batch_size, noise_dim):
|
||||
return numpy.random.normal(size=(batch_size, noise_dim)).astype('float32')
|
||||
```
|
||||
|
||||
The real samples (2-D uniform) are generated using the code below:
|
||||
```python
|
||||
# synthesize 2-D uniform data in gan_trainer.py:114
|
||||
def load_uniform_data():
|
||||
data = numpy.random.rand(1000000, 2).astype('float32')
|
||||
return data
|
||||
```
|
||||
|
||||
The generator and discriminator network are built using fully-connected layer and batch_norm layer, and are defined in gan_conf.py.
|
||||
|
||||
To train the GAN model, one can use the command below. The flag -d specifies the training data (cifar, mnist or uniform) and flag --useGpu specifies whether to use gpu for training (0 is cpu, 1 is gpu).
|
||||
```bash
|
||||
$python gan_trainer.py -d uniform --useGpu 1
|
||||
```
|
||||
The generated samples can be found in ./uniform_samples/ and one example is shown below as Figure 2. One can see that it roughly recovers the 2D uniform distribution.
|
||||
|
||||
<p align="center">
|
||||
<img src="./uniform_sample.png" width="300" height="300">
|
||||
</p>
|
||||
<p align="center">
|
||||
Figure 2. Uniform Sample
|
||||
</p>
|
||||
|
||||
## MNIST Example
|
||||
### Data preparation
|
||||
To download the MNIST data, one can use the following commands:
|
||||
```bash
|
||||
$cd data/
|
||||
$./get_mnist_data.sh
|
||||
```
|
||||
|
||||
### Model description
|
||||
Following the DC-Gan paper (https://arxiv.org/abs/1511.06434), we use convolution/convolution-transpose layer in the discriminator/generator network to better deal with images. The details of the network structures are defined in gan_conf_image.py.
|
||||
|
||||
### Training the model
|
||||
To train the GAN model on mnist data, one can use the following command:
|
||||
```bash
|
||||
$python gan_trainer.py -d mnist --useGpu 1
|
||||
```
|
||||
The generated sample images can be found at ./mnist_samples/ and one example is shown below as Figure 3.
|
||||
<p align="center">
|
||||
<img src="./mnist_sample.png" width="300" height="300">
|
||||
</p>
|
||||
<p align="center">
|
||||
Figure 3. MNIST Sample
|
||||
</p>
|
After Width: | Height: | Size: 28 KiB |
After Width: | Height: | Size: 20 KiB |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,56 @@
|
||||
/* Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserve.
|
||||
|
||||
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. */
|
||||
|
||||
#include "PaddleAPI.h"
|
||||
|
||||
#include "PaddleAPIPrivate.h"
|
||||
#include "paddle/trainer/ThreadParameterUpdater.h"
|
||||
|
||||
ParameterUpdater::ParameterUpdater() : m(new ParameterUpdaterPrivate()) {}
|
||||
|
||||
ParameterUpdater *ParameterUpdater::createLocalUpdater(
|
||||
OptimizationConfig *config) {
|
||||
auto param = new ParameterUpdater();
|
||||
param->m->updater.reset(new paddle::SgdThreadUpdater(config->m->getConfig()));
|
||||
return param;
|
||||
}
|
||||
|
||||
ParameterUpdater::~ParameterUpdater() { delete m; }
|
||||
|
||||
void ParameterUpdater::init(const GradientMachine &gm) {
|
||||
m->updater->init(gm.m->machine->getNonStaticParameters());
|
||||
}
|
||||
|
||||
void ParameterUpdater::startPass() { m->updater->startPass(); }
|
||||
|
||||
void ParameterUpdater::finishPass() { m->updater->finishPass(); }
|
||||
|
||||
PassType ParameterUpdater::startBatch(size_t batchSize) {
|
||||
return m->updater->startBatch((int64_t)batchSize);
|
||||
}
|
||||
|
||||
void ParameterUpdater::finishBatch(float cost) {
|
||||
m->updater->finishBatch(cost);
|
||||
}
|
||||
|
||||
void ParameterUpdater::update(Parameter *param) {
|
||||
auto paddleParam = param->m->getPtr();
|
||||
m->updater->update(paddleParam);
|
||||
}
|
||||
|
||||
void ParameterUpdater::restore() { m->updater->restore(); }
|
||||
|
||||
void ParameterUpdater::apply() { m->updater->apply(); }
|
||||
|
||||
void ParameterUpdater::catchUpWith() { m->updater->catchUpWith(); }
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue