精华内容
下载资源
问答
  • C#单个程序集代码热更新

    千次阅读 2018-01-27 22:55:50
     微软提供的标准方法是通过应用程序域来实现代码热更新,意思就是说,把自己想要进行热更新的代码放到另外一个应用程序,在检测到代码需要变更的时候,卸载掉那个程序域然后重新加载来实现代码热更新。...

     有的时候我们想更新我们正在执行中的代码,而不想软件重启。

      微软提供的标准方法是通过应用程序域来实现代码热更新,意思就是说,把自己想要进行热更新的代码放到另外一个应用程序域中,在检测到代码需要变更的时候,卸载掉那个程序域然后重新加载来实现代码热更新。按照微软的说法,一个应用程序域是无法实现代码热更新的。

      但是,一下方法确实是可以在单个应用程序域中实现代码热更新的,本人尝试过,在公司的上班中,软件的频繁重启让人很烦,又不能在另外一个程序域中执行,所以尝试了一下办法,在一个应用程序域中改变代码是可行的。

      方法如下:假设程序集A中有类a,程序集B中有类b,b要去调用a的方法,这个情况下可以对a的部分代码(仅对方法和属性有效)进行热更新。第一步是针对类a 进行代码重写,生成一个假的A.dll,但是具有和包含真A中类,字段,属性,和方法签名,把真A的Dll放到另外一个文件夹。当B去调用A中方法的时候,假的A会通过字节数组的形式将真A保存在自己的一个Assembly(最好放在一个其他类的静态变量中,我是那么做的)变量中,并且检测真A的文件改动,当真A发生变化的时候对Assembly变量通过字节数组的形式重新赋值。这个样子就可以就可以实现部分代码热更新。

      假A对真A代码的重写原则:假a类中至少要有三个变量,一个object变量,用来存对真a对象的引用。一个Type变量,用来存储真a的类型信息,还有真A的Assembly变量。假a在初始化的时候,要初始化刚才的那三个变量,对假a方法的调用通过反射转换为对真a对象(object变量,用来存对真a对象的引用)的调用。并且把真a方法的返回结果返回回去在真A变化之后,假A重新加载真A,这个时间点之后,在被创建的假a执行的代码已经是代码变化之后真A的代码,就可以实现代码特更新。

      把真A代码重构并且生成编译出假A是一个很费劲容易出错的过程,如下是我生成假A的代码逻辑,应该是存在某些问题的(嘿嘿,主要是说明方法)

    using Microsoft.CSharp;
    using System;
    using System.CodeDom;
    using System.CodeDom.Compiler;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using System.Reflection;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace DllLoad
    {
        class Core2
        {
    
    
            List<string> dlls = new List<string>();
            string outpath = string.Empty;
    
            public EventHandler CompileCompleted;
    
            public Core2()
            {
            }
    
            public void SetInfo(List<string> dlls, string outpath)
            {
                this.dlls = dlls;
                this.outpath = outpath;
            }
    
    
            public void StartCompile()
            {
                try
                {
                    dlls.ForEach(o =>
                    {
                        Assembly assem = Assembly.LoadFrom(o);
                        Compile(assem, outpath);
                    });
                }
                catch (Exception e)
                {
                    KeyValuePair<bool, string> error = new KeyValuePair<bool, string>(false, e.Message);
                    CompileCompleted(error, null);
                    return;
                }
                KeyValuePair<bool, string> correct = new KeyValuePair<bool, string>(true, null);
                CompileCompleted(correct, null);
            }
    
    
            public void Compile(Assembly assembly, string dllOutPath)
            {
                CodeCompileUnit compunit = new CodeCompileUnit();
                Dictionary<string, CodeNamespace> nameDic = new Dictionary<string, CodeNamespace>();
    
                FileInfo assemblyInfo = new FileInfo(assembly.Location);
                string fileName = assemblyInfo.Name;
    
                foreach (var typeinfo in assembly.DefinedTypes)
                {
                    string namespa = typeinfo.Namespace;
                    var space = new CodeNamespace(namespa);
                    if (!nameDic.ContainsKey(namespa))
                    {
                        nameDic.Add(namespa, space);
                        compunit.Namespaces.Add(space);
                        space.Imports.AddRange((new string[] {"System","System.Linq","System.Reflection","System.Text","Free"
                        }).ToList().Select(o => new CodeNamespaceImport(o)).ToArray());
                    }
    
                    #region 添加类中的变量
    
                    CodeTypeDeclaration clas = new CodeTypeDeclaration(typeinfo.Name);
                    CodeMemberField obj = new CodeMemberField(typeof(object), "obj");
                    obj.InitExpression = new CodePrimitiveExpression(null);
                    CodeMemberField self = new CodeMemberField(typeof(Type), "self");
                    self.InitExpression = new CodePrimitiveExpression(null);
                    CodeMemberField ass = new CodeMemberField(typeof(Assembly), "ass");
                    ass.InitExpression = new CodePrimitiveExpression(null);
                    clas.Members.Add(obj);
                    clas.Members.Add(self);
                    clas.Members.Add(ass);
                    #endregion
    
    
                    //添加构造函数
                    CodeConstructor constructor = new CodeConstructor()
                    {
                        Attributes = MemberAttributes.Public
                    };
                    constructor.Statements.Add(new CodeSnippetExpression("Inite()"));
                    clas.Members.Add(constructor);
    
                    //添加Inite() 函数
                    CodeMemberMethod Inite = new CodeMemberMethod()
                    {
                        Name = "Inite",
                        ReturnType = new CodeTypeReference(typeof(void))
                    };
                    Inite.Attributes = MemberAttributes.Private;
                    Inite.Statements.Add(new CodeSnippetStatement(@"
                            ass = FastLoadAssembly.GetAssembly();
                            self = ass.GetType(""{0}"");
                            obj = Activator.CreateInstance(self);
                    ".Replace("{0}", typeinfo.AsType().FullName)));
                    clas.Members.Add(Inite);
    
                    
                    clas.BaseTypes.AddRange(new List<CodeTypeReference> { new CodeTypeReference(typeinfo.BaseType) }.Union(typeinfo.GetInterfaces().Select(o => new CodeTypeReference(o))).Distinct().ToArray());
                    space.Imports.AddRange(new List<string> { typeinfo.Namespace }.Union(typeinfo.GetInterfaces().Select(o => o.Namespace)).Select(o => new CodeNamespaceImport(o)).Distinct().ToArray());
    
    
                    foreach (var met in typeinfo.GetMembers())
                    {
                        switch (met.MemberType)
                        {
                            case MemberTypes.Method:
                                {
                                    MethodInfo info = (MethodInfo)met;
                                    if (!info.IsPublic)
                                        continue;
                                    CodeMemberMethod metho = SolveMethod(met,typeinfo);
                                    clas.Members.Add(metho);
                                }
                                break;
    
                            case MemberTypes.Property:
                                {
    
                                }
                                break;
                        }
                    }
                    space.Types.Add(clas);
    
                }
                compunit.Namespaces.Add(GetCommonClass(dllOutPath + "\\" + fileName));
    
    
                CSharpCodeProvider cprovider = new CSharpCodeProvider();
    
                ICodeGenerator gen = cprovider.CreateGenerator();
                StringBuilder fileContent = new StringBuilder();
                using (StringWriter sw = new StringWriter(fileContent))
                {
                    gen.GenerateCodeFromCompileUnit(compunit, sw, new CodeGeneratorOptions());//想把生成的代码保存为cs文件
                }
    
                string reslut = fileContent.ToString();
    
                ICodeCompiler compiler = cprovider.CreateCompiler();
                //编译参数
                CompilerParameters cp = new CompilerParameters();
    
                // 把当前应用引用的程序集全部添加进来
                cp.ReferencedAssemblies.AddRange(AppDomain.CurrentDomain.GetAssemblies().Select(o => o.Location).Distinct().ToArray());
    
                cp.OutputAssembly = dllOutPath + "\\" + fileName;
                cp.GenerateInMemory = false; //是否只在内存中生成
                cp.IncludeDebugInformation = true;//包含调试符号  pdb文件
                cp.GenerateExecutable = false;//生成dll,不是exe 
                cp.WarningLevel = 4;
                cp.TreatWarningsAsErrors = false;
    
                string filePath = dllOutPath + "\\" + fileName + ".cs";
                File.WriteAllText(filePath, fileContent.ToString());
    
    
                CompilerResults cr = compiler.CompileAssemblyFromFile(cp, filePath); //保存文件再进行编译 待会儿调试就比较方便了 ,可以直接断点到刚才生成的文件里面
                //  CompilerResults cr = compiler.CompileAssemblyFromDom(cp, compunit); //这样的生成 不用写文件 ,就是调试麻烦
                String outputMessage = "";
                foreach (var item in cr.Output)
                {
                    outputMessage += item + Environment.NewLine;//调试的最终输出信息
                }
                if (cr.Errors.HasErrors)//有编译错误就抛出异常
                {
                    throw new Exception("error:" + Environment.NewLine + outputMessage);
                }
            }
    
    
    
            private CodeMemberMethod SolveMethod(MemberInfo info,TypeInfo type)
            {
                if (info.MemberType != MemberTypes.Method)
                    return null;
                MethodInfo method = (MethodInfo)info;
    
                CodeMemberMethod newMethod = new CodeMemberMethod()
                {
                    Name = method.Name,
                    ReturnType = new CodeTypeReference(method.ReturnType)
                };
    
                newMethod.Parameters.AddRange(
                    method.GetParameters().Select(o => {
                        var res = new CodeParameterDeclarationExpression
                        {
                            Type = new CodeTypeReference(o.ParameterType),
                            Name = o.Name
                        };
                        return res;
                    }).ToArray()
                );
                switch (method.Attributes)
                {
                    case MethodAttributes.Public | MethodAttributes.HideBySig:
                        {
                            if(type.DeclaredMembers.Contains(info) )
                                newMethod.Attributes = MemberAttributes.Public | MemberAttributes.Final;
                            else
                                newMethod.Attributes = MemberAttributes.Public | MemberAttributes.New | MemberAttributes.Final;
                        }
                        break;
                    case MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.Virtual:
                        newMethod.Attributes = MemberAttributes.Public | MemberAttributes.Override;
                        break;
                    case MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.Virtual | MethodAttributes.NewSlot:
                        newMethod.Attributes = MemberAttributes.Public;
                        break;
                }
                string statement = @" MethodInfo info= self.GetMethod(""{0}"");"
                                   + "\r\nObject result = info.Invoke(obj,  ";
    
                string param = string.Join(",", method.GetParameters().Select(o => o.Name).ToList());
                string expression = statement.Replace("{0}", info.Name);
                if (!string.IsNullOrWhiteSpace(param))
                    newMethod.Statements.Add(new CodeSnippetStatement(expression + "new object[] { " + param + " });"));
                else
                    newMethod.Statements.Add(new CodeSnippetStatement(expression + "null );"));
    
                if (method.ReturnType != typeof(void))
                {
                    string returnstr = @"return ({0})result;";
                    newMethod.Statements.Add(new CodeSnippetStatement(string.Format(returnstr, method.ReturnType.ToString())));
                }
                return newMethod;
            }
    
            
    
    
            private CodeNamespace GetCommonClass(string dllpath)
            {
                CodeNamespace space = new CodeNamespace("Free");
                CodeTypeDeclaration common = new CodeTypeDeclaration("FastLoadAssembly");
                space.Types.Add(common);
                space.Imports.AddRange((new string[] {"System.Reflection","System.IO"
                        }).ToList().Select(o => new CodeNamespaceImport(o)).ToArray());
                common.Attributes = MemberAttributes.Assembly;
    
                CodeMemberField assembly = new CodeMemberField(typeof(Assembly), "assembly");
                assembly.Attributes = MemberAttributes.Private | MemberAttributes.Static;
                common.Members.Add(assembly);
    
                CodeMemberField path = new CodeMemberField(typeof(string), "path");
                path.InitExpression = new CodePrimitiveExpression(dllpath);
    
    
                path.Attributes = MemberAttributes.Private | MemberAttributes.Const;
                common.Members.Add(path);
    
                CodeMemberField watch = new CodeMemberField(typeof(FileSystemWatcher), "watcher");
                watch.Attributes = MemberAttributes.Private | MemberAttributes.Static;
                common.Members.Add(watch);
                CodeMemberMethod Load = new CodeMemberMethod()
                {
                    Name = "Load",
                    ReturnType = new CodeTypeReference(typeof(void))
                };
                Load.Statements.Add(new CodeSnippetStatement(@"
                        byte[] buffer;
                        FileStream stream = File.OpenRead(path);
                        buffer = new byte[stream.Length];
                        stream.Read(buffer,0,(int)stream.Length);
                        stream.Close();
                        assembly = Assembly.Load(buffer);
                "));
    
                Load.Attributes = MemberAttributes.Private | MemberAttributes.Static;
    
                CodeMemberMethod GetAssembly = new CodeMemberMethod()
                {
                    Name = "GetAssembly",
                    ReturnType = new CodeTypeReference(typeof(Assembly))
                };
                GetAssembly.Statements.Add(new CodeSnippetStatement(@"
                       IniteWatcher();
                       if (assembly == null)
                        Load();
                        return assembly;
                    "));
    
                GetAssembly.Attributes = MemberAttributes.Public | MemberAttributes.Static;
    
                CodeMemberMethod IniteWatcher = new CodeMemberMethod()
                {
                    Name = "IniteWatcher",
                    ReturnType = new CodeTypeReference(typeof(void))
                };
                IniteWatcher.Statements.Add(new CodeSnippetStatement(@"
                        if (watcher==null)
                        {
                            FileInfo info = new FileInfo(path);
                            watcher = new FileSystemWatcher();
                            watcher.Filter = info.Name;
                            watcher.Path = info.DirectoryName;
                            watcher.EnableRaisingEvents = true;
                            watcher.Created += (s, o) => { Load(); };
                        }
                    "));
                IniteWatcher.Attributes = MemberAttributes.Private | MemberAttributes.Static;
    
                common.Members.Add(IniteWatcher);
                common.Members.Add(GetAssembly);
                common.Members.Add(Load);
    
                return space;
            }
    
            
    
    
        }
    }
    

     

     

    展开全文
  • 摘要:极限学习机(ELM)是当前...本篇博文尽量通俗易懂地对极限学习机的原理进行详细介绍,之后分析如何用MATLAB实现该算法并对代码进行解释。本文主要内容如下:算法的原理、算法程序实现、点击跳转至全部文件下载页
    图片展示

    摘要:极限学习机(ELM)是当前一类非常热门的机器学习算法,被用来训练单隐层前馈神经网络(SLFN)。本篇博文尽量通俗易懂地对极限学习机的原理进行详细介绍,之后分析如何用MATLAB实现该算法并对代码进行解释。本文主要内容如下:

    点击跳转至全部文件下载页


    1. 前言

        ELM自2004年南洋理工大学的黄广斌教授提出相关概念以来一直争议不断,但每年相关论文层出不穷,在过去的十年里其理论和应用被广泛研究。如果您想深入学习和了解ELM的原理,博主建议可在ScienceDirect的数据库中检索ELM相关论文,里面有众多优质论文其理解和表述将帮助你更准确了解ELM的内在原理。
        博主对于极限学习机(Extreme Learning Machine, ELM)的学习和研究已一年多,目前ELM相关两篇SCI论文已发表。后续转深度学习方向继续研究,这里就ELM作一个简单整理并给出实现代码,也希望对刚接触的朋友能够有所帮助。


    2. 算法的原理

        极限学习机(ELM)用来训练单隐藏层前馈神经网络(SLFN)与传统的SLFN训练算法不同,极限学习机随机选取输入层权重和隐藏层偏置,输出层权重通过最小化由训练误差项和输出层权重范数的正则项构成的损失函数,依据Moore-Penrose(MP)广义逆矩阵理论计算解析求出。理论研究表明,即使随机生成隐藏层节点,ELM仍保持SLFN的通用逼近能力。在过去的十年里,ELM的理论和应用被广泛研究,从学习效率的角度来看,极限学习机具有训练参数少、学习速度快、泛化能力强的优点。

        简单来说,极限学习机(ELM)模型的网络结构与单隐层前馈神经网络(SLFN)一样,只不过在训练阶段不再是传统的神经网络中屡试不爽的基于梯度的算法(后向传播),而采用随机的输入层权值和偏差,对于输出层权重则通过广义逆矩阵理论计算得到。所有网络节点上的权值和偏差得到后极限学习机(ELM)的训练就完成了,这时测试数据过来时利用刚刚求得的输出层权重便可计算出网络输出完成对数据的预测。

        ELM的理论推导比较简单,但是需要知道一些线性代数和矩阵论的知识,我会尽量写得简单一些,接下来就一起来推导一下吧。我们假设给定训练集 { x i , t i ∣ x i ∈ R D , t i ∈ R m , i = 1 , 2 , … , N } \left\{\mathrm{x}_{i}, \mathrm{t}_{i} | \mathrm{x}_{i} \in \mathrm{R}^{D}, \mathrm{t}_{i} \in \mathrm{R}^{m}, i=1,2, \ldots, N\right\} {xi,tixiRD,tiRm,i=1,2,,N}(符号式表达, x i \mathrm{x}_{i} xi表示第 i i i个数据示例, t i \mathrm{t}_{i} ti表示第 i i i个数据示例对应的标记,集合代指所有训练数据),极限学习机的隐藏层节点数为 L,与单隐层前馈神经网络的结构一样,极限学习机的网络结构如下图所示:

    图片展示

        对于一个神经网络而言,我们完全可以把它看成一个“函数”,单从输入输出看就显得简单许多。很明显上图中,从左往右该神经网络的输入是训练样本集 x \mathrm{x} x,中间有个隐藏层,从输入层到隐藏层之间是全连接,记隐藏层的输出为 H ( x ) H(\mathrm{x}) H(x),那么隐藏层输出 H ( x ) H(\mathrm{x}) H(x)的计算公式如下。
    H ( x ) = [ h 1 ( x ) , … , h L ( x ) ] (2.1) H(\mathrm{x})=\left[h_{1}(\mathrm{x}), \ldots, h_{L}(\mathrm{x})\right] \tag{2.1} H(x)=[h1(x),,hL(x)](2.1)    隐藏层的输出是输入乘上对应权重加上偏差,再经过一个非线性函数其所有节点结果求和得到。 H ( x ) = [ h 1 ( x ) , … , h L ( x ) ] \mathrm{H}(\mathrm{x})=\left[h_{1}(\mathrm{x}), \ldots, h_{L}(\mathrm{x})\right] H(x)=[h1(x),,hL(x)]ELM非线性映射(隐藏层输出矩阵), h i ( x ) h_{i}(\mathrm{x}) hi(x)是第 i i i个隐藏层节点的输出。隐藏层节点的输出函数不是唯一的,不同的输出函数可以用于不同的隐藏层神经元。通常,在实际应用中, h i ( x ) h_{i}(\mathrm{x}) hi(x)用如下的表示:
    h i ( x ) = g ( w i , b i , x ) = g ( w i x + b i ) , w i ∈ R D , b i ∈ R (2.2) h_{i}(\mathrm{x})=g\left(\mathrm{w}_{i}, b_{i}, \mathrm{x}\right)=g(\mathrm{w}_{i}\mathrm{x}+\mathrm{b}_{i}), \mathrm{w}_{i} \in \mathrm{R}^{D}, b_{i} \in R\tag{2.2} hi(x)=g(wi,bi,x)=g(wix+bi),wiRD,biR(2.2)    其中 g ( w i , b i , x ) g\left(\mathrm{w}_{i}, b_{i}, \mathrm{x}\right) g(wi,bi,x) w i \mathrm{w}_{i} wi b i b_{i} bi是隐藏层节点参数)是激活函数,是一个满足ELM通用逼近能力定理的非线性分段连续函数,常用的有Sigmoid函数、Gaussian函数等。如我们可以使用Sigmoid函数,那么式(2.2)中的 g g g函数为 g ( x ) = 1 1 + e − x = e x e x + 1 (2.3) g(x)=\frac{1}{1+e^{-x}}=\frac{e^{x}}{e^{x}+1}\tag{2.3} g(x)=1+ex1=ex+1ex(2.3)(将 w i x + b i \mathrm{w}_{i}\mathrm{x}+\mathrm{b}_{i} wix+bi代入 x x x即可),该函数图像如下图所示

    图片展示

        经过隐层后进入输出层,根据上面的图示和公式,那么用于“广义”的单隐藏层前馈神经网络ELM的输出是
    f L ( x ) = ∑ i = 1 L β i h i ( x ) = H ( x ) β (2.4) f_{L}(\mathrm{x})=\sum_{i=1}^{L} \boldsymbol{\beta}_{i} h_{i}(\mathrm{x})=\mathrm{H}(\mathrm{x}) \boldsymbol{\beta} \tag{2.4} fL(x)=i=1Lβihi(x)=H(x)β(2.4)其中 β = [ β 1 , … , β L ] T \boldsymbol{\beta}=\left[\boldsymbol{\beta}_{1}, \ldots, \boldsymbol{\beta}_{L}\right]^{T} β=[β1,,βL]T是隐藏层( L L L个节点)与输出层( m m m个节点, m ≥ 1 m \geq 1 m1)之间的输出权重。至此神经网络从输入到输出的操作就是上面公式的计算过程。需要注意的是,目前为止上面公式中的未知量有 w , b , β \mathrm{w}, \mathrm{b}, \boldsymbol{\beta} w,b,β,分别是隐藏层节点上的权值、偏差及输出权值。我们知道神经网络学习(或训练)的过程就是根据训练数据来调整神经元之间的权值以及偏差,而实际上学到的东西则蕴含在连接权值和偏差之中。接下来我们就要用ELM的机理来求解这三个值(ELM训练过程)。

        基本上,ELM训练SLFN分为两个主要阶段:(1)随机特征映射(2)线性参数求解

        第一阶段,隐藏层参数随机进行初始化,然后采用一些非线性映射作为激活函数,将输入数据映射到一个新的特征空间(称为ELM特征空间)。简单来说就是ELM隐层节点上的权值和偏差是随机产生的。随机特征映射阶段与许多现有的学习算法(如使用核函数进行特征映射的SVM、深度神经网络中使用限制玻尔兹曼机器(RBM)、用于特征学习的自动编码器/自动解码器)不同。ELM 中的非线性映射函数可以是任何非线性分段连续函数。在ELM中,隐藏层节点参数( w w w b b b)根据任意连续的概率分布随机生成(与训练数据无关),而不是经过训练确定的,从而致使与传统BP神经网络相比在效率方面占很大优势。

        经过第一阶段 w , b \mathrm{w}, \mathrm{b} w,b已随机产生而确定下来,由此可根据公式(2.1)和(2.2)计算出隐藏层输出 H H H。在ELM学习的第二阶段,我们只需要求解输出层的权值( β \beta β)。为了得到在训练样本集上具有良好效果的 β \beta β,需要保证其训练误差最小,我们可以用 H β \mathrm{H} \boldsymbol{\beta} Hβ是网络的输出,如公式(2.4))与样本标签 T T T求最小化平方差作为评价训练误差(目标函数),使得该目标函数最小的解就是最优解。即通过最小化近似平方差的方法对连接隐藏层和输出层的权重( β \beta β)进行求解,目标函数如下:
    min ⁡ ∥ H β − T ∥ 2 , β ∈ R L × m (2.5) \min \|\mathrm{H} \boldsymbol{\beta}-\mathrm{T}\|^{2} , \boldsymbol{\beta} \in \mathbf{R}^{L \times m} \tag{2.5} minHβT2,βRL×m(2.5)其中H是隐藏层的输出矩阵,T是训练数据的目标矩阵:
    H = [ h ( x 1 ) , . . . , h ( x N ) ] T = [ h 1 ( x 1 ) . . . h L ( x 1 ) . . . h 1 ( x N ) . . . h L ( x N ) ] , T = [ t 1 T . . . t N T ] (2.6) H=[h(x_1),...,h(x_{N})]^{T}=\begin{bmatrix} h_1(x_1)&...&h_L(x_1) \\ & ... & \\ h_1(x_N) &...&h_L(x_N) \end{bmatrix},T=\begin{bmatrix} t_{1}^{T}\\ ... \\ t_{N}^{T} \end{bmatrix} \tag{2.6} H=[h(x1),...,h(xN)]T=h1(x1)h1(xN).........hL(x1)hL(xN),T=t1T...tNT(2.6)通过线代和矩阵论的知识可推导得公式(2.5)的最优解为:
    β ∗ = H † T (2.7) \beta^{*}=\mathrm{H}^{\dagger} \mathrm{T} \tag{2.7} β=HT(2.7)其中 H † \mathrm{H}^{\dagger} H为矩阵HMoore–Penrose广义逆矩阵。

        这时问题就转化为求计算矩阵HMoore–Penrose广义逆矩阵,该问题主要的几种方法有正交投影法、正交化法、迭代法和奇异值分解法(SVD)。当 H T H H^{T}H HTH为非奇异(可逆)时可使用正交投影法,这时可得计算结果是:
    H † = ( H T H ) − 1 H T \mathbf{H}^{\dagger}=\left(\mathbf{H}^{T} \mathbf{H}\right)^{-1} \mathbf{H}^{T} H=(HTH)1HT    然而有时候在应用时会出现 H T H H^{T}H HTH为奇异的(不可逆)情况,因此正交投影法并不能很好地应用到所有的情况。由于使用了搜索和迭代,正交化方法和迭代方法具有局限性。而奇异值分解(SVD)总是可以用来计算 H H HMoore–Penrose广义逆,因此用于ELM的大多数实现中。具体的求解过程涉及矩阵论的知识,而且在许多编程语言中都已封装了求解广义逆的可调用的函数,限于篇幅这里博主就不多展开了。至此训练部分全部完成,在测试时利用训练得到的结果便可预测结果。

        综合上面说的,对于训练单隐层前馈神经网络的极限学习机算法做出总结,算法如下所示:

    算法:极限学习机(ELM)
    输入
        数据集: { x i , t i ∣ x i ∈ R D , t i ∈ R m , i = 1 , 2 , … , N } \left\{\mathrm{x}_{i}, \mathrm{t}_{i} | \mathrm{x}_{i} \in \mathrm{R}^{D}, \mathrm{t}_{i} \in \mathrm{R}^{m}, i=1,2, \ldots, N\right\} {xi,tixiRD,tiRm,i=1,2,,N}
        隐层神经元数目: L L L
        激活函数: g ( . ) g( .) g(.)
    输出
        输出权重: β \beta β
        1.随机产生输入权重 w w w和隐层偏差 b b b
        2.计算隐藏层输出 H = g ( x ∗ ω + b ) \mathrm{H}=\mathrm{g}(\mathrm{x} * \boldsymbol{\omega}+\mathrm{b}) H=g(xω+b)
        3.采用式(2.7)计算输出层权重 β \mathrm{\beta} β

        其实以上的理论尚有不足之处,根据Bartlett理论,对于达到较小的训练误差的前馈神经网络,通常其权值范数越小,网络趋向于获得更好的泛化性能。为了进一步提高传统极限学习机的稳定性和泛化能力,可以在上面目标函数中增加权值范数的约束项来重新求解输出权重,因此提出了优化等式约束的极限学习机。除此之外后来还有许多文章在原有极限学习机的理论和算法上进行了改进,这里就不多介绍了有兴趣的可以了解一下。


    3. 算法程序实现

        数学中的符号语言可谓精简巧妙,其简洁之美不经间引人入胜,同时它的复杂又让人觉得自己知道的还很匮乏。作为工科的我数学基本功仅限于刚考研时的临阵磨枪,如今的研究生阶段接触了最优理论、统计分析和矩阵论的知识但自感尚不能得其万一。上面的算法推导是完成了,但不代表程序就能完整照上面的步骤实现了,毕竟理论和实际总是有差距的。

        其实ELM的程序代码早已开放,提供源码下载的网站:黄广斌老师的ELM资源主页,上面已经有了MATLABC++pythonJava的版本,使用起来也比较方便。这里博主就其中MATLAB的代码介绍下算法的程序实现,为方便初学者理解源码,我只是在其中的大部分代码中进行了注释,当然为避免个人理解的偏差建议可去官网查看英文源码。

    function [TrainingTime, TestingTime, TrainingAccuracy, TestingAccuracy] = ELM(TrainingData_File, TestingData_File, Elm_Type, NumberofHiddenNeurons, ActivationFunction)
    %% ELM 算法程序
    % 调用方式: [TrainingTime, TestingTime, TrainingAccuracy, TestingAccuracy] = elm(TrainingData_File, TestingData_File, Elm_Type, NumberofHiddenNeurons, ActivationFunction)
    %
    % 输入:
    % TrainingData_File     - 训练数据集文件名
    % TestingData_File      - 测试训练集文件名
    % Elm_Type              - 任务类型:0 时为回归任务,1 时为分类任务
    % NumberofHiddenNeurons - ELM的隐层神经元数目
    % ActivationFunction    - 激活函数类型:
    %                           'sig' , Sigmoidal 函数
    %                           'sin' , Sine 函数
    %                           'hardlim' , Hardlim 函数
    %                           'tribas' , Triangular basis 函数
    %                           'radbas' , Radial basis 函数
    % 输出: 
    % TrainingTime          - ELM 训练花费的时间(秒)
    % TestingTime           - 测试数据花费的时间(秒)
    % TrainingAccuracy      - 训练的准确率(回归任务时为RMSE,分类任务时为分类正确率)                       
    % TestingAccuracy       - 测试的准确率(回归任务时为RMSE,分类任务时为分类正确率)
    %
    % 调用示例(回归): [TrainingTime, TestingTime, TrainingAccuracy, TestingAccuracy] = ELM('sinc_train', 'sinc_test', 0, 20, 'sig')
    % 调用示例(分类): [TrainingTime, TestingTime, TrainingAccuracy, TestingAccuracy] = ELM('diabetes_train', 'diabetes_test', 1, 20, 'sig')
    
    
    %% 数据预处理
    
    % 定义任务类型
    REGRESSION=0;
    CLASSIFIER=1;
    
    % 载入训练数据集
    train_data=load(TrainingData_File);
    T=train_data(:,1)';                   % 第一列:分类或回归的期望输出
    P=train_data(:,2:size(train_data,2))';% 第二列到最后一列:不同数据的属性
    clear train_data;                     % 清除中间变量
    
    % 载入测试数据集
    test_data=load(TestingData_File);
    TV.T=test_data(:,1)';                  % 第一列:分类或回归的期望输出
    TV.P=test_data(:,2:size(test_data,2))';% 第二列到最后一列:不同数据的属性
    clear test_data;                       % 清除中间变量
    
    % 获取训练、测试数据情况
    NumberofTrainingData=size(P,2);        % 训练数据中分类对象个数
    NumberofTestingData=size(TV.P,2);      % 测试数据中分类对象个数
    NumberofInputNeurons=size(P,1);        % 神经网络输入个数,训练数据的属性个数
    
    %% 分类任务时的数据编码
    if Elm_Type~=REGRESSION
        % 分类任务数据预处理
        sorted_target=sort(cat(2,T,TV.T),2);% 将训练数据和测试数据的期望输出合并成一行,然后按从小到大排序
        label=zeros(1,1);                   %  Find and save in 'label' class label from training and testing data sets
        label(1,1)=sorted_target(1,1);      % 存入第一个标签
        j=1;
        % 遍历所有数据集标签(期望输出)得到数据集的分类数目
        for i = 2:(NumberofTrainingData+NumberofTestingData)
            if sorted_target(1,i) ~= label(1,j)
                j=j+1;
                label(1,j) = sorted_target(1,i);
            end
        end
        number_class=j;                    % 统计数据集(训练数据和测试数据)一共有几类
        NumberofOutputNeurons=number_class;% 一共有几类,神经网络就有几个输出
           
        % 预定义期望输出矩阵
        temp_T=zeros(NumberofOutputNeurons, NumberofTrainingData);
        % 遍历所有训练数据的标记,扩充为num_class*NumberofTraingData的矩阵 
        for i = 1:NumberofTrainingData
            for j = 1:number_class
                if label(1,j) == T(1,i)
                    break; 
                end
            end
            temp_T(j,i)=1;                %一个矩阵,行是分类,列是对象,如果该对象在此类就置1
        end
        T=temp_T*2-1;                     % T为处理的期望输出矩阵,每个对象(列)所在的真实类(行)位置为1,其余为-1
    
        % 遍历所有测试数据的标记,扩充为num_class*NumberofTestingData的矩阵 
        temp_TV_T=zeros(NumberofOutputNeurons, NumberofTestingData);
        for i = 1:NumberofTestingData
            for j = 1:number_class
                if label(1,j) == TV.T(1,i)
                    break; 
                end
            end
            temp_TV_T(j,i)=1;            % 期望输出表示矩阵,行是分类,列是对象,如果该对象在此类就置1
        end
        TV.T=temp_TV_T*2-1;              % T为处理的期望输出矩阵,每个对象(列)所在的真实类(行)位置为1,其余为-1
    
    
    end  % Elm_Type
    
    %% 计算隐藏层的输出H
    start_time_train=cputime;           % 训练开始计时
    
    % 随机产生输入权值InputWeight (w_i)和隐层偏差biases BiasofHiddenNeurons (b_i)
    InputWeight=rand(NumberofHiddenNeurons,NumberofInputNeurons)*2-1; % 输入节点的权重在[-1,1]之间
    BiasofHiddenNeurons=rand(NumberofHiddenNeurons,1);                % 连接偏重在[0,1]之间
    tempH=InputWeight*P; % 不同对象的属性*权重
    clear P; % 释放空间 
    ind=ones(1,NumberofTrainingData);     % 训练集中分类对象的个数
    BiasMatrix=BiasofHiddenNeurons(:,ind);% 扩展BiasMatrix矩阵大小与H匹配 
    tempH=tempH+BiasMatrix;               % 加上偏差的最终隐层输入
    
    %计算隐藏层输出矩阵
    switch lower(ActivationFunction) % 选择激活函数,lower是将字母统一为小写
        case {'sig','sigmoid'}
            H = 1 ./ (1 + exp(-tempH));% Sigmoid 函数
        case {'sin','sine'}
            H = sin(tempH);            % Sine 函数
        case {'hardlim'}
            H = double(hardlim(tempH));% Hardlim 函数
        case {'tribas'}
            H = tribas(tempH);         % Triangular basis 函数
        case {'radbas'}
            H = radbas(tempH);         % Radial basis 函数
        % 可在此添加更多激活函数                
    end
    clear tempH;% 释放不在需要的变量
    
    %% 计算输出权重 OutputWeight (beta_i)
    OutputWeight=pinv(H') * T';   % 无正则化因子的应用,参考2006年 Neurocomputing 期刊上的论文
    % OutputWeight=inv(eye(size(H,1))/C+H * H') * H * T';   % faster method 1 ,参考 2012 IEEE TSMC-B 论文
    % OutputWeight=(eye(size(H,1))/C+H * H') \ H * T';      % faster method 2 ,refer to 2012 IEEE TSMC-B 论文
    
    end_time_train=cputime;
    TrainingTime=end_time_train-start_time_train; % 计算训练ELM时CPU花费的时间
    
    % 计算输出
    Y=(H' * OutputWeight)';                       % Y为训练数据输出(列向量) 
    if Elm_Type == REGRESSION 
        TrainingAccuracy=sqrt(mse(T - Y));        % 回归问题计算均方误差根
    end
    clear H;
    
    %% 计算测试数据的输出(预测标签)
    start_time_test=cputime;    % 测试计时
    tempH_test=InputWeight*TV.P;% 测试的输入
    clear TV.P;  
    
    ind=ones(1,NumberofTestingData);
    BiasMatrix=BiasofHiddenNeurons(:,ind); % 扩展BiasMatrix矩阵大小与H匹配 
    tempH_test=tempH_test + BiasMatrix;% 加上偏差的最终隐层输入
    switch lower(ActivationFunction)
        case {'sig','sigmoid'}% Sigmoid 函数   
            H_test = 1 ./ (1 + exp(-tempH_test));
        case {'sin','sine'}   % Sine 函数 
            H_test = sin(tempH_test);        
        case {'hardlim'}      % Hardlim 函数
            H_test = hardlim(tempH_test);        
        case {'tribas'}       % Triangular basis 函数
             H_test = tribas(tempH_test);        
        case {'radbas'}       % Radial basis 函数
             H_test = radbas(tempH_test);        
        % 可在此添加更多激活函数             
    end
    TY=(H_test' * OutputWeight)';                       %   TY: 测试数据的输出
    
    end_time_test=cputime;
    TestingTime=end_time_test-start_time_test;          % 计算ELM测试集时CPU花费的时间
    
    %% 计算准确率
    if Elm_Type == REGRESSION
        TestingAccuracy=sqrt(mse(TV.T - TY));           % 回归问题计算均方误差根
    end
    
    % 如果是分类问题计算分类的准确率
    if Elm_Type == CLASSIFIER 
        MissClassificationRate_Training=0;
        MissClassificationRate_Testing=0;
        % 计算训练集上的分类准确率
        for i = 1 : size(T, 2) 
            [x, label_index_expected]=max(T(:,i));
            [x, label_index_actual]=max(Y(:,i));
            if label_index_actual~=label_index_expected
                MissClassificationRate_Training=MissClassificationRate_Training+1;
            end
        end
        % 计算测试集上的分类准确率
        TrainingAccuracy=1-MissClassificationRate_Training/size(T,2); % 训练集分类正确率
        for i = 1 : size(TV.T, 2)
            [x, label_index_expected]=max(TV.T(:,i));
            [x, label_index_actual]=max(TY(:,i));
            if label_index_actual~=label_index_expected
                MissClassificationRate_Testing=MissClassificationRate_Testing+1;
            end
        end
        TestingAccuracy=1-MissClassificationRate_Testing/size(TV.T,2);  % 测试集分类正确率
    end
    

        在以上代码中博主已相当详实地进行了注释,需要注意的是程序中默认训练和测试文件中第一列上的数据为样本标记,其余所有列中数据为样本属性,因此在调用该函数时应先保证自己的数据集已经整理为正确的格式,如下面整理好的Iris数据集(第一列为样本标号)。

    图片展示

        至于数据集如何整理可参考博主前面的博文:UCI数据集整理(附论文常用数据集),其中提供了详细的数据集和相关介绍。这里我们写一个测试函数验证以上的ELM函数,对于分类任务我们选取经典的UCI Iris数据集,测试的代码如下:

    clear all 
    clc
    
    dataSet = load('iris.txt');                             % 载入数据集
    len_dataSet = size(dataSet,1);                          % 数据集样本数
    ind = randperm(len_dataSet);                            % 随机挑选数据
    train_set = dataSet(ind(1:round(len_dataSet*0.7)),:);   % 随机的70%数据作为训练集
    test_set = dataSet(ind(round(len_dataSet*0.7)+1:end),:);% 随机的30%数据作为测试集
    
    save iris_train.txt -ascii train_set                    % 保存训练集为txt文件
    save iris_test.txt -ascii test_set                      % 保存测试集为txt文件
    
    % 调用ELM函数
    [TrainingTime, TestingTime, TrainingAccuracy, TestingAccuracy] = ELM('iris_train.txt', 'iris_test.txt', 1, 20, 'sig');
    
    % 输出结果
    fprintf('训练集准确率:%g \n',TrainingAccuracy);
    fprintf('测试集准确率:%g \n',TestingAccuracy);
    

    运行结果如下:

    图片展示

        对于回归任务可选取Sinc的数据集(该数据已整理分为训练和测试集),测试的代码如下:

    clear all
    clc
    
    % 调用ELM函数
    [TrainingTime,TestingTime,TrainingAccuracy,TestingAccuracy] = ELM('sinc_train','sinc_test',0,20,'sig');
    
    % 输出结果
    fprintf('训练集MSE:%g \n',TrainingAccuracy);
    fprintf('测试集MSE:%g \n',TestingAccuracy);
    

    运行结果如下:

    图片展示


    下载链接
        若您想获得博文中涉及的实现完整全部程序文件(包括数据集,m, txt文件等,如下图),这里已打包上传至博主的CSDN下载资源中,下载后运行run_ELM1.mrun_ELM2.m文件即可运行。文件下载链接如下:
    文件情况
    下载链接1:博文中涉及的完整程序文件(CSDN下载)

    下载链接2:博文中涉及的完整程序文件(面包多下载)

    公众号获取
        本人微信公众号已创建,扫描以下二维码并关注公众号“AI技术研究与分享”,后台回复“EF20190607”即可获取全部资源文件。


    5. 结束语

        由于博主能力有限,博文中提及的方法与代码即使经过测试,也难免会有疏漏之处。希望您能热心指出其中的错误,以便下次修改时能以一个更完美更严谨的样子,呈现在大家面前。同时如果有更好的实现方法也请您不吝赐教。

        大家的点赞和关注是博主最大的动力,博主所有博文中的代码文件都可分享给您,如果您想要获取博文中的完整代码文件,可通过C币或积分下载,没有C币或积分的朋友可在关注、点赞博文后提供邮箱,我会在第一时间发送给您。博主后面会有更多的分享,敬请关注哦!

    展开全文
  • 之前毕业设计用TensorFlow做了手写汉字识别,使用的中科院的数据。 现在用Pytorch复现一下。 下载链接在文末

    之前毕业设计用TensorFlow做了手写汉字识别,使用的中科院的数据集。
    参考了一篇博客: TensorFlow与中文手写汉字识别
    现在用Pytorch复现一下。
    Github下载链接在文末

    如果有问题可以在评论区评论或者私信我,提问之前还请点个赞支持一下头秃博主哦


    环境:
    Pytorch:1.0.1 GPU版
    Ubuntu:16.04
    Python:3.5.2

    1 数据集整理:

    分为 train 和 test 文件夹,每个文件夹下每一类都分一个子文件夹并编号。
    在这里插入图片描述
    这是为了方便用 Python 做一个 txt 文件,指明所有图片数据的路径。在自定义数据集类的时候会用到。

    如果你没有数据集可以参考 TensorFlow与中文手写汉字识别 前面的部分下载及处理数据集。

    2 import

    import os
    import torch
    import torch.nn as nn
    import torch.nn.functional as  F
    import torch.optim as optim
    import torchvision.transforms as transforms
    from torch.utils.data import DataLoader, Dataset
    from PIL import Image
    import argparse # 提取命令行参数
    

    3 提取图片路径

    一个函数就可以实现了:

    def classes_txt(root, out_path, num_class=None):
        '''
        write image paths (containing class name) into a txt file.
        :param root: data set path
        :param out_path: txt file path
        :param num_class: how many classes needed
        :return: None
        '''
        dirs = os.listdir(root) # 列出根目录下所有类别所在文件夹名
        if not num_class:		# 不指定类别数量就读取所有
            num_class = len(dirs)
    
        if not os.path.exists(out_path): # 输出文件路径不存在就新建
            f = open(out_path, 'w')
            f.close()
    	# 如果文件中本来就有一部分内容,只需要补充剩余部分
    	# 如果文件中数据的类别数比需要的多就跳过
        with open(out_path, 'r+') as f:
            try:
                end = int(f.readlines()[-1].split('/')[-2]) + 1
            except:
                end = 0
            if end < num_class - 1:
                dirs.sort()
                dirs = dirs[end:num_class]
                for dir in dirs:
                    files = os.listdir(os.path.join(root, dir))
                    for file in files:
                        f.write(os.path.join(root, dir, file) + '\n')
    

    4 自定义数据集

    只需要重写 Dataset 里的 __init__, __getitem__, __len__ 就可以了。
    __getitem__在训练的时候返回输入网络的数据,图片和标签等等,需要和训练测试的程序配合。
    __len__ 返回数据集长度。

    class MyDataset(Dataset):
        def __init__(self, txt_path, num_class, transforms=None):
            super(MyDataset, self).__init__()
            images = [] # 存储图片路径
            labels = [] # 存储类别名,在本例中是数字
            # 打开上一步生成的txt文件
            with open(txt_path, 'r') as f:
                for line in f:
                    if int(line.split('/')[-2]) >= num_class:  # 只读取前 num_class 个类
                        break
                    line = line.strip('\n')
                    images.append(line)
                    labels.append(int(line.split('/')[-2]))
            self.images = images
            self.labels = labels
            self.transforms = transforms # 图片需要进行的变换,ToTensor()等等
    
        def __getitem__(self, index):
            image = Image.open(self.images[index]).convert('RGB') # 用PIL.Image读取图像
            label = self.labels[index]
            if self.transforms is not None:
                image = self.transforms(image) # 进行变换
            return image, label
    
        def __len__(self):
            return len(self.labels)
    

    5 搭建神经网络

    这里用一个简单的网络进行示例。
    两层卷积,三层全连接,20个类别的情况下可以训练至95%以上的准确率。

    class NetSmall(nn.Module):
        def __init__(self):
            super(NetSmall, self).__init__()
            self.conv1 = nn.Conv2d(1, 6, 3) # 3个参数分别是in_channels,out_channels,kernel_size,还可以加padding
            self.pool = nn.MaxPool2d(2, 2)
            self.conv2 = nn.Conv2d(6, 16, 5)
            self.fc1 = nn.Linear(2704, 512)
            self.fc2 = nn.Linear(512, 84)
            self.fc3 = nn.Linear(84, args.num_class) # 命令行参数,后面解释
    
        def forward(self, x):
            x = self.pool(F.relu(self.conv1(x)))
            x = self.pool(F.relu(self.conv2(x)))
            x = x.view(-1, 2704)
            x = F.relu(self.fc1(x))
            x = F.relu(self.fc2(x))
            x = self.fc3(x)
            return x
    

    补充:关于torch.nn 和 torch.nn.functional 的区别
    实现的功能比较近似,functional 是以函数的方式实现的,nn 是以类的方式实现的。所以 nn 封装的更好,可以在反向传播时实现自动保存导数等功能,但是在具体实现时还是调用了 functional 的函数。
    如果使用了dropout的话需要更改模型的state, model.train(), model.eval()
    参考:https://www.zhihu.com/question/66782101

    6 train, validation and inference

    这三个函数实现比较相似,train()会增加一层遍历数据集的循环,以及计算loss和反向传播。

    def train():
    	# 由于我的数据集图片尺寸不一,因此要进行resize,这里还可以加入数据增强,灰度变换,随机剪切等等
        transform = transforms.Compose([transforms.Resize((args.image_size, args.image_size)),
                                        transforms.Grayscale(),
                                        transforms.ToTensor()])
    
        train_set = MyDataset(args.root + '/train.txt', num_class=args.num_class, transforms=transform)
        train_loader = DataLoader(train_set, batch_size=args.batch_size, shuffle=True)
    	# 选择使用的设备
        device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')
        print(device)
    
        model = NetSmall()
        model.to(device)
    	# 训练模式
        model.train()
    
        criterion = nn.CrossEntropyLoss()
        optimizer = optim.Adam(model.parameters(), lr=0.001)
    	# 由命令行参数决定是否从之前的checkpoint开始训练
        if args.restore:
            checkpoint = torch.load(args.log_path)
            model.load_state_dict(checkpoint['model_state_dict'])
            optimizer.load_state_dict(checkpoint['optimizer_state_dict'])
            loss = checkpoint['loss']
            epoch = checkpoint['epoch']
        else:
            loss = 0.0
            epoch = 0
    
        while epoch < args.epoch:
            running_loss = 0.0
    
            for i, data in enumerate(train_loader):
            # 这里取出的数据就是 __getitem__() 返回的数据
                inputs, labels = data[0].to(device), data[1].to(device)
    
                optimizer.zero_grad()
                outs = model(inputs)
                loss = criterion(outs, labels)
                loss.backward()
                optimizer.step()
    
                running_loss += loss.item()
    
                if i % 200 == 199:  # every 200 steps
                    print('epoch %5d: batch: %5d, loss: %f' % (epoch + 1, i + 1, running_loss / 200))
                    running_loss = 0.0
    		# 保存 checkpoint
            if epoch % 10 == 9:
                print('Save checkpoint...')
                torch.save({'epoch': epoch,
                            'model_state_dict': model.state_dict(),
                            'optimizer_state_dict': optimizer.state_dict(),
                            'loss': loss},
                           args.log_path)
            epoch += 1
    
        print('Finish training')
    
    
    def validation():
        transform = transforms.Compose([transforms.Resize((args.image_size, args.image_size)),
                                        transforms.Grayscale(),
                                        transforms.ToTensor()])
    
        test_set = MyDataset(args.root + '/test.txt', num_class=args.num_class, transforms=transform)
        test_loader = DataLoader(test_set, batch_size=args.batch_size)
    
        device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')
        model = NetSmall()
        model.to(device)
    
        checkpoint = torch.load(args.log_path)
        model.load_state_dict(checkpoint['model_state_dict'])
    
        model.eval()
    
        total = 0.0
        correct = 0.0
        with torch.no_grad():
            for i, data in enumerate(test_loader):
                inputs, labels = data[0].cuda(), data[1].cuda()
                outputs = model(inputs)
                _, predict = torch.max(outputs.data, 1)
                total += labels.size(0)
                correct += sum(int(predict == labels)).item()
                # 根据评论区反馈,如果上面这句报错,可以换成下面这句试试:
                # correct += (predict == labels).sum().item()
    
                if i % 100 == 99:
                    print('batch: %5d,\t acc: %f' % (i + 1, correct / total))
        print('Accuracy: %.2f%%' % (correct / total * 100))
    
    
    def inference():
        print('Start inference...')
        transform = transforms.Compose([transforms.Resize((args.image_size, args.image_size)),
                                        transforms.Grayscale(),
                                        transforms.ToTensor()])
    
        f = open(args.root + '/test.txt')
        num_line = sum(line.count('\n') for line in f)
        f.seek(0, 0)
        # 在文件中随机取一个路径
        line = int(torch.rand(1).data * num_line - 10) # -10 for '\n's are more than lines
        while line > 0:
            f.readline()
            line -= 1
        img_path = f.readline().rstrip('\n')
        f.close()
        label = int(img_path.split('/')[-2])
        print('label:\t%4d' % label)
        input = Image.open(img_path).convert('RGB')
        input = transform(input)
        # 网络默认接受4维数据,即[Batch, Channel, Heigth, Width],所以要加1个维度
        input = input.unsqueeze(0)
        model = NetSmall()
        model.eval()
        checkpoint = torch.load(args.log_path)
        model.load_state_dict(checkpoint['model_state_dict'])
        output = model(input)
        _, pred = torch.max(output.data, 1)
        
        print('predict:\t%4d' % pred)
    

    7 命令行参数设置

    设定命令行参数可以在不修改程序的情况下更改一些需要调整的参数,比如batch_size, resize 之后的image_size, epoch的值, 模式等等。写在程序的 import 部分后面即可。

    parse = argparse.ArgumentParser(description='Params for training. ')
    # 数据集根目录
    parse.add_argument('--root', type=str, default='/home/chenyiran/character_rec/data', help='path to data set')
    # 模式,3选1
    parse.add_argument('--mode', type=str, default='train', choices=['train', 'validation', 'inference'])
    # checkpoint 路径
    parse.add_argument('--log_path', type=str, default=os.path.abspath('.') + '/log.pth', help='dir of checkpoints')
    
    parse.add_argument('--restore', type=bool, default=True, help='whether to restore checkpoints')
    
    parse.add_argument('--batch_size', type=int, default=16, help='size of mini-batch')
    parse.add_argument('--image_size', type=int, default=64, help='resize image')
    parse.add_argument('--epoch', type=int, default=100)
    # 我的数据集类别数是3755,所以给定了一个选择范围
    parse.add_argument('--num_class', type=int, default=100, choices=range(10, 3755))
    args = parse.parse_args()
    

    关于argpase的使用可以参考官方文档:https://docs.python.org/3.5/library/argparse.html

    8 最后,主程序

    很简单

    if __name__ == '__main__':
    
        classes_txt(args.root + '/train', args.root + '/train.txt', num_class=args.num_class)
        classes_txt(args.root + '/test', args.root + '/test.txt', num_class=args.num_class)
    
        if args.mode == 'train':
            train()
        elif args.mode == 'validation':
            validation()
        elif args.mode == 'inference':
            inference()
    

    以上就是所有的程序了。

    直接下载请移步:

    https://github.com/chenyr0021/Chinese_character_recognition/tree/master

    知乎:@陈小白233
    公众号:一本正经的搬砖日常

    不点个赞再走嘛

    展开全文
  • MyBatis面试题(2020最新版)

    万次阅读 多人点赞 2019-09-24 16:40:33
    整理好的MyBatis面试题库,史上最全的MyBatis面试题,...MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果。MyBatis 可以使用简单的 XML 或注解来配置和映射原生类型、接口和 Java 的 POJO(Plai...

    Java面试总结(2021优化版)已发布在个人微信公众号【技术人成长之路】,优化版首先修正了读者反馈的部分答案存在的错误,同时根据最新面试总结,删除了低频问题,添加了一些常见面试题,对文章进行了精简优化,欢迎大家关注!😊😊

    【技术人成长之路】,助力技术人成长!更多精彩文章第一时间在公众号发布哦!

    文章目录

    Java面试总结汇总,整理了包括Java基础知识,集合容器,并发编程,JVM,常用开源框架Spring,MyBatis,数据库,中间件等,包含了作为一个Java工程师在面试中需要用到或者可能用到的绝大部分知识。欢迎大家阅读,本人见识有限,写的博客难免有错误或者疏忽的地方,还望各位大佬指点,在此表示感激不尽。文章持续更新中…

    序号内容链接地址
    1Java基础知识面试题(2020最新版)https://thinkwon.blog.csdn.net/article/details/104390612
    2Java集合容器面试题(2020最新版)https://thinkwon.blog.csdn.net/article/details/104588551
    3Java异常面试题(2020最新版)https://thinkwon.blog.csdn.net/article/details/104390689
    4并发编程面试题(2020最新版)https://thinkwon.blog.csdn.net/article/details/104863992
    5JVM面试题(2020最新版)https://thinkwon.blog.csdn.net/article/details/104390752
    6Spring面试题(2020最新版)https://thinkwon.blog.csdn.net/article/details/104397516
    7Spring MVC面试题(2020最新版)https://thinkwon.blog.csdn.net/article/details/104397427
    8Spring Boot面试题(2020最新版)https://thinkwon.blog.csdn.net/article/details/104397299
    9Spring Cloud面试题(2020最新版)https://thinkwon.blog.csdn.net/article/details/104397367
    10MyBatis面试题(2020最新版)https://thinkwon.blog.csdn.net/article/details/101292950
    11Redis面试题(2020最新版)https://thinkwon.blog.csdn.net/article/details/103522351
    12MySQL数据库面试题(2020最新版)https://thinkwon.blog.csdn.net/article/details/104778621
    13消息中间件MQ与RabbitMQ面试题(2020最新版)https://thinkwon.blog.csdn.net/article/details/104588612
    14Dubbo面试题(2020最新版)https://thinkwon.blog.csdn.net/article/details/104390006
    15Linux面试题(2020最新版)https://thinkwon.blog.csdn.net/article/details/104588679
    16Tomcat面试题(2020最新版)https://thinkwon.blog.csdn.net/article/details/104397665
    17ZooKeeper面试题(2020最新版)https://thinkwon.blog.csdn.net/article/details/104397719
    18Netty面试题(2020最新版)https://thinkwon.blog.csdn.net/article/details/104391081
    19架构设计&分布式&数据结构与算法面试题(2020最新版)https://thinkwon.blog.csdn.net/article/details/105870730

    整理好的MyBatis面试题库,史上最全的MyBatis面试题,MyBatis面试宝典,特此分享给大家

    MyBatis简介

    MyBatis是什么?

    MyBatis 是一款优秀的持久层框架,一个半 ORM(对象关系映射)框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生类型、接口和 Java 的 POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。

    ORM是什么

    ORM(Object Relational Mapping),对象关系映射,是一种为了解决关系型数据库数据与简单Java对象(POJO)的映射关系的技术。简单的说,ORM是通过使用描述对象和数据库之间映射的元数据,将程序中的对象自动持久化到关系型数据库中。

    为什么说Mybatis是半自动ORM映射工具?它与全自动的区别在哪里?

    Hibernate属于全自动ORM映射工具,使用Hibernate查询关联对象或者关联集合对象时,可以根据对象关系模型直接获取,所以它是全自动的。

    而Mybatis在查询关联对象或关联集合对象时,需要手动编写sql来完成,所以,称之为半自动ORM映射工具。

    传统JDBC开发存在的问题

    • 频繁创建数据库连接对象、释放,容易造成系统资源浪费,影响系统性能。可以使用连接池解决这个问题。但是使用jdbc需要自己实现连接池。
    • sql语句定义、参数设置、结果集处理存在硬编码。实际项目中sql语句变化的可能性较大,一旦发生变化,需要修改java代码,系统需要重新编译,重新发布。不好维护。
    • 使用preparedStatement向占有位符号传参数存在硬编码,因为sql语句的where条件不一定,可能多也可能少,修改sql还要修改代码,系统不易维护。
    • 结果集处理存在重复代码,处理麻烦。如果可以映射成Java对象会比较方便。

    JDBC编程有哪些不足之处,MyBatis是如何解决这些问题的?

    1、数据库链接创建、释放频繁造成系统资源浪费从而影响系统性能,如果使用数据库连接池可解决此问题。

    解决:在mybatis-config.xml中配置数据链接池,使用连接池管理数据库连接。

    2、Sql语句写在代码中造成代码不易维护,实际应用sql变化的可能较大,sql变动需要改变java代码。

    解决:将Sql语句配置在XXXXmapper.xml文件中与java代码分离。

    3、向sql语句传参数麻烦,因为sql语句的where条件不一定,可能多也可能少,占位符需要和参数一一对应。

    解决: Mybatis自动将java对象映射至sql语句。

    4、对结果集解析麻烦,sql变化导致解析代码变化,且解析前需要遍历,如果能将数据库记录封装成pojo对象解析比较方便。

    解决:Mybatis自动将sql执行结果映射至java对象。

    Mybatis优缺点

    优点

    与传统的数据库访问技术相比,ORM有以下优点:

    • 基于SQL语句编程,相当灵活,不会对应用程序或者数据库的现有设计造成任何影响,SQL写在XML里,解除sql与程序代码的耦合,便于统一管理;提供XML标签,支持编写动态SQL语句,并可重用
    • 与JDBC相比,减少了50%以上的代码量,消除了JDBC大量冗余的代码,不需要手动开关连接
    • 很好的与各种数据库兼容(因为MyBatis使用JDBC来连接数据库,所以只要JDBC支持的数据库MyBatis都支持)
    • 提供映射标签,支持对象与数据库的ORM字段关系映射;提供对象关系映射标签,支持对象关系组件维护
    • 能够与Spring很好的集成

    缺点

    • SQL语句的编写工作量较大,尤其当字段多、关联表多时,对开发人员编写SQL语句的功底有一定要求
    • SQL语句依赖于数据库,导致数据库移植性差,不能随意更换数据库

    MyBatis框架适用场景

    • MyBatis专注于SQL本身,是一个足够灵活的DAO层解决方案。
    • 对性能的要求很高,或者需求变化较多的项目,如互联网项目,MyBatis将是不错的选择。

    Hibernate 和 MyBatis 的区别

    相同点

    都是对jdbc的封装,都是持久层的框架,都用于dao层的开发。

    不同点

    映射关系

    • MyBatis 是一个半自动映射的框架,配置Java对象与sql语句执行结果的对应关系,多表关联关系配置简单
    • Hibernate 是一个全表映射的框架,配置Java对象与数据库表的对应关系,多表关联关系配置复杂

    SQL优化和移植性

    • Hibernate 对SQL语句封装,提供了日志、缓存、级联(级联比 MyBatis 强大)等特性,此外还提供 HQL(Hibernate Query Language)操作数据库,数据库无关性支持好,但会多消耗性能。如果项目需要支持多种数据库,代码开发量少,但SQL语句优化困难。
    • MyBatis 需要手动编写 SQL,支持动态 SQL、处理列表、动态生成表名、支持存储过程。开发工作量相对大些。直接使用SQL语句操作数据库,不支持数据库无关性,但sql语句优化容易。

    开发难易程度和学习成本

    • Hibernate 是重量级框架,学习使用门槛高,适合于需求相对稳定,中小型的项目,比如:办公自动化系统

    • MyBatis 是轻量级框架,学习使用门槛低,适合于需求变化频繁,大型的项目,比如:互联网电子商务系统

    总结

    MyBatis 是一个小巧、方便、高效、简单、直接、半自动化的持久层框架,

    Hibernate 是一个强大、方便、高效、复杂、间接、全自动化的持久层框架。

    MyBatis的解析和运行原理

    MyBatis编程步骤是什么样的?

    1、 创建SqlSessionFactory

    2、 通过SqlSessionFactory创建SqlSession

    3、 通过sqlsession执行数据库操作

    4、 调用session.commit()提交事务

    5、 调用session.close()关闭会话

    请说说MyBatis的工作原理

    在学习 MyBatis 程序之前,需要了解一下 MyBatis 工作原理,以便于理解程序。MyBatis 的工作原理如下图

    MyBatis工作原理

    1)读取 MyBatis 配置文件:mybatis-config.xml 为 MyBatis 的全局配置文件,配置了 MyBatis 的运行环境等信息,例如数据库连接信息。

    2)加载映射文件。映射文件即 SQL 映射文件,该文件中配置了操作数据库的 SQL 语句,需要在 MyBatis 配置文件 mybatis-config.xml 中加载。mybatis-config.xml 文件可以加载多个映射文件,每个文件对应数据库中的一张表。

    3)构造会话工厂:通过 MyBatis 的环境等配置信息构建会话工厂 SqlSessionFactory。

    4)创建会话对象:由会话工厂创建 SqlSession 对象,该对象中包含了执行 SQL 语句的所有方法。

    5)Executor 执行器:MyBatis 底层定义了一个 Executor 接口来操作数据库,它将根据 SqlSession 传递的参数动态地生成需要执行的 SQL 语句,同时负责查询缓存的维护。

    6)MappedStatement 对象:在 Executor 接口的执行方法中有一个 MappedStatement 类型的参数,该参数是对映射信息的封装,用于存储要映射的 SQL 语句的 id、参数等信息。

    7)输入参数映射:输入参数类型可以是 Map、List 等集合类型,也可以是基本数据类型和 POJO 类型。输入参数映射过程类似于 JDBC 对 preparedStatement 对象设置参数的过程。

    8)输出结果映射:输出结果类型可以是 Map、 List 等集合类型,也可以是基本数据类型和 POJO 类型。输出结果映射过程类似于 JDBC 对结果集的解析过程。

    MyBatis的功能架构是怎样的

    Mybatis功能框架
    我们把Mybatis的功能架构分为三层:

    • API接口层:提供给外部使用的接口API,开发人员通过这些本地API来操纵数据库。接口层一接收到调用请求就会调用数据处理层来完成具体的数据处理。
    • 数据处理层:负责具体的SQL查找、SQL解析、SQL执行和执行结果映射处理等。它主要的目的是根据调用的请求完成一次数据库操作。
    • 基础支撑层:负责最基础的功能支撑,包括连接管理、事务管理、配置加载和缓存处理,这些都是共用的东西,将他们抽取出来作为最基础的组件。为上层的数据处理层提供最基础的支撑。

    MyBatis的框架架构设计是怎么样的

    Mybatis框架架构

    这张图从上往下看。MyBatis的初始化,会从mybatis-config.xml配置文件,解析构造成Configuration这个类,就是图中的红框。

    (1)加载配置:配置来源于两个地方,一处是配置文件,一处是Java代码的注解,将SQL的配置信息加载成为一个个MappedStatement对象(包括了传入参数映射配置、执行的SQL语句、结果映射配置),存储在内存中。

    (2)SQL解析:当API接口层接收到调用请求时,会接收到传入SQL的ID和传入对象(可以是Map、JavaBean或者基本数据类型),Mybatis会根据SQL的ID找到对应的MappedStatement,然后根据传入参数对象对MappedStatement进行解析,解析后可以得到最终要执行的SQL语句和参数。

    (3)SQL执行:将最终得到的SQL和参数拿到数据库进行执行,得到操作数据库的结果。

    (4)结果映射:将操作数据库的结果按照映射的配置进行转换,可以转换成HashMap、JavaBean或者基本数据类型,并将最终结果返回。

    为什么需要预编译

    1. 定义:
      SQL 预编译指的是数据库驱动在发送 SQL 语句和参数给 DBMS 之前对 SQL 语句进行编译,这样 DBMS 执行 SQL 时,就不需要重新编译。

    2. 为什么需要预编译
      JDBC 中使用对象 PreparedStatement 来抽象预编译语句,使用预编译。预编译阶段可以优化 SQL 的执行。预编译之后的 SQL 多数情况下可以直接执行,DBMS 不需要再次编译,越复杂的SQL,编译的复杂度将越大,预编译阶段可以合并多次操作为一个操作。同时预编译语句对象可以重复利用。把一个 SQL 预编译后产生的 PreparedStatement 对象缓存下来,下次对于同一个SQL,可以直接使用这个缓存的 PreparedState 对象。Mybatis默认情况下,将对所有的 SQL 进行预编译。

    Mybatis都有哪些Executor执行器?它们之间的区别是什么?

    Mybatis有三种基本的Executor执行器,SimpleExecutor、ReuseExecutor、BatchExecutor。

    SimpleExecutor:每执行一次update或select,就开启一个Statement对象,用完立刻关闭Statement对象。

    ReuseExecutor:执行update或select,以sql作为key查找Statement对象,存在就使用,不存在就创建,用完后,不关闭Statement对象,而是放置于Map<String, Statement>内,供下一次使用。简言之,就是重复使用Statement对象。

    BatchExecutor:执行update(没有select,JDBC批处理不支持select),将所有sql都添加到批处理中(addBatch()),等待统一执行(executeBatch()),它缓存了多个Statement对象,每个Statement对象都是addBatch()完毕后,等待逐一执行executeBatch()批处理。与JDBC批处理相同。

    作用范围:Executor的这些特点,都严格限制在SqlSession生命周期范围内。

    Mybatis中如何指定使用哪一种Executor执行器?

    在Mybatis配置文件中,在设置(settings)可以指定默认的ExecutorType执行器类型,也可以手动给DefaultSqlSessionFactory的创建SqlSession的方法传递ExecutorType类型参数,如SqlSession openSession(ExecutorType execType)。

    配置默认的执行器。SIMPLE 就是普通的执行器;REUSE 执行器会重用预处理语句(prepared statements); BATCH 执行器将重用语句并执行批量更新。

    Mybatis是否支持延迟加载?如果支持,它的实现原理是什么?

    Mybatis仅支持association关联对象和collection关联集合对象的延迟加载,association指的就是一对一,collection指的就是一对多查询。在Mybatis配置文件中,可以配置是否启用延迟加载lazyLoadingEnabled=true|false。

    它的原理是,使用CGLIB创建目标对象的代理对象,当调用目标方法时,进入拦截器方法,比如调用a.getB().getName(),拦截器invoke()方法发现a.getB()是null值,那么就会单独发送事先保存好的查询关联B对象的sql,把B查询上来,然后调用a.setB(b),于是a的对象b属性就有值了,接着完成a.getB().getName()方法的调用。这就是延迟加载的基本原理。

    当然了,不光是Mybatis,几乎所有的包括Hibernate,支持延迟加载的原理都是一样的。

    映射器

    #{}和${}的区别

    • #{}是占位符,预编译处理;${}是拼接符,字符串替换,没有预编译处理。

    • Mybatis在处理#{}时,#{}传入参数是以字符串传入,会将SQL中的#{}替换为?号,调用PreparedStatement的set方法来赋值。

    • Mybatis在处理 时 , 是 原 值 传 入 , 就 是 把 {}时,是原值传入,就是把 {}替换成变量的值,相当于JDBC中的Statement编译

    • 变量替换后,#{} 对应的变量自动加上单引号 ‘’;变量替换后,${} 对应的变量不会加上单引号 ‘’

    • #{} 可以有效的防止SQL注入,提高系统安全性;${} 不能防止SQL 注入

    • #{} 的变量替换是在DBMS 中;${} 的变量替换是在 DBMS 外

    模糊查询like语句该怎么写

    (1)’%${question}%’ 可能引起SQL注入,不推荐

    (2)"%"#{question}"%" 注意:因为#{…}解析成sql语句时候,会在变量外侧自动加单引号’ ',所以这里 % 需要使用双引号" ",不能使用单引号 ’ ',不然会查不到任何结果。

    (3)CONCAT(’%’,#{question},’%’) 使用CONCAT()函数,推荐

    (4)使用bind标签

    <select id="listUserLikeUsername" resultType="com.jourwon.pojo.User">
      <bind name="pattern" value="'%' + username + '%'" />
      select id,sex,age,username,password from person where username LIKE #{pattern}
    </select>
    

    在mapper中如何传递多个参数

    方法1:顺序传参法

    public User selectUser(String name, int deptId);
    
    <select id="selectUser" resultMap="UserResultMap">
        select * from user
        where user_name = #{0} and dept_id = #{1}
    </select>
    

    #{}里面的数字代表传入参数的顺序。

    这种方法不建议使用,sql层表达不直观,且一旦顺序调整容易出错。

    方法2:@Param注解传参法

    public User selectUser(@Param("userName") String name, int @Param("deptId") deptId);
    
    <select id="selectUser" resultMap="UserResultMap">
        select * from user
        where user_name = #{userName} and dept_id = #{deptId}
    </select>
    

    #{}里面的名称对应的是注解@Param括号里面修饰的名称。

    这种方法在参数不多的情况还是比较直观的,推荐使用。

    方法3:Map传参法

    public User selectUser(Map<String, Object> params);
    
    <select id="selectUser" parameterType="java.util.Map" resultMap="UserResultMap">
        select * from user
        where user_name = #{userName} and dept_id = #{deptId}
    </select>
    

    #{}里面的名称对应的是Map里面的key名称。

    这种方法适合传递多个参数,且参数易变能灵活传递的情况。

    方法4:Java Bean传参法

    public User selectUser(User user);
    
    <select id="selectUser" parameterType="com.jourwon.pojo.User" resultMap="UserResultMap">
        select * from user
        where user_name = #{userName} and dept_id = #{deptId}
    </select>
    

    #{}里面的名称对应的是User类里面的成员属性。

    这种方法直观,需要建一个实体类,扩展不容易,需要加属性,但代码可读性强,业务逻辑处理方便,推荐使用。

    Mybatis如何执行批量操作

    使用foreach标签

    foreach的主要用在构建in条件中,它可以在SQL语句中进行迭代一个集合。foreach标签的属性主要有item,index,collection,open,separator,close。

    • item  表示集合中每一个元素进行迭代时的别名,随便起的变量名;
    • index  指定一个名字,用于表示在迭代过程中,每次迭代到的位置,不常用;
    • open  表示该语句以什么开始,常用“(”;
    • separator表示在每次进行迭代之间以什么符号作为分隔符,常用“,”;
    • close  表示以什么结束,常用“)”。

    在使用foreach的时候最关键的也是最容易出错的就是collection属性,该属性是必须指定的,但是在不同情况下,该属性的值是不一样的,主要有一下3种情况:

    1. 如果传入的是单参数且参数类型是一个List的时候,collection属性值为list
    2. 如果传入的是单参数且参数类型是一个array数组的时候,collection的属性值为array
    3. 如果传入的参数是多个的时候,我们就需要把它们封装成一个Map了,当然单参数也可以封装成map,实际上如果你在传入参数的时候,在MyBatis里面也是会把它封装成一个Map的,
      map的key就是参数名,所以这个时候collection属性值就是传入的List或array对象在自己封装的map里面的key

    具体用法如下:

    <!-- 批量保存(foreach插入多条数据两种方法)
           int addEmpsBatch(@Param("emps") List<Employee> emps); -->
    <!-- MySQL下批量保存,可以foreach遍历 mysql支持values(),(),()语法 --> //推荐使用
    <insert id="addEmpsBatch">
        INSERT INTO emp(ename,gender,email,did)
        VALUES
        <foreach collection="emps" item="emp" separator=",">
            (#{emp.eName},#{emp.gender},#{emp.email},#{emp.dept.id})
        </foreach>
    </insert>
    
    <!-- 这种方式需要数据库连接属性allowMutiQueries=true的支持
     如jdbc.url=jdbc:mysql://localhost:3306/mybatis?allowMultiQueries=true -->  
    <insert id="addEmpsBatch">
        <foreach collection="emps" item="emp" separator=";">                                 
            INSERT INTO emp(ename,gender,email,did)
            VALUES(#{emp.eName},#{emp.gender},#{emp.email},#{emp.dept.id})
        </foreach>
    </insert>
    

    使用ExecutorType.BATCH

    Mybatis内置的ExecutorType有3种,默认为simple,该模式下它为每个语句的执行创建一个新的预处理语句,单条提交sql;而batch模式重复使用已经预处理的语句,并且批量执行所有更新语句,显然batch性能将更优; 但batch模式也有自己的问题,比如在Insert操作时,在事务没有提交之前,是没有办法获取到自增的id,这在某型情形下是不符合业务要求的

    具体用法如下

    //批量保存方法测试
    @Test  
    public void testBatch() throws IOException{
        SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
        //可以执行批量操作的sqlSession
        SqlSession openSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
    
        //批量保存执行前时间
        long start = System.currentTimeMillis();
        try {
            EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
            for (int i = 0; i < 1000; i++) {
                mapper.addEmp(new Employee(UUID.randomUUID().toString().substring(0, 5), "b", "1"));
            }
    
            openSession.commit();
            long end = System.currentTimeMillis();
            //批量保存执行后的时间
            System.out.println("执行时长" + (end - start));
            //批量 预编译sql一次==》设置参数==》10000次==》执行1次   677
            //非批量  (预编译=设置参数=执行 )==》10000次   1121
    
        } finally {
            openSession.close();
        }
    }
    

    mapper和mapper.xml如下

    public interface EmployeeMapper {   
        //批量保存员工
        Long addEmp(Employee employee);
    }
    
    <mapper namespace="com.jourwon.mapper.EmployeeMapper"
         <!--批量保存员工 -->
        <insert id="addEmp">
            insert into employee(lastName,email,gender)
            values(#{lastName},#{email},#{gender})
        </insert>
    </mapper>
    

    如何获取生成的主键

    对于支持主键自增的数据库(MySQL)

    <insert id="insertUser" useGeneratedKeys="true" keyProperty="userId" >
        insert into user( 
        user_name, user_password, create_time) 
        values(#{userName}, #{userPassword} , #{createTime, jdbcType= TIMESTAMP})
    </insert>
    

    parameterType 可以不写,Mybatis可以推断出传入的数据类型。如果想要访问主键,那么应当parameterType 应当是java实体或者Map。这样数据在插入之后 可以通过ava实体或者Map 来获取主键值。通过 getUserId获取主键

    不支持主键自增的数据库(Oracle)

    对于像Oracle这样的数据,没有提供主键自增的功能,而是使用序列的方式获取自增主键。
    可以使用<selectKey>标签来获取主键的值,这种方式不仅适用于不提供主键自增功能的数据库,也适用于提供主键自增功能的数据库
    <selectKey>一般的用法

    <selectKey keyColumn="id" resultType="long" keyProperty="id" order="BEFORE">
    </selectKey> 
    
    属性描述
    keyPropertyselectKey 语句结果应该被设置的目标属性。如果希望得到多个生成的列,也可以是逗号分隔的属性名称列表。
    keyColumn匹配属性的返回结果集中的列名称。如果希望得到多个生成的列,也可以是逗号分隔的属性名称列表。
    resultType结果的类型,MyBatis 通常可以推算出来。MyBatis 允许任何简单类型用作主键的类型,包括字符串。如果希望作用于多个生成的列,则可以使用一个包含期望属性的 Object 或一个 Map。
    order值可为BEFORE 或 AFTER。如果是 BEFORE,那么它会先执行selectKey设置 keyProperty 然后执行插入语句。如果为AFTER则相反。
    statementType使用何种语句类型,默认PREPARED。 有STATEMENT,PREPARED 和 CALLABLE 语句的映射类型。
    <insert id="insertUser" >
    	<selectKey keyColumn="id" resultType="long" keyProperty="userId" order="BEFORE">
    		SELECT USER_ID.nextval as id from dual 
    	</selectKey> 
    	insert into user( 
    	user_id,user_name, user_password, create_time) 
    	values(#{userId},#{userName}, #{userPassword} , #{createTime, jdbcType= TIMESTAMP})
    </insert>
    

    此时会将Oracle生成的主键值赋予userId变量。这个userId 就是USER对象的属性,这样就可以将生成的主键值返回了。如果仅仅是在insert语句中使用但是不返回,此时keyProperty=“任意自定义变量名”,resultType 可以不写。
    Oracle 数据库中的值要设置为 BEFORE ,这是因为 Oracle中需要先从序列获取值,然后将值作为主键插入到数据库中。

    扩展
    如果Mysql 使用selectKey的方式获取主键,需要注意下面两点:

    order : AFTER
    获取递增主键值 :SELECT LAST_INSERT_ID()

    当实体类中的属性名和表中的字段名不一样 ,怎么办

    第1种: 通过在查询的SQL语句中定义字段名的别名,让字段名的别名和实体类的属性名一致。

    <select id="getOrder" parameterType="int" resultType="com.jourwon.pojo.Order">
           select order_id id, order_no orderno ,order_price price form orders where order_id=#{id};
    </select>
    

    第2种: 通过<resultMap>来映射字段名和实体类属性名的一一对应的关系。

    <select id="getOrder" parameterType="int" resultMap="orderResultMap">
    	select * from orders where order_id=#{id}
    </select>
    
    <resultMap type="com.jourwon.pojo.Order" id="orderResultMap">
        <!–用id属性来映射主键字段–>
        <id property="id" column="order_id">
    
        <!–用result属性来映射非主键字段,property为实体类属性名,column为数据库表中的属性–>
        <result property ="orderno" column ="order_no"/>
        <result property="price" column="order_price" />
    </reslutMap>
    

    Mapper 编写有哪几种方式?

    第一种:接口实现类继承 SqlSessionDaoSupport:使用此种方法需要编写mapper 接口,mapper 接口实现类、mapper.xml 文件。

    (1)在 sqlMapConfig.xml 中配置 mapper.xml 的位置

    <mappers>
        <mapper resource="mapper.xml 文件的地址" />
        <mapper resource="mapper.xml 文件的地址" />
    </mappers>
    

    (2)定义 mapper 接口

    (3)实现类集成 SqlSessionDaoSupport

    mapper 方法中可以 this.getSqlSession()进行数据增删改查。

    (4)spring 配置

    <bean id=" " class="mapper 接口的实现">
        <property name="sqlSessionFactory"
        ref="sqlSessionFactory"></property>
    </bean>
    

    第二种:使用 org.mybatis.spring.mapper.MapperFactoryBean:

    (1)在 sqlMapConfig.xml 中配置 mapper.xml 的位置,如果 mapper.xml 和mappre 接口的名称相同且在同一个目录,这里可以不用配置

    <mappers>
        <mapper resource="mapper.xml 文件的地址" />
        <mapper resource="mapper.xml 文件的地址" />
    </mappers>
    

    (2)定义 mapper 接口:

    (3)mapper.xml 中的 namespace 为 mapper 接口的地址

    (4)mapper 接口中的方法名和 mapper.xml 中的定义的 statement 的 id 保持一致

    (5)Spring 中定义

    <bean id="" class="org.mybatis.spring.mapper.MapperFactoryBean">
        <property name="mapperInterface" value="mapper 接口地址" />
        <property name="sqlSessionFactory" ref="sqlSessionFactory" />
    </bean>
    

    第三种:使用 mapper 扫描器:

    (1)mapper.xml 文件编写:

    mapper.xml 中的 namespace 为 mapper 接口的地址;

    mapper 接口中的方法名和 mapper.xml 中的定义的 statement 的 id 保持一致;

    如果将 mapper.xml 和 mapper 接口的名称保持一致则不用在 sqlMapConfig.xml中进行配置。

    (2)定义 mapper 接口:

    注意 mapper.xml 的文件名和 mapper 的接口名称保持一致,且放在同一个目录

    (3)配置 mapper 扫描器:

    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="mapper 接口包地址
        "></property>
        <property name="sqlSessionFactoryBeanName"
        value="sqlSessionFactory"/>
    </bean>
    

    (4)使用扫描器后从 spring 容器中获取 mapper 的实现对象。

    什么是MyBatis的接口绑定?有哪些实现方式?

    接口绑定,就是在MyBatis中任意定义接口,然后把接口里面的方法和SQL语句绑定,我们直接调用接口方法就可以,这样比起原来了SqlSession提供的方法我们可以有更加灵活的选择和设置。

    接口绑定有两种实现方式

    通过注解绑定,就是在接口的方法上面加上 @Select、@Update等注解,里面包含Sql语句来绑定;

    通过xml里面写SQL来绑定, 在这种情况下,要指定xml映射文件里面的namespace必须为接口的全路径名。当Sql语句比较简单时候,用注解绑定, 当SQL语句比较复杂时候,用xml绑定,一般用xml绑定的比较多。

    使用MyBatis的mapper接口调用时有哪些要求?

    1、Mapper接口方法名和mapper.xml中定义的每个sql的id相同。

    2、Mapper接口方法的输入参数类型和mapper.xml中定义的每个sql 的parameterType的类型相同。

    3、Mapper接口方法的输出参数类型和mapper.xml中定义的每个sql的resultType的类型相同。

    4、Mapper.xml文件中的namespace即是mapper接口的类路径。

    最佳实践中,通常一个Xml映射文件,都会写一个Dao接口与之对应,请问,这个Dao接口的工作原理是什么?Dao接口里的方法,参数不同时,方法能重载吗

    Dao接口,就是人们常说的Mapper接口,接口的全限名,就是映射文件中的namespace的值,接口的方法名,就是映射文件中MappedStatement的id值,接口方法内的参数,就是传递给sql的参数。Mapper接口是没有实现类的,当调用接口方法时,接口全限名+方法名拼接字符串作为key值,可唯一定位一个MappedStatement,举例:com.mybatis3.mappers.StudentDao.findStudentById,可以唯一找到namespace为com.mybatis3.mappers.StudentDao下面id = findStudentById的MappedStatement。在Mybatis中,每一个<select><insert><update><delete>标签,都会被解析为一个MappedStatement对象。

    Dao接口里的方法,是不能重载的,因为是全限名+方法名的保存和寻找策略。

    Dao接口的工作原理是JDK动态代理,Mybatis运行时会使用JDK动态代理为Dao接口生成代理proxy对象,代理对象proxy会拦截接口方法,转而执行MappedStatement所代表的sql,然后将sql执行结果返回。

    Mybatis的Xml映射文件中,不同的Xml映射文件,id是否可以重复?

    不同的Xml映射文件,如果配置了namespace,那么id可以重复;如果没有配置namespace,那么id不能重复;毕竟namespace不是必须的,只是最佳实践而已。

    原因就是namespace+id是作为Map<String, MappedStatement>的key使用的,如果没有namespace,就剩下id,那么,id重复会导致数据互相覆盖。有了namespace,自然id就可以重复,namespace不同,namespace+id自然也就不同。

    简述Mybatis的Xml映射文件和Mybatis内部数据结构之间的映射关系?

    答:Mybatis将所有Xml配置信息都封装到All-In-One重量级对象Configuration内部。在Xml映射文件中,<parameterMap>标签会被解析为ParameterMap对象,其每个子元素会被解析为ParameterMapping对象。<resultMap>标签会被解析为ResultMap对象,其每个子元素会被解析为ResultMapping对象。每一个<select><insert><update><delete>标签均会被解析为MappedStatement对象,标签内的sql会被解析为BoundSql对象。

    Mybatis是如何将sql执行结果封装为目标对象并返回的?都有哪些映射形式?

    第一种是使用<resultMap>标签,逐一定义列名和对象属性名之间的映射关系。

    第二种是使用sql列的别名功能,将列别名书写为对象属性名,比如T_NAME AS NAME,对象属性名一般是name,小写,但是列名不区分大小写,Mybatis会忽略列名大小写,智能找到与之对应对象属性名,你甚至可以写成T_NAME AS NaMe,Mybatis一样可以正常工作。

    有了列名与属性名的映射关系后,Mybatis通过反射创建对象,同时使用反射给对象的属性逐一赋值并返回,那些找不到映射关系的属性,是无法完成赋值的。

    Xml映射文件中,除了常见的select|insert|updae|delete标签之外,还有哪些标签?

    还有很多其他的标签,<resultMap><parameterMap><sql><include><selectKey>,加上动态sql的9个标签,trim|where|set|foreach|if|choose|when|otherwise|bind等,其中<sql>为sql片段标签,通过<include>标签引入sql片段,<selectKey>为不支持自增的主键生成策略标签。

    Mybatis映射文件中,如果A标签通过include引用了B标签的内容,请问,B标签能否定义在A标签的后面,还是说必须定义在A标签的前面?

    虽然Mybatis解析Xml映射文件是按照顺序解析的,但是,被引用的B标签依然可以定义在任何地方,Mybatis都可以正确识别。

    原理是,Mybatis解析A标签,发现A标签引用了B标签,但是B标签尚未解析到,尚不存在,此时,Mybatis会将A标签标记为未解析状态,然后继续解析余下的标签,包含B标签,待所有标签解析完毕,Mybatis会重新解析那些被标记为未解析的标签,此时再解析A标签时,B标签已经存在,A标签也就可以正常解析完成了。

    高级查询

    MyBatis实现一对一,一对多有几种方式,怎么操作的?

    有联合查询和嵌套查询。联合查询是几个表联合查询,只查询一次,通过在resultMap里面的association,collection节点配置一对一,一对多的类就可以完成

    嵌套查询是先查一个表,根据这个表里面的结果的外键id,去再另外一个表里面查询数据,也是通过配置association,collection,但另外一个表的查询通过select节点配置。

    Mybatis是否可以映射Enum枚举类?

    Mybatis可以映射枚举类,不单可以映射枚举类,Mybatis可以映射任何对象到表的一列上。映射方式为自定义一个TypeHandler,实现TypeHandler的setParameter()和getResult()接口方法。

    TypeHandler有两个作用,一是完成从javaType至jdbcType的转换,二是完成jdbcType至javaType的转换,体现为setParameter()和getResult()两个方法,分别代表设置sql问号占位符参数和获取列查询结果。

    动态SQL

    Mybatis动态sql是做什么的?都有哪些动态sql?能简述一下动态sql的执行原理不?

    Mybatis动态sql可以让我们在Xml映射文件内,以标签的形式编写动态sql,完成逻辑判断和动态拼接sql的功能,Mybatis提供了9种动态sql标签trim|where|set|foreach|if|choose|when|otherwise|bind。

    其执行原理为,使用OGNL从sql参数对象中计算表达式的值,根据表达式的值动态拼接sql,以此来完成动态sql的功能。

    插件模块

    Mybatis是如何进行分页的?分页插件的原理是什么?

    Mybatis使用RowBounds对象进行分页,它是针对ResultSet结果集执行的内存分页,而非物理分页,可以在sql内直接书写带有物理分页的参数来完成物理分页功能,也可以使用分页插件来完成物理分页。

    分页插件的基本原理是使用Mybatis提供的插件接口,实现自定义插件,在插件的拦截方法内拦截待执行的sql,然后重写sql,根据dialect方言,添加对应的物理分页语句和物理分页参数。

    举例:select * from student,拦截sql后重写为:select t.* from (select * from student) t limit 0, 10

    简述Mybatis的插件运行原理,以及如何编写一个插件。

    Mybatis仅可以编写针对ParameterHandler、ResultSetHandler、StatementHandler、Executor这4种接口的插件,Mybatis使用JDK的动态代理,为需要拦截的接口生成代理对象以实现接口方法拦截功能,每当执行这4种接口对象的方法时,就会进入拦截方法,具体就是InvocationHandler的invoke()方法,当然,只会拦截那些你指定需要拦截的方法。

    实现Mybatis的Interceptor接口并复写intercept()方法,然后在给插件编写注解,指定要拦截哪一个接口的哪些方法即可,记住,别忘了在配置文件中配置你编写的插件。

    缓存

    Mybatis的一级、二级缓存

    1)一级缓存: 基于 PerpetualCache 的 HashMap 本地缓存,其存储作用域为 Session,当 Session flush 或 close 之后,该 Session 中的所有 Cache 就将清空,默认打开一级缓存。

    2)二级缓存与一级缓存其机制相同,默认也是采用 PerpetualCache,HashMap 存储,不同在于其存储作用域为 Mapper(Namespace),并且可自定义存储源,如 Ehcache。默认不打开二级缓存,要开启二级缓存,使用二级缓存属性类需要实现Serializable序列化接口(可用来保存对象的状态),可在它的映射文件中配置<cache/>

    3)对于缓存数据更新机制,当某一个作用域(一级缓存 Session/二级缓存Namespaces)的进行了C/U/D 操作后,默认该作用域下所有 select 中的缓存将被 clear。

    展开全文
  • C#程序集学习总结

    千次阅读 2017-12-03 11:36:42
    C#程序集的定义 程序集是包含一个或多个类型定义文件和资源文件的集合。...一旦CLR加载了程序集中包含清单的那个文件,它就可以确定程序集的其它文件哪些包含了程序正在引用的类型和资源。任何
  • 一、在 print 前建立一个txt python3: f = open('print.txt', 'w') print('this is a txt', file = f) f.close() python2: f = open('print.txt', 'w') print>>f, 'this is a txt' ...python print_...
  • C# 应用程序域和程序集

    千次阅读 2016-06-08 12:10:47
     进程(Process)是Windows系统的一个基本概念,它包含着一个运行程序所需要的资源。进程之间是相对独立的,一个进程无法直接访问另一个进程的数据(除非利用分布式计算方式),一个进程运行的失败也不会影响其他...
  • C# 程序集是啥

    千次阅读 2017-04-26 16:56:01
    是的,程序集(.netexe与dll的区别就是exe有程序接入口,即Main函数)就是.net框架下,可以被CLR加载并运行的一堆数据集(类似java的jar包,无法脱离虚拟机自己运行)。它们和之前C\C++生成的可执行程序和动态链接库...
  • 本文介绍了使用Python BeautifulSoup模块快速分离HTML报文程序代码的方法和案例,通过BeautifulSoup模块可简单快速完成从HTML文本分离程序代码,实现程序代码和分析主体的分离,方便后续程序的分析。
  • JAVA上百实例源码以及开源项目

    千次下载 热门讨论 2016-01-03 17:37:40
     Java非对称加密源程序代码实例,本例使用RSA加密技术,定义加密算法可用 DES,DESede,Blowfish等。  设定字符串为“张三,你好,我是李四”  产生张三的密钥对(keyPairZhang)  张三生成公钥(publicKeyZhang...
  • 为了方便使用,博文附上了包括数据整理及数据预处理在内的所有代码及处理好的数据,同时对代码进行了解释,其要点如下:UCI数据介绍、不同数据的整理程序、148个整理好的数据与对应程序
  • C# 动态加载程序集

    千次阅读 2015-08-13 22:01:38
    在.Net 程序集(Assembly)中保存了元数据(MetaData)信息,因此就可以通过分析元数据来获取程序集中的内容,比如类,方法,属性等,这大大方便了在运行时去动态创建实例。 MSDN解释如下: 反射提供了封装...
  • vscode安装使用教程

    万次阅读 多人点赞 2018-12-11 19:18:48
    Visual Studio Code (简称 VS Code / VSC) 是一款免费开源的现代化轻量级代码编辑器,支持几乎所有主流的开发语言的语法高亮、智能代码补全、自定义热键、括号匹配、代码片段、代码对比 Diff、GIT 等特性,支持插件...
  • 强命名程序集

    千次阅读 2015-11-02 14:35:42
    介绍了强命名程序集的概念介绍、来源、作用
  • 程序集与应用程序域关系讲解(1)

    千次阅读 2012-01-21 17:30:47
    只有相关的程序集被CLR加载到相应的应用程序域,才谈得上代码的执行。 基于应用程序域的隔离,归根结底是内存的隔离。一个基本的反映就是:在一个应用程序域创建的对象,不能直接在另一个应用程序域使用。...
  • 请看上图,MyTest程序集下面有个引用,引用里面大家都知道有很多dll,而我们的源代码中只有那5个using引用某dll里的具体的命名空间。 ( 引用这些dll只会对编译器造成一点影响,并不会影响最终生成的文件,更不会...
  • 当你在机器上启动某个程序时,它只是在自己的“bubble”里面运行,这个气泡的作用就是用来将同一时刻运行的所有程序进行分离。...此信息保存在UNIX/Linux系统的流程文件系统,该系统是一个虚拟文件...
  • 这一系列博文将介绍一下机器学习的数据预处理问题,以UCI数据为例详细介绍缺失值处理、连续特征离散化,特征归一化及离散特征的编码等问题,同时会附上处理的Matlab程序代码,这篇博文先介绍缺失值的处理,要点...
  • 有时候在整个项目架构里也需要动态创建程序集的需求,那如何创建程序集呢,请跟我来学习一下吧。  首先需要知道动态创建这些类型是使用的一些什么技术呢?其实只要相关动态加载程序集呀,类呀,都是使用反射,...
  • 关注小编微信公众号公众号【前端基础教程从0开始】回复“1”,拉你进程序员技术讨论群,群内有大神,可以免费提供问题解答。...在微信小程序开发的过程代码版本管理往往需要使用第三方工具进行管理。虽然微信We...
  • WPF开发教程

    万次阅读 多人点赞 2019-07-02 23:13:20
    在应用程序的调度程序中,您可以调用 TranslateAccelerator,它会探查 User32 的输入消息,并确定是否有任何消息与已注册的快捷键匹配。在 WPF ,上述内容不会起作用,因为系统是完全“可组合”的 – 任何元素都...
  • 当您使用 SQL Server 2005 的一个公共语言运行库对象时出现错误消息: 无法加载动态生成的序列化程序集 点击这里查看逐句英文对照机器翻译 查看机器翻译免责声明 查看本文应用于的产品 错误 #: 101935 ...
  • 然后看见有人说让复制一段代码保存成.bat文件,运行执行,好像是类似于修复工具的东西吧 反正照做 ,结果失败!!有人说打开注册表 找什么什么文件,尼玛 找了一大串,结果最后一个文件弄死找不到,,好了 白找...
  • C#如何实现从内存加载程序集

    千次阅读 2014-11-14 15:29:27
    首先,为了动态的在内存装载程序或程序集,我们以文件流的方式读取二进制文件,并将其以字节的形式保存在数组代码如下: //动态加载插件 String pluginFilePath = Path.GetDirectoryName(Application...
  • 使用SVN进行源代码版本控制时,我们希望能够快速找到每个发布版本对应的源代码版本,现在可以通过在程序集的版本信息增加SVN源码版本信息的方式来实现我们的要求。 现在我们定义每个程序集的版本信息的最末段表示...
  • 程序编译的过程就是将用户的文本形式的源代码(c/c++)转化成计算机可以直接执行的机器代码的过程。主要经过这么几个过程: 0、预编译,又称为预处理 , 是做些代码文本的替换工作 1、编译,由编译器将c源代码(....
  • 程序集

    千次阅读 2009-10-20 15:43:00
    程序集是任何 .NET Framework 应用程序的基本构造块。例如,在生成简单的 C# 应用程序时,Visual Studio 创建一个单个可移植可执行 (PE) 文件形式的程序集,明确地说就是一个 EXE 或 DLL。程序集包含描述它们自己的...
  • 在项目我们会经常用到App.config文件,有...这些配置有的保存当前程序集用到的一些可供外部改动的变量,比如: 这种的配置直接使用 ConfigurationManager.AppSettings["key名"]来读取比较方便。例如:
  • MATLAB 2018b 安装与简介

    万次阅读 多人点赞 2019-02-18 13:07:00
    另外matlab代码可以与其他语言集成,使您能够在Web、企业和生产系统部署算法和应用程序。与matlab2018a相比,matlab2018b拥有更多数据分析、机器学习和深度学习选项,并且速度比以往更快。其亮点...
  • System.Reflection 命名空间:包含通过检查托管代码中程序集、模块、成员、参数和其他实体的元数据来检索其相关信息的类型。 Assembly 类:表示一个程序集,它是一个可重用、无版本冲突并且可自我描述的公共语言...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 315,053
精华内容 126,021
关键字:

代码中保存程序集