可微分神经计算机及其家族,适用于PyTorch
包括:
- 可微分神经计算机 (DNC)
- 稀疏访问内存 (SAM)
- 稀疏可微分神经计算机 (SDNC)
这是可微分神经计算机的一个实现,该方法在论文使用动态外部存储的神经网络的混合计算,Graves等人中有所描述。稀疏DNC(SDNC)和稀疏访问内存(SAM)则在使用稀疏读写扩展记忆增强神经网络中有所描述。
安装
pip install dnc
从源代码安装
git clone https://github.com/ixaxaar/pytorch-dnc
cd pytorch-dnc
pip install -r ./requirements.txt
pip install -e .
要使用完全基于GPU的SDNC或SAM,请安装FAISS:
conda install faiss-gpu -c pytorch
运行测试需要pytest
架构
使用方法
DNC
构造函数参数:
以下是构造函数参数:
以下是构造函数参数:
参数 | 默认值 | 描述 |
---|---|---|
input_size | None | 输入向量的大小 |
hidden_size | None | 隐藏单元的大小 |
rnn_type | 'lstm' | 控制器中使用的循环单元类型 |
num_layers | 1 | 控制器中循环单元的层数 |
num_hidden_layers | 2 | 控制器每层的隐藏层数 |
bias | True | 偏置 |
batch_first | True | 数据是否以批次优先的方式输入 |
dropout | 0 | 控制器层间的dropout |
bidirectional | False | 控制器是否为双向(尚未实现) |
nr_cells | 5 | 内存单元的数量 |
read_heads | 2 | 读取头的数量 |
cell_size | 10 | 每个内存单元的大小 |
nonlinearity | 'tanh' | 如果使用'rnn'作为rnn_type ,RNN的非线性函数 |
gpu_id | -1 | GPU的ID,-1表示CPU |
independent_linears | False | 是否使用独立的线性单元来推导接口向量 |
share_memory | True | 是否在控制器层之间共享内存 |
以下是前向传播参数:
参数 | 默认值 | 描述 |
---|---|---|
input | - | 输入向量 (B*T*X) 或 (T*B*X) |
hidden | (None,None,None) | 隐藏状态 (控制器隐藏状态, 内存隐藏状态, 读取向量) |
reset_experience | False | 是否重置内存 |
pass_through_memory | True | 是否通过内存传递 |
使用示例
from dnc import DNC
rnn = DNC(
input_size=64,
hidden_size=128,
rnn_type='lstm',
num_layers=4,
nr_cells=100,
cell_size=32,
read_heads=4,
batch_first=True,
gpu_id=0
)
(controller_hidden, memory, read_vectors) = (None, None, None)
output, (controller_hidden, memory, read_vectors) = \
rnn(torch.randn(10, 4, 64), (controller_hidden, memory, read_vectors), reset_experience=True)
调试
debug
选项会使网络在每个前向步骤的第一个批次返回其内存隐藏向量(numpy ndarray
数组)。这些向量可以被分析或可视化,例如使用visdom。
from dnc import DNC
rnn = DNC(
input_size=64,
hidden_size=128,
rnn_type='lstm',
num_layers=4,
nr_cells=100,
cell_size=32,
read_heads=4,
batch_first=True,
gpu_id=0,
debug=True
)
(controller_hidden, memory, read_vectors) = (None, None, None)
output, (controller_hidden, memory, read_vectors), debug_memory = \
rnn(torch.randn(10, 4, 64), (controller_hidden, memory, read_vectors), reset_experience=True)
前向传播返回的内存向量(np.ndarray
):
键 | Y轴(维度) | X轴(维度) |
---|---|---|
debug_memory['memory'] | 层数 * 时间 | 记忆单元数 * 单元大小 |
debug_memory['link_matrix'] | 层数 * 时间 | 记忆单元数 * 记忆单元数 |
debug_memory['precedence'] | 层数 * 时间 | 记忆单元数 |
debug_memory['read_weights'] | 层数 * 时间 | 读头数 * 记忆单元数 |
debug_memory['write_weights'] | 层数 * 时间 | 记忆单元数 |
debug_memory['usage_vector'] | 层数 * 时间 | 记忆单元数 |
SDNC
构造函数参数:
以下是构造函数参数:
参数 | 默认值 | 描述 |
---|---|---|
input_size | None | 输入向量的大小 |
hidden_size | None | 隐藏单元的大小 |
rnn_type | 'lstm' | 控制器中使用的循环单元类型 |
num_layers | 1 | 控制器中循环单元的层数 |
num_hidden_layers | 2 | 控制器每层的隐藏层数 |
bias | True | 偏置 |
batch_first | True | 数据是否以批次优先方式输入 |
dropout | 0 | 控制器层间的dropout |
bidirectional | False | 控制器是否为双向(尚未实现) |
nr_cells | 5000 | 内存单元数量 |
read_heads | 4 | 读头数量 |
sparse_reads | 4 | 每个读头的稀疏内存读取次数 |
temporal_reads | 4 | 时间读取次数 |
cell_size | 10 | 每个内存单元的大小 |
nonlinearity | 'tanh' | 如果rnn_type 为'rnn',RNN的非线性函数 |
gpu_id | -1 | GPU的ID,-1表示CPU |
independent_linears | False | 是否使用独立的线性单元来推导接口向量 |
share_memory | True | 控制器层之间是否共享内存 |
以下是前向传播参数:
参数 | 默认值 | 描述 |
---|---|---|
input | - | 输入向量 (B*T*X) 或 (T*B*X) |
hidden | (None,None,None) | 隐藏状态 (控制器隐藏状态, 内存隐藏状态, 读取向量) |
reset_experience | False | 是否重置内存 |
pass_through_memory | True | 是否通过内存 |
使用示例
from dnc import SDNC
rnn = SDNC(
input_size=64,
hidden_size=128,
rnn_type='lstm',
num_layers=4,
nr_cells=100,
cell_size=32,
read_heads=4,
sparse_reads=4,
batch_first=True,
gpu_id=0
)
(controller_hidden, memory, read_vectors) = (None, None, None)
output, (controller_hidden, memory, read_vectors) = \
rnn(torch.randn(10, 4, 64), (controller_hidden, memory, read_vectors), reset_experience=True)
调试
debug
选项会使网络在每个前向步骤的第一个批次返回其内存隐藏向量(numpy ndarray
数组)。这些向量可以被分析或可视化,例如使用visdom。
from dnc import SDNC
rnn = SDNC(
input_size=64,
hidden_size=128,
rnn_type='lstm',
num_layers=4,
nr_cells=100,
cell_size=32,
read_heads=4,
batch_first=True,
sparse_reads=4,
temporal_reads=4,
gpu_id=0,
debug=True
)
(controller_hidden, memory, read_vectors) = (None, None, None)
output, (controller_hidden, memory, read_vectors), debug_memory = \
rnn(torch.randn(10, 4, 64), (controller_hidden, memory, read_vectors), reset_experience=True)
前向传播返回的内存向量(np.ndarray
):
键 | Y轴(维度) | X轴(维度) |
---|---|---|
debug_memory['memory'] | 层数 * 时间 | 单元数 * 单元大小 |
debug_memory['visible_memory'] | 层数 * 时间 | 稀疏读取+2*时间读取+1 * 单元数 |
debug_memory['read_positions'] | 层数 * 时间 | 稀疏读取+2*时间读取+1 |
debug_memory['link_matrix'] | 层数 * 时间 | 稀疏读取+2时间读取+1 * 稀疏读取+2时间读取+1 |
debug_memory['rev_link_matrix'] | 层数 * 时间 | 稀疏读取+2时间读取+1 * 稀疏读取+2时间读取+1 |
debug_memory['precedence'] | 层数 * 时间 | 单元数 |
debug_memory['read_weights'] | 层数 * 时间 | 读取头数 * 单元数 |
debug_memory['write_weights'] | 层数 * 时间 | 单元数 |
debug_memory['usage'] | 层数 * 时间 | 单元数 |
SAM
构造函数参数:
以下是构造函数参数:
参数 | 默认值 | 描述 |
---|---|---|
input_size | None | 输入向量的大小 |
hidden_size | None | 隐藏单元的大小 |
rnn_type | 'lstm' | 控制器中使用的循环单元类型 |
num_layers | 1 | 控制器中循环单元的层数 |
num_hidden_layers | 2 | 控制器每层的隐藏层数 |
bias | True | 偏置 |
batch_first | True | 数据是否以批次优先的方式输入 |
dropout | 0 | 控制器层之间的dropout |
bidirectional | False | 控制器是否为双向(尚未实现) |
nr_cells | 5000 | 内存单元的数量 |
read_heads | 4 | 读取头的数量 |
sparse_reads | 4 | 每个读取头的稀疏内存读取次数 |
cell_size | 10 | 每个内存单元的大小 |
nonlinearity | 'tanh' | 如果使用'rnn'作为rnn_type ,RNN的非线性函数 |
gpu_id | -1 | GPU的ID,-1表示使用CPU |
independent_linears | False | 是否使用独立的线性单元来推导接口向量 |
share_memory | True | 是否在控制器层之间共享内存 |
以下是前向传播参数:
参数 | 默认值 | 描述 |
---|---|---|
input | - | 输入向量 (B*T*X) 或 (T*B*X) |
hidden | (None,None,None) | 隐藏状态 (控制器隐藏状态, 内存隐藏状态, 读取向量) |
reset_experience | False | 是否重置内存 |
pass_through_memory | True | 是否通过内存 |
使用示例
from dnc import SAM
rnn = SAM(
input_size=64,
hidden_size=128,
rnn_type='lstm',
num_layers=4,
nr_cells=100,
cell_size=32,
read_heads=4,
sparse_reads=4,
batch_first=True,
gpu_id=0
)
(controller_hidden, memory, read_vectors) = (None, None, None)
output, (controller_hidden, memory, read_vectors) = \
rnn(torch.randn(10, 4, 64), (controller_hidden, memory, read_vectors), reset_experience=True)
调试
debug
选项会使网络在每次前向传播时返回第一个批次的内存隐藏向量(numpy ndarray
)。
这些向量可以被分析或可视化,例如使用visdom。
from dnc import SAM
rnn = SAM(
input_size=64,
hidden_size=128,
rnn_type='lstm',
num_layers=4,
nr_cells=100,
cell_size=32,
read_heads=4,
batch_first=True,
sparse_reads=4,
gpu_id=0,
debug=True
)
(controller_hidden, memory, read_vectors) = (None, None, None)
output, (controller_hidden, memory, read_vectors), debug_memory = \
rnn(torch.randn(10, 4, 64), (controller_hidden, memory, read_vectors), reset_experience=True)
前向传播返回的内存向量(np.ndarray
):
键 | Y轴(维度) | X轴(维度) |
---|---|---|
debug_memory['memory'] | 层数 * 时间 | 单元数 * 单元大小 |
debug_memory['visible_memory'] | 层数 * 时间 | 稀疏读取+2*时间读取+1 * 单元数 |
debug_memory['read_positions'] | 层数 * 时间 | 稀疏读取+2*时间读取+1 |
debug_memory['read_weights'] | 层数 * 时间 | 读取头数 * 单元数 |
debug_memory['write_weights'] | 层数 * 时间 | 单元数 |
debug_memory['usage'] | 层数 * 时间 | 单元数 |
任务
复制任务(带课程学习和泛化)
原始论文中描述的复制任务已包含在仓库中。
从项目根目录:
python ./tasks/copy_task.py -cuda 0 -optim rmsprop -batch_size 32 -mem_slot 64 # (类似原始实现)
python ./tasks/copy_task.py -cuda 0 -lr 0.001 -rnn_type lstm -nlayer 1 -nhlayer 2 -dropout 0 -mem_slot 32 -batch_size 1000 -optim adam -sequence_max_length 8 # (更快收敛)
对于SDNC:
python ./tasks/copy_task.py -cuda 0 -lr 0.001 -rnn_type lstm -memory_type sdnc -nlayer 1 -nhlayer 2 -dropout 0 -mem_slot 100 -mem_size 10 -read_heads 1 -sparse_reads 10 -batch_size 20 -optim adam -sequence_max_length 10
对于SDNC的课程学习:
python ./tasks/copy_task.py -cuda 0 -lr 0.001 -rnn_type lstm -memory_type sdnc -nlayer 1 -nhlayer 2 -dropout 0 -mem_slot 100 -mem_size 10 -read_heads 1 -sparse_reads 4 -temporal_reads 4 -batch_size 20 -optim adam -sequence_max_length 4 -curriculum_increment 2 -curriculum_freq 10000
要查看完整的选项列表,请运行:
python ./tasks/copy_task.py --help
复制任务可以使用Visdom来调试内存。
需要额外的步骤:
pip install visdom
python -m visdom.server
在浏览器中打开http://localhost:8097/,然后执行复制任务:
python ./tasks/copy_task.py -cuda 0
Visdom仪表板每隔-summarize_freq次迭代会以热图形式显示批次0的内存:
[图片]
泛化加法任务
加法任务如这个GitHub拉取请求中所述。
这个任务
- 创建大小为input_size的独热向量,每个代表一个数字
- 将一串向量输入网络
- 网络的输出相加得到解码输出的总和
该任务首先训练网络处理长度约100的序列,然后测试网络是否能泛化到长度约1000的序列。
python ./tasks/adding_task.py -cuda 0 -lr 0.0001 -rnn_type lstm -memory_type sam -nlayer 1 -nhlayer 1 -nhid 100 -dropout 0 -mem_slot 1000 -mem_size 32 -read_heads 1 -sparse_reads 4 -batch_size 20 -optim rmsprop -input_size 3 -sequence_max_length 100
泛化Argmax任务
第二个加法任务与第一个类似,但网络在最后一个时间步的输出应为输入的argmax。
python ./tasks/argmax_task.py -cuda 0 -lr 0.0001 -rnn_type lstm -memory_type dnc -nlayer 1 -nhlayer 1 -nhid 100 -dropout 0 -mem_slot 100 -mem_size 10 -read_heads 2 -batch_size 1 -optim rmsprop -sequence_max_length 15 -input_size 10 -iterations 10000
代码结构
1. DNC:
- dnc/dnc.py - 控制器代码。
- dnc/memory.py - 内存模块。
2. SDNC:
- dnc/sdnc.py - 控制器代码,继承自dnc.py。
- dnc/sparse_temporal_memory.py - 内存模块。
- dnc/flann_index.py - 使用kNN的内存索引。
3. SAM:
- dnc/sam.py - 控制器代码,继承自dnc.py。
- dnc/sparse_memory.py - 内存模块。
- dnc/flann_index.py - 使用kNN的内存索引。
4. 测试:
- 所有测试都在./tests文件夹中。
一般值得注意的事项
1. SDNC使用FLANN近似最近邻库,及其Python绑定pyflann3和FAISS。
FLANN可以通过pip自动安装作为依赖项,或从源代码安装(例如使用OpenMP进行多线程):
# 首先安装openmp: 例如在Arch上运行 `sudo pacman -S openmp`。
git clone git://github.com/mariusmuja/flann.git
cd flann
mkdir build
cd build
cmake ..
make -j 4
sudo make install
可以使用以下命令安装FAISS:
conda install faiss-gpu -c pytorch
FAISS速度更快,具有GPU实现,并且可与PyTorch张量互操作。我们默认尝试使用FAISS,如果没有FAISS则会退而使用FLANN。
2. 梯度中出现"nan"值很常见,请尝试使用不同的批量大小。
创建此仓库时参考的仓库:
- [deepmind/dnc](https://github.com/deepmind/dnc)
- [ypxie/pytorch-NeuCom](https://github.com/ypxie/pytorch-NeuCom)
- [jingweiz/pytorch-dnc](https://github.com/jingweiz/pytorch-dnc)