Lambda表达式

引言:

   对于刚刚接触Lambda表达式的朋友们,可能会对Lambda表达式感到非常疑惑,它到底是个什么什么样的技术呢?以及它有什么好处和先进的地方呢?下面的介绍将会解除你这些疑惑。

 

一、Lambda表达式的演变过程

Lambda表达式其实大家可以理解为它是一个匿名函数(对于匿名函数的介绍大家可以参考我这篇文章), Lambda表达式可以包含表达式和语句,并且可以用于创建委托,以及C#编译器也能将它转换成表达式树。

对于Lambda表达式中都会使用这个运算符——“=>”,它读成“goes to” ,该运算符的左边为输入参数,右边是表达式或者语句块,下面就看看Lambda表达式是如何来创建委托实例(代码同时也给出了Lambda表达式从匿名方法的演示过程,从而帮助大家更好的理解Lambda表达式是匿名函数的概念,只不过C#3 中提出的Lambda表达式比匿名函数的使用更加简洁和直观了,其实原理都是一样的, 编译器同样会把Lambda表达式编译成匿名函数,也就是一个名字的方法):

复制代码
using System;

namespace Lambda表达式Demo
{
    class Program
    {
        /// <summary>
        /// Lambda 表达式使用演示
        /// </summary>
        /// <param name="args"></param>
        static void Main(string[] args)
        {
            // Lambda表达式的演变过程
            // 下面是C# 1中创建委托实例的代码
            Func<string, int> delegatetest1 = new Func<string, int>(Callbackmethod);
            
            //// C# 2中用匿名方法来创建委托实例,此时就不需要额外定义回调方法Callbackmethod
            Func<string, int> delegatetest2 = delegate(string text)
            {
                return text.Length;
            };

            //// C# 3中使用Lambda表达式来创建委托实例
            Func<string, int> delegatetest3 = (string text) => text.Length;

            //// 可以省略参数类型string,把上面代码再简化为:
            Func<string, int> delegatetest4 = (text) => text.Length;

            //// 如果Lambda表达式只需一个参数,并且那个参数可以隐式指定类型时,
            // 此时可以把圆括号也省略,简化为:
            Func<string, int> delegatetest = text => text.Length;

            // 调用委托
            Console.WriteLine("使用Lambda表达式返回字符串的长度为: " + delegatetest("learning hard"));
            Console.Read();
        }

        /// <summary>
        /// 回调方法
        /// 如果使用了Lambda表达式和匿名函数,此方法就不需要额外定义
        /// </summary>
        /// <param name="text"></param>
        /// <returns></returns>
        private static int Callbackmethod(string text)
        {
            return text.Length;
        }
    }
}
复制代码

运行结果为:

上面代码中都有详细的演变过程,这里就不多解释了,希望通过这部分之后,大家可以对Lambda表达式有进一步的理解,其实Lambda表达式就是匿名方法,其中使用Lambda表达式来创建委托实例,我们却没有指出创建的委托类型,其中编译器会帮助我们去推断委托类型,从而简化我们创建委托类型所需要的代码,从而更加简洁,所以Lambda表达式可以总结为——它是在匿名方法的基础上,再进一步地简化了创建委托实例所需要的代码。

二、Lambda表达式的使用

为了帮助大家更好的理解Lambda表达式,下面演示下用Lambda表达式来记录事件(代码中Lambda运算符的右边调用了一个回调方法ReportEvent()):

复制代码
using System;
using System.Windows.Forms;

namespace Lambda表达式来记录事件Demo
{
    class Program
    {
        static void Main(string[] args)
        {
            // 新建一个button实例
            Button button1 = new Button() { Text ="点击我"};

            // C# 2中使用匿名方法来订阅事件
            //button1.Click+=delegate (object sender,EventArgs e)
            //{
            //    ReportEvent("Click事件", sender, e);
            //};
            //button1.KeyPress += delegate (object sender, KeyPressEventArgs e)
            //{
            //    ReportEvent("KeyPress事件,即键盘按下事件", sender, e);
            //};

            // C# 3Lambda表达式方式来订阅事件
            // 与上面使用匿名方法来订阅事件是不是看出简单了很多,并且也直观了
            button1.Click += (sender, e) => ReportEvent("Click事件", sender, e);
            button1.KeyPress += (sender, e) => ReportEvent("KeyPress事件,即键盘按下事件", sender, e);

            // C# 3之前初始化对象时使用下面代码
            //Form form = new Form();
            //form.Name = "在控制台中创建的窗体";
            //form.AutoSize = true;
            //form.Controls.Add(button1);

            // C# 3中使用对象初始化器
            // 与上面代码的比较中,也可以看出使用对象初始化之后代码简化了很多
            Form form = new Form { Name = "在控制台中创建的窗体", AutoSize = true, Controls = { button1 } };
          
            // 运行窗体
            Application.Run(form);
        }

        // 记录事件的回调方法
        private static void ReportEvent(string title, object sender, EventArgs e)
        {
            Console.WriteLine("发生的事件为:{0}", title);
            Console.WriteLine("发生事件的对象为:{0}", sender);
            Console.WriteLine("发生事件参数为: {0}", e.GetType());
            Console.WriteLine();
            Console.WriteLine();
        }
    }
}
复制代码

运行结果:

从上面代码中可以看出,使用Lambda表达式之后代码确实简洁了很多,上面代码中都有详细的注释,这里就不解释了,大家可以查看代码中的注释来进行理解,并且代码中注释部分也列出了C# 3之前是如何实现这样的代码的, 这样有利于比较,从而帮助大家更好的认识到Lambda所带来的好处和进一步来理解Lambda表达式。

三、表达式树

上面指出Lambda表达式除了可以用来创建委托外,C#编译器还可以将他们转换成表达式树——用于表示Lambda表达式逻辑的一种数据结构,表达式树也可以称作表达式目录树,它将代码表示成一个对象树,而不是可执行的代码。对于刚接触哦表达式树的朋友肯定会问——为什么需要把Lambda表达式转化为表达式目录树呢?对于表达式树的提出主要是为后面Linq to SQL 做铺垫,一个Linq to SQL 的查询语句并不是在C#的程序中执行的,而是C#编译器把它转化为SQL 语句,然后再在数据库中执行。在我们使用Linq to SQL的时候都需要添加一个Linq to SQL的类,该类的扩展名dbml,该的作用就是帮助我们把Linq to SQL 的语句映射为SQL语句,然后再在数据库中执行SQL语句,把返回的结果再返回给一个IQueryable集合,所以Linq to SQL 也采用了通常的ORM(Object—Relationship—Mapping)来设计的,相当于是一个ORM框架,不过这个框架只能与微软的SQL server数据库进行映射,对于其他类型的数据库却不可以,然而很多其他开发人员却对此进行了一些扩展,扩展了对其他数据库的支持。前不久还在博客园中发布了开源的Linq框架的,名字为ELinq,其他它就是对Linq to SQL的一个扩展,使Linq语句可以映射到其他数据库的查询语句。

下面先看看如何把Lambda表达式转化为表达式目录树(其中需要引入一个新的命名空间—— System.Linq.Expressions):

复制代码
using System;

// 引用额外的命名空间
using System.Linq.Expressions;

namespace 表达式树Demo
{
    class Program
    {
        /// <summary>
        /// 表达式树的演示
        /// </summary>
        /// <param name="args"></param>
        static void Main(string[] args)
        {
            #region 将Lambda表达式转换为表达式树演示
            // 将Lambda表达式转换为Express<T>的表达式树
            // 此时express不是可执行的代码,它现在是一个表达式树的数据结构
            Console.WriteLine("将Lambda表达式转化为表达式树的演示:");
            Expression<Func<int, int, int>> expression = (a, b) => a + b;

            // 获得表达式树的参数
            Console.WriteLine("参数1: {0},参数2:{1}", expression.Parameters[0],expression.Parameters[1]);

            // 既然叫做树,那肯定有左右节点
            // 获取表达式树的主体部分
            BinaryExpression body = (BinaryExpression)expression.Body;
            
            // 左节点,每个节点本身就是一个表达式对象
            ParameterExpression left = (ParameterExpression)body.Left;

            // 右节点
            ParameterExpression right = (ParameterExpression)body.Right;

            Console.WriteLine("表达式主体为:");
            Console.WriteLine(expression.Body);
            Console.WriteLine("表达式树左节点为:{0}{4} 节点类型为:{1}{4}{4} 表达式右节点为:{2}{4} 节点类型为:{3}{4}", left.Name, left.NodeType, right.Name, right.NodeType,Environment.NewLine);
            Console.Read();
            #endregion 

            #region 把表达式树转化回可执行代码

            // Compile方法生成Lambda表达式的委托
            Console.WriteLine("按下Enter键进入将表达式树转换为Lambda表达式的委托演示:");
            int result = expression.Compile()(2, 3);
            Console.WriteLine("调用Lambda表达式委托结果为:" + result);
            Console.ReadKey();
            #endregion
        }
    }
}
复制代码

运行结果:

上面代码首先把Lambda表达式转化为表达式树,下面这行代码就是把Lambda表达式转化为表达式树:

 Expression<Func<int, int, int>> expression = (a, b) => a + b;

之后对于表达式树这种数据结构进行分析来获得该树中的主体和左右节点是什么,获得主体和左右节点的代码如下:

复制代码
 // 获取表达式树的主体部分
            BinaryExpression body = (BinaryExpression)expression.Body;
            
            // 左节点,每个节点本身就是一个表达式对象
            ParameterExpression left = (ParameterExpression)body.Left;

            // 右节点
            ParameterExpression right = (ParameterExpression)body.Right;
复制代码

从上面代码可以得出——树中的每个节点都是一个表达式(ParameterExpression和BinaryExpression都是继承Expression的,所以左右节点都是表达式),分析完表达式树之后,代码中还演示了如果把表达式树转化为可执行的代码,即转化为Lambda表达式的委托对象(此时调用Expression的Compile()方法来转化为可执行代码),通过调用委托来获得结果。 

 关于Lambda表达式树的更多信息还可以参看这篇博客:http://www.cnblogs.com/tianfan/archive/2010/03/05/expression-tree-basics.html (博主翻译的还可以) 

四、总结

 到这里本专题的内容也介绍的差不多了,希望通过本专题使一些之前对Lambda表达式感到疑惑的朋友们现在可以理解Lambda表达式,因为只有理解好Lambda表达式之后,对于Linq的学习就可以说是轻而易举了。

 补充:

1.匿名函数不等于匿名方法,匿名函数包含了匿名方法和lambda表达式这两种概念。
匿名函数:{匿名方法,lambda表达式}

lambda作为表达式,可以被C#编译器转换为委托,也可以被编译器转换为表达式树,匿名方法只能转换为委托。

两者的共通点是都能被编译器转换成为委托,lambda表达式能完成几乎所有匿名方法能完成的事。

作为委托和表达式树,两者在IL阶段表示就不一样了。作为委托的IL,在运行期间直接被CLR所执行,而作为表达式树,是不被CLR所直接执行,而是通过相应的Provider转换为所需要的东西,比如说可以转换为SQL,也可以转换为JAVA。(引自留言中浪雪朋友的意见)

 

文章出自:http://www.cnblogs.com/zhili/archive/2012/12/12/LambdaExpression.html

### RT-DETRv3 网络结构分析 RT-DETRv3 是一种基于 Transformer 的实时端到端目标检测算法,其核心在于通过引入分层密集正监督方法以及一系列创新性的训练策略,解决了传统 DETR 模型收敛慢和解码器训练不足的问题。以下是 RT-DETRv3 的主要网络结构特点: #### 1. **基于 CNN 的辅助分支** 为了增强编码器的特征表示能力,RT-DETRv3 引入了一个基于卷积神经网络 (CNN) 的辅助分支[^3]。这一分支提供了密集的监督信号,能够与原始解码器协同工作,从而提升整体性能。 ```python class AuxiliaryBranch(nn.Module): def __init__(self, in_channels, out_channels): super(AuxiliaryBranch, self).__init__() self.conv = nn.Conv2d(in_channels, out_channels, kernel_size=3, padding=1) self.bn = nn.BatchNorm2d(out_channels) def forward(self, x): return F.relu(self.bn(self.conv(x))) ``` 此部分的设计灵感来源于传统的 CNN 架构,例如 YOLO 系列中的 CSPNet 和 PAN 结构[^2],这些技术被用来优化特征提取效率并减少计算开销。 --- #### 2. **自注意力扰动学习策略** 为解决解码器训练不足的问题,RT-DETRv3 提出了一种名为 *self-att 扰动* 的新学习策略。这种策略通过对多个查询组中阳性样本的标签分配进行多样化处理,有效增加了阳例的数量,进而提高了模型的学习能力和泛化性能。 具体实现方式是在训练过程中动态调整注意力权重分布,确保更多的高质量查询可以与真实标注 (Ground Truth) 进行匹配。 --- #### 3. **共享权重解编码器分支** 除了上述改进外,RT-DETRv3 还引入了一个共享权重的解编码器分支,专门用于提供密集的正向监督信号。这一设计不仅简化了模型架构,还显著降低了参数量和推理时间,使其更适合实时应用需求。 ```python class SharedDecoderEncoder(nn.Module): def __init__(self, d_model, nhead, num_layers): super(SharedDecoderEncoder, self).__init__() decoder_layer = nn.TransformerDecoderLayer(d_model=d_model, nhead=nhead) self.decoder = nn.TransformerDecoder(decoder_layer, num_layers=num_layers) def forward(self, tgt, memory): return self.decoder(tgt=tgt, memory=memory) ``` 通过这种方式,RT-DETRv3 实现了高效的目标检测流程,在保持高精度的同时大幅缩短了推理延迟。 --- #### 4. **与其他模型的关系** 值得一提的是,RT-DETRv3 并未完全抛弃经典的 CNN 技术,而是将其与 Transformer 结合起来形成混合架构[^4]。例如,它采用了 YOLO 系列中的 RepNCSP 模块替代冗余的多尺度自注意力层,从而减少了不必要的计算负担。 此外,RT-DETRv3 还借鉴了 DETR 的一对一匹配策略,并在此基础上进行了优化,进一步提升了小目标检测的能力。 --- ### 总结 综上所述,RT-DETRv3 的网络结构主要包括以下几个关键组件:基于 CNN 的辅助分支、自注意力扰动学习策略、共享权重解编码器分支以及混合编码器设计。这些技术创新共同推动了实时目标检测领域的发展,使其在复杂场景下的表现更加出色。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值