几何建模与处理-神经网络拟合

390 阅读3分钟

本文涉及到机器学习的基本概念,机器学习是一门专业性很强的学科,本篇文章只做最基本的说明,体会机器学习在图形学中的应用。

神经网络拟合原理

如果你对机器学习一无所知,不知道什么是偏导,什么是梯度下降,建议花点时间学习相关的概念。参考:机器学习基础

上篇文章中,高斯函数拟合的参数是通过解方程,或求最小二乘得到近似解,然后插值出连续的曲线。

引入神经网络,通过梯度下降的方式,来"探索"条件mini=0n(yiαixi)2min\sum_{i=0}^{n} (y_i - \alpha_i x^i)^2成立时,对应的α\alpha值,α=[α0,α1,,αn]\alpha = [\alpha_0, \alpha_1, \dots, \alpha_n]

下图是个最简单的模型,包含"输入层"、"隐藏层"、"输出层"。

神经网络

在这篇文章的主题中,隐藏层仅设置一层:

  • 输入层为已知点的 "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

Anaconda工具

  • 报错 "ModuleNotFoundError: No module named 'encodings'"时,需要配置环境变量 Run-->Edit Configuration --> Environment Variables

环境变量

  • MAC平台,链接Python库,使用dylib格式的动态库,使用.a文件运行会报错 Symbol not found: _PyByteArray_Type

  • 注意引用的库的位置,可能你的电脑里装了很多版本的python,别弄混了

cmake编写

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);
}

代码地址

github.com/summer-go/G…