ns3-gym真的好难学,网上可以参考的例子也太少了,如果有用这个做路由的麻烦联系我交流一下吧,太痛苦了

一、安装
之前的文章已经提到过了,这里不赘述了

二、运行简单的例子

用两个终端的方式运行感觉更加直观,但是仍然有些坑:

  1. Terminal 1:最好用sudo模式,可能是哪里的权限不够,反正我只用waf指令时好时坏,也是挺神奇的,这里实际上是在运行/contrib/opengym/examples/目录下,"opengym"文件夹里的"sim.cc",在后续更复杂的例子里,其实ns3环境是配置在"mygym.cc"里面的,最终通过头文件"mygym.h"被"sim.cc"调用
  2. Terminal 2:Ubuntu 20.04自带的protobuf版本相对低了一点,直接运行会报错,所以在运行时先设置环境变量PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION=python。这里是执行/contrib/opengym/examples/目录下,"opengym"文件夹对应的test.py
# 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端:

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端:

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里定义的各个空间函数属性不能改,这个应该是定义在如下的接口回调函数里了,要实现什么功能在对应的函数内部改动就行

  // 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会突然发疯报错,我什么代码都没改它也报错。。。这个时候多半是终端卡住了,新建终端,先重新编译再输入指令就行)

sudo ./waf configure --enable-test --enable-examples
sudo ./waf build

# 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(不再具体创建环境了,只是定义一些宏观的参数):

  // OpenGym Env
  Ptr<OpenGymInterface> openGymInterface = CreateObject<OpenGymInterface> (openGymPort);
  Ptr<MyGymEnv> myGymEnv = CreateObject<MyGymEnv> (Seconds(envStepTime));
  myGymEnv->SetOpenGymInterface(openGymInterface);

mygym.h(创建一个自定义的 Gym 环境类,该类继承自OpenGymEnv):

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"不一样的地方贴出来了,后面依然是对各个要素空间的定义,用到了tuple\dict两种数据类型):

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的,要与具体的网络环境耦合还需要更深层次的探索

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐