MLP深度学习算法示例源代码解析

1. 引言

在深度学习领域,多层感知器(MLP)是一种基础且强大的神经网络模型。本文将详细介绍一个MLP深度学习算法的示例源代码,包括代码结构、功能实现以及关键技术点的分析。

2. 整体流程概述

整个MLP深度学习算法的实现主要包括以下几个步骤:
1. 读取配置文件
2. 创建输出文件夹
3. 读取输入数据
4. 创建数据集
5. 构建MLP模型
6. 训练MLP模型
7. 评估MLP模型

下面是整体流程的mermaid流程图:

graph TD;
    A[读取配置文件] --> B[创建输出文件夹];
    B --> C[读取输入数据];
    C --> D[创建数据集];
    D --> E[构建MLP模型];
    E --> F[训练MLP模型];
    F --> G[评估MLP模型];
3. 代码详细解析
3.1 主程序文件 MLP_experiment.py

主程序文件负责整个流程的控制,以下是关键代码及解析:

# -*- coding: utf-8 -*-
import os 
import sys 
import shutil 
from datetime import datetime,timedelta 
import random 
import numpy as np 
sys.path.append('./modules') 
from MyMLP.v191015.bl_mlp       import BL_MLP 
from MyMLP.v191015.timeseries   import MyLSTMTimeSeries 
sys.path.append('./../modules') 
from utilities.v190422.config   import CConfig 

if __name__ == "__main__" : 
    # Read configuration values from xml file 
    root    = CConfig(sys.argv[1],target="execute_test").get() 
    INPUT_FILE_PATH         = root.get("inputFilePath",default=".") 
    OUTPUT_DIRECTORY_ROOT   = root.get("outputDirectoryRoot",default=".") 
    IN_VARIABLES            = root.get("inputVariables",default=[]) 
    OUT_VARIABLES           = root.get("outputVariables",default=[]) 
    HIDDEN_UNITS            = root.get("hiddenUnits",default=10) 
    SERIES_LENGTH           = root.get("seriesLength",default=6) 
    EPOCH                   = root.get("epoch",default=100) 
    RANDOM_SEED             = root.get("randomSeed",default=0) 
    MAX                     = root.get("max",default=[]) 
    MIN                     = root.get("min",default=[]) 
    TRAINING_DATES          = root.get("trainingDates",default=[]) 
    TEST_DATES              = root.get("testDates",default=[]) 
    IGNORE_HOURS            = root.gets("ignoreHours",default=[]) 
    random.seed(RANDOM_SEED) 
    np.random.seed(RANDOM_SEED) 

上述代码主要完成了以下几个任务:
1. 导入必要的库
2. 从XML配置文件中读取配置参数
3. 设置随机种子

    # Create output folder 
    OUTPUT_DIRECTORY_PATH   = OUTPUT_DIRECTORY_ROOT + "/" + os.path.basename(sys.argv[1]).split(".")[0] 
    if os.path.exists( OUTPUT_DIRECTORY_PATH ) : 
        shutil.rmtree( OUTPUT_DIRECTORY_PATH ) 
    os.mkdir( OUTPUT_DIRECTORY_PATH ) 
    # Copy xml file to output folder 
    shutil.copy( sys.argv[1], OUTPUT_DIRECTORY_PATH + "/" + os.path.basename(sys.argv[1]) ) 

这部分代码用于创建输出文件夹,并将配置文件复制到输出文件夹中。

    # Read input data from a csv file 
    with open( INPUT_FILE_PATH,'r' ) as fin : 
        columns = fin.readline().strip().split(",") 
        data    = dict(zip( columns,[[] for c in columns] )) 
        for line in fin : 
            items   = line.strip().split(",") 
            for (c,clm) in enumerate(columns) : 
                try : 
                    data[clm].append( float(items[c]) ) 
                except ValueError : 
                    data[clm].append( items[c] ) 
    print "success to loading" 
    sequence_data   = dict() 
    for (i,t) in enumerate( data["TIME"] ): 
        sequence_data[t] = [ data[v][i] for v in IN_VARIABLES+OUT_VARIABLES ] 

此部分代码从CSV文件中读取输入数据,并将其存储在字典中。

    train_series    = dict() 
    test_series     = dict() 
    # Create dataset log output file 
    with open( OUTPUT_DIRECTORY_PATH+"/train_series.csv",'w' ) as fout : 
        fout.write( "seriesID,target_frame\n" ) 
    with open( OUTPUT_DIRECTORY_PATH+"/test_series.csv",'w' ) as fout : 
        fout.write( "seriesID,target_frame\n" ) 
    # Create a dataset 
    # For mlp, fix SERIES_LENGTH to 1 
    for i in range( len(sequence_data) ) : 
        dt_tmp      = datetime.strptime( data["TIME"][i], "%Y-%m-%d %H:%M:%S" ) 
        series_dts  = [ datetime.strftime( dt_tmp+timedelta(minutes=d*5), "%Y-%m-%d %H:%M:%S" ) for d in range(-SERIES_LENGTH+1,1) ] 
        # Remove data contained in IGNORE_HOURS 
        if True in [ h.time()<=dt_tmp.time() and dt_tmp.time()<t.time() for (h,t) in IGNORE_HOURS ] : 
            continue 
        sequences   = [ sequence_data[t] if t in sequence_data else None for t in series_dts ] 
        if None in sequences : 
            continue 
        # min-max normalization 
        series  = MyLSTMTimeSeries( sequences, in_area=(0,len(IN_VARIABLES)), 
                                    out_area=(len(IN_VARIABLES), len(IN_VARIABLES)+len(OUT_VARIABLES)) ) 
        series.MaxMinScaling( MAX,MIN ) 
        # Create training dataset 
        if True in [ d.date()==dt_tmp.date() for d in TRAINING_DATES ] : 
            train_series[data["TIME"][i]] = series 
            # Output training data set log 
            with open( OUTPUT_DIRECTORY_PATH+"/train_series.csv",'a' ) as fout : 
                fout.write( "%d,"%(len(train_series)) + ",".join(series_dts)+"\n" ) 
        # Create evaluation data set 
        elif True in [ d.date()==dt_tmp.date() for d in TEST_DATES ] : 
            test_series[data["TIME"][i]] = series 
            # Output evaluation data set log 
            with open( OUTPUT_DIRECTORY_PATH+"/test_series.csv",'a' ) as fout : 
                fout.write( "%d,"%(len(test_series)) + ",".join(series_dts)+"\n" ) 
    print "success to create time series, start learning..." 

这部分代码用于创建数据集,包括训练集和测试集,并进行最小 - 最大归一化处理。

    # Building MLPs 
    mlp = BL_MLP( in_units=len(IN_VARIABLES), hidden_units=HIDDEN_UNITS, 
                  out_units=len(OUT_VARIABLES), seed=RANDOM_SEED ) 
    # Shuffle the order of training data sets 
    shuffled_keys   = random.sample( train_series.keys(), len(train_series.keys()) ) 
    shuffled_vals   = [ train_series[k] for k in shuffled_keys ] 
    # MLP Learning 
    with open( OUTPUT_DIRECTORY_PATH+"/loss.csv",'w' ) as fout : 
        for epoch in range(EPOCH) : 
            total_avg_loss  = mlp.Train( shuffled_vals, epoch, OUTPUT_DIRECTORY_PATH) 
            print epoch,total_avg_loss 
            fout.write( "%d,%.08f\n"%(epoch,total_avg_loss) ) 

此部分代码构建了MLP模型,并进行训练,记录每个epoch的损失值。

    # Input training data into trained MLP 
    predicted   = mlp.Predict( shuffled_vals, OUTPUT_DIRECTORY_PATH+"/train_val.csv" ) 
    with open( OUTPUT_DIRECTORY_PATH+"/train_result_W15.csv",'w' ) as fout : 
        fout.write( "id,time,predicted,answer,m_predicted,m_answer\n" ) 
        errors      = [] 
        m_errors    = [] 
        al      = MAX[(IN_VARIABLES+OUT_VARIABLES).index("W15n")] - MIN[(IN_VARIABLES+OUT_VARIABLES).index("W15n")] 
        bt      = MIN[(IN_VARIABLES+OUT_VARIABLES).index("W15n")] 
        # Output values and correct answers, each with scale returned, to csv file 
        for (i,p,a) in predicted["result"] : 
            print i,p,a 
            fout.write( "%d,%s,%.08f,%.08f,%.08f,%.08f\n"%(i, shuffled_keys[i],p[0],a[0],p[0]*al+bt,a[0]*al+bt) ) 
            # Calculate RMSE 
            errors.append( (p[0]-a[0])**2 ) 
            m_errors.append( ((p[0]*al+bt)-(a[0]*al+bt))**2 ) 
        RMSE    = np.sqrt(np.mean( errors )) 
        mRMSE   = np.sqrt(np.mean( m_errors )) 
        # Output RMSE and original scale RMSE to csv file 
        fout.write( "\n\nRMSE,%.08f\nmRMSE[kWh],%.08f\n"%(RMSE,mRMSE) ) 
    with open( OUTPUT_DIRECTORY_PATH+"/train_result_TSA15.csv",'w' ) as fout : 
        fout.write( "id,time,predicted,answer,m_predicted,m_answer\n" ) 
        errors      = [] 
        m_errors    = [] 
        al      = MAX[(IN_VARIABLES+OUT_VARIABLES).index("TSA15n")] - MIN[(IN_VARIABLES+OUT_VARIABLES).index("TSA15n")] 
        bt      = MIN[(IN_VARIABLES+OUT_VARIABLES).index("TSA15n")] 
        # Output values and correct answers, each with scale returned, to csv file 
        for (i,p,a) in predicted["result"] : 
            print i,p,a 
            fout.write( "%d,%s,%.08f,%.08f,%.08f,%.08f\n"%(i, shuffled_keys[i],p[1],a[1],p[1]*al+bt,a[1]*al+bt) ) 
            # Calculate RMSE 
            errors.append( (p[1]-a[1])**2 ) 
            m_errors.append( ((p[1]*al+bt)-(a[1]*al+bt))**2 ) 
        RMSE    = np.sqrt(np.mean( errors )) 
        mRMSE   = np.sqrt(np.mean( m_errors )) 
        # Output RMSE and original scale RMSE to csv file 
        fout.write( "\n\nRMSE,%.08f\nmRMSE[kWh],%.08f\n"%(RMSE,mRMSE) ) 

这部分代码使用训练好的MLP模型对训练数据进行预测,并计算均方根误差(RMSE)。

    print "end learning, start evaluating..." 
    # MLP Evaluation 
    test_keys       = [ key for key in sorted(test_series.keys()) if datetime.strptime(key,"%Y-%m-%d %H:%M:%S").minute%15==14 ] 
    shuffled_keys   = test_keys 
    shuffled_vals   = [ test_series[k] for k in test_keys ] 
    predicted   = mlp.Predict( shuffled_vals, OUTPUT_DIRECTORY_PATH+"/test_val.csv" ) 
    with open( OUTPUT_DIRECTORY_PATH+"/test_result_W15.csv",'w' ) as fout : 
        fout.write( "id,time,predicted,answer,m_predicted,m_answer\n" ) 
        errors      = [] 
        m_errors    = [] 
        al      = MAX[(IN_VARIABLES+OUT_VARIABLES).index("W15n")] - MIN[(IN_VARIABLES+OUT_VARIABLES).index("W15n")] 
        bt      = MIN[(IN_VARIABLES+OUT_VARIABLES).index("W15n")] 
        # Output values and correct answers, each with scale returned, to csv file 
        for (i,p,a) in predicted["result"] : 
            print i,p,a 
            fout.write( "%d,%s,%.08f,%.08f,%.08f,%.08f\n"%(i, shuffled_keys[i],p[0],a[0],p[0]*al+bt,a[0]*al+bt) ) 
            # Calculate RMSE 
            errors.append( (p[0]-a[0])**2 ) 
            m_errors.append( ((p[0]*al+bt)-(a[0]*al+bt))**2 ) 
        RMSE    = np.sqrt(np.mean( errors )) 
        mRMSE   = np.sqrt(np.mean( m_errors )) 
        # Output RMSE and original scale RMSE to csv file 
        fout.write( "\n\nRMSE,%.08f\nmRMSE[kWh],%.08f\n"%(RMSE,mRMSE) ) 
    with open( OUTPUT_DIRECTORY_PATH+"/test_result_TSA15.csv",'w' ) as fout : 
        fout.write( "id,time,predicted,answer,m_predicted,m_answer\n" ) 
        errors      = [] 
        m_errors    = [] 
        al      = MAX[(IN_VARIABLES+OUT_VARIABLES).index("TSA15n")] - MIN[(IN_VARIABLES+OUT_VARIABLES).index("TSA15n")] 
        bt      = MIN[(IN_VARIABLES+OUT_VARIABLES).index("TSA15n")] 
        # Output values and correct answers, each with scale returned, to csv file 
        for (i,p,a) in predicted["result"] : 
            print i,p,a 
            fout.write( "%d,%s,%.08f,%.08f,%.08f,%.08f\n"%(i, shuffled_keys[i],p[1],a[1],p[1]*al+bt,a[1]*al+bt) ) 
            # Calculate RMSE 
            errors.append( (p[1]-a[1])**2 ) 
            m_errors.append( ((p[1]*al+bt)-(a[1]*al+bt))**2 ) 
        RMSE    = np.sqrt(np.mean( errors )) 
        mRMSE   = np.sqrt(np.mean( m_errors )) 
        # Output RMSE and original scale RMSE to csv file 
        fout.write( "\n\nRMSE,%.08f\nmRMSE[kWh],%.08f\n"%(RMSE,mRMSE) ) 
    print "success, exit" 

最后,使用测试数据对MLP模型进行评估,并计算RMSE。

3.2 MLP模型相关文件
3.2.1 bl_mlp.py

该文件定义了MLP模型的相关类和方法,以下是关键代码及解析:

# -*- coding: utf-8 -*-
import sys 
import random 
import numpy as np 
from logger import Logger 
sys.path.append('./../modules') 
from TsutsuiNN.v191004.chain    import Chain 
from TsutsuiNN.v191004.linear   import Linear 
import TsutsuiNN.v191004.optimizers     as optimizers 

class MyMLP( object ) : 
    class MLPLogger( Logger ) : 
        def __init__(self,model=None,enable=True) : 
            super(MyMLP.MLPLogger,self).__init__(enable) 
            self.__model = model 
        def WriteBreak( self, note ) : 
            if not self.IsEnable : return 
            txt = "==========" + str(note) + "==========" 
            self.Logging(txt) 
        # Outputs input values 
        def WriteModelInput( self, input ) : 
            if not self.IsEnable : return 
            input = input[0] 
            txt = "model_input" 
            for i in input : 
                txt += ",%.08f"%(i) 
            self.Logging(txt) 
        # Output the value of the activation function of one layer 
        def WriteL1Activation( self ) : 
            if not self.IsEnable : return 
            activation = self.__model.l1.Z[0] 
            txt = "l1activation" 
            for a in activation : 
                txt += ",%.08f"%(a) 
            self.Logging(txt) 
        # Output the values of the activation functions of the two layers 
        def WriteL2Activation( self ) : 
            if not self.IsEnable : return 
            activation = self.__model.l2.Z[0] 
            txt = "l2activation" 
            for a in activation : 
                txt += ",%.08f"%(a) 
            self.Logging(txt) 
        # Outputs bias values for one layer 
        def WriteL1Bias( self ) : 
            if not self.IsEnable : return 
            for i,bias in enumerate(self.__model.l1.b.T): 
                txt = "l1bias_%d"%(i) 
                for b in bias : 
                    txt += ",%.08f"%(b) 
                self.Logging(txt) 
        # Output 1-layer weights 
        def WriteL1Weight( self ) : 
            if not self.IsEnable : return 
            for i,weight in enumerate(self.__model.l1.W.T) : 
                txt = "l1weight_%d"%(i) 
                for w in weight : 
                    txt += ",%.08f"%(w) 
                self.Logging(txt) 
        # Output bias values for two layers 
        def WriteL2Bias( self ) : 
            if not self.IsEnable : return 
            for i,bias in enumerate(self.__model.l2.b.T): 
                txt = "l2bias_%d"%(i) 
                for b in bias : 
                    txt += ",%.08f"%(b) 
                self.Logging(txt) 
        # Output 2-layer weights 
        def WriteL2Weight( self ) : 
            if not self.IsEnable : return 
            for i,weight in enumerate(self.__model.l2.W.T) : 
                txt = "l2weight_%d"%(i) 
                for w in weight : 
                    txt += ",%.08f"%(w) 
                self.Logging(txt) 
    def __init__( self, in_units=1, hidden_units=5, out_units=1, seed=100, log_enable=True ) : 
        self.__model        = Chain( "mean_squared_error", l1=Linear(in_units, hidden_units, 
                                                                      "sigmoid"), l2=Linear(hidden_units, out_units, "sigmoid") ) 
        self.__logger       = self.MLPLogger(self.__model,log_enable) 
        self.__seed         = seed 
        self.__optimizer    = optimizers.Adam()     
        self.__optimizer.setup( self.__model ) 
        self.InitializeParams() 
    @property 
    def Logger(self) : 
        return self.__logger 
    # Initialize weights and biases 
    def InitializeParams( self ) : 
        np.random.seed( self.__seed ) 
        for param in self.__model.params() : 
            param[:] = np.random.uniform( -0.35, 0.35, param.shape ) 
    # MLP forward propagation 
    def Update( self, x_t ) : 
        x = np.array(x_t, dtype=np.float32).reshape(1,len(x_t)) 
        y = self.__model.forward(x) 
        # Output logs to csv file 
        self.Logger.WriteModelInput(x) 
        self.Logger.WriteL1Activation() 
        self.Logger.WriteL2Activation() 
        # Return MLP output values 
        return y 
    # MLP Learning 
    def Train( self, series, answers, log_note="train" ) : 
        if len(series) == 0 : 
            print "trainig series is empty @ func Train" 
            return None 
        # Initialize time series history 
        self.__model.Clear() 
        self.Logger.WriteBreak(log_note) 
        # Calculate error 
        losses      = None 
        for i,(x_t, a_t) in enumerate( zip(series, answers) ) : 
            self.Update(x_t) 
            a = np.array(a_t, dtype=np.float32).reshape(1,len(a_t)) 
            if losses is None : 
                losses  = self.__model.loss(a) 
            else : 
                losses += self.__model.loss(a) 
        # Error back propagation + updated weights and biases 
        self.__LearnFromLoss() 
        # Output logs to csv file 
        self.Logger.WriteL1Weight() 
        self.Logger.WriteL2Weight() 
        self.Logger.WriteL1Bias() 
        self.Logger.WriteL2Bias() 
        # return an error 
        return losses / len(series) 
    def __LearnFromLoss( self ) : 
        # backward propagation error 
        self.__model.backward() 
        # Update weights and biases 
        self.__optimizer.update() 
    # MLP Estimation 
    def Predict( self, series, log_note="predict" ) : 
        if len(series) == 0 : 
            print "input series is empty @ func Predict" 
            return None 
        # Initialize time series history 
        self.__model.Clear() 
        self.Logger.WriteBreak(log_note) 
        ret = [] 
        for x_t in series : 
            ret.append( self.Update(x_t).reshape(-1,) ) 
        return ret 

class BL_MLP(MyMLP) : 
    def __init__(self, in_units=2, hidden_units=10, out_units=2, seed=100): 
        super(BL_MLP,self).__init__( in_units, hidden_units, out_units, seed) 
    # MLP Learning 
    def Train(self, training_series,epoch, output_directory_path) : 
        total_loss  = 0.0 
        for i,tr_series in enumerate(training_series) : 
            series, answers = tr_series.Get() 
            total_loss += super(BL_MLP,self).Train( series,answers,"series%03d"%(i) ) 
        self.Logger.Output( output_directory_path+"/%s_epoch.csv"%epoch ) 
        self.Logger.Clear() 
        # return an error 
        return total_loss / len(training_series) 
    # MLP Estimation 
    def Predict(self, predict_series, output_file_path) : 
        ret = dict( result=[], predicted=[], answers=[] ) 
        for i,pr_series in enumerate(predict_series) : 
            series, answers = pr_series.Get() 
            predicted       = super(BL_MLP,self).Predict( series,"series%03d"%(i) ) 
            ret["result"].append( [i,predicted[-1],answers[-1]] ) 
            ret["predicted"].append( predicted ) 
            ret["answers"].append( answers ) 
        self.Logger.Output( output_file_path ) 
        self.Logger.Clear() 
        # Return output and correct values 
        return ret 

该文件定义了 MyMLP BL_MLP 两个类,其中 MyMLP 类包含了MLP的基本操作,如前向传播、训练和预测, BL_MLP 类继承自 MyMLP 类,并对训练和预测方法进行了扩展。

以下是 MyMLP 类的主要功能总结:
| 功能 | 方法 | 描述 |
| ---- | ---- | ---- |
| 初始化 | __init__ | 初始化MLP模型,包括创建神经网络链、设置日志记录器、优化器等 |
| 初始化参数 | InitializeParams | 初始化神经网络的权重和偏置 |
| 前向传播 | Update | 执行MLP的前向传播,并记录相关日志 |
| 训练 | Train | 训练MLP模型,计算损失并进行反向传播更新参数 |
| 预测 | Predict | 使用训练好的MLP模型进行预测 |

BL_MLP 类在 MyMLP 类的基础上,对训练和预测方法进行了批量处理,以适应多个时间序列数据的训练和预测。

3.2.2 logger.py

该文件定义了日志记录器类,用于记录MLP模型的相关信息,以下是代码:

class Logger(object) : 
    def __init__(self,enable=True) : 
        self.__log = [] 
        self.__enable = enable 
    @property 
    def IsEnable(self) : 
        return self.__enable 
    def Logging(self,val) : 
        if self.IsEnable : 
            self.__log.append( val ) 
    def Output( self,file_path ) : 
        if self.IsEnable : 
            fout = open( file_path,'w' ) 
            for l in self.__log : 
                fout.write(l+"\n") 
            fout.close() 
    def Clear(self) : 
        if self.IsEnable : 
            self.__log = [] 
    def Enable(self) : 
        self.__enable = True 
    def Disable(self) : 
        self.__enable = False 

Logger 类提供了日志记录、输出和清空等功能,通过设置 enable 参数可以控制日志记录的开关。

3.2.3 timeseries.py

该文件定义了时间序列相关的类,用于处理时间序列数据,以下是关键代码及解析:

class TimeSeries(object) : 
    def __init__(self,series=None,note="") : 
        self.__series = series if series!=None else [] 
        self.__note   = note 
    def Add(self,vector) : 
        self.__series.append(vector) 
    def Get(self) : 
        return self.__series 
    def At(self,time) : 
        try: 
            return self.__series[time] 
        except IndexError : 
            return None 
    def Length(self) : 
        return len(self.__series) 
    def LengthAt(self,time) : 
        try: 
            return len(self.__series[time]) 
        except IndexError : 
            return -1 
    def Note(self) : 
        return self.__note 
    def Show(self) : 
        for series in self.__series : 
            for s in series : 
                print "%.03f"%(s), 
            print "" 
        print "Length : %4d\n"%(self.Length()) 
        return 

class LearningTimeSeries(TimeSeries) : 
    def __init__(self, series=None, answer=None, note="") : 
        super(LearningTimeSeries,self).__init__(series,note) 
        self.__answer = answer if answer!=None else [] 
    def Add(self,vector,answer) : 
        super(LearningTimeSeries,self).Add(vector) 
        self.__answer.append( answer ) 
    def Get(self) : 
        return super(LearningTimeSeries,self).Get(),self.__answer 
    def AnswerLength(self) : 
        return len(self.__answer) 
    def AnswerLengthAt(self,time) : 
        try: 
            return len(self.__answer[time]) 
        except IndexError : 
            return -1 
    def Show(self) : 
        super(LearningTimeSeries,self).Show() 
        print "-------------------" 
        for ans in self.__answer : 
            for a in ans : 
                print "%.03f"%(a), 
            print "" 
        print "answer range is",self.AnswerLength() 
        return 

import sys 
import numpy as np 
class MyLSTMTimeSeries(LearningTimeSeries) : 
    def __init__(self, series=None, in_area=(0,4), out_area=(4,14), note="") : 
        super(MyLSTMTimeSeries,self).__init__(series=None,answer=None, note=note) 
        self.__IN_AREA  = in_area 
        self.__OUT_AREA = out_area 
        for vector in series : 
            self.Add( vector ) 
    def Add(self,vector) : 
        length = len(vector) 
        if length < self.__OUT_AREA[1] : 
            print "vector length is short : %d, need %d"%(length,self.__OUT_AREA[1]) 
            return 
        v   = vector[ self.__IN_AREA[0]:self.__IN_AREA[1] ] 
        a   = vector[ self.__OUT_AREA[0]:self.__OUT_AREA[1] ] 
        super(MyLSTMTimeSeries,self).Add(v,a) 
    def MaxMinScaling(self,max,min) : 
        max_v   = np.array( max[ self.__IN_AREA[0]:self.__IN_AREA[1] ] ) 
        min_v   = np.array( min[ self.__IN_AREA[0]:self.__IN_AREA[1] ] ) 
        max_a   = np.array( max[ self.__OUT_AREA[0]:self.__OUT_AREA[1] ] ) 
        min_a   = np.array( min[ self.__OUT_AREA[0]:self.__OUT_AREA[1] ] ) 
        v_den   = np.array( [(x-n) if x!=n else x for (x,n) in zip(max_v,min_v)] ) 
        a_den   = np.array( [(x-n) if x!=n else x for (x,n) in zip(max_a,min_a)] ) 
        series,answer  = super(MyLSTMTimeSeries,self).Get() 
        for t in range(len( series )) : 
            series[t]    = list( (np.array(series[t])-min_v)/v_den ) 
            answer[t]    = list( (np.array(answer[t])-min_a)/a_den ) 
        return True 

该文件定义了三个类: TimeSeries LearningTimeSeries MyLSTMTimeSeries
- TimeSeries 类是一个基本的时间序列类,提供了添加、获取、显示等基本操作。
- LearningTimeSeries 类继承自 TimeSeries 类,增加了答案数据的管理,用于训练和评估。
- MyLSTMTimeSeries 类继承自 LearningTimeSeries 类,进一步增加了输入和输出区域的定义,并提供了最小 - 最大归一化方法。

以下是 MyLSTMTimeSeries 类的主要功能总结:
| 功能 | 方法 | 描述 |
| ---- | ---- | ---- |
| 初始化 | __init__ | 初始化时间序列数据,设置输入和输出区域 |
| 添加数据 | Add | 添加时间序列数据,并根据输入和输出区域进行分割 |
| 最小 - 最大归一化 | MaxMinScaling | 对时间序列数据进行最小 - 最大归一化处理 |

3.3 神经网络相关文件
3.3.1 chain.py

该文件定义了神经网络链类,用于构建和管理神经网络,以下是关键代码及解析:

# -*- coding: utf-8 -*- 
import sys 
import numpy as np 
from linear     import Linear 
from lstm       import LSTM 
from function   import Error 

class Chain( object ) : 
    def __init__( self, error_function=None, **links ) : 
        self.__outputs  = None 
        # Array for storing time history 
        self.__y        = [] 
        self.__targets  = [] 
        # Check if a miscalculation function is specified 
        if error_function is not None : 
            self.__error_func   = Error( error_function ) 
        else : 
            self.__error_func   = None 
        # NeuralNetwork construction 
        self.linknames  = [] 
        for name,value in links.items() : 
            self.__AddLink( name, value ) 
            self.linknames.append( name ) 
        self.linknames = sorted( self.linknames ) 
    # Add attribute to Chain class 
    def __AddLink( self, name, value ) : 
        if name in self.__dict__ : 
            raise AttributeError( "cannot register a new link %s: attribute exists" %name ) 
        if not isinstance( value, ( Linear, LSTM, Chain ) ) : 
            raise TypeError( "can register Linear object or LSTM object or Chain object" ) 
        setattr( self, name, value ) 
    # Return weights and biases 
    def params( self ) : 
        ret = [] 
        for name in self.linknames : 
            ret += self.__dict__[name].params() 
        return ret 
    # forward propagation 
    def forward( self, inputs ) : 
        if not isinstance( inputs, np.ndarray ) : 
            print "inputs type is not np.ndarray @Chain" 
            print "inputs : ", inputs 
            exit() 
        for name in self.linknames : 
            inputs = self.__dict__[name].forward( inputs ) 
        self.__outputs  = inputs 
        # Save NN output to an array for time series history storage 
        self.__y.append( self.__outputs ) 
        # Return NN output 
        return self.__outputs 
    def loss( self, targets ) : 
        # Calculate error 
        ERROR   = self.__error_func.Get() 
        loss    = ERROR( self.__outputs, targets ) 
        # Stores errors in an array for time-series history storage 
        self.__targets.append( targets ) 
        # return an error 
        return loss 
    # backward propagation error 
    def backward( self, W_upper=None, delta_upper=None, value=None ) : 
        if self.__error_func is not None : 
            ERROR_DIFF  = self.__error_func.Differentiate().Get() 
            value       = map( lambda y,targets: ERROR_DIFF( y, targets ), self.__y,self.__targets ) 
        for name in self.linknames[::-1] : 
            if W_upper is None and delta_upper is None : 
                W_upper, delta_upper = self.__dict__[name].backward( W_upper, delta_upper, value ) 
            else : 
                W_upper, delta_upper = self.__dict__[name].backward( W_upper, delta_upper ) 
        return W_upper, delta_upper 
    # Return weight gradient and bias gradient 
    def grads( self ) : 
        ret = [] 
        for name in self.linknames : 
            ret += self.__dict__[name].grads() 
        return ret 
    # Initialize time series history 
    def Clear( self ) : 
        self.__y        = [] 
        self.__targets  = [] 
        for name in self.linknames : 
            self.__dict__[name].Clear() 

Chain 类的主要功能包括:
| 功能 | 方法 | 描述 |
| ---- | ---- | ---- |
| 初始化 | __init__ | 初始化神经网络链,设置误差函数,添加神经网络层 |
| 添加链接 | __AddLink | 向神经网络链中添加链接(层) |
| 获取参数 | params | 返回神经网络的所有参数(权重和偏置) |
| 前向传播 | forward | 执行神经网络的前向传播 |
| 计算损失 | loss | 计算神经网络的损失 |
| 反向传播 | backward | 执行神经网络的反向传播,计算梯度 |
| 获取梯度 | grads | 返回神经网络的所有梯度 |
| 清空历史 | Clear | 清空时间序列历史记录 |

通过 Chain 类,可以方便地构建和管理复杂的神经网络结构,实现前向传播、反向传播和参数更新等操作。

3.3.2 linear.py

该文件定义了线性层类,用于实现神经网络中的线性变换,以下是关键代码及解析:

# -*- coding: utf-8 -*-
import sys 
import numpy as np 
from function       import Activation 

class Linear( object ) : 
    def __init__( self, in_units, out_units, activation_function="identity", nobias=False ) : 
        self.__in_units         = in_units 
        self.__out_units        = out_units 
        self.__activation_func  = Activation(activation_function) 
        self.__nobias           = nobias 
        self.Z                  = None 
        # Array for storing time history 
        self.__U = [] 
        self.__Z_lower  = [] 
        self.delta      = [] 
        # Create weights and biases 
        self.CreateParams() 
    def CreateParams( self ) : 
        # Create weights 
        self.W = np.empty(( self.__in_units, self.__out_units )) 
        # Create bias 
        if not self.__nobias : 
            self.b = np.empty(( 1, self.__out_units )) 
        else : 
            self.b = None 
    def params( self ) : 
        # Return weights and biases 
        if not self.__nobias : 
            return [ self.W, self.b ] 
        # return something (that has been moved) 
        else : 
            return [ self.W ] 
    # forward propagation calculations 
    def forward( self, inputs ) : 
        if not isinstance( inputs, np.ndarray ) : 
            print "inputs type is not np.ndarray @Linear" 
            print "inputs : ", inputs 
            exit() 
        Z_lower     = inputs 
        ACTIVATION  = self.__activation_func.Get() 
        # Calculate total node inputs 
        if not self.__nobias : 
            ones    = np.ones(( 1, Z_lower.shape[0] )) 
            try : 
                U   = np.dot( Z_lower, self.W ) + np.dot( ones.T, self.b ) 
            except : 
                print "lower_Z : ", Z_lower 
                print "W : ", self.W 
                print "ones.T : ", ones.T 
                print "b : ", self.b 
                exit() 
        else : 
            try : 
                U    = np.dot(  Z_lower, self.W ) 
            except : 
                print "lower_Z : ", Z_lower 
                print "W : ", self.W 
                exit() 
        # Save total inputs and previous layer outputs to an array for time series # history storage 
        self.__U.append( U ) 
        self.__Z_lower.append( Z_lower ) 
        # Calculate output values for nodes 
        self.Z = ACTIVATION( U ) 
        # Returns the output value of a node 
        return self.Z 
    # backward propagation error 
    def backward( self, W_upper, delta_upper, ERROR_DIFF_VALUE=None ) : 
        ACTIVATION_DIFF = self.__activation_func.Differentiate().Get() 
        if W_upper is None and delta_upper is None : 
            self.delta  = map( lambda value, U: value * ACTIVATION_DIFF(U), 
                               ERROR_DIFF_VALUE, self.__U ) 
        else : 
            self.delta  = map( lambda U, d: ACTIVATION_DIFF(U) * np.dot( d, W_upper.T ), 
                               self.__U, delta_upper ) 
        return [ self.W, self.delta ] 
    def grads( self ) : 
        Ns  = self.delta[0].shape[0]    # sample size 
        Nt  = len(self.delta)           # time series size 
        # Calculate weight gradient 
        dW  = map( lambda Z_lower, d: np.dot( Z_lower.T, d ) / Ns, self.__Z_lower, self.delta ) 
        dW  = sum(dW) / Nt 
        if not self.__nobias : 
            # Calculate bias gradient 
            ones    = np.ones(( 1, Ns )) 
            db      = map( lambda d: np.dot( ones, d ) / Ns, self.delta ) 
            db      = sum(db) / Nt 
            # Return weight gradient and bias gradient 
            return [ dW, db ] 
        else : 
            # return a weight gradient 
            return [ dW ] 
    # Returns a time-series history of total inputs 
    def U( self ) : 
        return self.__U 
    # Initialize time series history 
    def Clear( self ) : 
        self.__U        = [] 
        self.__Z_lower  = [] 
        self.delta      = [] 

Linear 类的主要功能包括:
| 功能 | 方法 | 描述 |
| ---- | ---- | ---- |
| 初始化 | __init__ | 初始化线性层,设置输入和输出单元数、激活函数、偏置等 |
| 创建参数 | CreateParams | 创建线性层的权重和偏置 |
| 获取参数 | params | 返回线性层的参数(权重和偏置) |
| 前向传播 | forward | 执行线性层的前向传播,计算输出值 |
| 反向传播 | backward | 执行线性层的反向传播,计算梯度 |
| 获取梯度 | grads | 返回线性层的梯度(权重和偏置梯度) |
| 获取总输入历史 | U | 返回线性层的总输入时间序列历史 |
| 清空历史 | Clear | 清空线性层的时间序列历史记录 |

通过 Linear 类,可以方便地实现神经网络中的线性变换,并进行前向传播和反向传播计算。

3.3.3 function.py

该文件定义了激活函数、误差函数及其导数类,以下是关键代码及解析:

# -*- coding: utf-8 -*-
import inspect 
import sys 
import numpy as np 

class GetFunction( object ) : 
    def __init__( self, function_name ) : 
        self.name = function_name 
    def Get( self ) : 
        for name,value in inspect.getmembers( self, inspect.ismethod ) : 
            if name == self.name : 
                return value 
        raise AttributeError( "%s doesnot exist. select from %s" %( self.name, 
                                                                   inspect.getmembers( self, inspect.ismethod ) ) ) 
        exit() 

# activation function 
class Activation( GetFunction ) : 
    def __init__( self, function_name ) : 
        super( Activation, self ).__init__( function_name ) 
    # Returns the differentiated value 
    def Differentiate( self ) : 
        return Differential( self.name ) 
    # Identity mapping 
    def identity( self, inputs ) : 
        return inputs 
    # Sigmoid function 
    def sigmoid( self, inputs ) : 
        ret = 1.0 / ( 1.0+np.exp(-inputs) ) 
        return ret 
    # tanh 
    def tanh( self, inputs ) : 
        return np.tanh( inputs ) 
    # relu function 
    def relu( self, inputs ) : 
        return np.where( inputs<0, 0, inputs ) 

# error function 
class Error( GetFunction ) : 
    def __init__( self, function_name ) : 
        super( Error, self ).__init__( function_name ) 
    # Returns the differentiated value 
    def Differentiate( self ) : 
        return Differential( self.name ) 
    # squared error 
    def squared_error( self, inputs, targets ) : 
        ret = np.sum( (inputs - targets)**2 ) 
        return ret 
    # mean squared error 
    def mean_squared_error( self, inputs, targets ) : 
        ret = np.average( (inputs - targets)**2 ) 
        return ret 

# Returns a differential value 
class Differential( GetFunction ) : 
    def __init__( self, function_name ) : 
        super( Differential, self ).__init__( function_name ) 
    # Returns the derivative of the identity map 
    def identity( self, inputs ) : 
        ret = np.ones(inputs.shape) 
        return ret 
    # Returns the derivative of the sigmoid function 
    def sigmoid( self, inputs ) : 
        f   = Activation( "sigmoid" ).Get()( inputs ) 
        ret = f * ( 1-f ) 
        return ret 
    # Returns the derivative of tanh 
    def tanh( self, inputs ) : 
        f   = Activation( "tanh" ).Get()( inputs ) 
        ret = 1 - f * f 
        return ret 
    # Returns the derivative of the relu function 
    def relu( self, inputs ) : 
        return np.where( inputs<0, 0, 1 ) 
    # Returns the derivative of the squared error 
    def squared_error( self, inputs, targets ) : 
        ret = 2 * ( inputs - targets ) 
        return ret 
    # Returns the derivative of the mean squared error 
    def mean_squared_error( self, inputs, targets ) : 
        ret = ( 2 / inputs.size ) * ( inputs - targets ) 
        return ret 

该文件定义了三个主要类: GetFunction Activation Error ,以及一个辅助类 Differential
- GetFunction 类是一个基类,用于根据函数名获取相应的函数。
- Activation 类继承自 GetFunction 类,提供了常见的激活函数,如恒等映射、Sigmoid、tanh和ReLU,并可以获取其导数。
- Error 类继承自 GetFunction 类,提供了常见的误差函数,如平方误差和均方误差,并可以获取其导数。
- Differential 类继承自 GetFunction 类,用于获取激活函数和误差函数的导数。

以下是这些类的主要功能总结:
| 类 | 功能 | 方法 | 描述 |
| ---- | ---- | ---- | ---- |
| GetFunction | 函数获取 | Get | 根据函数名获取相应的函数 |
| Activation | 激活函数 | identity sigmoid tanh relu | 实现常见的激活函数 |
| | 导数获取 | Differentiate | 获取激活函数的导数 |
| Error | 误差函数 | squared_error mean_squared_error | 实现常见的误差函数 |
| | 导数获取 | Differentiate | 获取误差函数的导数 |
| Differential | 导数计算 | identity sigmoid tanh relu squared_error mean_squared_error | 计算激活函数和误差函数的导数 |

通过这些类,可以方便地使用和管理激活函数、误差函数及其导数,为神经网络的训练提供支持。

3.4 优化器相关文件

optimizers.py
该文件定义了优化器类,用于更新神经网络的参数,以下是关键代码及解析:

import sys 
import numpy as np 

class SetModel( object ) : 
    def __init__( self ) : 
        self.model  = None 
    def setup( self, model ) : 
        self.model  = model 

class SGD( SetModel ) : 
    def __init__( self, epsilon=0.01 ) : 
        self.epsilon    = epsilon 
    def update( self ) : 
        for name in self.model.linknames : 
            link = self.model.__dict__[name] 
            for param, grad in zip( link.params(), link.grads() ) : 
                param[:] = param - self.epsilon * grad 

class Adam( SetModel ) : 
    def __init__( self, alpha=0.001, beta1=0.9, beta2=0.999, epsilon=10**(-8) ) : 
        self.alpha      = alpha 
        self.beta1      = beta1 
        self.beta2      = beta2 
        self.epsilon    = epsilon 
        self.t          = 0 
        self.save_moments = [] 
    def update( self ) : 
        self.t += 1 
        if self.t == 1 : 
            for i in range( len(self.model.params()) ) : 
                self.save_moments.append( ApplyAdam() ) 
        for i, (param, grad) in enumerate( zip(self.model.params(), self.model.grads()) ) : 
            param[:] = self.save_moments[i]( param, grad ) 

class ApplyAdam( object ) : 
    def __init__( self, alpha=Adam().alpha, beta1=Adam().beta1, beta2=Adam().beta2, 
                  epsilon=Adam().epsilon ) : 
        self.alpha      = alpha 
        self.beta1      = beta1 
        self.beta2      = beta2 
        self.epsilon    = epsilon 
        self.t          = 0 
        self.m          = 0 
        self.v          = 0 
    def __call__( self, param, grad ) : 
        self.t += 1 
        self.m  = self.beta1 * self.m + ( 1-self.beta1 ) * grad 
        self.v  = self.beta2 * self.v + ( 1-self.beta2 ) * grad**2 
        hat_m   = self.m / ( 1-self.beta1**self.t ) 
        hat_v   = self.v / ( 1-self.beta2**self.t ) 
        param   = param - self.alpha * hat_m / ( hat_v**0.5+self.epsilon ) 
        return param 

该文件定义了三个主要类: SetModel SGD Adam ,以及一个辅助类 ApplyAdam
- SetModel 类是一个基类,用于设置优化器所作用的模型。
- SGD 类继承自 SetModel 类,实现了随机梯度下降(SGD)优化算法。
- Adam 类继承自 SetModel 类,实现了Adam优化算法。
- ApplyAdam 类是一个辅助类,用于实现Adam优化算法的具体更新步骤。

以下是这些类的主要功能总结:
| 类 | 功能 | 方法 | 描述 |
| ---- | ---- | ---- | ---- |
| SetModel | 模型设置 | setup | 设置优化器所作用的模型 |
| SGD | 优化更新 | update | 使用随机梯度下降算法更新模型参数 |
| Adam | 优化更新 | update | 使用Adam优化算法更新模型参数 |
| ApplyAdam | 参数更新 | __call__ | 实现Adam优化算法的具体参数更新步骤 |

通过这些类,可以方便地选择和使用不同的优化器来更新神经网络的参数,提高训练效率和性能。

3.5 配置文件相关文件

config.py
该文件定义了配置文件读取和处理类,用于读取和解析XML配置文件,以下是关键代码及解析:

import os 
import xml.etree.ElementTree as ET 
import copy 
from datetime import datetime,timedelta 

class CConfig : 
    def __init__( self,ifp,target=None ) : 
        self.ifp  = ifp 
        self.root = None 
        self.__read(target) 
    def __read(self,target=None): 
        self.root = ET.parse(self.ifp).getroot() 
        if not target is None : 
            text    = self.root.find("target").text 
            if not text is None and text != target : 
                print "!!!!!     <target> not match : source->",target,"config->",text,"     !!!!!" 
                return self 
    def get(self) : 
        return CElement( self.root ) 
    @staticmethod 
    def create(key,attr={}) : 
        elm = ET.Element(key) 
        for key in attr : elm.set(key,attr[key]) 
        return CElement( elm ) 

class CElement : 
    def __init__(self,elm) : 
        self.__element  = elm 
        self.__list     = list(elm) 
        self.__tags     = [] 
        for e in self.__list : self.__tags.append( e.tag ) 
    def __iadd__(self,elm) : 
        if not isinstance(elm,CElement) : 
            print "elm is not CElement" 
            return self 
        for e in elm.list() : 
            if self.__element.find(e.tag) is None : 
                self.__element.append( e ) 
            else : 
                self.__itrappend( e, self.__element.findall(e.tag) ) 
        self.__update() 
        return self 
    def __itrappend(self,e_from,e_tos) : 
        for e_to in e_tos : 
            for ef_child in list(e_from) : 
                if e_to.find(ef_child.tag) is not None : 
                    self.__itrappend( ef_child, e_to.findall(ef_child.tag) ) 
                else : 
                    e_to.append(ef_child)

### MLP深度学习算法示例源代码解析(续)

#### 4. 配置文件示例

`MLP_config.xml`
该文件是一个XML配置文件,用于配置MLP深度学习算法的参数,以下是具体内容:

```xml
<?xml version="1.0"?> 
<root> 
    <target>execute_test</target> 
    <inputFilePath form="path">./sm190905_extract_for_exp.csv</inputFilePath> 
    <outputDirectoryRoot form="path">.</outputDirectoryRoot> 
    <inputVariables form="sarray">P_L W5 TO Rs Cth T_SA PLp1 PLp2 PLp3 TSAp1 TSAp2 TSAp3</inputVariables> 
    <outputVariables form="sarray">W15n TSA15n</outputVariables> 
    <hiddenUnits form="int">10</hiddenUnits> 
    <seriesLength form="int">1</seriesLength> 
    <epoch form="int">100</epoch> 
    <ignoreHours form="tarray">12:00:00 13:00:00</ignoreHours> 
    <randomSeed form="int">12345</randomSeed> 
    <trainingDates form="darray">20180801 20180802 20180806 20180807 20180808 20180809 20180810 20180816 20180817 20180820 20180821 20180823 20180824 20180828 20180829 20180830 20180831</trainingDates> 
    <testDates form="darray">20180803 20180822</testDates> 
    <max form="farray">16.00 1.33 40.50 40.00 48.00 8.50 16.00 16.00 16.00 8.50 8.50 8.50 3.99 8.50</max> 
    <min form="farray">0.00 0.00 22.00 0.00 0.00 -3.00 0.00 0.00 0.00 -3.00 -3.00 -3.00 0.00 -3.00</min> 
</root>

以下是该配置文件的参数说明:
| 参数 | 描述 |
| ---- | ---- |
| <target> | 执行目标,用于验证配置文件是否匹配 |
| <inputFilePath> | 输入数据文件的路径 |
| <outputDirectoryRoot> | 输出目录的根路径 |
| <inputVariables> | 输入变量列表 |
| <outputVariables> | 输出变量列表 |
| <hiddenUnits> | 隐藏层单元数 |
| <seriesLength> | 时间序列长度 |
| <epoch> | 训练轮数 |
| <ignoreHours> | 忽略的时间段 |
| <randomSeed> | 随机种子 |
| <trainingDates> | 训练日期列表 |
| <testDates> | 测试日期列表 |
| <max> | 输入和输出变量的最大值列表 |
| <min> | 输入和输出变量的最小值列表 |

5. 代码使用步骤

以下是使用上述代码实现MLP深度学习算法的详细步骤:

  1. 准备配置文件 :创建一个XML配置文件,如 MLP_config.xml ,并根据需要修改配置参数。
  2. 准备输入数据 :准备一个CSV文件,包含时间序列数据,并确保文件路径与配置文件中的 <inputFilePath> 一致。
  3. 运行主程序 :在命令行中运行 MLP_experiment.py 文件,并将配置文件作为参数传入,例如:
python MLP_experiment.py MLP_config.xml
  1. 查看输出结果 :程序运行完成后,会在输出目录中生成多个CSV文件,包括训练集和测试集的日志文件、损失值文件、预测结果文件等。可以查看这些文件来评估模型的性能。

以下是整个流程的mermaid流程图:

graph LR;
    A[准备配置文件] --> B[准备输入数据];
    B --> C[运行主程序];
    C --> D[查看输出结果];
6. 关键技术点分析
6.1 最小 - 最大归一化

在数据处理阶段,使用了最小 - 最大归一化方法对时间序列数据进行预处理。最小 - 最大归一化的公式如下:
[
x_{norm} = \frac{x - x_{min}}{x_{max} - x_{min}}
]
其中,(x) 是原始数据,(x_{min}) 和 (x_{max}) 分别是数据的最小值和最大值,(x_{norm}) 是归一化后的数据。

最小 - 最大归一化的优点是可以将数据缩放到 [0, 1] 区间,有助于提高神经网络的训练效率和稳定性。在代码中, MyLSTMTimeSeries 类的 MaxMinScaling 方法实现了最小 - 最大归一化:

def MaxMinScaling(self,max,min) : 
    max_v   = np.array( max[ self.__IN_AREA[0]:self.__IN_AREA[1] ] ) 
    min_v   = np.array( min[ self.__IN_AREA[0]:self.__IN_AREA[1] ] ) 
    max_a   = np.array( max[ self.__OUT_AREA[0]:self.__OUT_AREA[1] ] ) 
    min_a   = np.array( min[ self.__OUT_AREA[0]:self.__OUT_AREA[1] ] ) 
    v_den   = np.array( [(x-n) if x!=n else x for (x,n) in zip(max_v,min_v)] ) 
    a_den   = np.array( [(x-n) if x!=n else x for (x,n) in zip(max_a,min_a)] ) 
    series,answer  = super(MyLSTMTimeSeries,self).Get() 
    for t in range(len( series )) : 
        series[t]    = list( (np.array(series[t])-min_v)/v_den ) 
        answer[t]    = list( (np.array(answer[t])-min_a)/a_den ) 
    return True
6.2 反向传播算法

在MLP训练过程中,使用了反向传播算法来计算误差并更新神经网络的参数。反向传播算法的核心思想是通过链式法则,从输出层开始,逐步向后计算每个神经元的误差梯度,然后根据梯度更新权重和偏置。

在代码中, Chain 类的 backward 方法实现了反向传播算法:

def backward( self, W_upper=None, delta_upper=None, value=None ) : 
    if self.__error_func is not None : 
        ERROR_DIFF  = self.__error_func.Differentiate().Get() 
        value       = map( lambda y,targets: ERROR_DIFF( y, targets ), self.__y,self.__targets ) 
    for name in self.linknames[::-1] : 
        if W_upper is None and delta_upper is None : 
            W_upper, delta_upper = self.__dict__[name].backward( W_upper, delta_upper, value ) 
        else : 
            W_upper, delta_upper = self.__dict__[name].backward( W_upper, delta_upper ) 
    return W_upper, delta_upper
6.3 优化算法

在代码中,使用了Adam优化算法来更新神经网络的参数。Adam优化算法结合了动量法和自适应学习率的思想,能够自适应地调整每个参数的学习率,从而提高训练效率和性能。

Adam优化算法的更新公式如下:
[
m_t = \beta_1 m_{t-1} + (1 - \beta_1) g_t
]
[
v_t = \beta_2 v_{t-1} + (1 - \beta_2) g_t^2
]
[
\hat{m} t = \frac{m_t}{1 - \beta_1^t}
]
[
\hat{v}_t = \frac{v_t}{1 - \beta_2^t}
]
[
\theta_t = \theta
{t-1} - \alpha \frac{\hat{m}_t}{\sqrt{\hat{v}_t} + \epsilon}
]
其中,(m_t) 和 (v_t) 分别是一阶矩估计和二阶矩估计,(\hat{m}_t) 和 (\hat{v}_t) 是修正后的一阶矩估计和二阶矩估计,(\theta_t) 是参数,(g_t) 是梯度,(\alpha) 是学习率,(\beta_1) 和 (\beta_2) 是衰减率,(\epsilon) 是一个小的常数,用于避免分母为零。

在代码中, Adam 类和 ApplyAdam 类实现了Adam优化算法:

class Adam( SetModel ) : 
    def __init__( self, alpha=0.001, beta1=0.9, beta2=0.999, epsilon=10**(-8) ) : 
        self.alpha      = alpha 
        self.beta1      = beta1 
        self.beta2      = beta2 
        self.epsilon    = epsilon 
        self.t          = 0 
        self.save_moments = [] 
    def update( self ) : 
        self.t += 1 
        if self.t == 1 : 
            for i in range( len(self.model.params()) ) : 
                self.save_moments.append( ApplyAdam() ) 
        for i, (param, grad) in enumerate( zip(self.model.params(), self.model.grads()) ) : 
            param[:] = self.save_moments[i]( param, grad ) 

class ApplyAdam( object ) : 
    def __init__( self, alpha=Adam().alpha, beta1=Adam().beta1, beta2=Adam().beta2, 
                  epsilon=Adam().epsilon ) : 
        self.alpha      = alpha 
        self.beta1      = beta1 
        self.beta2      = beta2 
        self.epsilon    = epsilon 
        self.t          = 0 
        self.m          = 0 
        self.v          = 0 
    def __call__( self, param, grad ) : 
        self.t += 1 
        self.m  = self.beta1 * self.m + ( 1-self.beta1 ) * grad 
        self.v  = self.beta2 * self.v + ( 1-self.beta2 ) * grad**2 
        hat_m   = self.m / ( 1-self.beta1**self.t ) 
        hat_v   = self.v / ( 1-self.beta2**self.t ) 
        param   = param - self.alpha * hat_m / ( hat_v**0.5+self.epsilon ) 
        return param 
7. 总结

本文详细介绍了一个MLP深度学习算法的示例源代码,包括代码结构、功能实现以及关键技术点的分析。通过阅读本文,你可以了解到如何使用Python实现一个简单的MLP模型,并进行数据处理、模型训练和评估。同时,还介绍了最小 - 最大归一化、反向传播算法和Adam优化算法等关键技术点,这些技术点是深度学习中常用的方法,对于提高模型的性能和训练效率非常重要。

希望本文对你理解和使用MLP深度学习算法有所帮助。如果你有任何问题或建议,可以在评论区留言。

Logo

码道开发者社区,聚焦华为云码道 CodeArts 代码智能体,沉淀 Agent、Skill、鸿蒙开发实战内容,供开发者查阅资料、交流技术、分享工程实践

更多推荐