22、MLP深度学习算法示例源代码解析
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深度学习算法的详细步骤:
-
准备配置文件
:创建一个XML配置文件,如
MLP_config.xml,并根据需要修改配置参数。 -
准备输入数据
:准备一个CSV文件,包含时间序列数据,并确保文件路径与配置文件中的
<inputFilePath>一致。 -
运行主程序
:在命令行中运行
MLP_experiment.py文件,并将配置文件作为参数传入,例如:
python MLP_experiment.py MLP_config.xml
- 查看输出结果 :程序运行完成后,会在输出目录中生成多个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深度学习算法有所帮助。如果你有任何问题或建议,可以在评论区留言。
更多推荐



所有评论(0)