自动编码器模型和代码解释

自动编码器模型和代码解释CNN算法与程序研究 1)      深度学习基本理论方法http://wenku.baidu.com/view/2e630ddfc5da50e2524d7ff3 特征多,给出的信息多,识别准确性会提升。但是,计算复杂度增加,搜索的空间大,可以用来训练的数据在每个特征上就会稀疏。采用层次网络结构,BP一层隐层节点的浅层模型,带有一层隐层节点(如SVM、Boostin

大家好,又见面了,我是你们的朋友全栈君。

   CNN算法与程序研究

 

1)      深度学习基本理论方法

http://wenku.baidu.com/view/2e630ddfc5da50e2524d7ff3

 

特征多,给出的信息多,识别准确性会提升。

但是,计算复杂度增加,搜索的空间大,可以用来训练的数据在每个特征上就会稀疏。

采用层次网络结构,

BP一层隐层节点的浅层模型,带有一层隐层节点(如SVM、Boosting)

对复杂函数的表示能力和泛化能力有限

 

深度学习指的是:多隐层的人工神经网络具有优异的特征学习能力,学习得到的特征对数据有更本质的刻画,从而有利于可视化或分类;深度神经网络在训练上的难度,可以通过“逐层初始化”(layer-wise pre-training)来有效克服,逐层初始化可通过无监督学习实现的。

与浅层学习区别:

1)强调了模型结构的深度,通常有5-10多层的隐层节点

2)明确突出了特征学习的重要性,通过逐层特征变换,将样本在原空间的特征表示变换到一个新特征空间,从而使分类或预测更加容易。与人工规则构造特征的方法相比,利用大数据来学习特征,更能够刻画数据的丰富内在信息。

自动编码器模型和代码解释

可通过学习一种深层非线性网络结构,实现复杂函数逼近,表征输入数据分布式表示

 

不同点:

神经网络:采用BP算法调整参数,即采用迭代算法来训练整个网络。随机设定初值,计算当前网络的输出,然后根据当前输出和样本真实标签之间的差去改变前面各层的参数,直到收敛;

深度学习:采用逐层训练机制。采用该机制的原因在于如果采用BP机制,对于一个deep network(7层以上),残差传播到最前面的层将变得很小,出现所谓的gradient diffusion(梯度扩散)—-【梯度扩散问题。因为当网络层次比较深时,在计算损失函数的偏导时一般需要使用BP算法,但是这些梯度值随着深度慢慢靠前而显著下降,这样导致前面的网络对最终的损失函数的贡献很小。这样的话前面的权值更新速度就非常非常慢了】

 

不采用BP算法的原因

(1)反馈调整时,梯度越来越稀疏,从顶层越往下,误差校正信号越来越小;

(2)收敛易至局部最小,由于是采用随机值初始化,当初值是远离最优区域时易导致这一情况;

(3)BP算法需要有标签数据来训练,但大部分数据是无标签的

 

深度学习训练过程

第一步:采用自下而上的无监督学习

1)逐层构建单层神经元。

2)每层采用wake-sleep算法进行调优。每次仅调整一层,逐层调整。

 这个过程可以看作是一个featurelearning的过程,是和传统神经网络区别最大的部分。

 

wake-sleep算法:

 

1)wake阶段:

  认知过程,通过下层的输入特征(Input)和向上的认知(Encoder)权重产生每一层的抽象表示(Code),再通过当前的生成(Decoder)权重产生一个重建信息(Reconstruction),计算输入特征和重建信息残差,使用梯度下降修改层间的下行生成(Decoder)权重。也就是“如果现实跟我想象的不一样,改变我的生成权重使得我想象的东西变得与现实一样”。

2)sleep阶段:

  生成过程,通过上层概念(Code)和向下的生成(Decoder)权重,生成下层的状态,再利用认知(Encoder)权重产生一个抽象景象。利用初始上层概念和新建抽象景象的残差,利用梯度下降修改层间向上的认知(Encoder)权重。也就是“如果梦中的景象不是我脑中的相应概念,改变我的认知权重使得这种景象在我看来就是这个概念”。

自动编码器模型和代码解释

第二步:自顶向下的监督学习      

     这一步是在第一步学习获得各层参数进的基础上,在最顶的编码层添加一个分类器(例如罗杰斯特回归、SVM等),而后通过带标签数据的监督学习,利用梯度下降法去微调整个网络参数

     深度学习的第一步实质上是一个网络参数初始化过程。区别于传统神经网络初值随机初始化,深度学习模型是通过无监督学习输入数据的结构得到的,因而这个初值更接近全局最优,从而能够取得更好的效果

自动编码器模型和代码解释

自动编码器( AutoEncoder

稀疏自动编码器

自动编码器模型和代码解释

自动编码器模型和代码解释

自动编码器模型和代码解释

稀疏自动编码器(SparseAutoEncoder)

1)Training阶段:给定一系列的样本图片[x1, x 2, …],我们需要学习得到一组基[Φ1, Φ2, …],也就是字典。

自动编码器模型和代码解释

可使用K-SVD方法交替迭代调整a [k],Φ [k],直至收敛,从而可以获得一组可以良好表示这一系列x的字典。

 

2)Coding阶段:给定一个新的图片x,由上面得到的字典,利用OMP算法求解一个LASSO问题得到稀疏向量a。这个稀疏向量就是这个输入向量x的一个稀疏表达。

自动编码器模型和代码解释

降噪自动编码器(Denoising AutoEncoders)

自动编码器模型和代码解释

在自动编码器的基础上,对训练数据加入噪声,自动编码器必须学习去去除这种噪声而获得真正的没有被噪声污染过的输入。因此,这就迫使编码器去学习输入信号的更加鲁棒的表达,这也是它的泛化能力比一般编码器强的原因

限制波尔兹曼机(RestrictedBoltzmann Machine)

自动编码器模型和代码解释

假设有一个二部图,同层节点之间没有链接,一层是可视层,即输入数据层(v),一层是隐藏层(h),如果假设所有的节点都是随机二值( 0,1值)变量节点,同时假设全概率分布p(v,h)满足Boltzmann 分布,我们称这个模型是Restricted BoltzmannMachine (RBM)。

4.主函数代码:test_sae.m

clear all; closeall; clc;

%% //导入数据

loadmnist_uint8;

train_x =double(train_x)/255;

test_x  =double(test_x)/255;

train_y =double(train_y);

test_y  =double(test_y);

 

%%一:采用autoencoder进行预训练

rng(0);%高版本的matlab可以使用这个语句,低版本会出错

sae =saesetup([784 200 100]);

sae.ae{1}.activation_function      =’sigm’;

sae.ae{1}.learningRate             =1;

sae.ae{1}.inputZeroMaskedFraction  =0.;

sae.ae{2}.activation_function      =’sigm’;

sae.ae{2}.learningRate             =1;

sae.ae{2}.inputZeroMaskedFraction  =0.;

opts.numepochs=   1;

opts.batchsize =100;

visualize(sae.ae{1}.W{1}(:,2:end)’)

%二:fine_tuning过程

% Use the SDAEto initialize a FFNN

nn =nnsetup([784 200 100 10]);

nn.activation_function             =’sigm’;

nn.learningRate                    =1;

%add pretrained weights

nn.W{1} =sae.ae{1}.W{1};

nn.W{2} =sae.ae{2}.W{1};

% Train the FFNN

opts.numepochs=   1;

opts.batchsize =100;

nn = nntrain(nn,train_x, train_y, opts);

[er, bad] =nntest(nn, test_x, test_y);

str =sprintf(‘testing error rate is: %f’,er);

disp(str)

 

三:pre_training阶段代码详解

3.1:网络结构建立

   Sae网络就是堆叠多个autoencoder网络,所以此网络结构由2autoencoder构成,分别是:v—h1—vh1—h2—h1.

此处主要利用saesetup函数来实现,

3.1.1 Saesetup函数说明

输入参数:size为构建网络的节点向量,由于此处pre_training阶段的网络为784-200-100;所以size=[784 200  100]

输出参数:sae元包矩阵,每一个元包矩阵对应一个autoencoder网络;例如sae.ae{1}存放的就是v——h1——v这个autoencoder

函数体说明:通过for循环2次,调用nnsetup函数两次,生成两个autoencoder

function sae=saesetup(size)

    for u = 2:numel(size)

       sae.ae{u-1}= nnsetup([size(u-1) size(u) size(u-1)]);

    end

end

autoencoder构建:我们知道autoencoder就是一个v——h——v 的3层神经网络;所以主要通过nnsetup来构建。

3.1.2 nnsetup函数说明

输入参数:nnsetup(architecture)architecture顾名思义,就是构建网络的结构,也就是每层网络的神经元节点个数,例如本题为第一个autoencoder结构为v—h1—v  [784 200 784]

输出参数nn:一个元包矩阵,元包矩阵中存放着一个autoencoder网络的各种配置参数

函数体说明:

Encoder过程:y=f(xw’+b)

激活函数nn.activation_function:f(),一般有sigmoid和tanh两种;

学习率  nn.learningRate:控制学习的速度,

动量项 nn.momentum:调节在应用minibatch方法训练网络时,每次新权值更新值和上一次权值更行值的相对比例;

权值惩罚项 nn.weightPenaltyL2:防止权值过大,发生过拟合现象;

稀疏目标 nn.sparsityTarget:通过控制隐层激活值的平均值,来控制隐层激活值的稀疏性

Decoder过程:x=g(wy+b)

解码激活函数设置:sigmoid和tanh两种

其他参数:例如dae中的加噪比例参数,dropout中的隐层节点的忽略比例等

权值W初始化:

W结构初始化:权值矩阵W的行列数,例如若使用:h=f(x*w’+b),本例中第一个autoencoder中,权值矩阵w1为200*785  ,(785=784+1,1为偏置项b,后面介绍)

W元素初始化:W的元素初始值为一个0均值的随机矩阵,权值系数很小,这样可以防止过拟合

nn.W{i – 1} =(a*b);

a=rand(nn.size(i),nn.size(i – 1)+1) – 0.5)%生产一个0均值,范围元素值在[-0.5 0.5]范围内的矩阵

b=2 * 4 * sqrt(6 /(nn.size(i) + nn.size(i – 1))%缩减矩阵元素值到一个更小的范围

3.2:网络训练阶段

此阶段主要调用saetrain函数来训练网络;

 3.2.1 Seatrain函数说明

输入参数:saetrain(sae, x, opts)

Saestep1阶段设置好的网络结构;x为输入数据;opts中有两个参数;其中opts.numepochs为训练次数,所有样本一共训练多少次;opts.batchsize参数,为在对所有样本进行minibatch训练时,每个batch的容量,即每个batch的样本个数。

输出参数:训练好的元包矩阵sae

函数体说明:通过for循环,调用nntrain函数,依次训练每个autoencoder

3.2.2 Nntrain函数说明:

输入参数:nntrain(nn, train_x, train_y, opts, val_x, val_y)

其中nn为一个元包矩阵,nn中存放的是网络的配置参数;train_x,train_y就是输入数据,和目标数据;在autoencoder网络中,都是输入数据x,opts参数,已经说过;至于val_x和val_y做什么的不清楚,训练中暂时用不到。

输出参数:[nn, L],已经训练好的网络参数,主要训练W和b,L为损失函数值,在autoencoder中是重构误差。

函数体说明:

1.minibatch部分

所谓的minibatch训练方法就是把所有训练样本分成多个batch,然后依次训练每个batch中的样本。

将训练集事先分成包含几十或几百个样本的小批量数据进行计算将更高效,这主要是可以利用图形处理器Gpu和matlab中矩阵之间相乘运算的优势。

m = size(train_x,1);%提取样本总数

batchsize =opts.batchsize; %每个batch中样本个数

numepochs =opts.numepochs; %所有样本训练次数

numbatches = m /batchsize; %所有样本分成多少个batch

 2.训练过程

for i = 1 : numepochs%所有样本训练次数

    kk=randperm(m); %形成样本编号的随机向量

   for l = 1 : numbatches%每次提取batchsize个样本,一共提取numbatches次

       batch_x =train_x(kk((l – 1) * batchsize + 1 : l * batchsize), :);

       batch_y =train_y(kk((l – 1) * batchsize + 1 : l * batchsize), :);

       nn =nnff(nn, batch_x, batch_y); %前馈计算网络

       nn =nnbp(nn);%计算误差和权值梯度

       nn =nnapplygrads(nn);%参数更新

       L(n) =nn.L;

       n = n + 1;

end

end

在nntrain末尾还可以缩减学习率,使更新步长逐渐变小。

nn.learningRate =nn.learningRate *nn.scaling_learningRate;

3.2.3 前馈函数nnff说明

输入参数:nnff(nn, batch_x, batch_y),这个就不说了,和上面一样

输出参数:元包矩阵nn,主要计算了隐层的激活函数值。

函数体说明:

1 添加偏置项

x = [ones(m,1)x];

nn.a{1} = x;

一般前馈计算过程为,h=f(xw’+b);其中参数b即为偏置项;此时在计算过程中,需要复制”b维数,是的复制后的b可以和wx乘积结果可以相加(详见SparseAutoEncoder稀疏编码详解));这样每次复制b,会增大计算量;本代码中,把偏置项,直接添加到输入数据x中,在x前添加一列0元素,作为偏置项b;这是样输入数据矩阵,就变成了60000*785,这也就和前面为什么要把权值矩阵w1定义为为200*785,而不是200*784了。

所以最后权值矩阵W1为200*785;W2为100*201 ;W3为10*101。

这样前馈计算过程,由h=f(xw’+b),变成了h=f(x*w’)

2 encoder和decoder过程

Encoder阶段:

    for i = 2 : n-1

        switchnn.activation_function

            case ‘sigm’

                % Calculate theunit’s outputs (including the bias term)

               nn.a{i}= sigm(nn.a{i – 1} * nn.W{i – 1}’);

            case ‘tanh_opt’

               nn.a{i}= tanh_opt(nn.a{i – 1} * nn.W{i – 1}’);

        end

       nn.a{i} =[ones(m,1) nn.a{i}];%给下一个网络的输入添加偏置项

    end

decoder阶段:

根据选择的decoder函数,来计算;由于此处是autoencoder,通过 nn.a{n} = sigm(nn.a{n – 1} * nn.W{n – 1}’);来生成输入数据的近似估计,以便用来求重构误差。

    switch nn.output

        case ‘sigm’

           nn.a{n}= sigm(nn.a{n – 1} * nn.W{n – 1}’);

        case ‘linear’

           nn.a{n}= nn.a{n – 1} * nn.W{n – 1}’;

        case ‘softmax’

           nn.a{n}= nn.a{n – 1} * nn.W{n – 1}’;

           nn.a{n}= exp(bsxfun(@minus, nn.a{n}, max(nn.a{n},[],2)));

           nn.a{n}= bsxfun(@rdivide, nn.a{n}, sum(nn.a{n}, 2));

    end

3.计算损失函数

   nn.e = y -nn.a{n};

    switch nn.output

        case {
‘sigm’‘linear’}

           nn.L =1/2 * sum(sum(nn.e .^ 2)) / m; %均方误差

        case ‘softmax’

           nn.L =-sum(sum(y .* log(nn.a{n}))) / m;

    end

  

3.2.4反向函数nnbp说明:

输入,输出参数:nn = nnbp(nn),都是元胞矩阵nn

 这里首先要复习一下,bp算法的误差传播方法。

误差反向传播:

最后一层误差:nl为最后一层的层数,z为最后一层的输入,上一层的输出值

自动编码器模型和代码解释

Sigmoid函数的导数为f’(a)=a(1-a)

输出层误差代码:

switch nn.output

        case ‘sigm’

           d{n} =- nn.e .* (nn.a{n} .* (1 – nn.a{n}));

        case {
‘softmax’,‘linear’}

           d{n} =- nn.e;

end

中间层误差:本层和下一层的连接权值,乘以,上一层误差,在乘以,本层输入的导数。

自动编码器模型和代码解释

for i = (n -1) : -1 :2

        各层激活函数求导

        switchnn.activation_function

            case ‘sigm’

               d_act= nn.a{i} .* (1 – nn.a{i});

            case ‘tanh_opt’

               d_act= 1.7159 * 2/3 * (1 – 1/(1.7159)^2 * nn.a{i}.^2);

        end

        计算误差

        if i+1==n

           d{i} =(d{i + 1} * nn.W{i} + sparsityError) .* d_act;

%倒数第二层的残杀d{nl-1},是由最后一层残差d{nl}传播得到的,而最后一层的残差没有添加偏置项,也就是输出层节点是10,而不是11;所以误差可以直接通过连接的权值矩阵向前传递。

        else

           d{i} =(d{i + 1}(:,2:end) * nn.W{i} + sparsityError) .* d_act;

倒数第二层以前的残差d{i}是由上一层残差d{i+1}传递过来的;而中间层再向前传递的时候每层都加了偏置项,而这里我们要计算的是连接权值矩阵W的残差,所以应该把添加的偏置项去掉,所以去挑d{i+1}的第一列,使用d{i + 1}(:,2:end),而不是整个d{i+1}

        end

end

权值更新量计算:

l层的权值更新量delta_W=下一层网络的误差 * 本层输入

自动编码器模型和代码解释

l层偏置项更新量delta_b=残差

自动编码器模型和代码解释

为了避免在小批量数据的样本容量发生改变时,学习率也必须做相应的修改;通常的做法是在参数的更新过程中昬使用参数的平均梯度;即总梯度除以数据容量。

for i = 1 : (n- 1)

        if i+1==n

           nn.dW{i}= (d{i + 1}’ * nn.a{i}) / size(d{i + 1}, 1);

        else

           nn.dW{i}= (d{i + 1}(:,2:end)’ * nn.a{i}) / size(d{i + 1},1);     

        end

end

权值更新:

基本的梯度下降更新:无动量项,无权值惩罚项

w=w-alpha*delta_W

for i = 1 : (nn.n – 1)

        if(nn.weightPenaltyL2>0)

           dW =nn.dW{i} + nn.weightPenaltyL2 * [zeros(size(nn.W{i},1),1) nn.W{i}(:,2:end)];

        else

           dW =nn.dW{i};

        end

       dW =nn.learningRate * dW;

        if(nn.momentum>0)

           nn.vW{i}= nn.momentum*nn.vW{i} + dW;

           dW =nn.vW{i};

        end

       nn.W{i} =nn.W{i} – dW;

end

四、fine_tuning阶段

4.1初始化网络结构

1.首先通过nnsetup784,200,100,10)函数来构建网络;此时不是通过saesetup函数来构建;

2.设置网络激活函数,学习率等参数

3.Pre_training阶段,学习到的权值矩阵,作为网络权值的初始值;

nn.W{1} =sae.ae{1}.W{1};

nn.W{2}= sae.ae{2}.W{1};

4.2 fine_tuning

利用nntrain函数,使用真实的标签数据train_y来微调整个网络

nn= nntrain(nn, train_x, train_y, opts);

4.3:预测阶段 (此部分代码详解见博文:Denosing Autoencoder训练过程代码详解

[er,bad] =nntest(nn, test_x, test_y);



7.普通deep autoencoder训练过程

本文主要参考Deeplearn toolbox中代码

matlabDeep Learning toolbox,见:https://github.com/rasmusbergpalm/DeepLearnToolbox

一:加载数据 

二:pre_training阶段

2.1初始化DAE网络框架

sae = saesetup([784 100 100]);%建立一个3层网络

在函数saesetup函数内部,循环调用nnsetup函数,此处2次调用nnsetup函数

最终saesetup([784 100 100]);建立一个2个autoencoder网络

分别是:784 ——100——784100 ——100——100

2.1.1   nnsetup函数说明;

nn.size   =architecture;   nn.n=numel(nn.size)=3;

初始化激活函数类型,学习率,动量项,稀疏项等参数。

初始化网络结构:

for i= 2 : nn.n=3   %第一次循环调用:输入nnsetup([784  100 784])

经过2次循环,初始化一个784 ——100——784 的autoencoder网络。

2.2初始化SAE网络的训练参数

初始化激活函数类型(此处默认为tanh_opt函数),学习率,噪声比例等参数。

2.3开始训练SAE网络

sae = saetrain(sae, train_x,opts);%输入网络结构,样本数据,批处理的个数

saetrain内部调用nntrain函数来训练sae的每个autoencoder子网络.

sae.ae{i} = nntrain(sae.ae{i},x, x, opts);%循环两次

2.3.1 nntrain函数说明

输入参数:nntrain(nn, train_x, train_y, opts,val_x, val_y)

由于是autoencoder网络,此处的train_y是输入数据train_x

整个数据循环训练numepochs次,每次将整个数据分成numbatches个组,进行minibatch训练。

每次minibatch训练过程:

从整个数据中,“随机提取”minibatch个batch_x和batch_y数据;

调用nnff函数前馈计算 损失函数nn.L,和误差向量 nn.e

调用nnbp函数,反向计算误差dw{i}

调用nnapplygrads函数,来更新权值

三:fine_tuning 阶段

3.1初始化前馈网络

nn = nnsetup([784 100 100 10]);

把pre_training阶段训练的权值矩阵赋值给前馈网络。

nn.W{1} = sae.ae{1}.W{1};

nn.W{2} = sae.ae{2}.W{1};

 

3.2标签数据来fine_tuning整个网络

nn = nntrain(nn, train_x,train_y, opts);

 

四:测试数据

[er, bad] = nntest(nn, test_x,test_y);

 4.1 预测样本分类

labels = nnpredict(nn, x);

nnpredict函数首先实现一个网络的前馈计算,计算样本属于每个分类的“概率”

nn = nnff(nn, x,zeros(size(x,1), nn.size(end)));

根据最大化原则,确定样本的分类,并提取类别标签

[~, i] = max(nn.a{end},[],2);%max(a,[],2)提取矩阵a每行的最大值

labels = i;

4.2计算错误率

[~, expected] = max(y,[],2);

bad = find(labels ~= expected);

er = numel(bad) / size(x, 1);


Denosing Autoencoder训练过程

Denoising autoencode主要通过在nntrain函数中对输入数据加入噪声,其他训练部分相同

一:Pre_training阶段

数据加噪:

sae.ae{1}.inputZeroMaskedFraction   = 0.5;

sae.ae{2}.inputZeroMaskedFraction   = 0.5;

由于是使用saetrain函数来分别训练每个autoencoder网络,所以这里对每个网络的输入数据都加入50%的随机噪声。

加噪代码:

if(nn.inputZeroMaskedFraction~= 0)

batch_x=batch_x.*(rand(size(batch_x))>nn.inputZeroMaskedFraction);

end

自动编码器模型和代码解释

加入50%随机噪声后,原始数据

自动编码器模型和代码解释


自动编码器模型和代码解释

二:fine_tuning阶段

由于此处直接使用的是nntrain,微调整个网络;所以只在在调用nntrain函数时,把原始输入数据train_x加入噪声

 

代码的一些困惑:

在pre_training阶段,在nnsetup中,初始化每个autoencoder的前馈计算的encoder阶段使用的是tanh函数,decoder阶段使用的是sigmoid函数,这两个阶段为什么不一样呢?其次就是最后fine_tuning过程,前馈计算过程都使用的是sigmoid函数,这个也和pre_training过程的不一样。这个木有想明白

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/134281.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • js倒计时代码最简单的(js倒计时10秒代码)

    第一种:精确到秒的javascript倒计时代码HTML代码:离2010年还有:startclock()vartimerID=null;vartimerRunning=false;functionshowtime(){Today=newDate();varNowHour=T

  • 十五种文本编辑器

    十五种文本编辑器很多时候比如编程查看代码或者打开各种文档下我们都会用到文本编辑器,Windows自带的记事本功能很简陋并且打开大文件很慢,因此很多童鞋都会有自己喜欢的一款文本编辑器。在这里,西西挑选前15个最佳的文本编辑器,这些编辑器实际上主要适合程序员!如果觉得这些文本编辑器足够您的使用,欢迎点赞,如果还有更好的,可以给我们推荐哦。1.Notepad++中文版:这是Windows记事本一个最好…

  • STL之Map的运用

    STL之Map的运用

    2021年11月17日
  • 什么是代码_大专程序员有人要吗

    什么是代码_大专程序员有人要吗什么是代码,什么是数据。代码是能够处理其他数据的数据,而数据是储存信息的数据。数据有时能够变成代码,代码也可以变成数据。甚至,一段数据中某一段,都可以单独拿出来,通过执行器执行(执行器,比如exe的执

  • java发送邮件-模板

    java发送邮件-模板今天写完了一个关于使用模板发送邮件的代码,作为例子保存着,希望以后用得着,也希望能够帮助到需要帮助的人以163网易邮箱为例,使用java发送邮件,发送以邮件时使用模板(.ftl文件转换为html)发送邮件内容,并附带上附件,可抄送给多个人。项目的结构目录如下邮箱配置文件mail.properties参数如下#mailsendersettings#forexample:smtp.1

  • leetcode happy number

    leetcode happy number

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号