Chapter 4 深入理解Caffe MNIST DEMO中的LeNet网络模型

明代思想家王阳明提出了“知行合一”,谓认识事物的道理与在现实中运用此道理,是密不可分的一回事。我以为这样的中国哲学话语,对于学习者来说,极具启发意义,要细细体会。中华文明源远流长,很多做人做事的道理,孕育其中,需用心体会,并学以致用。

以“知”促“行”、以“行”促“知”、知行合一。——The unity of Inner knowledge and action.

在chapter 3 中提供了一个很好的实践样例,这个样例在windows下运行了Caffe源代码的MNIST Demo。本章将以该实践为基础来深入理解LeNet网络模型。

1. 初见LeNet原始模型

Fig.1. Architecture of original LeNet-5.

图片来源: Lecun, et al., Gradient-based learning applied to document recognition, P IEEE, vol. 86, no. 11, 1998, pp. 2278-2324.

在这篇图片的论文中,详细描述了LeNet-5的结构。

这里不对LeNet-5原始模型进行讨论。可以参考这些资料:

http://blog.csdn.net/qiaofangjie/article/details/16826849

http://blog.csdn.net/xuanyuansen/article/details/41800721

2. Caffe LeNet的网络结构

他山之石,可以攻玉。本来是准备画出Caffe LeNet的图的,但发现已经有人做了,并且画的很好,就直接拿过来辅助理解了。

第3部分图片来源:http://www.2cto.com/kf/201606/518254.html

先从整体上感知Caffe LeNet的拓扑图,由于Caffe中定义网络的结构采用的是bottom&top这种上下结构,所以这里的图也采用这种方式展现出来,更加方便理解。

Fig.2. Architecture of caffe LeNet.

From bottom to top: Data Layer, conv1, pool1, conv2, pool2, ip1, relu1, ip2, [accuracy]loss.

本节接下来将按照这个顺序依次理解Caffe LeNet的网络结构。

3. 逐层理解Caffe LeNet

本节将采用定义与图解想结合的方式逐层理解Caffe LeNet的结构。

3.1 Data Layer

#==============定义TRAIN的数据层============================================
layer { 
  name: "mnist" #定义该层的名字
  type: "Data"  #该层的类型是数据
  top: "data"   #该层生成一个data blob
  top: "label"  #该层生成一个label blob
  include {
    phase: TRAIN #说明该层只在TRAIN阶段使用
  }
  transform_param {
    scale: 0.00390625 #数据归一化系数,1/256,归一到[0,1)
  }
  data_param {
    source: "E:/MyCode/DL/caffe-master/examples/mnist/mnist_train_lmdb" #训练数据的路径
    batch_size: 64 #批量处理的大小
    backend: LMDB
  }
}
#==============定义TEST的数据层============================================
layer { 
  name: "mnist"
  type: "Data"
  top: "data"
  top: "label"
  include {
    phase: TEST #说明该层只在TEST阶段使用
  }
  transform_param {
    scale: 0.00390625
  }
  data_param {
    source: "E:/MyCode/DL/caffe-master/examples/mnist/mnist_test_lmdb" #测试数据的路径
    batch_size: 100
    backend: LMDB
  }
}

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

Fig.3. Architecture of data layer.

Fig.3 是train情况下,数据层读取lmdb数据,每次读取64条数据,即N=64。

Caffe中采用4D表示,N*C*H*W(Num*Channels*Height*Width)。

3.2 Conv1 Layer

#==============定义卷积层1=============================
layer {
  name: "conv1"       #该层的名字conv1,即卷积层1
  type: "Convolution" #该层的类型是卷积层
  bottom: "data"      #该层使用的数据是由数据层提供的data blob
  top: "conv1"        #该层生成的数据是conv1
  param {
    lr_mult: 1        #weight learning rate(简写为lr)权值的学习率,1表示该值是lenet_solver.prototxt中base_lr: 0.01的1倍
  }
  param {
    lr_mult: 2        #bias learning rate偏移值的学习率,2表示该值是lenet_solver.prototxt中base_lr: 0.01的2倍
  }
  convolution_param {
    num_output: 20    #产生20个输出通道
    kernel_size: 5    #卷积核的大小为5*5
    stride: 1         #卷积核移动的步幅为1
    weight_filler {
      type: "xavier"  #xavier算法,根据输入和输出的神经元的个数自动初始化权值比例
    }
    bias_filler {
      type: "constant"  #将偏移值初始化为“稳定”状态,即设为默认值0
    }
  }
}

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

Fig.4. Architecture of conv1 layer.

conv1的数据变化的情况:batch_size*1*28*28->batch_size*20*24*24

3.3 Pool1 Layer

#==============定义池化层1=============================
layer {
  name: "pool1"
  type: "Pooling"
  bottom: "conv1"     #该层使用的数据是由conv1层提供的conv1
  top: "pool1"        #该层生成的数据是pool1
  pooling_param {
    pool: MAX         #采用最大值池化
    kernel_size: 2    #池化核大小为2*2
    stride: 2         #池化核移动的步幅为2,即非重叠移动
  }
}

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

Fig.5. Architecture of pool1 layer.

池化层1过程数据变化:batch_size*20*24*24->batch_size*20*12*12

3.4 Conv2 Layer

#==============定义卷积层2=============================
layer {
  name: "conv2"
  type: "Convolution"
  bottom: "pool1"
  top: "conv2"
  param {
    lr_mult: 1
  }
  param {
    lr_mult: 2
  }
  convolution_param {
    num_output: 50
    kernel_size: 5
    stride: 1
    weight_filler {
      type: "xavier"
    }
    bias_filler {
      type: "constant"
    }
  }
}

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

conv2层的图与Fig.4 类似,卷积层2过程数据变化:batch_size*20*12*12->batch_size*50*8*8。

3.5 Pool2 Layer

#==============定义池化层2=============================
layer {
  name: "pool2"
  type: "Pooling"
  bottom: "conv2"
  top: "pool2"
  pooling_param {
    pool: MAX
    kernel_size: 2
    stride: 2
  }
}

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

pool2层图与Fig.5类似,池化层2过程数据变化:batch_size*50*8*8->batch_size*50*4*4。

3.6 Ip1 Layer

#==============定义全连接层1=============================
layer {
  name: "ip1"
  type: "InnerProduct" #该层的类型为全连接层
  bottom: "pool2"
  top: "ip1"
  param {
    lr_mult: 1
  }
  param {
    lr_mult: 2
  }
  inner_product_param {
    num_output: 500 #有500个输出通道
    weight_filler {
      type: "xavier"
    }
    bias_filler {
      type: "constant"
    }
  }
}

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

Fig.6. Architecture of ip11 layer.

ip1过程数据变化:batch_size*50*4*4->batch_size*500*1*1。

此处的全连接是将C*H*W转换成1D feature vector,即800->500.

3.7 Relu1 Layer

#==============定义ReLU1层=============================
layer {
  name: "relu1"
  type: "ReLU"
  bottom: "ip1"
  top: "ip1"
}

Fig.7. Architecture of relu1 layer.

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

ReLU1层过程数据变化:batch_size*500*1*1->batch_size*500*1*1

3.8 Ip2 Layer

#==============定义全连接层2============================
layer {
  name: "ip2"
  type: "InnerProduct"
  bottom: "ip1"
  top: "ip2"
  param {
    lr_mult: 1
  }
  param {
    lr_mult: 2
  }
  inner_product_param {
    num_output: 10          #10个输出数据,对应0-9十个数字
    weight_filler {
      type: "xavier"
    }
    bias_filler {
      type: "constant"
    }
  }
}

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

ip2过程数据变化:batch_size*500*1*1->batch_size*10*1*1

3.9 Loss Layer

#==============定义损失函数层============================
layer {
  name: "loss"
  type: "SoftmaxWithLoss"
  bottom: "ip2"
  bottom: "label"
  top: "loss"
}

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

Fig.8. Architecture of loss layer.

损失层过程数据变化:batch_size*10*1*1->batch_size*10*1*1

note:注意到caffe LeNet中有一个accuracy layer的定义,这是输出测试结果的层。

4. Caffe LeNet的完整定义

name: "LeNet" #定义网络的名字
#==============定义TRAIN的数据层============================================
layer { 
  name: "mnist" #定义该层的名字
  type: "Data"  #该层的类型是数据
  top: "data"   #该层生成一个data blob
  top: "label"  #该层生成一个label blob
  include {
    phase: TRAIN #说明该层只在TRAIN阶段使用
  }
  transform_param {
    scale: 0.00390625 #数据归一化系数,1/256,归一到[0,1)
  }
  data_param {
    source: "E:/MyCode/DL/caffe-master/examples/mnist/mnist_train_lmdb" #训练数据的路径
    batch_size: 64 #批量处理的大小
    backend: LMDB
  }
}
#==============定义TEST的数据层============================================
layer { 
  name: "mnist"
  type: "Data"
  top: "data"
  top: "label"
  include {
    phase: TEST #说明该层只在TEST阶段使用
  }
  transform_param {
    scale: 0.00390625
  }
  data_param {
    source: "E:/MyCode/DL/caffe-master/examples/mnist/mnist_test_lmdb" #测试数据的路径
    batch_size: 100
    backend: LMDB
  }
}
#==============定义卷积层1=============================
layer {
  name: "conv1"       #该层的名字conv1,即卷积层1
  type: "Convolution" #该层的类型是卷积层
  bottom: "data"      #该层使用的数据是由数据层提供的data blob
  top: "conv1"        #该层生成的数据是conv1
  param {
    lr_mult: 1        #weight learning rate(简写为lr)权值的学习率,1表示该值是lenet_solver.prototxt中base_lr: 0.01的1倍
  }
  param {
    lr_mult: 2        #bias learning rate偏移值的学习率,2表示该值是lenet_solver.prototxt中base_lr: 0.01的2倍
  }
  convolution_param {
    num_output: 20    #产生20个输出通道
    kernel_size: 5    #卷积核的大小为5*5
    stride: 1         #卷积核移动的步幅为1
    weight_filler {
      type: "xavier"  #xavier算法,根据输入和输出的神经元的个数自动初始化权值比例
    }
    bias_filler {
      type: "constant"  #将偏移值初始化为“稳定”状态,即设为默认值0
    }
  }
}#卷积过程数据变化:batch_size*1*28*28->batch_size*20*24*24
#==============定义池化层1=============================
layer {
  name: "pool1"
  type: "Pooling"
  bottom: "conv1"     #该层使用的数据是由conv1层提供的conv1
  top: "pool1"        #该层生成的数据是pool1
  pooling_param {
    pool: MAX         #采用最大值池化
    kernel_size: 2    #池化核大小为2*2
    stride: 2         #池化核移动的步幅为2,即非重叠移动
  }
}#池化层1过程数据变化:batch_size*20*24*24->batch_size*20*12*12
#==============定义卷积层2=============================
layer {
  name: "conv2"
  type: "Convolution"
  bottom: "pool1"
  top: "conv2"
  param {
    lr_mult: 1
  }
  param {
    lr_mult: 2
  }
  convolution_param {
    num_output: 50
    kernel_size: 5
    stride: 1
    weight_filler {
      type: "xavier"
    }
    bias_filler {
      type: "constant"
    }
  }
}#卷积层2过程数据变化:batch_size*20*12*12->batch_size*50*8*8
#==============定义池化层2=============================
layer {
  name: "pool2"
  type: "Pooling"
  bottom: "conv2"
  top: "pool2"
  pooling_param {
    pool: MAX
    kernel_size: 2
    stride: 2
  }
}#池化层2过程数据变化:batch_size*50*8*8->batch_size*50*4*4
#==============定义全连接层1=============================
layer {
  name: "ip1"
  type: "InnerProduct" #该层的类型为全连接层
  bottom: "pool2"
  top: "ip1"
  param {
    lr_mult: 1
  }
  param {
    lr_mult: 2
  }
  inner_product_param {
    num_output: 500 #有500个输出通道
    weight_filler {
      type: "xavier"
    }
    bias_filler {
      type: "constant"
    }
  }
}#全连接层1过程数据变化:batch_size*50*4*4->batch_size*500*1*1
#==============定义ReLU1层=============================
layer {
  name: "relu1"
  type: "ReLU"
  bottom: "ip1"
  top: "ip1"
}#ReLU1层过程数据变化:batch_size*500*1*1->batch_size*500*1*1
#==============定义全连接层2============================
layer {
  name: "ip2"
  type: "InnerProduct"
  bottom: "ip1"
  top: "ip2"
  param {
    lr_mult: 1
  }
  param {
    lr_mult: 2
  }
  inner_product_param {
    num_output: 10          #10个输出数据,对应0-9十个数字
    weight_filler {
      type: "xavier"
    }
    bias_filler {
      type: "constant"
    }
  }
}#全连接层2过程数据变化:batch_size*500*1*1->batch_size*10*1*1
#==============定义显示准确率结果层============================
layer {
  name: "accuracy"
  type: "Accuracy"
  bottom: "ip2"
  bottom: "label"
  top: "accuracy"
  include {
    phase: TEST
  }
}
#==============定义损失函数层============================
layer {
  name: "loss"
  type: "SoftmaxWithLoss"
  bottom: "ip2"
  bottom: "label"
  top: "loss"
}#损失层过程数据变化:batch_size*10*1*1->batch_size*10*1*1

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

时间: 2024-08-25 14:45:55

Chapter 4 深入理解Caffe MNIST DEMO中的LeNet网络模型的相关文章

Demo中的IOC自定义实现

在做练习的时候,小小项目,使用IOC控件觉得麻烦,使用工厂觉得不高大上啊,自己写个简陋的依赖注入IOC吧; 控制反转(IOC)是管理映射依赖的的,是依赖倒置(DIP)的实现方式; 依赖倒置(DIP)是设计原则,控制反转(IOC)是具体实现,依赖注入(DI)是控制反转的具体实现; 解决方案的目录: IOC 有3个类,一个是用来保存依赖关系的实体类(EntityIOC),一个是保存依赖关系的类(InterviewsDependencyResolver),一个是外部调用类(InterviewsIOC)

理解ASP.NET MVC中的ModelBinder

模型绑定的本质 任何控制器方法的执行都受action invoker组件(下文用invoker代替)控制.对于每个Action方法的参数,这个invoker组件都会获取一个Model Binder Object(模型绑定器对象).Model Binder的职责包括为Action方法参数寻找一个可能的值(从HTTP请求上下文).每个参数都可以绑定到不同的Model Binder:但是大部分情况我们都使用的是默认模型绑定器-DefaultModelBinder(如果我们没有显式设置使用自定义的Mod

IM开发基础知识补课(四):正确理解HTTP短连接中的Cookie、Session和Token

本文引用了简书作者“骑小猪看流星”技术文章“Cookie.Session.Token那点事儿”的部分内容,感谢原作者. 1.前言 众所周之,IM是个典型的快速数据流交换系统,当今主流IM系统(尤其移动端IM)的数据流交换方式都是Http短连接+TCP或UDP长连接来实现.Http短连接主要用于从服务器读取各种持久化信息:比如用户信息.聊天历史记录.好友列表等等,长连接则是用于实时的聊天消息或指令的接收和发送. 作为IM系统中不可或缺的技术,Http短连的重要性无可替代,但Http作为传统互联网信

理解ruby on rails中的ActiveRecord::Relation

ActiveRecord::Relation是rails3中添加的.rails2中的finders, named_scope, with_scope 等用法,在rails3统一为一种Relation用法. 以下是返回ActiveRecord::Relation的方法: bind create_with distinct eager_load extending from group having includes joins limit lock none offset order preloa

【JavaScript】理解与使用Javascript中的回调函数

在Javascript中,函数是第一类对象,这意味着函数可以像对象一样按照第一类管理被使用.既然函数实际上是对象:它们能被“存储”在变量中,能作为函数参数被传递,能在函数中被创建,能从函数中返回. 因为函数是第一类对象,我们可以在Javascript使用回调函数.在下面的文章中,我们将学到关于回调函数的方方面面.回调函数可能是在Javascript中使用最多的函数式编程技巧,虽然在字面上看起来它们一直一小段Javascript或者jQuery代码,但是对于许多开发者来说它任然是一个谜.在阅读本文

The Courtyard demo中的Game视图相机同步

The Courtyard的demo中,会发现Scene视图和Game视图是编辑器下同步的,它通过一个CopySceneView.cs脚本实现 Scene视图和Game视图的显示效果还是有区别的,毕竟有相机滤镜.同步后可以更好的调试

理解与使用Javascript中的回调函数

在Javascript中,函数是第一类对象,这意味着函数可以像对象一样按照第一类管理被使用.既然函数实际上是对象:它们能被“存储”在变量中,能作为函数参数被传递,能在函数中被创建,能从函数中返回. 因为函数是第一类对象,我们可以在Javascript使用回调函数.在下面的文章中,我们将学到关于回调函数的方方面面.回调函数可能是在 Javascript中使用最多的函数式编程技巧,虽然在字面上看起来它们一直一小段Javascript或者jQuery代码,但是对于许多开发者来说 它任然是一个谜.在阅读

Struts2+Urlrewrite实现伪静态化 demo中介绍了三种情况的配置,下载即可运行。

原文:Struts2+Urlrewrite实现伪静态化 demo中介绍了三种情况的配置,下载即可运行. 源代码下载地址:http://www.zuidaima.com/share/1550463499504640.htm 为大家提供了3种模式的url进行访问,以便应付大家的开发,更多的模式大家可以自行组合. html:http://localhost:8686/Sturts2UrlReWrite/line.html xxx::http://localhost:8686/Sturts2UrlReW

理解ASP.NET MVC中的ActionResult

通常我们在一个ASP.NET MVC项目中创建一个Controller的时候,Index()方法默认的返回类型都是ActionResult,通过查看UML图,ActionResult实际上是一个抽象类,因此实际返回的类型是该抽象类的子类. Ø ActionResult及其子类的UML图   有关ActionResult及其子类的UML图如下所示: 由于图片比较大,所以在浏览器中看起来可能比较小,也不太方便,大家可以点击这里下载大图,使用专业的图片浏览器打开来看. 下载大图 Ø ActionRes