|
|
/*
|
||
|
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||
|
|
* or more contributor license agreements. See the NOTICE file
|
||
|
|
* distributed with this work for additional information
|
||
|
|
* regarding copyright ownership. The ASF 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.
|
||
|
|
*/
|
||
|
|
|
||
|
|
/*!
|
||
|
|
*/
|
||
|
|
#include <fstream>
|
||
|
|
#include <map>
|
||
|
|
#include <string>
|
||
|
|
#include <vector>
|
||
|
|
#include <cstdlib>
|
||
|
|
#include "mxnet-cpp/MxNetCpp.h"
|
||
|
|
#include "utils.h"
|
||
|
|
|
||
|
|
using namespace mxnet::cpp;
|
||
|
|
|
||
|
|
class Lenet {
|
||
|
|
public:
|
||
|
|
Lenet()
|
||
|
|
: ctx_cpu(Context(DeviceType::kCPU, 0)),
|
||
|
|
#if !MXNET_USE_CUDA
|
||
|
|
ctx_dev(Context(DeviceType::kCPU, 0))
|
||
|
|
#else
|
||
|
|
ctx_dev(Context(DeviceType::kGPU, 0))
|
||
|
|
#endif
|
||
|
|
{}
|
||
|
|
|
||
|
|
void Run(int max_epoch) {
|
||
|
|
/*
|
||
|
|
* LeCun, Yann, Leon Bottou, Yoshua Bengio, and Patrick Haffner.
|
||
|
|
* "Gradient-based learning applied to document recognition."
|
||
|
|
* Proceedings of the IEEE (1998)
|
||
|
|
* */
|
||
|
|
|
||
|
|
/*define the symbolic net*/
|
||
|
|
Symbol data = Symbol::Variable("data");
|
||
|
|
Symbol data_label = Symbol::Variable("data_label");
|
||
|
|
Symbol conv1_w("conv1_w"), conv1_b("conv1_b");
|
||
|
|
Symbol conv2_w("conv2_w"), conv2_b("conv2_b");
|
||
|
|
Symbol conv3_w("conv3_w"), conv3_b("conv3_b");
|
||
|
|
Symbol fc1_w("fc1_w"), fc1_b("fc1_b");
|
||
|
|
Symbol fc2_w("fc2_w"), fc2_b("fc2_b");
|
||
|
|
|
||
|
|
Symbol conv1 =
|
||
|
|
Convolution("conv1", data, conv1_w, conv1_b, Shape(5, 5), 20);
|
||
|
|
Symbol tanh1 = Activation("tanh1", conv1, ActivationActType::kTanh);
|
||
|
|
Symbol pool1 = Pooling("pool1", tanh1, Shape(2, 2), PoolingPoolType::kMax,
|
||
|
|
false, false, PoolingPoolingConvention::kValid, Shape(2, 2));
|
||
|
|
|
||
|
|
Symbol conv2 = Convolution("conv2", pool1, conv2_w, conv2_b,
|
||
|
|
Shape(5, 5), 50);
|
||
|
|
Symbol tanh2 = Activation("tanh2", conv2, ActivationActType::kTanh);
|
||
|
|
Symbol pool2 = Pooling("pool2", tanh2, Shape(2, 2), PoolingPoolType::kMax,
|
||
|
|
false, false, PoolingPoolingConvention::kValid, Shape(2, 2));
|
||
|
|
|
||
|
|
Symbol conv3 = Convolution("conv3", pool2, conv3_w, conv3_b,
|
||
|
|
Shape(2, 2), 500);
|
||
|
|
Symbol tanh3 = Activation("tanh3", conv3, ActivationActType::kTanh);
|
||
|
|
Symbol pool3 = Pooling("pool3", tanh3, Shape(2, 2), PoolingPoolType::kMax,
|
||
|
|
false, false, PoolingPoolingConvention::kValid, Shape(1, 1));
|
||
|
|
|
||
|
|
Symbol flatten = Flatten("flatten", pool3);
|
||
|
|
Symbol fc1 = FullyConnected("fc1", flatten, fc1_w, fc1_b, 500);
|
||
|
|
Symbol tanh4 = Activation("tanh4", fc1, ActivationActType::kTanh);
|
||
|
|
Symbol fc2 = FullyConnected("fc2", tanh4, fc2_w, fc2_b, 10);
|
||
|
|
|
||
|
|
Symbol lenet = SoftmaxOutput("softmax", fc2, data_label);
|
||
|
|
|
||
|
|
for (auto s : lenet.ListArguments()) {
|
||
|
|
LG << s;
|
||
|
|
}
|
||
|
|
|
||
|
|
/*setup basic configs*/
|
||
|
|
int val_fold = 1;
|
||
|
|
int W = 28;
|
||
|
|
int H = 28;
|
||
|
|
int batch_size = 42;
|
||
|
|
float learning_rate = 1e-4;
|
||
|
|
float weight_decay = 1e-4;
|
||
|
|
|
||
|
|
/*prepare the data*/
|
||
|
|
std::vector<float> data_vec, label_vec;
|
||
|
|
size_t data_count = GetData(&data_vec, &label_vec);
|
||
|
|
const float *dptr = data_vec.data();
|
||
|
|
const float *lptr = label_vec.data();
|
||
|
|
NDArray data_array = NDArray(Shape(data_count, 1, W, H), ctx_cpu,
|
||
|
|
false); // store in main memory, and copy to
|
||
|
|
// device memory while training
|
||
|
|
NDArray label_array =
|
||
|
|
NDArray(Shape(data_count), ctx_cpu,
|
||
|
|
false); // it's also ok if just store them all in device memory
|
||
|
|
data_array.SyncCopyFromCPU(dptr, data_count * W * H);
|
||
|
|
label_array.SyncCopyFromCPU(lptr, data_count);
|
||
|
|
data_array.WaitToRead();
|
||
|
|
label_array.WaitToRead();
|
||
|
|
|
||
|
|
size_t train_num = data_count * (1 - val_fold / 10.0);
|
||
|
|
train_data = data_array.Slice(0, train_num);
|
||
|
|
train_label = label_array.Slice(0, train_num);
|
||
|
|
val_data = data_array.Slice(train_num, data_count);
|
||
|
|
val_label = label_array.Slice(train_num, data_count);
|
||
|
|
|
||
|
|
LG << "here read fin";
|
||
|
|
|
||
|
|
/*init some of the args*/
|
||
|
|
// map<string, NDArray> args_map;
|
||
|
|
args_map["data"] = data_array.Slice(0, batch_size).Copy(ctx_dev);
|
||
|
|
args_map["data_label"] = label_array.Slice(0, batch_size).Copy(ctx_dev);
|
||
|
|
NDArray::WaitAll();
|
||
|
|
|
||
|
|
LG << "here slice fin";
|
||
|
|
/*
|
||
|
|
* we can also feed in some of the args other than the input all by
|
||
|
|
* ourselves,
|
||
|
|
* fc2-w , fc1-b for example:
|
||
|
|
* */
|
||
|
|
// args_map["fc2_w"] =
|
||
|
|
// NDArray(mshadow::Shape2(500, 4 * 4 * 50), ctx_dev, false);
|
||
|
|
// NDArray::SampleGaussian(0, 1, &args_map["fc2_w"]);
|
||
|
|
// args_map["fc1_b"] = NDArray(mshadow::Shape1(10), ctx_dev, false);
|
||
|
|
// args_map["fc1_b"] = 0;
|
||
|
|
|
||
|
|
lenet.InferArgsMap(ctx_dev, &args_map, args_map);
|
||
|
|
Optimizer* opt = OptimizerRegistry::Find("ccsgd");
|
||
|
|
opt->SetParam("momentum", 0.9)
|
||
|
|
->SetParam("rescale_grad", 1.0)
|
||
|
|
->SetParam("clip_gradient", 10)
|
||
|
|
->SetParam("lr", learning_rate)
|
||
|
|
->SetParam("wd", weight_decay);
|
||
|
|
|
||
|
|
Executor *exe = lenet.SimpleBind(ctx_dev, args_map);
|
||
|
|
auto arg_names = lenet.ListArguments();
|
||
|
|
|
||
|
|
for (int ITER = 0; ITER < max_epoch; ++ITER) {
|
||
|
|
size_t start_index = 0;
|
||
|
|
while (start_index < train_num) {
|
||
|
|
if (start_index + batch_size > train_num) {
|
||
|
|
start_index = train_num - batch_size;
|
||
|
|
}
|
||
|
|
args_map["data"] =
|
||
|
|
train_data.Slice(start_index, start_index + batch_size)
|
||
|
|
.Copy(ctx_dev);
|
||
|
|
args_map["data_label"] =
|
||
|
|
train_label.Slice(start_index, start_index + batch_size)
|
||
|
|
.Copy(ctx_dev);
|
||
|
|
start_index += batch_size;
|
||
|
|
NDArray::WaitAll();
|
||
|
|
|
||
|
|
exe->Forward(true);
|
||
|
|
exe->Backward();
|
||
|
|
// Update parameters
|
||
|
|
for (size_t i = 0; i < arg_names.size(); ++i) {
|
||
|
|
if (arg_names[i] == "data" || arg_names[i] == "data_label") continue;
|
||
|
|
opt->Update(i, exe->arg_arrays[i], exe->grad_arrays[i]);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
LG << "Iter " << ITER
|
||
|
|
<< ", accuracy: " << ValAccuracy(batch_size * 10, lenet);
|
||
|
|
}
|
||
|
|
delete exe;
|
||
|
|
delete opt;
|
||
|
|
}
|
||
|
|
|
||
|
|
private:
|
||
|
|
Context ctx_cpu;
|
||
|
|
Context ctx_dev;
|
||
|
|
std::map<std::string, NDArray> args_map;
|
||
|
|
NDArray train_data;
|
||
|
|
NDArray train_label;
|
||
|
|
NDArray val_data;
|
||
|
|
NDArray val_label;
|
||
|
|
|
||
|
|
size_t GetData(std::vector<float> *data, std::vector<float> *label) {
|
||
|
|
const char *train_data_path = "./data/mnist_data/mnist_train.csv";
|
||
|
|
std::ifstream inf(train_data_path);
|
||
|
|
std::string line;
|
||
|
|
inf >> line; // ignore the header
|
||
|
|
size_t _N = 0;
|
||
|
|
while (inf >> line) {
|
||
|
|
for (auto &c : line) c = (c == ',') ? ' ' : c;
|
||
|
|
std::stringstream ss;
|
||
|
|
ss << line;
|
||
|
|
float _data;
|
||
|
|
ss >> _data;
|
||
|
|
label->push_back(_data);
|
||
|
|
while (ss >> _data) data->push_back(_data / 256.0);
|
||
|
|
_N++;
|
||
|
|
}
|
||
|
|
inf.close();
|
||
|
|
return _N;
|
||
|
|
}
|
||
|
|
|
||
|
|
float ValAccuracy(int batch_size, Symbol lenet) {
|
||
|
|
size_t val_num = val_data.GetShape()[0];
|
||
|
|
|
||
|
|
size_t correct_count = 0;
|
||
|
|
size_t all_count = 0;
|
||
|
|
|
||
|
|
size_t start_index = 0;
|
||
|
|
while (start_index < val_num) {
|
||
|
|
if (start_index + batch_size > val_num) {
|
||
|
|
start_index = val_num - batch_size;
|
||
|
|
}
|
||
|
|
args_map["data"] =
|
||
|
|
val_data.Slice(start_index, start_index + batch_size).Copy(ctx_dev);
|
||
|
|
args_map["data_label"] =
|
||
|
|
val_label.Slice(start_index, start_index + batch_size).Copy(ctx_dev);
|
||
|
|
start_index += batch_size;
|
||
|
|
NDArray::WaitAll();
|
||
|
|
|
||
|
|
Executor *exe = lenet.SimpleBind(ctx_dev, args_map);
|
||
|
|
exe->Forward(false);
|
||
|
|
|
||
|
|
const auto &out = exe->outputs;
|
||
|
|
NDArray out_cpu = out[0].Copy(ctx_cpu);
|
||
|
|
NDArray label_cpu =
|
||
|
|
val_label.Slice(start_index - batch_size, start_index).Copy(ctx_cpu);
|
||
|
|
|
||
|
|
NDArray::WaitAll();
|
||
|
|
|
||
|
|
const mx_float *dptr_out = out_cpu.GetData();
|
||
|
|
const mx_float *dptr_label = label_cpu.GetData();
|
||
|
|
for (int i = 0; i < batch_size; ++i) {
|
||
|
|
float label = dptr_label[i];
|
||
|
|
int cat_num = out_cpu.GetShape()[1];
|
||
|
|
float p_label = 0, max_p = dptr_out[i * cat_num];
|
||
|
|
for (int j = 0; j < cat_num; ++j) {
|
||
|
|
float p = dptr_out[i * cat_num + j];
|
||
|
|
if (max_p < p) {
|
||
|
|
p_label = j;
|
||
|
|
max_p = p;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if (label == p_label) correct_count++;
|
||
|
|
}
|
||
|
|
all_count += batch_size;
|
||
|
|
|
||
|
|
delete exe;
|
||
|
|
}
|
||
|
|
return correct_count * 1.0 / all_count;
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
int main(int argc, char const *argv[]) {
|
||
|
|
TRY
|
||
|
|
Lenet lenet;
|
||
|
|
lenet.Run(argc > 1 ? strtol(argv[1], nullptr, 10) : 100000);
|
||
|
|
MXNotifyShutdown();
|
||
|
|
CATCH
|
||
|
|
return 0;
|
||
|
|
}
|