今天主要时实现三种神经元的c++代码,并且展示他的膜电位随着时间的变化而变化,如果时间充足我会记录一下电位,并且画出其膜电位随着时间的变化。
首先时父类Neuron神经元,考虑lif和izhikevich神经元共有的属性为膜电压和上次方法脉冲的时间,因此我将父类设计成如下
#ifndef NEURON_H
#define NEURON_H
class Neuron
{
protected:
double v; //膜电压
double last_fired; //上次发放脉冲的的时间
public:
Neuron() = default;
~Neuron() = default;
virtual bool update(float dt, float current_t) = 0;
double getVoltage() {
return v;
}
void setVoltag(double membraneVoltage){
v = membraneVoltage;
}
};
#endif
而lif神经元较为简单,易于实现,代码如下所示
#ifndef LIFNEURON_H
#define LIFNEURON_H
#include "Neuron.h"
#include "NeuronParams.h"
class LIFNeuron : public Neuron
{
private:
double i_e; //输入电流
NeuronParams* params; //参数
public:
LIFNeuron(NeuronParams *params_)
{
init(params_,-65.0);
}
void init(NeuronParams *params_,double v_init)
{
params = params_;
v = v_init;
i_e = 0;
last_fired = -params->refractory;
}
bool update(float dt, float current_t)
{
bool fired = false;
if(current_t >= params->refractory + last_fired) //判断是否在不应期
{
//累加电流
i_e += params->i_offset;
// 更新方程
v += dt * ((params->v_rest - v + i_e * params->r_m)/params->tau_m);
// 判断是否达到阈值
if(v > params->v_thresh) {
v = params->v_reset;
last_fired = current_t;
fired = true;
}
}
// 注入电流设置为0
i_e = 0;
return fired;
}
};
#endif
还有izhikevich神经元代码
#ifndef IZHIKEVICH_H
#define IZHIKEVICH_H
#include "Neuron.h"
#include "NeuronParams.h"
class Izhikevich : public Neuron
{
private:
double a, b, c, d; // 模型参数
double u;
double i_e; //输入电流
double last_fired; //上次发放脉冲的的时间
NeuronParams* params;
public:
Izhikevich(NeuronParams *params_)
{
init(params_,-65.0,-30.0);
}
void init(NeuronParams *params_,double v_init,double u_init)
{
params = params_;
v = v_init;
u = u_init;
i_e = 0;
last_fired = -params->refractory;
}
bool update(float dt, float current_t)
{
bool fired = false;
double v_old = v;
double u_old = u;
i_e += params->i_offset;
v += dt * ( 0.04 * v_old * v_old + 5.0 * v_old + 140.0 - u_old + i_e );
u += dt * params->a * ( params->b * v_old - u_old );
if(v >= params->v_thresh)
{
v = params->c;
v += params->d;
last_fired = current_t;
fired = true;
}
i_e = 0;
return false;
}
~Izhikevich() = default;
};
#endif
主函数验证代码:
#include<iostream>
#include <fstream>
#include "LIFNeuron.h"
#include "NeuronParams.h"
#include "Izhikevich.h"
int main(int argc, char const *argv[])
{
std::string filename = "izh.txt"; // 文件名
std::ofstream file(filename); // 创建文件流对象
if (!file.is_open())
{
std::cout<<"打开文件失败!"<<std::endl;
exit(-1);
}
NeuronParams *c_params = (NeuronParams *)malloc(sizeof(NeuronParams));
c_params->tau_m = 10.0f;
c_params->refractory = 2.0f;
c_params->v_rest = -65.0f;
c_params->v_thresh = -50.0f;
c_params->v_reset = -65.0f;
c_params->i_offset = 2.0f;
c_params->c_m = 0.250f;
c_params->r_m = 40.0f;
LIFNeuron lifNeuron(c_params);
// for (int i = 0; i < 100; i++)
// {
// lifNeuron.update(0.1,i*0.1);
// //file<<lifNeuron.getVoltage()<<std::endl;
// }
c_params->a = 0.02;
c_params->b = 0.2;
c_params->c = -65.0;
c_params->d = 8.0;
c_params->v_thresh = 30.0;
Izhikevich izh(c_params);
for (int i = 0; i < 100; i++)
{
izh.update(0.1,i*0.1);
printf("%lf\n",izh.getVoltage());
file<<izh.getVoltage()<<std::endl;
}
file.close(); // 关闭文件流
return 0;
}
以上代码中的实现细节很简单,但是涉及的参数量众多,我暂时还有想好如何去管理这些参数量。全部写成一个数据结构如下:
#ifndef NEURONPARAMS_H
#define NEURONPARAMS_H
struct NeuronParams
{
// LIF神经元的参数
float v_rest; // 静息电位
float v_reset; // 重置电位
float tau_m; // 膜时间常数
float refractory; //不应期时间
float c_m; //膜电容
float r_m; //膜电阻
// Izhikevich参数
double a;
double b;
double c;
double d;
//lif神经元和突触公共有的参数
float v_thresh; // 脉冲发放阈值
float i_offset; // 注入电流
};
#endif
无论是lif和izh都采用了欧拉法,很简单且易于实现。如果感兴趣,我可以把龙格库卡的四阶求微分的代码给出来。但是我的主要任务是分布式的实现,因此,暂时不给出。
神经元只是模拟很简单的一部分,接下来我会思考如何使用突触来连接这些神经元,并且能够实现脉冲的交互,这可能要花几天时间,突触也同样有很多的类型,比如静态突触,STDP等,我计划先实现静态突触,然后再去实现更有难度的突触,目前计划和神经元一样,给突触做一个父类,其他突触类型都继承于它。
明天计划做一个简单的静态突触,并且实现两个神经元之间的脉冲交互。