ns3-gym真的好难学,网上可以参考的例子也太少了,如果有用这个做路由的麻烦联系我交流一下吧,太痛苦了
一、安装
之前的文章已经提到过了,这里不赘述了
二、运行简单的例子
用两个终端的方式运行感觉更加直观,但是仍然有些坑:
- Terminal 1:最好用sudo模式,可能是哪里的权限不够,反正我只用waf指令时好时坏,也是挺神奇的,这里实际上是在运行/contrib/opengym/examples/目录下,"opengym"文件夹里的"sim.cc",在后续更复杂的例子里,其实ns3环境是配置在"mygym.cc"里面的,最终通过头文件"mygym.h"被"sim.cc"调用
- Terminal 2:Ubuntu 20.04自带的protobuf版本相对低了一点,直接运行会报错,所以在运行时先设置环境变量
PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION=python
。这里是执行/contrib/opengym/examples/目录下,"opengym"文件夹对应的test.py
bash
# Terminal 1
cd ~/ns-allinone-3.35/ns-3.35
sudo ./waf --run "opengym"
# Terminal 2
cd ~/ns-allinone-3.35/ns-3.35/contrib/opengym/examples/opengym/
PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION=python ./test.py --start=0
三、例子代码详解
整体的代码结构图如下,摘自对应的论文。这里的接口主要用的是ZMQ Socket通信,另一个ns3-ai用的是共享内存的方法,据说会比ns3-gym快100倍,但是资料也不多,有时间再试一下
1.基本的接口
python端:
python
import gym
import ns3gym
import MyAgent
from ns3gym import ns3env
#env = gym.make('ns3-v0') <--- causes some errors with the new OpenAI Gym framework, please use ns3env.Ns3Env()
env = ns3env.Ns3Env()
obs = env.reset()
agent = MyAgent.Agent()
while True:
action = agent.get_action(obs)
obs, reward, done, info = env.step(action)
if done:
break
env.close()
ns3端:
cpp
Ptr<OpenGymSpace> GetObservationSpace();
Ptr<OpenGymSpace> GetActionSpace();
Ptr<OpenGymDataContainer> GetObservation();
float GetReward();
bool GetGameOver();
std::string GetExtraInfo();
bool ExecuteActions(Ptr<OpenGymDataContainer> action);
2.Example1:"opengym"详解
这个需要sim.cc和test.py对照着来看,懒得打字了,我直接把关键的函数、属性、两个端口的交互画在纸上了:
首先初始化环境会有一个observation(step0),然后每一轮按照action-obs+reward+done+info的方式迭代(step++),迭代是否结束是sim.cc里面的done布尔值决定的。
obs收集到的状态是在(low, high)范围内生成的5个随机数,reward是每次迭代+1(我后来改成了reward=action),action是在离散空间下的[0,1,2,3,4],agent通过sample随机选择动作。
这里还需要对相应的数据类型有一些了解:
我稍微改动了一下代码,让每一轮迭代更清晰了一些。
python端:
ns3端:
PS. 这里和普通的gym环境不太一样:在sim.cc里定义的各个空间函数属性不能改,这个应该是定义在如下的接口回调函数里了,要实现什么功能在对应的函数内部改动就行
cpp
// OpenGym Env
Ptr<OpenGymInterface> openGym = CreateObject<OpenGymInterface> (openGymPort);
openGym->SetGetActionSpaceCb( MakeCallback (&MyGetActionSpace) );
openGym->SetGetObservationSpaceCb( MakeCallback (&MyGetObservationSpace) );
openGym->SetGetGameOverCb( MakeCallback (&MyGetGameOver) );
openGym->SetGetObservationCb( MakeCallback (&MyGetObservation) );
openGym->SetGetRewardCb( MakeCallback (&MyGetReward) );
openGym->SetGetExtraInfoCb( MakeCallback (&MyGetExtraInfo) );
openGym->SetExecuteActionsCb( MakeCallback (&MyExecuteActions) );
Simulator::Schedule (Seconds(0.0), &ScheduleNextStateRead, envStepTime, openGym);
3.Example2:"opengym-2"详解
运行指令和之前一毛一样,只需要需修改代码路径,还是最好开两个终端(有时候ns3会突然发疯报错,我什么代码都没改它也报错。。。这个时候多半是终端卡住了,重开一个就行)
bash
# Terminal 1
cd ~/ns-allinone-3.35/ns-3.35
sudo ./waf --run "opengym-2"
# Terminal 2
cd ~/ns-allinone-3.35/ns-3.35/contrib/opengym/examples/opengym-2/
PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION=python ./test.py --start=0
这个比较特别的是把ns3仿真环境单独写成了mygym.cc,再在sim.cc里面通过头文件mygym.h进行引用,其实具体的obs\reward\action定义都和"opengym"差不多,只是用到了更丰富的数据类型。后面如果要用到比较复杂的ns3环境的话,估计也会这样写更方便调试。
sim.cc(不再具体创建环境了,只是定义一些宏观的参数):
cpp
// OpenGym Env
Ptr<OpenGymInterface> openGymInterface = CreateObject<OpenGymInterface> (openGymPort);
Ptr<MyGymEnv> myGymEnv = CreateObject<MyGymEnv> (Seconds(envStepTime));
myGymEnv->SetOpenGymInterface(openGymInterface);
mygym.h(创建一个自定义的 Gym 环境类,该类继承自OprnGymEnv):
cpp
class MyGymEnv : public OpenGymEnv
{
public:
MyGymEnv ();
MyGymEnv (Time stepTime);
virtual ~MyGymEnv ();
static TypeId GetTypeId (void);
virtual void DoDispose ();
Ptr<OpenGymSpace> GetActionSpace();
Ptr<OpenGymSpace> GetObservationSpace();
bool GetGameOver();
Ptr<OpenGymDataContainer> GetObservation();
float GetReward();
std::string GetExtraInfo();
bool ExecuteActions(Ptr<OpenGymDataContainer> action);
private:
void ScheduleNextStateRead();
Time m_interval;
};
mygym.cc(对应mygym.h分别进行定义,这里只把和"opengym/sim.cc"不一样的地方贴出来了,后面依然是对各个要素空间的定义,用到了tuplr\dict两种数据类型):
cpp
MyGymEnv::MyGymEnv ()
{
NS_LOG_FUNCTION (this);
m_interval = Seconds(0.1);
Simulator::Schedule (Seconds(0.0), &MyGymEnv::ScheduleNextStateRead, this);
}
MyGymEnv::MyGymEnv (Time stepTime)
{
NS_LOG_FUNCTION (this);
m_interval = stepTime;
Simulator::Schedule (Seconds(0.0), &MyGymEnv::ScheduleNextStateRead, this);
}
void
MyGymEnv::ScheduleNextStateRead ()
{
NS_LOG_FUNCTION (this);
Simulator::Schedule (m_interval, &MyGymEnv::ScheduleNextStateRead, this);
Notify();
}
MyGymEnv::~MyGymEnv ()
{
NS_LOG_FUNCTION (this);
}
TypeId
MyGymEnv::GetTypeId (void)
{
static TypeId tid = TypeId ("MyGymEnv")
.SetParent<OpenGymEnv> ()
.SetGroupName ("OpenGym")
.AddConstructor<MyGymEnv> ()
;
return tid;
}
void
MyGymEnv::DoDispose ()
{
NS_LOG_FUNCTION (this);
}
这两个简单的例子其实重在展示基础的交互过程和丰富的数据类型,并没有涉及到强化学习的内核,比如说reward如何基于state(obs)和action定义,action又是怎么作用于state的,要与具体的网络环境耦合还需要更深层次的探索