Substrate 区块链开发实战:从报错排查到自定义 Pallet 与运行时升级

本文深入解析Substrate区块链框架的核心设计,重点阐述运行时(WASM)、FRAME pallet开发、无分叉升级机制及存储迁移。涵盖交易池验证、权重与费用模型、共识层(Aura/Babe/Grandpa)分工,并提供自定义pallet编写、Polkadot.js交互、运行时升级实操及常见报错排查(如签名不一致、存储前缀变更、迁移失败等)。内容聚焦区块链底层开发关键技术,面向具备Rust基础的开发者。

1. 从一条报错日志说起:为什么我要啃 Substrate

去年冬天,我在调试一条基于 Substrate 的链时,节点日志里反复出现同一行 BadProof ,交易池里的交易像被黑洞吞掉一样,广播出去就没了下文。那会儿我对 Substrate 的理解还停留在“波卡用的那个框架”,知道它用 Rust 写、能一键起链、有 WASM 运行时,但真到排查问题时,才发现自己根本没摸到它的骨架。后来花了整整两周,把 sc-consensus 、 sp-runtime 、 frame-system 这几个 crate 的源码翻了个遍,才把这条报错背后的签名验证链路理清楚。从那以后我就养成了一个习惯:每碰一个 Substrate 项目,先把它的运行时结构和共识层拆开看一遍,再动手写业务逻辑。

这篇东西就是那两周折腾的沉淀。我会从 Substrate 的整体设计思路讲起,拆解它的运行时、存储、共识、交易池这几个核心模块,然后给出一套从零起链到自定义 pallet 的完整实操流程,最后把我在实际项目里踩过的坑整理成一份速查表。适合谁看?如果你已经会写 Rust,想搞清楚区块链底层框架到底怎么运转,或者你正在用 Substrate 做一条业务链、却总被运行时升级和存储迁移卡住,那这篇内容应该能帮你省下不少翻源码的时间。如果你连 Rust 的所有权都还没搞明白,建议先去补一下语言基础,Substrate 的泛型和 trait 约束密度不低,硬啃容易劝退。

2. Substrate 的整体设计思路拆解

2.1 为什么是“框架”而不是“链”

很多人第一次接触 Substrate,会下意识把它当成一条现成的链,就像拿一条公链的代码改改参数就能发车。这个理解偏差挺致命的。Substrate 本质上是一个 区块链开发框架 ,它提供的是构建链所需的通用组件:网络层、共识层、存储层、运行时执行环境、交易池、RPC 接口。它不预设你的业务逻辑,也不规定你必须用哪种共识。你可以把它类比成 Web 开发里的 Spring 或者 Django——框架把路由、ORM、中间件这些脏活累活都封装好了,你只需要写业务代码。

这个定位带来的直接好处是 组件可替换 。网络层默认用 libp2p,但你可以换成别的;共识层默认提供 Aura、Babe、Grandpa 几种实现,也可以自己写;运行时可以用 FRAME 框架快速搭,也可以手写裸运行时。我在一个联盟链项目里就把默认的 Grandpa 换成了自定义的权威证明共识,因为参与方都是已知机构,不需要拜占庭容错的最终性 gadget,换成简单的多签确认反而延迟更低。

2.2 运行时才是链的“大脑”

Substrate 最核心的设计决策,是把 状态转换逻辑编译成 WASM 字节码 ,存在链上,由节点解释执行。这个设计叫“运行时”(Runtime)。传统链把业务逻辑硬编码在节点客户端里,升级要硬分叉;Substrate 把逻辑抽出来变成链上存储的一部分,升级只需要发一笔 set_code 交易,把新的 WASM blob 写进去,下一个区块就用新逻辑执行。这就是所谓的 无分叉运行时升级 。

我第一次看到这个机制时觉得挺玄乎,后来想明白了:节点客户端只负责网络、共识、存储这些“不变”的部分,真正决定“什么交易合法、状态怎么变”的运行时,是可以在链上热替换的。这就像手机的操作系统内核不变,但上面的 App 可以随时更新。代价是运行时执行要走 WASM 解释器,性能比原生代码差一些,所以 Substrate 用了 wasmtime 做 JIT 编译,把热点路径的性能拉回来不少。

2.3 FRAME 把开发效率拉满

裸写运行时太痛苦了,你得手动处理存储、派发调用、定义事件。Substrate 提供了 FRAME (Framework for Runtime Aggregation of Modularized Entities),用宏和 trait 把常见模式抽象出来。一个 pallet 就是一个业务模块,里面定义存储项、可调用函数、事件、错误类型,然后用 construct_runtime! 宏把它们组装成完整运行时。

FRAME 的设计哲学是 约定优于配置 。比如存储项用 #[pallet::storage] 标注,宏会自动生成读写接口和元数据;可调用函数用 #[pallet::call] 标注,宏会处理参数解码、权重扣费、事件派发。我刚开始用的时候觉得宏太魔法,后来看多了 #[pallet::storage] 展开后的代码,才理解它其实是在帮你生成 StorageMap 、 StorageValue 这些类型的访问器,省掉大量样板代码。

3. 核心模块的细节与实操要点

3.1 存储层:键值数据库之上的状态抽象

Substrate 的存储底层是 RocksDB (也有 ParityDB 可选),但你在写 pallet 时几乎不会直接碰它。FRAME 提供了几种存储抽象:

  • StorageValue<T> :存单个值,比如 TotalIssuance 。
  • StorageMap<K, V> :键值映射,比如 AccountBalances 。
  • StorageDoubleMap<K1, K2, V> :双键映射,比如 Approvals (owner + spender)。
  • CountedStorageMap :带计数的映射,方便遍历。

这里有个关键点: 所有存储操作都在一个“存储覆盖层”里进行,区块执行完才统一提交 。这意味着你在同一个区块里读到的值,是包含前面交易修改后的结果。我踩过一个坑:在一个 pallet 里先读 StorageValue 判断余额,然后调用另一个 pallet 扣款,结果扣款后没重新读,导致后续逻辑用了过期数据。正确做法是每次依赖前一个操作的结果时,都重新从存储读一次。

存储的另一个重点是 前缀 。每个 pallet 的存储项都会自动加一个前缀,避免不同 pallet 的键冲突。这个前缀由 construct_runtime! 里的 pallet 名决定。如果你改了 pallet 名,存储前缀就变了,链上升级时会导致读不到旧数据。我在一次重构里把 pallet_assets 改名成 pallet_tokens ,结果升级后所有资产余额归零,排查了半天才想起前缀这回事。所以 pallet 名一旦上线就别改 ,要改就得写迁移逻辑。

3.2 交易池与有效性检查

交易池(Transaction Pool)负责接收、验证、排序待打包的交易。一笔交易进池要过几道关:

  1. 签名验证 :检查签名是否对应发送者。
  2. nonce 检查 :确保 nonce 连续,防止重放。
  3. 有效性检查 :调用运行时的 validate_transaction ,检查余额、权重等。
  4. 优先级排序 :按 tip 和权重算优先级,决定打包顺序。

我开头提到的 BadProof 就是卡在第一步。后来发现是客户端用的签名类型和运行时期望的不一致——客户端用 sr25519 ,运行时配置成了 ed25519 。Substrate 支持多种签名方案,但 运行时和客户端必须对齐 ,否则签名验证永远过不了。这个配置在 construct_runtime! 的 UncheckedExtrinsic 类型参数里,改的时候要两边一起改。

交易池还有个容易忽略的点: 它不保证交易一定上链 。交易进池只代表通过了初步检查,真正打包时还要再跑一遍 validate_transaction ,因为状态可能已经变了。所以你在写 pallet 的 validate 钩子时,不能假设状态和进池时一样,得重新检查。

3.3 共识层:Aura、Babe 与 Grandpa 的分工

Substrate 默认提供两套共识组合:

  • Aura + Grandpa :Aura 负责出块(轮流出块人),Grandpa 负责最终性(拜占庭容错的投票)。
  • Babe + Grandpa :Babe 是插槽拍卖式的出块,适合公链场景。

Aura 的逻辑很简单:一组权威节点按顺序轮流出块,每个槽位一个块。它的优点是简单、延迟低;缺点是如果出块人掉线,那个槽位就空了,没有备选。Babe 则允许一个槽位有多个候选出块人,通过 VRF 随机选一个,抗掉线能力更强,但实现复杂。

Grandpa 是最终性 gadget,它让节点对“哪个块已经不可回滚”达成共识。Grandpa 的投票是两阶段的:prevote 和 precommit,达到 2/3 权重就最终确认。我在测试网跑的时候发现 Grandpa 的最终性延迟大概 2-3 个区块,比出块慢一拍,这是正常的,因为最终性需要收集足够多的投票。

注意:如果你做的是单节点测试链,Grandpa 可能一直不产生最终性,因为投票权重不够。这时候可以关掉 Grandpa,只用 Aura 出块,开发体验会好很多。

3.4 权重与费用:别让一笔交易打爆区块

Substrate 用**权重(Weight) 衡量交易消耗的计算资源,用 费用(Fee)**衡量经济成本。权重是二维的: ref_time (计算时间)和 proof_size (存储证明大小)。每个区块有最大权重限制,交易打包时不能超过这个限制。

写 pallet 时,你必须为每个可调用函数标注权重。FRAME 提供了 #[pallet::weight] 属性,可以写固定值,也可以用 WeightInfo trait 动态计算。我建议 用 benchmark 自动生成权重 ,而不是拍脑袋写固定值。因为固定值要么估高了浪费区块空间,要么估低了导致交易失败。Substrate 的 benchmark 工具能跑出每个函数的实际权重,生成 weights.rs 文件,直接引用就行。

费用方面,默认的费用模型是 base_fee + length_fee + weight_fee ,可以通过 transaction_payment pallet 调整。我做过一个高频交易的业务链,把 base_fee 调到很低, weight_fee 调高,这样小额交易便宜、大计算量交易贵,符合业务预期。

4. 从零起链到自定义 Pallet 的完整实操

4.1 环境准备与依赖安装

先装 Rust 工具链和 Substrate 的依赖:

# 安装 Rust
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source ~/.cargo/env

# 安装 wasm 目标
rustup target add wasm32-unknown-unknown

# 安装 substrate 相关工具
cargo install --git https://github.com/paritytech/substrate substrate-node-template

实际上更推荐用 substrate-node-template 仓库直接克隆,它已经配好了依赖和构建脚本:

git clone https://github.com/substrate-developer-hub/substrate-node-template
cd substrate-node-template
cargo build --release

第一次编译会比较久,大概 20-40 分钟,取决于机器性能。编译过程中会下载大量 crate,建议挂个国内镜像源加速。我在 ~/.cargo/config.toml 里配了 [source.crates-io] replace-with = 'ustc' ,速度能快不少。

编译完成后,用 --dev 模式起链:

./target/release/node-template --dev

--dev 模式会自动生成一个开发账户、预置一些余额、用即时出块( --instant-seal ),适合本地调试。你会看到日志里不断打印出块信息,说明链跑起来了。

4.2 用 Polkadot.js 连接并交互

起链后,打开 Polkadot.js 应用,连接到 ws://127.0.0.1:9944 。在“开发者”->“链状态”里能看到当前区块高度在涨,在“账户”里能看到预置的 Alice、Bob 等开发账户。

试着发一笔转账:选 Alice,收款人选 Bob,金额填 1000,签名并发送。几秒后就能在“网络”->“浏览器”里看到这笔交易被打包。这一步验证了整条链路是通的:交易池接收、运行时验证、出块、状态更新。

4.3 写一个自定义 Pallet:从模板到业务逻辑

假设我们要做一个“打卡”pallet,用户可以每天打卡一次,连续打卡有奖励。先创建 pallet 目录:

cd substrate-node-template
mkdir -p pallets/checkin/src
touch pallets/checkin/Cargo.toml
touch pallets/checkin/src/lib.rs

Cargo.toml 里声明依赖:

[package]
name = "pallet-checkin"
version = "0.1.0"
edition = "2021"

[dependencies]
codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false }
scale-info = { version = "2.0.0", default-features = false, features = ["derive"] }
frame-support = { version = "4.0.0-dev", default-features = false }
frame-system = { version = "4.0.0-dev", default-features = false }

[features]
default = ["std"]
std = [
    "codec/std",
    "scale-info/std",
    "frame-support/std",
    "frame-system/std",
]

lib.rs 里写核心逻辑:

#![cfg_attr(not(feature = "std"), no_std)]

pub use pallet::*;

#[frame_support::pallet]
pub mod pallet {
    use frame_support::pallet_prelude::*;
    use frame_system::pallet_prelude::*;

    #[pallet::pallet]
    pub struct Pallet<T>(_);

    #[pallet::config]
    pub trait Config: frame_system::Config {
        type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
    }

    #[pallet::storage]
    pub type LastCheckin<T: Config> = StorageMap<_, Blake2_128Concat, T::AccountId, BlockNumberFor<T>>;

    #[pallet::storage]
    pub type CheckinCount<T: Config> = StorageMap<_, Blake2_128Concat, T::AccountId, u32>;

    #[pallet::event]
    #[pallet::generate_deposit(pub(super) fn deposit_event)]
    pub enum Event<T: Config> {
        CheckedIn { who: T::AccountId, count: u32 },
    }

    #[pallet::error]
    pub enum Error<T> {
        AlreadyCheckedInToday,
    }

    #[pallet::call]
    impl<T: Config> Pallet<T> {
        #[pallet::call_index(0)]
        #[pallet::weight(10_000)]
        pub fn check_in(origin: OriginFor<T>) -> DispatchResult {
            let who = ensure_signed(origin)?;
            let now = frame_system::Pallet::<T>::block_number();
            let one_day: BlockNumberFor<T> = 600u32.into(); // 假设 6 秒一个块,一天 600 块

            if let Some(last) = LastCheckin::<T>::get(&who) {
                ensure!(now - last >= one_day, Error::<T>::AlreadyCheckedInToday);
            }

            let count = CheckinCount::<T>::get(&who).unwrap_or(0) + 1;
            LastCheckin::<T>::insert(&who, now);
            CheckinCount::<T>::insert(&who, count);

            Self::deposit_event(Event::CheckedIn { who, count });
            Ok(())
        }
    }
}

然后在 runtime/src/lib.rs 里注册这个 pallet:

// 引入 pallet
pub use pallet_checkin;

// 在 construct_runtime! 里加一行
construct_runtime!(
    pub enum Runtime where
        Block = Block,
        NodeBlock = opaque::Block,
        UncheckedExtrinsic = UncheckedExtrinsic,
    {
        System: frame_system,
        Timestamp: pallet_timestamp,
        Balances: pallet_balances,
        Checkin: pallet_checkin,  // 新增
    }
);

// 实现 Config
impl pallet_checkin::Config for Runtime {
    type RuntimeEvent = RuntimeEvent;
}

编译、起链、在 Polkadot.js 里调用 checkin.checkIn() ,就能看到事件和存储更新。这个流程走通后,你就掌握了 Substrate 开发的核心循环:写 pallet -> 注册 -> 编译 -> 测试。

4.4 运行时升级:把新逻辑推上链

改完 pallet 后,重新编译得到新的 WASM:

cargo build --release -p node-template-runtime

WASM 文件在 target/release/wbuild/node-template-runtime/node_template_runtime.compact.compressed.wasm 。然后在 Polkadot.js 的“开发者”->“sudo”里,用 system.setCode 提交这个 WASM。如果是 sudo 链,直接调用;如果是治理链,得走提案投票。

升级后,链上运行时就变成新的了,但 存储不会自动迁移 。如果你改了存储结构(比如加了新字段、改了类型),必须写 on_runtime_upgrade 钩子做迁移。我见过最惨的案例是有人改了 StorageMap 的键类型,升级后所有数据读不出来,链直接废了。所以 改存储结构前一定要写迁移逻辑,并在测试网验证 。

5. 常见问题与排查技巧实录

5.1 交易一直 pending 不上链

这是最常见的问题。排查顺序:

  1. 看交易池状态 :在 Polkadot.js 的“网络”->“交易池”里看交易是否在池里。如果不在,说明进池就被拒了。
  2. 看 nonce :如果 nonce 不连续,交易会卡住。比如你发了 nonce 5,但 nonce 4 还没上链,5 就会一直等。
  3. 看权重和费用 :如果交易权重超过区块限制,或者余额不够付手续费,会被拒。
  4. 看签名 : BadProof 就是签名问题,检查客户端和运行时的签名类型是否一致。

我遇到过一次交易池满了导致新交易进不来,后来调大了 pool limits 的 max_ready 和 max_future 参数才解决。

5.2 运行时升级后节点起不来

通常是 WASM 编译目标不对。Substrate 运行时必须编译成 wasm32-unknown-unknown 目标,如果你用了 wasm32-wasi 或者没加 --release ,生成的 WASM 可能不兼容。另外, Cargo.toml 里的 crate-type 要包含 cdylib :

[lib]
crate-type = ["cdylib", "rlib"]

漏了 cdylib 就不会生成 WASM 文件,升级时找不到 blob。

5.3 存储迁移写错导致数据丢失

迁移逻辑必须 幂等 且 可重入 。我建议用 StorageVersion 做版本标记:

#[pallet::storage]
pub type StorageVersion<T> = StorageValue<_, u32, ValueQuery>;

#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
    fn on_runtime_upgrade() -> Weight {
        if StorageVersion::<T>::get() == 0 {
            // 执行迁移
            StorageVersion::<T>::put(1);
        }
        T::DbWeight::get().reads_writes(1, 1)
    }
}

这样即使升级钩子被调用多次,也只会迁移一次。千万别在迁移里做复杂计算,因为 on_runtime_upgrade 有区块权重限制,超了会导致升级失败。

5.4 常见问题速查表

问题现象 可能原因 排查方法 解决方案
交易 pending 不上链 nonce 不连续 查交易池 nonce 补发缺失 nonce 的交易
BadProof 签名类型不匹配 对比客户端和运行时配置 统一签名方案
升级后节点崩溃 WASM 目标错误 检查 crate-type 和编译目标 用 wasm32-unknown-unknown
存储读不到数据 pallet 名改了 检查存储前缀 回滚 pallet 名或写迁移
区块不出 共识节点掉线 查 Aura/Babe 日志 重启节点或增加出块人
权重超限 函数权重估低 跑 benchmark 用生成的权重替换固定值

实操心得:每次改完 pallet,先在 --dev 模式跑一遍完整流程,再用 try-runtime 工具在测试网模拟升级。 try-runtime 能在不实际升级的情况下检查迁移逻辑,省掉很多回滚的麻烦。

6. 我在实际项目里踩过的几个坑

第一个坑是 存储前缀 。前面提过,改 pallet 名会导致前缀变化。我当时的解决方案是写了一个迁移脚本,把旧前缀下的数据逐个搬到新前缀,但这个过程很慢,因为要遍历所有键。后来学乖了,pallet 名从项目第一天就定死,再也不改。

第二个坑是 权重计算 。我一开始图省事,所有函数都写 10_000 的固定权重。结果上线后发现,有些复杂函数实际消耗远超标称值,导致区块执行超时。后来老老实实跑 benchmark,每个函数生成独立权重,问题才解决。benchmark 的配置有点繁琐,但一次配好后面就省心了。

第三个坑是 事件索引 。Substrate 的事件默认不带索引,前端要过滤事件得遍历整个区块。我在一个需要实时监听转账的项目里,给 Balances 的 Transfer 事件加了索引,前端就能按账户直接查。加索引的方法是在事件定义里用 #[pallet::event] 的 index 属性,但要注意索引数量有限制,别滥用。

第四个坑是 测试覆盖率 。Substrate 的 pallet 测试用 mock 运行时,我一开始只测了正常路径,没测边界条件。结果上线后遇到一个除零错误,因为某个存储值在特定情况下会是 0。后来补了 #[test] 用例,覆盖了余额为 0、nonce 溢出、重复调用等场景,才把稳定性拉上来。

7. 后续可以怎么扩展

如果你已经把基础流程跑通了,下一步可以试试这几个方向。一是 自定义共识 ,把 Aura 换成自己的出块逻辑,比如基于信誉分的轮换机制。二是 跨链消息 ,用 XCM 格式和其他链通信,Substrate 内置了 XCM 支持,但配置比较复杂,建议先从简单的资产转移开始。三是 零知识证明集成 ,在 pallet 里调用 arkworks 或 bellman 做隐私交易验证,这个门槛较高,需要一定的密码学基础。

我个人的体会是,Substrate 的学习曲线前陡后缓。刚开始被泛型和宏绕得头晕,但一旦理解了“运行时是链上 WASM、pallet 是业务模块、权重是资源计量”这三个核心概念,后面就是查文档和写代码的体力活了。真正难的不是语法,而是 对区块链状态机思维的理解 ——每一笔交易都是对状态的转换,每一次升级都是对规则的修改,而你要保证这个过程永远可验证、可回滚、可迁移。这个思维方式一旦建立起来,用不用 Substrate 都能受益。

深度学习工具包Deprecation notice.-----This toolbox is outdated and no longer maintained.There are much better tools available for deep learning than this toolbox, e.g. [Theano](http://deeplearning.net/software/theano/), [torch](http://torch.ch/) or [tensorflow](http://www.tensorflow.org/)I would suggest you use one of the tools mentioned above rather than use this toolbox.Best, Rasmus.DeepLearnToolbox================A Matlab toolbox for Deep Learning.Deep Learning is a new subfield of machine learning that focuses on learning deep hierarchical models of data.It is inspired by the human brain's apparent deep (layered, hierarchical) architecture.A good overview of the theory of Deep Learning theory is[Learning Deep Architectures for AI](http://www.iro.umontreal.ca/~bengioy/papers/ftml_book.pdf)For a more informal introduction, see the following videos by Geoffrey Hinton and Andrew Ng.* [The Next Generation of Neural Networks](http://www.youtube.com/watch?v=AyzOUbkUf3M) (Hinton, 2007)* [Recent Developments in Deep Learning](http://www.youtube.com/watch?v=VdIURAu1-aU) (Hinton, 2010)* [Unsupervised Feature Learning and Deep Learning](http://www.youtube.com/watch?v=ZmNOAtZIgIk) (Ng, 2011)If you use this toolbox in your research please cite [Prediction as a candidate for learning deep hierarchical models of data](http://www2.imm.dtu.dk/pubdb/views/publication_details.php?id=6284)```@MASTERSTHESIS\{IMM2012-06284, author = "R. B. Palm", title = "Prediction as a candidate for learning deep hierarchical models of data", year = "2012",}```Contact: rasmusbergpalm at gmail dot comDirectories included in the toolbox-----------------------------------`NN/` - A library for Feedforward Backpropagation Neural Networks`CNN/` - A library for Convolutional Neural Networks`DBN/` - A library for Deep Belief Networks`SAE/` - A library for Stacked Auto-Encoders`CAE/` - A library for Convolutional Auto-Encoders`util/` - Utility functions used by the libraries`data/` - Data used by the examples`tests/` - unit tests to verify toolbox is workingFor references on each library check REFS.mdSetup-----1. Download.2. addpath(genpath('DeepLearnToolbox'));Example: Deep Belief Network---------------------```matlabfunction test_example_DBNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit RBM and visualize its weightsrand('state',0)dbn.sizes = [100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);figure; visualize(dbn.rbm{1}.W'); % Visualize the RBM weights%% ex2 train a 100-100 hidden unit DBN and use its weights to initialize a NNrand('state',0)%train dbndbn.sizes = [100 100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);%unfold dbn to nnnn = dbnunfoldtonn(dbn, 10);nn.activation_function = 'sigm';%train nnopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.10, 'Too big error');```Example: Stacked Auto-Encoders---------------------```matlabfunction test_example_SAEload mnist_uint8;train_x = double(train_x)/255;test_x = double(test_x)/255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit SDAE and use it to initialize a FFNN% Setup and train a stacked denoising autoencoder (SDAE)rand('state',0)sae = saesetup([784 100]);sae.ae{1}.activation_function = 'sigm';sae.ae{1}.learningRate = 1;sae.ae{1}.inputZeroMaskedFraction = 0.5;opts.numepochs = 1;opts.batchsize = 100;sae = saetrain(sae, train_x, opts);visualize(sae.ae{1}.W{1}(:,2:end)')% Use the SDAE to initialize a FFNNnn = nnsetup([784 100 10]);nn.activation_function = 'sigm';nn.learningRate = 1;nn.W{1} = sae.ae{1}.W{1};% Train the FFNNopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.16, 'Too big error');```Example: Convolutional Neural Nets---------------------```matlabfunction test_example_CNNload mnist_uint8;train_x = double(reshape(train_x',28,28,60000))/255;test_x = double(reshape(test_x',28,28,10000))/255;train_y = double(train_y');test_y = double(test_y');%% ex1 Train a 6c-2s-12c-2s Convolutional neural network %will run 1 epoch in about 200 second and get around 11% error. %With 100 epochs you'll get around 1.2% errorrand('state',0)cnn.layers = { struct('type', 'i') %input layer struct('type', 'c', 'outputmaps', 6, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %sub sampling layer struct('type', 'c', 'outputmaps', 12, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %subsampling layer};cnn = cnnsetup(cnn, train_x, train_y);opts.alpha = 1;opts.batchsize = 50;opts.numepochs = 1;cnn = cnntrain(cnn, train_x, train_y, opts);[er, bad] = cnntest(cnn, test_x, test_y);%plot mean squared errorfigure; plot(cnn.rL);assert(er<0.12, 'Too big error');```Example: Neural Networks---------------------```matlabfunction test_example_NNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);% normalize[train_x, mu, sigma] = zscore(train_x);test_x = normalize(test_x, mu, sigma);%% ex1 vanilla neural netrand('state',0)nn = nnsetup([784 100 10]);opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samples[nn, L] = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.08, 'Too big error');%% ex2 neural net with L2 weight decayrand('state',0)nn = nnsetup([784 100 10]);nn.weightPenaltyL2 = 1e-4; % L2 weight decayopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex3 neural net with dropoutrand('state',0)nn = nnsetup([784 100 10]);nn.dropoutFraction = 0.5; % Dropout fraction opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex4 neural net with sigmoid activation functionrand('state',0)nn = nnsetup([784 100 10]);nn.activation_function = 'sigm'; % Sigmoid activation functionnn.learningRate = 1; % Sigm require a lower learning rateopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex5 plotting functionalityrand('state',0)nn = nnsetup([784 20 10]);opts.numepochs = 5; % Number of full sweeps through datann.output = 'softmax'; % use softmax outputopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex6 neural net with sigmoid activation and plotting of validation and training error% split training data into training and validation datavx = train_x(1:10000,:);tx = train_x(10001:end,:);vy = train_y(1:10000,:);ty = train_y(10001:end,:);rand('state',0)nn = nnsetup([784 20 10]); nn.output = 'softmax'; % use softmax outputopts.numepochs = 5; % Number of full sweeps through dataopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, tx, ty, opts, vx, vy); % nntrain takes validation set as last two arguments (optionally)[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');```[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/rasmusbergpalm/deeplearntoolbox/trend.png)](https://bitdeli.com/free "Bitdeli Badge")
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值