本文涉及到机器学习的基本概念,机器学习是一门专业性很强的学科,本篇文章只做最基本的说明,体会机器学习在图形学中的应用。
神经网络拟合原理
如果你对机器学习一无所知,不知道什么是偏导,什么是梯度下降,建议花点时间学习相关的概念。参考:机器学习基础
上篇文章中,高斯函数拟合的参数是通过解方程,或求最小二乘得到近似解,然后插值出连续的曲线。
引入神经网络,通过梯度下降的方式,来"探索"条件成立时,对应的值,
下图是个最简单的模型,包含"输入层"、"隐藏层"、"输出层"。
在这篇文章的主题中,隐藏层仅设置一层:
- 输入层为已知点的 "x" 坐标
- 隐藏层为高斯基函数的组合
- 输出层为已知点的 "y" 坐标
求出模型参数后,就可以拟合曲线,后面插值的操作和上一篇文章一样,不再赘述。
工程实现
可以观察到,如果点比较少,使用神经网络来拟合,有点大材小用,而且并不一定比直接解方程效果好。所以机器学习也不是万能的,更熟悉领域的背景知识,才能选择最合适的解决方案.
代码说明
代码基于"games102-陈昱文-作业"修改完善,IDE使用CLion
使用tensorflow来做模型训练,python接口比较方便。
所以整个工程的设计为: C++中实现交互,python封装模型训练。
- main.cpp 核心流程
- RBfNetwork 封装python交互接口
- rbf_nn.py 模型训练
开发环境
Mac CLion。笔者基于Mac平台开发,其他平台应该大同小异。笔者调通开发环境,花了三个晚上!!!
- python安装 使用Anaconda配置,傻瓜式(网上有很多教程),安装tensorflow
注意,tensorflow 支持python 3.5~3.8
- 报错 "ModuleNotFoundError: No module named 'encodings'"时,需要配置环境变量 Run-->Edit Configuration --> Environment Variables
-
MAC平台,链接Python库,使用dylib格式的动态库,使用.a文件运行会报错 Symbol not found: _PyByteArray_Type
-
注意引用的库的位置,可能你的电脑里装了很多版本的python,别弄混了
C++ python交互
如果你有基于c++&&Lua混编的经验,就很好理解其原理了。网上有不少文章讲C++ & python交互。
- 引入头文件
#include "python3.6m/Python.h"
- 初始化:
// 初始化python环境
Py_Initialize();
// 设置包含当前目录,即包含我们自己写的python文件
PyRun_SimpleString("import sys");
PyRun_SimpleString("sys.path.append('./')");
// 加载我们自己写的python文件,注意去掉.py后缀
PyObject *module_name = PyUnicode_DecodeFSDefault("rbf_nn");
// 导入模型
py_module = PyImport_Import(module_name);
// 后去稍后需要调用的python函数(训练算法接口)
py_func_train = PyObject_GetAttrString(py_module, "train");
- 调用python接口,代码不复杂,看着大概就能猜出来每一个api的意图:
std::vector<Eigen::Vector2f> RbfNetwork::Train(const std::vector<Eigen::Vector2f> &in_pos, int num_middle, int epochs,
float lb, float rb, float step) {
PyObject *func_args = PyTuple_New(5);
int n_points_in = in_pos.size();
PyObject *x_list = PyList_New(n_points_in);
PyObject *y_list = PyList_New(n_points_in);
for (size_t i = 0; i < in_pos.size(); i++) {
PyList_SetItem(x_list, i, PyFloat_FromDouble(in_pos[i].x()));
PyList_SetItem(y_list, i, PyFloat_FromDouble(in_pos[i].y()));
}
std::vector<float> x_pred;
for (float x = lb; x <= rb; x += step) {
x_pred.push_back(x);
}
PyObject *xp_list = PyList_New(x_pred.size());
for (size_t i = 0; i < x_pred.size(); i++) {
PyList_SetItem(xp_list, i, PyFloat_FromDouble(x_pred[i]));
}
PyTuple_SetItem(func_args, 0, x_list);
PyTuple_SetItem(func_args, 1, y_list);
PyTuple_SetItem(func_args, 2, PyLong_FromLong(num_middle));
PyTuple_SetItem(func_args, 3, PyLong_FromLong(epochs));
PyTuple_SetItem(func_args, 4, xp_list);
PyObject *fn_ret = PyObject_CallObject(py_func_train, func_args);
PyErr_Print();
if (!fn_ret) {
std::cout << "Failed to call Python function 'train'" << std::endl;
PyErr_Print();
Py_DECREF(x_list);
Py_DECREF(y_list);
Py_DECREF(xp_list);
Py_DECREF(func_args);
return {};
}
std::vector<Eigen::Vector2f> y_pred(x_pred.size());
for (size_t i = 0; i < y_pred.size(); i++) {
y_pred[i] = { x_pred[i], PyFloat_AsDouble(PyList_GetItem(fn_ret, i)) };
}
Py_DECREF(x_list);
Py_DECREF(y_list);
Py_DECREF(xp_list);
Py_DECREF(func_args);
return y_pred;
}
- 释放内存:
void RbfNetwork::Finalize() {
Py_DECREF(py_func_train);
Py_DECREF(py_module);
}