用 Python 从零开始创建神经网络(十八):模型对象(Model Object)
模型对象(Model Object)
- 引言
- 到目前为止的完整代码:
引言
我们构建了一个可以执行前向传播、反向传播以及精度测量等辅助任务的模型。通过编写相当多的代码并在一些较大的代码块中进行修改,我们实现了这些功能。此时,将模型本身转化为一个对象的做法开始显得更有意义,特别是当我们希望保存和加载这个对象以用于未来的预测任务时。此外,我们还可以利用这个对象减少一些常见代码行,使得与当前代码库的协作更加便捷,同时也更容易构建新的模型。为了完成模型对象的转换,我们将使用我们最近工作的模型,即使用正弦数据的回归模型:
from nnfs.datasets import sine_dataX, y = sine_data()
有了数据之后,我们制作模型类的第一步就是添加我们想要的各层。因此,我们可以通过以下操作来开始我们的模型类:
# Model class
class Model:def __init__(self):# Create a list of network objectsself.layers = []# Add objects to the modeldef add(self, layer):self.layers.append(layer)
这样,我们就可以使用模型对象的添加方法来添加图层。仅这一点就能大大提高可读性。让我们添加一些图层:
# Instantiate the model
model = Model()# Add layers
model.add(Layer_Dense(1, 64))
model.add(Activation_ReLU())
model.add(Layer_Dense(64, 64))
model.add(Activation_ReLU())
model.add(Layer_Dense(64, 1))
model.add(Activation_Linear())
我们现在也可以查询这个模型:
print(model.layers)
>>>
[<__main__.Layer_Dense object at 0x000001D1EB2A2900>,
<__main__.Activation_ReLU object at 0x000001D1EB2A2180>,
<__main__.Layer_Dense object at 0x000001D1EB2A3F20>,
<__main__.Activation_ReLU object at 0x000001D1EB2B9220>,
<__main__.Layer_Dense object at 0x000001D1EB2BB800>,
<__main__.Activation_Linear object at 0x000001D1EB2BBA40>]
除了添加层,我们还想为模型设置损失函数和优化器。为此,我们将创建一个名为 set 的方法:
# Set loss and optimizer
def set(self, *, loss, optimizer):self.loss = lossself.optimizer = optimizer
在参数定义中使用星号(*)表示后续的参数(在本例中是loss
和optimizer
)为关键字参数。由于这些参数没有默认值,因此它们是必需的关键字参数,也就是说必须通过名称和值的形式传递,从而使代码更加易读。
现在,我们可以将一个调用此方法的语句添加到我们新创建的模型对象中,并传递loss和optimizer对象:
# Create dataset
X, y = sine_data()# Instantiate the model
model = Model()# Add layers
model.add(Layer_Dense(1, 64))
model.add(Activation_ReLU())
model.add(Layer_Dense(64, 64))
model.add(Activation_ReLU())
model.add(Layer_Dense(64, 1))
model.add(Activation_Linear())# Set loss and optimizer objects
model.set(loss=Loss_MeanSquaredError(),optimizer=Optimizer_Adam(learning_rate=0.005, decay=1e-3),)
设置好模型的层、损失函数和优化器后,下一步就是训练了,因此我们要添加一个 train
方法。现在,我们先将其作为一个占位符,不久后再进行填充:
# Train the model
def train(self, X, y, *, epochs=1, print_every=1):# Main training loopfor epoch in range(1, epochs+1):# Temporarypass
然后,我们可以在模型定义中添加对 train 方法的调用。我们将传递训练数据、epochs 的数量(10000,我们目前使用的是),以及打印训练摘要的频率。我们不需要或不希望每一步都打印,因此我们将对其进行配置:
# Create dataset
X, y = sine_data()# Instantiate the model
model = Model()# Add layers
model.add(Layer_Dense(1, 64))
model.add(Activation_ReLU())
model.add(Layer_Dense(64, 64))
model.add(Activation_ReLU())
model.add(Layer_Dense(64, 1))
model.add(Activation_Linear())# Set loss and optimizer objects
model.set(loss=Loss_MeanSquaredError(),optimizer=Optimizer_Adam(learning_rate=0.005, decay=1e-3),)model.train(X, y, epochs=10000, print_every=100)
要进行训练,我们需要执行前向传播。在对象中执行前向传播稍微复杂一些,因为我们需要在层的循环中完成此操作,并且需要知道前一层的输出以正确地传递数据。查询前一层的一个问题是,第一层没有“前一层”。我们定义的第一层是第一隐含层。因此,我们的一个选择是创建一个“输入层”。这被认为是神经网络中的一层,但没有与之相关的权重和偏置。输入层仅包含训练数据,我们仅在循环迭代层时将其用作第一层的“前一层”。我们将创建一个新类,并像调用Layer_Dense
类一样调用它,称为Layer_Input
:
# Input "layer"
class Layer_Input:# Forward passdef forward(self, inputs):self.output = inputs
forward
方法将训练样本设置为self.output
。这一属性与其他层是通用的。这里没有必要实现反向传播方法,因为我们永远不会用到它。现在可能看起来创建这个类有点多余,但希望很快你就会明白我们将如何使用它。接下来,我们要为模型的每一层设置前一层和后一层的属性。我们将在Model
类中创建一个名为finalize
的方法:
# Finalize the modeldef finalize(self):# Create and set the input layerself.input_layer = Layer_Input()# Count all the objectslayer_count = len(self.layers)# Iterate the objectsfor i in range(layer_count):# If it's the first layer,# the previous layer object is the input layerif i == 0:self.layers[i].prev = self.input_layerself.layers[i].next = self.layers[i+1]# All layers except for the first and the lastelif i < layer_count - 1:self.layers[i].prev = self.layers[i-1]self.layers[i].next = self.layers[i+1]# The last layer - the next object is the losselse:self.layers[i].prev = self.layers[i-1]self.layers[i].next = self.loss
这段代码创建了一个输入层,并为模型对象的self.layers
列表中的每一层设置了next
和prev
引用。我们创建了Layer_Input
类,以便在循环中为第一隐藏层设置prev
属性,因为我们将以统一的方式调用所有层。对于最后一层,其next
层将是我们已经创建的损失函数。
现在,我们已经为模型对象执行前向传播所需的层信息准备就绪,让我们添加一个forward
方法。我们将同时在训练时和之后仅进行预测(也称为模型推理)时使用这个forward
方法。以下是在Model
类中继续添加的代码:
# Forward pass
class Model:...# Performs forward passdef forward(self, X):# Call forward method on the input layer# this will set the output property that# the first layer in "prev" object is expectingself.input_layer.forward(X)# Call forward method of every object in a chain# Pass output of the previous object as a parameterfor layer in self.layers:layer.forward(layer.prev.output)# "layer" is now the last object from the list,# return its outputreturn layer.output
在这种情况下,我们传入输入数据 X X X,然后简单地通过 Model
对象中的 input_layer
处理该数据,这会在该对象中创建一个 output
属性。从这里开始,我们迭代 self.layers
中的层,这些层从第一个隐藏层开始。对于每一层,我们对上一层的输出数据 layer.prev.output
执行前向传播。对于第一个隐藏层,layer.prev
是 self.input_layer
。调用每一层的 forward
方法时会创建该层的 output
属性,然后该属性会作为输入传递到下一层的 forward
方法调用中。一旦我们遍历了所有层,就会返回最后一层的输出。
这就是一次前向传播。现在,让我们将这个前向传播方法调用添加到 Model
类的 train
方法中:
# Forward pass
class Model:...# Train the modeldef train(self, X, y, *, epochs=1, print_every=1):# Main training loopfor epoch in range(1, epochs+1):# Perform the forward passoutput = self.forward(X)# Temporaryprint(output)sys.exit()
到目前为止的完整Model
类:
# Model class
class Model:def __init__(self):# Create a list of network objectsself.layers = []# Add objects to the modeldef add(self, layer):self.layers.append(layer)# Set loss and optimizerdef set(self, *, loss, optimizer):self.loss = lossself.optimizer = optimizer# Train the modeldef train(self, X, y, *, epochs=1, print_every=1):# Main training loopfor epoch in range(1, epochs+1):# Perform the forward passoutput = self.forward(X)# Temporaryprint(output)sys.exit()# Finalize the modeldef finalize(self):# Create and set the input layerself.input_layer = Layer_Input()# Count all the objectslayer_count = len(self.layers)# Iterate the objectsfor i in range(layer_count):# If it's the first layer,# the previous layer object is the input layerif i == 0:self.layers[i].prev = self.input_layerself.layers[i].next = self.layers[i+1]# All layers except for the first and the lastelif i < layer_count - 1:self.layers[i].prev = self.layers[i-1]self.layers[i].next = self.layers[i+1]# The last layer - the next object is the losselse:self.layers[i].prev = self.layers[i-1]self.layers[i].next = self.loss# Performs forward passdef forward(self, X):# Call forward method on the input layer# this will set the output property that# the first layer in "prev" object is expectingself.input_layer.forward(X)# Call forward method of every object in a chain# Pass output of the previous object as a parameterfor layer in self.layers:layer.forward(layer.prev.output)# "layer" is now the last object from the list,# return its outputreturn layer.output
最后,我们可以在主代码中添加 finalize
方法调用(请记住,除其他事项外,该方法还能让模型的图层知道它们的上一层和下一层)。
# Create dataset
X, y = sine_data()# Instantiate the model
model = Model()# Add layers
model.add(Layer_Dense(1, 64))
model.add(Activation_ReLU())
model.add(Layer_Dense(64, 64))
model.add(Activation_ReLU())
model.add(Layer_Dense(64, 1))
model.add(Activation_Linear())# Set loss and optimizer objects
model.set(loss=Loss_MeanSquaredError(),optimizer=Optimizer_Adam(learning_rate=0.005, decay=1e-3),)# Finalize the model
model.finalize()model.train(X, y, epochs=10000, print_every=100)
>>>
[[ 0.00000000e+00]
[-1.13209149e-08]
[-2.26418297e-08]
...
[-1.12869511e-05]
[-1.12982725e-05]
[-1.13095930e-05]]
此时,我们已经在Model
类中覆盖了模型的前向传播。我们仍需要计算损失和准确率,并进行反向传播。在此之前,我们需要知道哪些层是“可训练的”,也就是说这些层具有我们可以调整的权重和偏置。为此,我们需要检查层是否有weights
或biases
属性。我们可以通过以下代码进行检查:
# 如果层包含一个名为“weights”的属性, # 那么它是一个可训练层 - # 将其添加到可训练层列表中 # 我们不需要检查偏置 - # 检查权重已经足够了 if hasattr(self.layers[i], 'weights'):self.trainable_layers.append(self.layers[i])
其中, i i i 是层列表中某一层的索引。我们将把这段代码添加到 finalize
方法中。以下是目前该方法的完整代码:
# Finalize the modeldef finalize(self):# Create and set the input layerself.input_layer = Layer_Input()# Count all the objectslayer_count = len(self.layers)# Initialize a list containing trainable layers:self.trainable_layers = []# Iterate the objectsfor i in range(layer_count):# If it's the first layer,# the previous layer object is the input layerif i == 0:self.layers[i].prev = self.input_layerself.layers[i].next = self.layers[i+1]# All layers except for the first and the lastelif i < layer_count - 1:self.layers[i].prev = self.layers[i-1]self.layers[i].next = self.layers[i+1]# The last layer - the next object is the loss# Also let's save aside the reference to the last object# whose output is the model's outputelse:self.layers[i].prev = self.layers[i-1]self.layers[i].next = self.lossself.output_layer_activation = self.layers[i]# 如果层包含一个名为“weights”的属性, # 那么它是一个可训练层 - # 将其添加到可训练层列表中 # 我们不需要检查偏置 - # 检查权重已经足够了 if hasattr(self.layers[i], 'weights'):self.trainable_layers.append(self.layers[i])
接下来,我们将修改普通 Loss
类,使其包含以下内容:
# Common loss class
class Loss:... # Calculates the data and regularization losses# given model output and ground truth valuesdef calculate(self, output, y):# Calculate sample lossessample_losses = self.forward(output, y)# Calculate mean lossdata_loss = np.mean(sample_losses)# Return the data and regularization lossesreturn data_loss, self.regularization_loss() # Set/remember trainable layersdef remember_trainable_layers(self, trainable_layers):self.trainable_layers = trainable_layers
commonLoss
类中的 remember_trainable_layers
方法“告知”损失对象哪些是 Model
对象中的可训练层。在单次调用期间,calculate
方法已被修改为还会返回 self.regularization_loss()
的值。regularization_loss
方法目前需要一个层对象,但随着在 remember_trainable_layers
方法中设置了 self.trainable_layers
属性,我们现在可以迭代所有可训练层,以计算整个模型的正则化损失,而不是每次仅针对一个层进行计算:
# Common loss class
class Loss:...# Regularization loss calculationdef regularization_loss(self): # 0 by defaultregularization_loss = 0# Calculate regularization loss# iterate all trainable layersfor layer in self.trainable_layers:# L1 regularization - weights# calculate only when factor greater than 0if layer.weight_regularizer_l1 > 0:regularization_loss += layer.weight_regularizer_l1 * np.sum(np.abs(layer.weights))# L2 regularization - weightsif layer.weight_regularizer_l2 > 0:regularization_loss += layer.weight_regularizer_l2 * np.sum(layer.weights * layer.weights)# L1 regularization - biases# calculate only when factor greater than 0if layer.bias_regularizer_l1 > 0:regularization_loss += layer.bias_regularizer_l1 * np.sum(np.abs(layer.biases))# L2 regularization - biasesif layer.bias_regularizer_l2 > 0:regularization_loss += layer.bias_regularizer_l2 * np.sum(layer.biases * layer.biases)return regularization_loss
为了计算准确率,我们需要预测结果。目前,根据模型的类型,预测需要不同的代码。例如,对于 softmax
分类器,我们使用 np.argmax()
,但对于回归,由于输出层使用线性激活函数,预测结果直接为输出值。理想情况下,我们需要一个预测方法,该方法能够为我们的模型选择合适的预测方式。为此,我们将在每个激活函数类中添加一个 predictions
方法:
# Softmax activation
class Activation_Softmax:... # Calculate predictions for outputsdef predictions(self, outputs):return np.argmax(outputs, axis=1)
# Sigmoid activation
class Activation_Sigmoid:...# Calculate predictions for outputsdef predictions(self, outputs):return (outputs > 0.5) * 1
# Linear activation
class Activation_Linear:...# Calculate predictions for outputsdef predictions(self, outputs):return outputs
在 predictions
函数内部进行的所有计算与之前章节中针对适当模型所执行的计算相同。尽管我们没有计划将 ReLU 激活函数用于输出层的激活函数,但我们为了完整性仍会在此处包含它:
# ReLU activation
class Activation_ReLU: ...# Calculate predictions for outputsdef predictions(self, outputs):return outputs
我们仍然需要在 Model
对象中为最终层的激活函数设置一个引用。之后我们可以调用 predictions
方法,该方法将根据输出计算并返回预测值。我们将在 Model
类的 finalize
方法中设置这一引用。
# Model class
class Model:...# Finalize the modeldef finalize(self):...# The last layer - the next object is the loss# Also let's save aside the reference to the last object# whose output is the model's outputelse:self.layers[i].prev = self.layers[i-1]self.layers[i].next = self.lossself.output_layer_activation = self.layers[i]
就像不同的预测方法一样,我们也需要以不同的方式计算准确率。我们将以类似于特定损失类对象实现的方式来实现这一功能——创建特定的准确率类及其对象,并将它们与模型关联。
首先,我们会编写一个通用的 Accuracy
类,该类目前只包含一个方法 calculate
,用于返回根据比较结果计算的准确率。我们已经在代码中添加了对 self.compare
方法的调用,但这个方法目前还不存在。我们将在继承自 Accuracy
类的其他类中创建该方法。现在只需要知道这个方法会返回一个由 True
和 False
值组成的列表,指示预测是否与真实值匹配。接下来,我们计算这些值的平均值(True
被视为1,False
被视为0),并将其作为准确率返回。代码如下:
# Common accuracy class
class Accuracy:# Calculates an accuracy# given predictions and ground truth valuesdef calculate(self, predictions, y):# Get comparison resultscomparisons = self.compare(predictions, y)# Calculate an accuracyaccuracy = np.mean(comparisons)# Return accuracyreturn accuracy
接下来,我们可以使用这个通用的 Accuracy
类,通过继承它并进一步构建针对特定类型模型的功能。通常情况下,每个这些类都会包含两个方法:init
(不要与 Python 类的 __init__
方法混淆)用于从模型对象内部进行初始化,以及 compare
用于执行比较计算。
对于回归模型,init
方法将计算准确率的精度(与我们之前为回归模型编写并在训练循环之前运行的内容相同)。compare
方法将包含我们在训练循环中实际实现的比较代码,使用 self.precision
。需要注意的是,初始化时不会重新计算精度,除非通过将 reinit
参数设置为 True
强制重新计算。这种设计允许多种用例,包括独立设置 self.precision
、在需要时调用 init
(例如,在模型创建过程中从外部调用),甚至多次调用 init
(这将在后续某些情况下非常有用):
# Accuracy calculation for regression model
class Accuracy_Regression(Accuracy):def __init__(self):# Create precision propertyself.precision = None# Calculates precision value# based on passed in ground truthdef init(self, y, reinit=False):if self.precision is None or reinit:self.precision = np.std(y) / 250# Compares predictions to the ground truth valuesdef compare(self, predictions, y):return np.absolute(predictions - y) < self.precision
然后,我们可以通过在 Model
类的 set
方法中,以与当前设置损失函数和优化器相同的方式设置准确率对象。
# Model class
class Model:...# Set loss, optimizer and accuracydef set(self, *, loss, optimizer, accuracy):self.loss = lossself.optimizer = optimizerself.accuracy = accuracy
然后,我们可以在完成前向传播代码之后,将损失和准确率的计算添加到模型中。需要注意的是,我们还在 train
方法的开头通过 self.accuracy.init(y)
初始化准确率,并且可以多次调用,如之前提到的那样。在回归准确率的情况下,这将在第一次调用时进行一次精度计算。以下是实现了损失和准确率计算的 train
方法代码:
# Model class
class Model:...# Train the modeldef train(self, X, y, *, epochs=1, print_every=1):# Initialize accuracy objectself.accuracy.init(y)# Main training loopfor epoch in range(1, epochs+1):# Perform the forward passoutput = self.forward(X)# Calculate lossdata_loss, regularization_loss = self.loss.calculate(output, y)loss = data_loss + regularization_loss# Get predictions and calculate an accuracypredictions = self.output_layer_activation.predictions(output)accuracy = self.accuracy.calculate(predictions, y)
最后,我们将在 finalize
方法中通过调用先前创建的 remember_trainable_layers
方法并传入 Loss
类的对象来实现(self.loss.remember_trainable_layers(self.trainable_layers)
)。以下是目前为止的完整模型类代码:
# Model class
class Model:def __init__(self):# Create a list of network objectsself.layers = []# Add objects to the modeldef add(self, layer):self.layers.append(layer)# Set loss, optimizer and accuracydef set(self, *, loss, optimizer, accuracy):self.loss = lossself.optimizer = optimizerself.accuracy = accuracy# Finalize the modeldef finalize(self):# Create and set the input layerself.input_layer = Layer_Input()# Count all the objectslayer_count = len(self.layers)# Initialize a list containing trainable layers:self.trainable_layers = []# Iterate the objectsfor i in range(layer_count):# If it's the first layer,# the previous layer object is the input layerif i == 0:self.layers[i].prev = self.input_layerself.layers[i].next = self.layers[i+1]# All layers except for the first and the lastelif i < layer_count - 1:self.layers[i].prev = self.layers[i-1]self.layers[i].next = self.layers[i+1]# The last layer - the next object is the loss# Also let's save aside the reference to the last object# whose output is the model's outputelse:self.layers[i].prev = self.layers[i-1]self.layers[i].next = self.lossself.output_layer_activation = self.layers[i]# 如果层包含一个名为“weights”的属性, # 那么它是一个可训练层 - # 将其添加到可训练层列表中 # 我们不需要检查偏置 - # 检查权重已经足够了 if hasattr(self.layers[i], 'weights'):self.trainable_layers.append(self.layers[i])# Update loss object with trainable layersself.loss.remember_trainable_layers(self.trainable_layers)# Train the modeldef train(self, X, y, *, epochs=1, print_every=1):# Initialize accuracy objectself.accuracy.init(y)# Main training loopfor epoch in range(1, epochs+1):# Perform the forward passoutput = self.forward(X)# Calculate lossdata_loss, regularization_loss = self.loss.calculate(output, y)loss = data_loss + regularization_loss# Get predictions and calculate an accuracypredictions = self.output_layer_activation.predictions(output)accuracy = self.accuracy.calculate(predictions, y)# Performs forward passdef forward(self, X):# Call forward method on the input layer# this will set the output property that# the first layer in "prev" object is expectingself.input_layer.forward(X)# Call forward method of every object in a chain# Pass output of the previous object as a parameterfor layer in self.layers:layer.forward(layer.prev.output)# "layer" is now the last object from the list,# return its outputreturn layer.output
Loss
类的全部代码:
# Common loss class
class Loss:# Regularization loss calculationdef regularization_loss(self): # 0 by defaultregularization_loss = 0# Calculate regularization loss# iterate all trainable layersfor layer in self.trainable_layers:# L1 regularization - weights# calculate only when factor greater than 0if layer.weight_regularizer_l1 > 0:regularization_loss += layer.weight_regularizer_l1 * np.sum(np.abs(layer.weights))# L2 regularization - weightsif layer.weight_regularizer_l2 > 0:regularization_loss += layer.weight_regularizer_l2 * np.sum(layer.weights * layer.weights)# L1 regularization - biases# calculate only when factor greater than 0if layer.bias_regularizer_l1 > 0:regularization_loss += layer.bias_regularizer_l1 * np.sum(np.abs(layer.biases))# L2 regularization - biasesif layer.bias_regularizer_l2 > 0:regularization_loss += layer.bias_regularizer_l2 * np.sum(layer.biases * layer.biases)return regularization_loss# Set/remember trainable layersdef remember_trainable_layers(self, trainable_layers):self.trainable_layers = trainable_layers# Calculates the data and regularization losses# given model output and ground truth valuesdef calculate(self, output, y):# Calculate sample lossessample_losses = self.forward(output, y)# Calculate mean lossdata_loss = np.mean(sample_losses)# Return the data and regularization lossesreturn data_loss, self.regularization_loss()
现在我们已经完成了完整的前向传播并计算了损失和准确率,接下来可以开始反向传播。在 Model
类中的 backward
方法在结构上与 forward
方法类似,只是顺序相反并使用不同的参数。按照之前训练方法中的反向传播,我们需要调用损失对象的 backward
方法来创建 dinputs
属性。接着,我们将按照相反的顺序遍历所有层,调用它们的 backward
方法,并将下一层(正常顺序中的下一层)的 dinputs
属性作为参数传入,从而有效地反向传播由该下一层返回的梯度。请记住,我们已经将损失对象设置为最后一层(输出层)的下一层。
# Model class
class Model:...# Performs backward passdef backward(self, output, y):# First call backward method on the loss# this will set dinputs property that the last# layer will try to access shortlyself.loss.backward(output, y)# Call backward method going through all the objects# in reversed order passing dinputs as a parameterfor layer in reversed(self.layers):layer.backward(layer.next.dinputs)
接下来,我们将在 train
方法的末尾调用该 backward
方法:
# Perform backward passself.backward(output, y)
在完成反向传播之后,最后一个操作是进行优化。之前,我们针对每一个可训练的层多次调用优化器对象的 update_params
方法。现在,我们需要通过遍历可训练层的列表并在循环中调用 update_params()
方法,使这段代码更加通用:
# Optimize (update parameters)self.optimizer.pre_update_params()for layer in self.trainable_layers:self.optimizer.update_params(layer)self.optimizer.post_update_params()
然后我们可以输出有用的信息——此时,train
方法的最后一个参数就派上了用场:
# Print a summaryif not epoch % print_every:print(f'epoch: {epoch}, ' +f'acc: {accuracy:.3f}, ' +f'loss: {loss:.3f} (' +f'data_loss: {data_loss:.3f}, ' +f'reg_loss: {regularization_loss:.3f}), ' +f'lr: {self.optimizer.current_learning_rate}')
# Model class
class Model:...# Train the modeldef train(self, X, y, *, epochs=1, print_every=1):# Initialize accuracy objectself.accuracy.init(y)# Main training loopfor epoch in range(1, epochs+1):# Perform the forward passoutput = self.forward(X)# Calculate lossdata_loss, regularization_loss = self.loss.calculate(output, y)loss = data_loss + regularization_loss# Get predictions and calculate an accuracypredictions = self.output_layer_activation.predictions(output)accuracy = self.accuracy.calculate(predictions, y)# Perform backward passself.backward(output, y)# Optimize (update parameters)self.optimizer.pre_update_params()for layer in self.trainable_layers:self.optimizer.update_params(layer)self.optimizer.post_update_params()# Print a summaryif not epoch % print_every:print(f'epoch: {epoch}, ' +f'acc: {accuracy:.3f}, ' +f'loss: {loss:.3f} (' +f'data_loss: {data_loss:.3f}, ' +f'reg_loss: {regularization_loss:.3f}), ' +f'lr: {self.optimizer.current_learning_rate}')
现在,我们可以将精度类对象传入模型,并测试模型的性能:
>>>
epoch: 100, acc: 0.006, loss: 0.085 (data_loss: 0.085, reg_loss: 0.000), lr: 0.004549590536851684
epoch: 200, acc: 0.032, loss: 0.035 (data_loss: 0.035, reg_loss: 0.000), lr: 0.004170141784820684
...
epoch: 9900, acc: 0.934, loss: 0.000 (data_loss: 0.000, reg_loss: 0.000), lr: 0.00045875768419121016
epoch: 10000, acc: 0.970, loss: 0.000 (data_loss: 0.000, reg_loss: 0.000), lr: 0.00045458678061641964
我们的新模型表现良好,现在我们能够通过 Model
类更轻松地创建新模型。我们需要继续修改这些类,以支持全新的模型。例如,我们尚未处理二元逻辑回归。为此,我们需要添加两点内容。首先,我们需要计算分类准确率:
# Accuracy calculation for classification model
class Accuracy_Categorical(Accuracy):# No initialization is neededdef init(self, y):pass# Compares predictions to the ground truth valuesdef compare(self, predictions, y):if len(y.shape) == 2:y = np.argmax(y, axis=1)return predictions == y
这与分类的准确率计算相同,只是将其封装到一个类中,并增加了一个切换参数。当该类与二元交叉熵模型一起使用时,这个切换参数会禁用将独热编码转换为稀疏标签的操作,因为该模型始终需要真实值是一个二维数组,并且它们未进行独热编码。需要注意的是,这里并未执行任何初始化,但该方法需要存在,因为它将在 Model
类的 train
方法中调用。接下来,我们需要添加的是使用验证数据对模型进行验证的能力。验证只需要执行前向传播并计算损失(仅数据损失)。我们将修改 Loss
类的 calculate
方法,以使其也能够计算验证损失:
# Common loss class
class Loss:...# Calculates the data and regularization losses# given model output and ground truth valuesdef calculate(self, output, y, *, include_regularization=False):# Calculate sample lossessample_losses = self.forward(output, y)# Calculate mean lossdata_loss = np.mean(sample_losses)# If just data loss - return itif not include_regularization:return data_loss# Return the data and regularization lossesreturn data_loss, self.regularization_loss()
我们新增了一个参数和条件,以仅返回数据损失,因为在这种情况下不会使用正则化损失。为了运行它,我们将以与训练数据相同的方式传递预测值和目标值。默认情况下,我们不会返回正则化损失,这意味着我们需要更新 train
方法中对该方法的调用,以在训练期间包含正则化损失:
# Calculate lossdata_loss, regularization_loss = self.loss.calculate(output, y, include_regularization=True)
然后我们可以将验证代码添加到 Model
类中的 train
方法中。我们向函数添加了 validation_data
参数,该参数接受一个包含验证数据(样本和目标)的元组;添加了一个 if
语句检查是否存在验证数据;如果存在,则执行代码对这些数据进行前向传播,按照与训练期间相同的方式计算损失和准确率,并打印结果:
# Model class
class Model:...# Train the modeldef train(self, X, y, *, epochs=1, print_every=1, validation_data=None):...# If there is the validation dataif validation_data is not None:# For better readabilityX_val, y_val = validation_data# Perform the forward passoutput = self.forward(X_val)# Calculate the lossloss = self.loss.calculate(output, y_val)# Get predictions and calculate an accuracypredictions = self.output_layer_activation.predictions(output)accuracy = self.accuracy.calculate(predictions, y_val)# Print a summaryprint(f'validation, ' +f'acc: {accuracy:.3f}, ' +f'loss: {loss:.3f}')
现在我们可以通过以下代码创建测试数据并测试二元逻辑回归模型:
# Create train and test dataset
X, y = spiral_data(samples=100, classes=2)
X_test, y_test = spiral_data(samples=100, classes=2)# Reshape labels to be a list of lists
# Inner list contains one output (either 0 or 1)
# per each output neuron, 1 in this case
y = y.reshape(-1, 1)
y_test = y_test.reshape(-1, 1)# Instantiate the model
model = Model()# Add layers
model.add(Layer_Dense(2, 64, weight_regularizer_l2=5e-4, bias_regularizer_l2=5e-4))
model.add(Activation_ReLU())
model.add(Layer_Dense(64, 1))
model.add(Activation_Sigmoid())# Set loss, optimizer and accuracy objects
model.set(loss=Loss_BinaryCrossentropy(),optimizer=Optimizer_Adam(decay=5e-7),accuracy=Accuracy_Categorical())# Finalize the model
model.finalize()# Train the model
model.train(X, y, validation_data=(X_test, y_test), epochs=10000, print_every=100)
>>>
epoch: 100, acc: 0.625, loss: 0.675 (data_loss: 0.674, reg_loss: 0.001), lr: 0.0009999505024501287
epoch: 200, acc: 0.630, loss: 0.669 (data_loss: 0.668, reg_loss: 0.001), lr: 0.0009999005098992651
...
epoch: 9900, acc: 0.905, loss: 0.312 (data_loss: 0.276, reg_loss: 0.037), lr: 0.0009950748768967994
epoch: 10000, acc: 0.905, loss: 0.312 (data_loss: 0.275, reg_loss: 0.036), lr: 0.0009950253706593885
validation, acc: 0.775, loss: 0.423
现在,我们已经简化了前向传播和反向传播代码,包括验证过程,这是重新引入Dropout的好时机。回顾一下,Dropout是一种通过禁用或过滤掉某些神经元来正则化和提高模型泛化能力的方法。如果在我们的模型中使用Dropout,那么在进行验证和推理(预测)时,我们需要确保不使用Dropout。在之前的代码中,通过在验证过程中不调用Dropout的前向传播方法实现了这一点。这里,我们有一个通用方法,用于同时执行训练和验证的前向传播,因此需要一种不同的方法来关闭Dropout——即在训练过程中通知各层,并让它们“决定”是否包括计算。我们要做的第一件事是为所有层和激活函数类的前向传播方法添加一个布尔参数training
,因为我们需要以统一的方式调用它们:
# Forward passdef forward(self, inputs, training):
当我们不处于训练模式时,可以在Layer_Dropout
类中将输出直接设置为输入,并在不改变输出的情况下从方法中返回:
# If not in the training mode - return valuesif not training:self.output = inputs.copy()return
我们在培训时,会让dropout
参与进来:
# Dropout
class Layer_Dropout: ...# Forward passdef forward(self, inputs, training):# Save input valuesself.inputs = inputs# If not in the training mode - return valuesif not training:self.output = inputs.copy()return# Generate and save scaled maskself.binary_mask = np.random.binomial(1, self.rate, size=inputs.shape) / self.rate# Apply mask to output valuesself.output = inputs * self.binary_mask
接下来,我们修改Model
类的forward
方法,添加training
参数,并调用各层的forward
方法以传递该参数的值:
# Model class
class Model:...# Performs forward passdef forward(self, X, training):# Call forward method on the input layer# this will set the output property that# the first layer in "prev" object is expectingself.input_layer.forward(X, training)# Call forward method of every object in a chain# Pass output of the previous object as a parameterfor layer in self.layers:layer.forward(layer.prev.output, training)# "layer" is now the last object from the list,# return its outputreturn layer.output
我们还需要更新Model
类中的train
方法,因为在调用forward
方法时,training
参数需要被设置为True
:
# Perform the forward passoutput = self.forward(X, training=True)
然后在验证过程中将其设置为False
:
# Perform the forward passoutput = self.forward(X_val, training=False)
# Model class
class Model:...# Train the modeldef train(self, X, y, *, epochs=1, print_every=1, validation_data=None):# Initialize accuracy objectself.accuracy.init(y)# Main training loopfor epoch in range(1, epochs+1):# Perform the forward passoutput = self.forward(X, training=True)# Calculate lossdata_loss, regularization_loss = self.loss.calculate(output, y, include_regularization=True)loss = data_loss + regularization_loss# Get predictions and calculate an accuracypredictions = self.output_layer_activation.predictions(output)accuracy = self.accuracy.calculate(predictions, y)# Perform backward passself.backward(output, y)# Optimize (update parameters)self.optimizer.pre_update_params()for layer in self.trainable_layers:self.optimizer.update_params(layer)self.optimizer.post_update_params()# Print a summaryif not epoch % print_every:print(f'epoch: {epoch}, ' +f'acc: {accuracy:.3f}, ' +f'loss: {loss:.3f} (' +f'data_loss: {data_loss:.3f}, ' +f'reg_loss: {regularization_loss:.3f}), ' +f'lr: {self.optimizer.current_learning_rate}')# If there is the validation dataif validation_data is not None:# For better readabilityX_val, y_val = validation_data# Perform the forward passoutput = self.forward(X_val, training=False)# Calculate the lossloss = self.loss.calculate(output, y_val)# Get predictions and calculate an accuracypredictions = self.output_layer_activation.predictions(output)accuracy = self.accuracy.calculate(predictions, y_val)# Print a summaryprint(f'validation, ' +f'acc: {accuracy:.3f}, ' +f'loss: {loss:.3f}')
最后,我们需要处理Model
类中结合了Softmax
激活和CrossEntropy
损失的类。这里的挑战在于,之前我们是为每个模型单独手动定义前向传播和后向传播的。然而,现在我们在计算的两个方向上都有循环,对输出和梯度的计算有统一的方式,以及其他改进。我们不能简单地移除Softmax
激活和Categorical Cross-Entropy
损失并用一个结合了两者的对象替代它们。按照目前的代码,这种方式是行不通的,因为我们以特定的方式处理输出激活函数和损失函数。
由于结合对象仅优化了后向传播的部分,我们决定让前向传播保持不变,仍然使用单独的Softmax
激活和Categorical Cross-Entropy
损失对象,只处理后向传播部分。
首先,我们需要自动确定当前模型是否是一个分类器,以及它是否使用了Softmax
激活和Categorical Cross-Entropy
损失。这可以通过检查最后一层对象的类名(这是一个激活函数对象)以及损失函数对象的类名来实现。我们将在finalize
方法的末尾添加此检查:
# If output activation is Softmax and# loss function is Categorical Cross-Entropy# create an object of combined activation# and loss function containing# faster gradient calculationif isinstance(self.layers[-1], Activation_Softmax) and isinstance(self.loss, Loss_CategoricalCrossentropy):# Create an object of combined activation# and loss functionsself.softmax_classifier_output = Activation_Softmax_Loss_CategoricalCrossentropy()
为了进行此检查,我们使用了 Python 的isinstance
函数。如果给定对象是指定类的实例,isinstance
函数将返回True
。如果两个检查都返回True
,我们将设置一个新属性,该属性包含Activation_Softmax_Loss_CategoricalCrossentropy
类的对象。
我们还需要在Model
类的构造函数中,将此属性初始化为None
值:
# Softmax classifier's output objectself.softmax_classifier_output = None
最后一步是在反向传播期间检查这个对象是否已设置,如果已设置则使用它。为此,我们需要稍微修改当前的反向传播代码以单独处理这种情况。
首先,我们调用组合对象的backward
方法;然后,由于我们不会调用激活函数对象(即层列表中的最后一个对象)的backward
方法,因此需要用在激活/损失对象中计算出的梯度来设置该对象的dinputs
属性。最后,我们可以对除最后一层以外的所有层进行迭代并执行它们的反向传播操作:
# If softmax classifierif self.softmax_classifier_output is not None:# First call backward method# on the combined activation/loss# this will set dinputs propertyself.softmax_classifier_output.backward(output, y)# Since we'll not call backward method of the last layer# which is Softmax activation# as we used combined activation/loss# object, let's set dinputs in this objectself.layers[-1].dinputs = self.softmax_classifier_output.dinputs# Call backward method going through# all the objects but last# in reversed order passing dinputs as a parameterfor layer in reversed(self.layers[:-1]):layer.backward(layer.next.dinputs)return
到目前为止的完整模型类代码如下:
# Model class
class Model:def __init__(self):# Create a list of network objectsself.layers = []# Softmax classifier's output objectself.softmax_classifier_output = None# Add objects to the modeldef add(self, layer):self.layers.append(layer)# Set loss, optimizer and accuracydef set(self, *, loss, optimizer, accuracy):self.loss = lossself.optimizer = optimizerself.accuracy = accuracy# Finalize the modeldef finalize(self):# Create and set the input layerself.input_layer = Layer_Input()# Count all the objectslayer_count = len(self.layers)# Initialize a list containing trainable layers:self.trainable_layers = []# Iterate the objectsfor i in range(layer_count):# If it's the first layer,# the previous layer object is the input layerif i == 0:self.layers[i].prev = self.input_layerself.layers[i].next = self.layers[i+1]# All layers except for the first and the lastelif i < layer_count - 1:self.layers[i].prev = self.layers[i-1]self.layers[i].next = self.layers[i+1]# The last layer - the next object is the loss# Also let's save aside the reference to the last object# whose output is the model's outputelse:self.layers[i].prev = self.layers[i-1]self.layers[i].next = self.lossself.output_layer_activation = self.layers[i]# If layer contains an attribute called "weights",# it's a trainable layer -# add it to the list of trainable layers# We don't need to check for biases -# checking for weights is enough if hasattr(self.layers[i], 'weights'):self.trainable_layers.append(self.layers[i])# Update loss object with trainable layersself.loss.remember_trainable_layers(self.trainable_layers)# If output activation is Softmax and# loss function is Categorical Cross-Entropy# create an object of combined activation# and loss function containing# faster gradient calculationif isinstance(self.layers[-1], Activation_Softmax) and isinstance(self.loss, Loss_CategoricalCrossentropy):# Create an object of combined activation# and loss functionsself.softmax_classifier_output = Activation_Softmax_Loss_CategoricalCrossentropy()# Train the modeldef train(self, X, y, *, epochs=1, print_every=1, validation_data=None):# Initialize accuracy objectself.accuracy.init(y)# Main training loopfor epoch in range(1, epochs+1):# Perform the forward passoutput = self.forward(X, training=True)# Calculate lossdata_loss, regularization_loss = self.loss.calculate(output, y, include_regularization=True)loss = data_loss + regularization_loss# Get predictions and calculate an accuracypredictions = self.output_layer_activation.predictions(output)accuracy = self.accuracy.calculate(predictions, y)# Perform backward passself.backward(output, y)# Optimize (update parameters)self.optimizer.pre_update_params()for layer in self.trainable_layers:self.optimizer.update_params(layer)self.optimizer.post_update_params()# Print a summaryif not epoch % print_every:print(f'epoch: {epoch}, ' +f'acc: {accuracy:.3f}, ' +f'loss: {loss:.3f} (' +f'data_loss: {data_loss:.3f}, ' +f'reg_loss: {regularization_loss:.3f}), ' +f'lr: {self.optimizer.current_learning_rate}')# If there is the validation dataif validation_data is not None:# For better readabilityX_val, y_val = validation_data# Perform the forward passoutput = self.forward(X_val, training=False)# Calculate the lossloss = self.loss.calculate(output, y_val)# Get predictions and calculate an accuracypredictions = self.output_layer_activation.predictions(output)accuracy = self.accuracy.calculate(predictions, y_val)# Print a summaryprint(f'validation, ' +f'acc: {accuracy:.3f}, ' +f'loss: {loss:.3f}')# Performs forward passdef forward(self, X, training):# Call forward method on the input layer# this will set the output property that# the first layer in "prev" object is expectingself.input_layer.forward(X, training)# Call forward method of every object in a chain# Pass output of the previous object as a parameterfor layer in self.layers:layer.forward(layer.prev.output, training)# "layer" is now the last object from the list,# return its outputreturn layer.output# Performs backward passdef backward(self, output, y):# If softmax classifierif self.softmax_classifier_output is not None:# First call backward method# on the combined activation/loss# this will set dinputs propertyself.softmax_classifier_output.backward(output, y)# Since we'll not call backward method of the last layer# which is Softmax activation# as we used combined activation/loss# object, let's set dinputs in this objectself.layers[-1].dinputs = self.softmax_classifier_output.dinputs# Call backward method going through# all the objects but last# in reversed order passing dinputs as a parameterfor layer in reversed(self.layers[:-1]):layer.backward(layer.next.dinputs)return# First call backward method on the loss# this will set dinputs property that the last# layer will try to access shortlyself.loss.backward(output, y)# Call backward method going through all the objects# in reversed order passing dinputs as a parameterfor layer in reversed(self.layers):layer.backward(layer.next.dinputs)
此外,我们将不再需要Activation_Softmax_Loss_CategoricalCrossentropy
类的初始化器和前向传播方法,因此我们可以将它们移除,仅保留反向传播方法:
# Softmax classifier - combined Softmax activation
# and cross-entropy loss for faster backward step
class Activation_Softmax_Loss_CategoricalCrossentropy(): ...# Backward passdef backward(self, dvalues, y_true):# Number of samplessamples = len(dvalues) # Copy so we can safely modifyself.dinputs = dvalues.copy()# Calculate gradientself.dinputs[range(samples), y_true] -= 1# Normalize gradientself.dinputs = self.dinputs / samples
现在我们可以通过使用 Dropout
来测试更新后的 Model
对象:
# Create dataset
X, y = spiral_data(samples=1000, classes=3)
X_test, y_test = spiral_data(samples=100, classes=3)
# Instantiate the model
model = Model()
# Add layers
model.add(Layer_Dense(2, 512, weight_regularizer_l2=5e-4, bias_regularizer_l2=5e-4))
model.add(Activation_ReLU())
model.add(Layer_Dropout(0.1))
model.add(Layer_Dense(512, 3))
model.add(Activation_Softmax())
# Set loss, optimizer and accuracy objects
model.set(loss=Loss_CategoricalCrossentropy(),optimizer=Optimizer_Adam(learning_rate=0.05, decay=5e-5),accuracy=Accuracy_Categorical())
# Finalize the model
model.finalize()
# Train the model
model.train(X, y, validation_data=(X_test, y_test), epochs=10000, print_every=100)
>>>
epoch: 100, acc: 0.716, loss: 0.726 (data_loss: 0.666, reg_loss: 0.060), lr:
0.04975371909050202
epoch: 200, acc: 0.787, loss: 0.615 (data_loss: 0.538, reg_loss: 0.077), lr:
0.049507401356502806
...
epoch: 9900, acc: 0.861, loss: 0.436 (data_loss: 0.389, reg_loss: 0.046),
lr: 0.0334459346466437
epoch: 10000, acc: 0.880, loss: 0.394 (data_loss: 0.347, reg_loss: 0.047),
lr: 0.03333444448148271
validation, acc: 0.867, loss: 0.379
看起来一切都按预期工作。现在有了这个 Model 类,我们可以定义新的模型,而无需重复编写大量代码。重复编写代码不仅令人厌烦,还更容易出现一些难以察觉的小错误。
到目前为止的完整代码:
import numpy as np
import nnfs
from nnfs.datasets import sine_data, spiral_data
import sysnnfs.init()# Dense layer
class Layer_Dense:# Layer initializationdef __init__(self, n_inputs, n_neurons,weight_regularizer_l1=0, weight_regularizer_l2=0,bias_regularizer_l1=0, bias_regularizer_l2=0):# Initialize weights and biases# self.weights = 0.01 * np.random.randn(n_inputs, n_neurons)self.weights = 0.1 * np.random.randn(n_inputs, n_neurons)self.biases = np.zeros((1, n_neurons))# Set regularization strengthself.weight_regularizer_l1 = weight_regularizer_l1self.weight_regularizer_l2 = weight_regularizer_l2self.bias_regularizer_l1 = bias_regularizer_l1self.bias_regularizer_l2 = bias_regularizer_l2# Forward passdef forward(self, inputs, training):# Remember input valuesself.inputs = inputs# Calculate output values from inputs, weights and biasesself.output = np.dot(inputs, self.weights) + self.biases# Backward passdef backward(self, dvalues):# Gradients on parametersself.dweights = np.dot(self.inputs.T, dvalues)self.dbiases = np.sum(dvalues, axis=0, keepdims=True)# Gradients on regularization# L1 on weightsif self.weight_regularizer_l1 > 0:dL1 = np.ones_like(self.weights)dL1[self.weights < 0] = -1self.dweights += self.weight_regularizer_l1 * dL1# L2 on weightsif self.weight_regularizer_l2 > 0:self.dweights += 2 * self.weight_regularizer_l2 * self.weights# L1 on biasesif self.bias_regularizer_l1 > 0:dL1 = np.ones_like(self.biases)dL1[self.biases < 0] = -1self.dbiases += self.bias_regularizer_l1 * dL1# L2 on biasesif self.bias_regularizer_l2 > 0:self.dbiases += 2 * self.bias_regularizer_l2 * self.biases# Gradient on valuesself.dinputs = np.dot(dvalues, self.weights.T)# Dropout
class Layer_Dropout: # Initdef __init__(self, rate):# Store rate, we invert it as for example for dropout# of 0.1 we need success rate of 0.9self.rate = 1 - rate# Forward passdef forward(self, inputs, training):# Save input valuesself.inputs = inputs# If not in the training mode - return valuesif not training:self.output = inputs.copy()return# Generate and save scaled maskself.binary_mask = np.random.binomial(1, self.rate, size=inputs.shape) / self.rate# Apply mask to output valuesself.output = inputs * self.binary_mask# Backward passdef backward(self, dvalues):# Gradient on valuesself.dinputs = dvalues * self.binary_mask# Input "layer"
class Layer_Input:# Forward passdef forward(self, inputs, training):self.output = inputs# ReLU activation
class Activation_ReLU: # Forward passdef forward(self, inputs, training):# Remember input valuesself.inputs = inputs# Calculate output values from inputsself.output = np.maximum(0, inputs)# Backward passdef backward(self, dvalues):# Since we need to modify original variable,# let's make a copy of values firstself.dinputs = dvalues.copy()# Zero gradient where input values were negativeself.dinputs[self.inputs <= 0] = 0# Calculate predictions for outputsdef predictions(self, outputs):return outputs# Softmax activation
class Activation_Softmax:# Forward passdef forward(self, inputs, training):# Remember input valuesself.inputs = inputs# Get unnormalized probabilitiesexp_values = np.exp(inputs - np.max(inputs, axis=1, keepdims=True))# Normalize them for each sampleprobabilities = exp_values / np.sum(exp_values, axis=1, keepdims=True)self.output = probabilities# Backward passdef backward(self, dvalues):# Create uninitialized arrayself.dinputs = np.empty_like(dvalues)# Enumerate outputs and gradientsfor index, (single_output, single_dvalues) in enumerate(zip(self.output, dvalues)):# Flatten output arraysingle_output = single_output.reshape(-1, 1)# Calculate Jacobian matrix of the output andjacobian_matrix = np.diagflat(single_output) - np.dot(single_output, single_output.T)# Calculate sample-wise gradient# and add it to the array of sample gradientsself.dinputs[index] = np.dot(jacobian_matrix, single_dvalues)# Calculate predictions for outputsdef predictions(self, outputs):return np.argmax(outputs, axis=1)# Sigmoid activation
class Activation_Sigmoid:# Forward passdef forward(self, inputs, training):# Save input and calculate/save output# of the sigmoid functionself.inputs = inputsself.output = 1 / (1 + np.exp(-inputs))# Backward passdef backward(self, dvalues):# Derivative - calculates from output of the sigmoid functionself.dinputs = dvalues * (1 - self.output) * self.output# Calculate predictions for outputsdef predictions(self, outputs):return (outputs > 0.5) * 1# Linear activation
class Activation_Linear:# Forward passdef forward(self, inputs, training):# Just remember valuesself.inputs = inputsself.output = inputs# Backward passdef backward(self, dvalues):# derivative is 1, 1 * dvalues = dvalues - the chain ruleself.dinputs = dvalues.copy()# Calculate predictions for outputsdef predictions(self, outputs):return outputs# SGD optimizer
class Optimizer_SGD:# Initialize optimizer - set settings,# learning rate of 1. is default for this optimizerdef __init__(self, learning_rate=1., decay=0., momentum=0.):self.learning_rate = learning_rateself.current_learning_rate = learning_rateself.decay = decayself.iterations = 0self.momentum = momentum# Call once before any parameter updatesdef pre_update_params(self):if self.decay:self.current_learning_rate = self.learning_rate * (1. / (1. + self.decay * self.iterations))# Update parametersdef update_params(self, layer):# If we use momentumif self.momentum:# If layer does not contain momentum arrays, create them# filled with zerosif not hasattr(layer, 'weight_momentums'):layer.weight_momentums = np.zeros_like(layer.weights)# If there is no momentum array for weights# The array doesn't exist for biases yet either.layer.bias_momentums = np.zeros_like(layer.biases)# Build weight updates with momentum - take previous# updates multiplied by retain factor and update with# current gradientsweight_updates = self.momentum * layer.weight_momentums - self.current_learning_rate * layer.dweightslayer.weight_momentums = weight_updates# Build bias updatesbias_updates = self.momentum * layer.bias_momentums - self.current_learning_rate * layer.dbiaseslayer.bias_momentums = bias_updates# Vanilla SGD updates (as before momentum update)else:weight_updates = -self.current_learning_rate * layer.dweightsbias_updates = -self.current_learning_rate * layer.dbiases# Update weights and biases using either# vanilla or momentum updateslayer.weights += weight_updateslayer.biases += bias_updates# Call once after any parameter updatesdef post_update_params(self):self.iterations += 1 # Adagrad optimizer
class Optimizer_Adagrad:# Initialize optimizer - set settingsdef __init__(self, learning_rate=1., decay=0., epsilon=1e-7):self.learning_rate = learning_rateself.current_learning_rate = learning_rateself.decay = decayself.iterations = 0self.epsilon = epsilon# Call once before any parameter updatesdef pre_update_params(self):if self.decay:self.current_learning_rate = self.learning_rate * (1. / (1. + self.decay * self.iterations))# Update parametersdef update_params(self, layer):# If layer does not contain cache arrays,# create them filled with zerosif not hasattr(layer, 'weight_cache'):layer.weight_cache = np.zeros_like(layer.weights)layer.bias_cache = np.zeros_like(layer.biases)# Update cache with squared current gradientslayer.weight_cache += layer.dweights**2layer.bias_cache += layer.dbiases**2# Vanilla SGD parameter update + normalization# with square rooted cachelayer.weights += -self.current_learning_rate * layer.dweights / (np.sqrt(layer.weight_cache) + self.epsilon)layer.biases += -self.current_learning_rate * layer.dbiases / (np.sqrt(layer.bias_cache) + self.epsilon)# Call once after any parameter updatesdef post_update_params(self):self.iterations += 1# RMSprop optimizer
class Optimizer_RMSprop: # Initialize optimizer - set settingsdef __init__(self, learning_rate=0.001, decay=0., epsilon=1e-7, rho=0.9):self.learning_rate = learning_rateself.current_learning_rate = learning_rateself.decay = decayself.iterations = 0self.epsilon = epsilonself.rho = rho# Call once before any parameter updatesdef pre_update_params(self):if self.decay:self.current_learning_rate = self.learning_rate * (1. / (1. + self.decay * self.iterations))# Update parametersdef update_params(self, layer):# If layer does not contain cache arrays,# create them filled with zerosif not hasattr(layer, 'weight_cache'):layer.weight_cache = np.zeros_like(layer.weights)layer.bias_cache = np.zeros_like(layer.biases)# Update cache with squared current gradientslayer.weight_cache = self.rho * layer.weight_cache + (1 - self.rho) * layer.dweights**2layer.bias_cache = self.rho * layer.bias_cache + (1 - self.rho) * layer.dbiases**2# Vanilla SGD parameter update + normalization# with square rooted cachelayer.weights += -self.current_learning_rate * layer.dweights / (np.sqrt(layer.weight_cache) + self.epsilon)layer.biases += -self.current_learning_rate * layer.dbiases / (np.sqrt(layer.bias_cache) + self.epsilon)# Call once after any parameter updatesdef post_update_params(self):self.iterations += 1# Adam optimizer
class Optimizer_Adam:# Initialize optimizer - set settingsdef __init__(self, learning_rate=0.001, decay=0., epsilon=1e-7, beta_1=0.9, beta_2=0.999):self.learning_rate = learning_rateself.current_learning_rate = learning_rateself.decay = decayself.iterations = 0self.epsilon = epsilonself.beta_1 = beta_1self.beta_2 = beta_2# Call once before any parameter updatesdef pre_update_params(self):if self.decay:self.current_learning_rate = self.learning_rate * (1. / (1. + self.decay * self.iterations)) # Update parametersdef update_params(self, layer):# If layer does not contain cache arrays,# create them filled with zerosif not hasattr(layer, 'weight_cache'):layer.weight_momentums = np.zeros_like(layer.weights)layer.weight_cache = np.zeros_like(layer.weights)layer.bias_momentums = np.zeros_like(layer.biases)layer.bias_cache = np.zeros_like(layer.biases)# Update momentum with current gradientslayer.weight_momentums = self.beta_1 * layer.weight_momentums + (1 - self.beta_1) * layer.dweightslayer.bias_momentums = self.beta_1 * layer.bias_momentums + (1 - self.beta_1) * layer.dbiases# Get corrected momentum# self.iteration is 0 at first pass# and we need to start with 1 hereweight_momentums_corrected = layer.weight_momentums / (1 - self.beta_1 ** (self.iterations + 1))bias_momentums_corrected = layer.bias_momentums / (1 - self.beta_1 ** (self.iterations + 1))# Update cache with squared current gradientslayer.weight_cache = self.beta_2 * layer.weight_cache + (1 - self.beta_2) * layer.dweights**2layer.bias_cache = self.beta_2 * layer.bias_cache + (1 - self.beta_2) * layer.dbiases**2# Get corrected cacheweight_cache_corrected = layer.weight_cache / (1 - self.beta_2 ** (self.iterations + 1))bias_cache_corrected = layer.bias_cache / (1 - self.beta_2 ** (self.iterations + 1))# Vanilla SGD parameter update + normalization# with square rooted cachelayer.weights += -self.current_learning_rate * weight_momentums_corrected / (np.sqrt(weight_cache_corrected) + self.epsilon)layer.biases += -self.current_learning_rate * bias_momentums_corrected / (np.sqrt(bias_cache_corrected) + self.epsilon)# Call once after any parameter updatesdef post_update_params(self):self.iterations += 1# Common loss class
class Loss:# Regularization loss calculationdef regularization_loss(self): # 0 by defaultregularization_loss = 0# Calculate regularization loss# iterate all trainable layersfor layer in self.trainable_layers:# L1 regularization - weights# calculate only when factor greater than 0if layer.weight_regularizer_l1 > 0:regularization_loss += layer.weight_regularizer_l1 * np.sum(np.abs(layer.weights))# L2 regularization - weightsif layer.weight_regularizer_l2 > 0:regularization_loss += layer.weight_regularizer_l2 * np.sum(layer.weights * layer.weights)# L1 regularization - biases# calculate only when factor greater than 0if layer.bias_regularizer_l1 > 0:regularization_loss += layer.bias_regularizer_l1 * np.sum(np.abs(layer.biases))# L2 regularization - biasesif layer.bias_regularizer_l2 > 0:regularization_loss += layer.bias_regularizer_l2 * np.sum(layer.biases * layer.biases)return regularization_loss# Set/remember trainable layersdef remember_trainable_layers(self, trainable_layers):self.trainable_layers = trainable_layers# Calculates the data and regularization losses# given model output and ground truth valuesdef calculate(self, output, y, *, include_regularization=False):# Calculate sample lossessample_losses = self.forward(output, y)# Calculate mean lossdata_loss = np.mean(sample_losses)# If just data loss - return itif not include_regularization:return data_loss# Return the data and regularization lossesreturn data_loss, self.regularization_loss() # Cross-entropy loss
class Loss_CategoricalCrossentropy(Loss):# Forward passdef forward(self, y_pred, y_true):# Number of samples in a batchsamples = len(y_pred)# Clip data to prevent division by 0# Clip both sides to not drag mean towards any valuey_pred_clipped = np.clip(y_pred, 1e-7, 1 - 1e-7)# Probabilities for target values -# only if categorical labelsif len(y_true.shape) == 1:correct_confidences = y_pred_clipped[range(samples),y_true]# Mask values - only for one-hot encoded labelselif len(y_true.shape) == 2:correct_confidences = np.sum(y_pred_clipped * y_true, axis=1)# Lossesnegative_log_likelihoods = -np.log(correct_confidences)return negative_log_likelihoods# Backward passdef backward(self, dvalues, y_true):# Number of samplessamples = len(dvalues)# Number of labels in every sample# We'll use the first sample to count themlabels = len(dvalues[0])# If labels are sparse, turn them into one-hot vectorif len(y_true.shape) == 1:y_true = np.eye(labels)[y_true]# Calculate gradientself.dinputs = -y_true / dvalues# Normalize gradientself.dinputs = self.dinputs / samples# Softmax classifier - combined Softmax activation
# and cross-entropy loss for faster backward step
class Activation_Softmax_Loss_CategoricalCrossentropy(): # # Creates activation and loss function objects# def __init__(self):# self.activation = Activation_Softmax()# self.loss = Loss_CategoricalCrossentropy()# # Forward pass# def forward(self, inputs, y_true):# # Output layer's activation function# self.activation.forward(inputs)# # Set the output# self.output = self.activation.output# # Calculate and return loss value# return self.loss.calculate(self.output, y_true)# Backward passdef backward(self, dvalues, y_true):# Number of samplessamples = len(dvalues) # If labels are one-hot encoded,# turn them into discrete valuesif len(y_true.shape) == 2:y_true = np.argmax(y_true, axis=1)# Copy so we can safely modifyself.dinputs = dvalues.copy()# Calculate gradientself.dinputs[range(samples), y_true] -= 1# Normalize gradientself.dinputs = self.dinputs / samples# Binary cross-entropy loss
class Loss_BinaryCrossentropy(Loss): # Forward passdef forward(self, y_pred, y_true):# Clip data to prevent division by 0# Clip both sides to not drag mean towards any valuey_pred_clipped = np.clip(y_pred, 1e-7, 1 - 1e-7)# Calculate sample-wise losssample_losses = -(y_true * np.log(y_pred_clipped) + (1 - y_true) * np.log(1 - y_pred_clipped))sample_losses = np.mean(sample_losses, axis=-1)# Return lossesreturn sample_losses # Backward passdef backward(self, dvalues, y_true):# Number of samplessamples = len(dvalues)# Number of outputs in every sample# We'll use the first sample to count themoutputs = len(dvalues[0])# Clip data to prevent division by 0# Clip both sides to not drag mean towards any valueclipped_dvalues = np.clip(dvalues, 1e-7, 1 - 1e-7)# Calculate gradientself.dinputs = -(y_true / clipped_dvalues - (1 - y_true) / (1 - clipped_dvalues)) / outputs# Normalize gradientself.dinputs = self.dinputs / samples# Mean Squared Error loss
class Loss_MeanSquaredError(Loss): # L2 loss# Forward passdef forward(self, y_pred, y_true):# Calculate losssample_losses = np.mean((y_true - y_pred)**2, axis=-1)# Return lossesreturn sample_losses# Backward passdef backward(self, dvalues, y_true):# Number of samplessamples = len(dvalues)# Number of outputs in every sample# We'll use the first sample to count themoutputs = len(dvalues[0])# Gradient on valuesself.dinputs = -2 * (y_true - dvalues) / outputs# Normalize gradientself.dinputs = self.dinputs / samples# Mean Absolute Error loss
class Loss_MeanAbsoluteError(Loss): # L1 lossdef forward(self, y_pred, y_true):# Calculate losssample_losses = np.mean(np.abs(y_true - y_pred), axis=-1)# Return lossesreturn sample_losses# Backward passdef backward(self, dvalues, y_true):# Number of samplessamples = len(dvalues)# Number of outputs in every sample# We'll use the first sample to count themoutputs = len(dvalues[0])# Calculate gradientself.dinputs = np.sign(y_true - dvalues) / outputs# Normalize gradientself.dinputs = self.dinputs / samples# Common accuracy class
class Accuracy:# Calculates an accuracy# given predictions and ground truth valuesdef calculate(self, predictions, y):# Get comparison resultscomparisons = self.compare(predictions, y)# Calculate an accuracyaccuracy = np.mean(comparisons)# Return accuracyreturn accuracy # Accuracy calculation for classification model
class Accuracy_Categorical(Accuracy):# No initialization is neededdef init(self, y):pass# Compares predictions to the ground truth valuesdef compare(self, predictions, y):if len(y.shape) == 2:y = np.argmax(y, axis=1)return predictions == y# Accuracy calculation for regression model
class Accuracy_Regression(Accuracy):def __init__(self):# Create precision propertyself.precision = None# Calculates precision value# based on passed in ground truthdef init(self, y, reinit=False):if self.precision is None or reinit:self.precision = np.std(y) / 250# Compares predictions to the ground truth valuesdef compare(self, predictions, y):return np.absolute(predictions - y) < self.precision# Model class
class Model:def __init__(self):# Create a list of network objectsself.layers = []# Softmax classifier's output objectself.softmax_classifier_output = None# Add objects to the modeldef add(self, layer):self.layers.append(layer)# Set loss, optimizer and accuracydef set(self, *, loss, optimizer, accuracy):self.loss = lossself.optimizer = optimizerself.accuracy = accuracy# Finalize the modeldef finalize(self):# Create and set the input layerself.input_layer = Layer_Input()# Count all the objectslayer_count = len(self.layers)# Initialize a list containing trainable layers:self.trainable_layers = []# Iterate the objectsfor i in range(layer_count):# If it's the first layer,# the previous layer object is the input layerif i == 0:self.layers[i].prev = self.input_layerself.layers[i].next = self.layers[i+1]# All layers except for the first and the lastelif i < layer_count - 1:self.layers[i].prev = self.layers[i-1]self.layers[i].next = self.layers[i+1]# The last layer - the next object is the loss# Also let's save aside the reference to the last object# whose output is the model's outputelse:self.layers[i].prev = self.layers[i-1]self.layers[i].next = self.lossself.output_layer_activation = self.layers[i]# If layer contains an attribute called "weights",# it's a trainable layer -# add it to the list of trainable layers# We don't need to check for biases -# checking for weights is enough if hasattr(self.layers[i], 'weights'):self.trainable_layers.append(self.layers[i])# Update loss object with trainable layersself.loss.remember_trainable_layers(self.trainable_layers)# If output activation is Softmax and# loss function is Categorical Cross-Entropy# create an object of combined activation# and loss function containing# faster gradient calculationif isinstance(self.layers[-1], Activation_Softmax) and isinstance(self.loss, Loss_CategoricalCrossentropy):# Create an object of combined activation# and loss functionsself.softmax_classifier_output = Activation_Softmax_Loss_CategoricalCrossentropy()# Train the modeldef train(self, X, y, *, epochs=1, print_every=1, validation_data=None):# Initialize accuracy objectself.accuracy.init(y)# Main training loopfor epoch in range(1, epochs+1):# Perform the forward passoutput = self.forward(X, training=True)# Calculate lossdata_loss, regularization_loss = self.loss.calculate(output, y, include_regularization=True)loss = data_loss + regularization_loss# Get predictions and calculate an accuracypredictions = self.output_layer_activation.predictions(output)accuracy = self.accuracy.calculate(predictions, y)# Perform backward passself.backward(output, y)# Optimize (update parameters)self.optimizer.pre_update_params()for layer in self.trainable_layers:self.optimizer.update_params(layer)self.optimizer.post_update_params()# Print a summaryif not epoch % print_every:print(f'epoch: {epoch}, ' +f'acc: {accuracy:.3f}, ' +f'loss: {loss:.3f} (' +f'data_loss: {data_loss:.3f}, ' +f'reg_loss: {regularization_loss:.3f}), ' +f'lr: {self.optimizer.current_learning_rate}')# If there is the validation dataif validation_data is not None:# For better readabilityX_val, y_val = validation_data# Perform the forward passoutput = self.forward(X_val, training=False)# Calculate the lossloss = self.loss.calculate(output, y_val)# Get predictions and calculate an accuracypredictions = self.output_layer_activation.predictions(output)accuracy = self.accuracy.calculate(predictions, y_val)# Print a summaryprint(f'validation, ' +f'acc: {accuracy:.3f}, ' +f'loss: {loss:.3f}')# Performs forward passdef forward(self, X, training):# Call forward method on the input layer# this will set the output property that# the first layer in "prev" object is expectingself.input_layer.forward(X, training)# Call forward method of every object in a chain# Pass output of the previous object as a parameterfor layer in self.layers:layer.forward(layer.prev.output, training)# "layer" is now the last object from the list,# return its outputreturn layer.output# Performs backward passdef backward(self, output, y):# If softmax classifierif self.softmax_classifier_output is not None:# First call backward method# on the combined activation/loss# this will set dinputs propertyself.softmax_classifier_output.backward(output, y)# Since we'll not call backward method of the last layer# which is Softmax activation# as we used combined activation/loss# object, let's set dinputs in this objectself.layers[-1].dinputs = self.softmax_classifier_output.dinputs# Call backward method going through# all the objects but last# in reversed order passing dinputs as a parameterfor layer in reversed(self.layers[:-1]):layer.backward(layer.next.dinputs)return# First call backward method on the loss# this will set dinputs property that the last# layer will try to access shortlyself.loss.backward(output, y)# Call backward method going through all the objects# in reversed order passing dinputs as a parameterfor layer in reversed(self.layers):layer.backward(layer.next.dinputs) # # Create dataset
# X, y = sine_data()# # Instantiate the model
# model = Model()# # Add layers
# model.add(Layer_Dense(1, 64))
# model.add(Activation_ReLU())
# model.add(Layer_Dense(64, 64))
# model.add(Activation_ReLU())
# model.add(Layer_Dense(64, 1))
# model.add(Activation_Linear())# # Set loss and optimizer objects
# model.set(
# loss=Loss_MeanSquaredError(),
# optimizer=Optimizer_Adam(learning_rate=0.005, decay=1e-3),
# accuracy=Accuracy_Regression()
# )# # Finalize the model
# model.finalize()# model.train(X, y, epochs=10000, print_every=100)
#########################################################################################
# # Create train and test dataset
# X, y = spiral_data(samples=100, classes=2)
# X_test, y_test = spiral_data(samples=100, classes=2)# # Reshape labels to be a list of lists
# # Inner list contains one output (either 0 or 1)
# # per each output neuron, 1 in this case
# y = y.reshape(-1, 1)
# y_test = y_test.reshape(-1, 1)# # Instantiate the model
# model = Model()# # Add layers
# model.add(Layer_Dense(2, 64, weight_regularizer_l2=5e-4, bias_regularizer_l2=5e-4))
# model.add(Activation_ReLU())
# model.add(Layer_Dense(64, 1))
# model.add(Activation_Sigmoid())# # Set loss, optimizer and accuracy objects
# model.set(
# loss=Loss_BinaryCrossentropy(),
# optimizer=Optimizer_Adam(decay=5e-7),
# accuracy=Accuracy_Categorical()
# )# # Finalize the model
# model.finalize()# # Train the model
# model.train(X, y, validation_data=(X_test, y_test), epochs=10000, print_every=100)
#########################################################################################
# Create dataset
X, y = spiral_data(samples=1000, classes=3)
X_test, y_test = spiral_data(samples=100, classes=3)
# Instantiate the model
model = Model()
# Add layers
model.add(Layer_Dense(2, 512, weight_regularizer_l2=5e-4, bias_regularizer_l2=5e-4))
model.add(Activation_ReLU())
model.add(Layer_Dropout(0.1))
model.add(Layer_Dense(512, 3))
model.add(Activation_Softmax())
# Set loss, optimizer and accuracy objects
model.set(loss=Loss_CategoricalCrossentropy(),optimizer=Optimizer_Adam(learning_rate=0.05, decay=5e-5),accuracy=Accuracy_Categorical())
# Finalize the model
model.finalize()
# Train the model
model.train(X, y, validation_data=(X_test, y_test), epochs=10000, print_every=100)
本章的章节代码、更多资源和勘误表:https://nnfs.io/ch18
相关文章:
用 Python 从零开始创建神经网络(十八):模型对象(Model Object)
模型对象(Model Object) 引言到目前为止的完整代码: 引言 我们构建了一个可以执行前向传播、反向传播以及精度测量等辅助任务的模型。通过编写相当多的代码并在一些较大的代码块中进行修改,我们实现了这些功能。此时,…...
Springboot 升级带来的Swagger异常
当升级到Springboot 2.6.0 以上的版本后,Swagger 就不能正常工作了, 启动时报如下错误。当然如果你再使用sping boot Actuator 和 Springfox, 也会引起相关的NPE error. (github issue: https://github.com/springfox/springfox/issues/3462) NFO | jvm 1 | 2022/04…...
【蓝桥杯研究生组】第15届Java试题答案整理
D 题 试题 D: 商品库存管理 时间限制: 3.0s 内存限制: 512.0MB 本题总分:10 分 【问题描述】 在库存管理系统中,跟踪和调节商品库存量是关键任务之一。小蓝经营的仓库中存有多种商品,这些商品根据类别和规格被有序地分类并编号,…...
数据结构(链式栈)
链式栈 链式栈(Linked Stack)是一种基于链表的数据结构,用于实现栈(后进先出,LIFO)的特性。与基于数组的栈不同,链式栈通过动态分配内存来存储数据,这使得它更加灵活,能…...
《代码随想录》Day22打卡!
回溯算法 《代码随想录》回溯算法:组合 本题完整题目如下: 本题的完整思路如下: 1.本题使用回溯算法,其实回溯和递归是一样的道理,也是分为三步曲进行: 2.第一步:确定递归函数的返回值和参数&…...
NetSuite Formula(HTML)超链打开Transaction
当Saved Search作为Sublist应用在Form时,如果Document Number是Group过的,则会出现如下超链失效的情况。 解决办法: 可以利用Saved Search中的Formula(HTML)功能来构建超链,用于打开Transaction。 以下图…...
传统听写与大模型听写比对
在快节奏的现代生活中,听写技能仍然是学习语言和提升认知能力的重要环节。然而,传统的听写练习往往枯燥乏味,且效率不高。现在,随着人工智能技术的发展,大模型听写工具的问世,为传统听写带来了革命性的变革…...
本地快速推断的语言模型比较:Apple MLX、Llama.cpp与Hugging Face Candle Rust
本地快速推断的语言模型比较:Apple MLX、Llama.cpp与Hugging Face Candle Rust 在自然语言处理(NLP)部署中,推断速度是一个关键因素,尤其是对于支持大型语言模型(LLM)的应用来说。随着Apple M1…...
Tomcat调优相关理解
什么是QPS? 是Queries Per Second 的缩写,是指服务器每秒查询数,比如定义一个a接口,该接口是10QPS,那么就是指该接口每秒可以处理10个请求 springboot默认并发处理数是多少? springboot并发处理要看serv…...
python爬虫--小白篇【selenium自动爬取文件】
一、问题描述 在学习或工作中需要爬取文件资源时,由于文件数量太多,手动单个下载文件效率低,操作麻烦,采用selenium框架自动爬取文件数据是不二选择。如需要爬取下面网站中包含的全部pdf文件,并将其转为Markdown格式。…...
Flink读写Kafka(DataStream API)
在Flink里,已经预定义了kafka connector,使用该connector我们可以读写kafka,并且能实现exactly once的语义。 要使用需要引入相关的maven依赖,在这里,因为读写kafka,就会涉及一个问题,kafka-client和broker的版本兼容问题,不过因为kafka client和broker的双向兼容的良…...
活动预告 | Microsoft 安全在线技术公开课:通过扩展检测和响应抵御威胁
课程介绍 通过 Microsoft Learn 免费参加 Microsoft 安全在线技术公开课,掌握创造新机遇所需的技能,加快对 Microsoft Cloud 技术的了解。参加我们举办的“通过扩展检测和响应抵御威胁”技术公开课活动,了解如何更好地在 Microsoft 365 Defen…...
nginx核心配置文件及常用功能
华子目录 配置文件说明配置文件格式说明nginx配置文件中的变量默认nginx.conf配置文件格式说明main全局配置events配置段 nginx配置中的root和aliaslocation用法详解虚拟主机配置nginx账户认证功能nginx自定义错误页面nginx自定义日志 配置文件说明 nginx官方帮助文档…...
基于AT89C51单片机的可暂停八路抢答器设计
点击链接获取Keil源码与Project Backups仿真图: https://download.csdn.net/download/qq_64505944/90196607?spm1001.2014.3001.5503 C15 部分参考设计如下: 摘要 随着社会进步和科技发展,电子设备在各类活动中的应用日益普遍,…...
github加速源配置
访问github速度很慢? 试试一下方法 1: 编辑配置 vim /etc/docker/daemon.json 2:都复制粘贴上 { "registry-mirrors": [ "https://docker.211678.top", "https://docker.1panel.live…...
骑行解压:身心的奇妙之旅,VELO Angel Revo坐垫
在快节奏的都市生活中,骑行不仅是一种健康的生活方式,更是一种心灵的释放。从心理生理学的角度来看,骑行能够促使身体分泌内啡肽,带来愉悦感,同时,它还能转移注意力,缓解焦虑。在这场身心的奇妙…...
(七)- plane/crtc/encoder/connector objects
1,framebuffer/plane Rockchip RK3399 - DRM framebuffer、plane基础知识 - 大奥特曼打小怪兽 - 博客园 2,crtc Rockchip RK3399 - DRM crtc基础知识 - 大奥特曼打小怪兽 - 博客园 3,encoder/connector/bridge Rockchip RK3399 - DRM en…...
从零开始:如何在 .NET Core 中优雅地读取和管理配置文件
在.net中的配置文件系统支持丰富的配置源,包括文件(json、xml、ini等)、注册表、环境变量、命令行、Azure Key Vault等,还可以配置自定义配置源并跟踪配置的改变,然后按照优先级进行覆盖,总之对文件的配置有很多方法,这…...
Python中PDF转Word的技术
Python PDF转Word技术概述 在日常办公和数据处理中,经常需要将PDF文档转换为Word文档,以便进行编辑、修改或格式调整。Python作为一种强大的编程语言,提供了多种库和工具来实现这一功能。以下是对Python中PDF转Word技术的详细介绍。 一、技…...
挑战春招找到java后端实习第一天(1.1)
八股文 1.java中有哪些集合类请简单介绍一下 集合类分为两大类Collection和Map。前者是对象的集合,后者是键值对。 Collection分为List,Set,Queue三个接口。 List有LinkedList,ArrayList,Vector Set(不…...
leetcode hot 小偷
class Solution(object):def rob(self, nums):""":type nums: List[int]:rtype: int"""# 使用动态规划,把之前的给保存起来ans[0,nums[-1]]for i in range(1,len(nums)):ans.append(max(ans[-1],ans[-2]nums[-1*i-1]))return ans[-1]…...
一、Git与GitHub基础说明
Git与GitHub Git与GitHub一、Git1定义2核心功能(1) 版本控制(2) 分支管理(3) 合并操作 二、GitHub1定义2核心功能(1)远程仓库托管(2)Pull Requests(拉取请求)(3) Issue Tracking(问题跟踪)(4) 团队管理(5) 社交功能(6)个人资料和贡…...
Unity-Mirror网络框架-从入门到精通之Room示例
文章目录 前言Room示例场景设置NetworkRoomManagerSpawnerRewardRoomPlayerGamePlayer 最后 前言 在现代游戏开发中,网络功能日益成为提升游戏体验的关键组成部分。Mirror是一个用于Unity的开源网络框架,专为多人游戏开发设计。它使得开发者能够轻松实现…...
httpslocalhostindex 配置的nginx,一刷新就报404了
当你的Nginx配置导致页面刷新时报404错误时,通常是由于以下几个原因造成的: 静态文件路径配置错误:Nginx没有正确地指向静态文件的目录。前端路由问题:如果是SPA(单页应用),刷新页面时Nginx没有…...
Java重要面试名词整理(十九):Seata
文章目录 分布式事务概述实现思路:两阶段提交协议(2PC) SeataSeata的三大角色Seata的生命周期Seata解决方案 AT模式一阶段二阶段 XA模式TCC模式如何处理空回滚如何处理幂等如何处理悬挂 SAGA模式四种模式对比 分布式事务概述 在微服务架构中,完成某一个…...
OpenCV和PyQt的应用
1.创建一个 PyQt 应用程序,该应用程序能够: 使用 OpenCV 加载一张图像。在 PyQt 的窗口中显示这张图像。提供四个按钮(QPushButton): 一个用于将图像转换为灰度图一个用于将图像恢复为原始彩色图一个用于将图像进行翻…...
【Linux】进程间通信(一)
目录 一、进程间通信1.1 进程间通信目的1.2 理解进程间通信1.3 进程间通信发展1.4 进程间通信分类 二、管道2.1 什么是管道2.2 管道的原理2.3 匿名管道2.3.1 pipe函数2.3.2 匿名管道的实现2.3.3 匿名管道小结2.3.3.1 匿名管道的四种情况2.3.3.2 匿名管道的五种特性 2.3.4 匿名管…...
Fama MacBeth两步法与多因子模型的回归检验
Fama MacBeth两步法与多因子模型的回归检验 – 潘登同学的因子投资笔记 本文观点来自最近学习的石川老师《因子投资:方法与实践》一书 文章目录 Fama MacBeth两步法与多因子模型的回归检验 -- 潘登同学的因子投资笔记 多因子回归检验时序回归检验截面回归检验Fama–…...
Postman[4] 环境设置
作用:不同的环境可以定义不同的参数,在运行请求时可以根据自己的需求选择需要的环境 1.创建Environment 步骤: Environment-> ->命名->添加环境变量 2.使用Environment 步骤:Collection- >右上角选择需要的环境...
【paddle】初次尝试
张量 张量是 paddlepaddle, torch, tensorflow 等 python 主流机器学习包中唯一通货变量,因此应当了解其基本的功能。 张量 paddle.Tensor 与 numpy.array 的转化 import paddle as paddle import matplotlib.pyplot as plt apaddle.to_t…...
开源架构中的数据库选择优化版
上一篇文章推荐: 开源架构学习指南:文档与资源的智慧锦囊(New) 我管理的社区推荐:【青云交社区】和【架构师社区】 推荐技术圈福利社群:点击快速加入 开源架构中的数据库选择优化版 一、引言二、关系型开源…...
Echarts+vue电商平台数据可视化——webSocket改造项目
websocket的基本使用,用于测试前端能否正常获取到后台数据 后台代码编写: const path require("path"); const fileUtils require("../utils/file_utils"); const WebSocket require("ws"); // 创建WebSocket服务端的…...
【网络安全实验室】SQL注入实战详情
如果额头终将刻上皱纹,你只能做到,不让皱纹刻在你的心上 1.最简单的SQL注入 查看源代码,登录名为admin 最简单的SQL注入,登录名写入一个常规的注入语句: 密码随便填,验证码填正确的,点击登录…...
【信息系统项目管理师】第14章:项目沟通管理过程详解
更多内容请见: 备考信息系统项目管理师-专栏介绍和目录 文章目录 一、规划沟通管理1、输入2、工具与技术3、输出二、管理沟通1、输入2、工具与技术3、输出三、监督沟通1、输入2、工具与技术3、输出一、规划沟通管理 定义:规划沟通管理是基于每个干系人或干系人群体的信息需求…...
YOLOv5部署到web端(flask+js简单易懂)
文章目录 前言最终实现效果图后端实现 主界面检测函数检测结果显示 前端实现 主界面(index.html)显示图片界面 总结 前言 最近,老板让写一个程序把yolov5检测模型部署到web端,在网页直接进行目标检测。经过1个星期的努力,终于实…...
什么是自治系统和非自治系统
自治系统 自治系统的特征是其状态方程不依赖于时间。举个简单的例子,考虑一阶常微分方程: d x d t − x \frac{dx}{dt} -x dtdx−x 这是一个经典的指数衰减过程,其中状态 (x) 随时间 (t) 衰减。这个系统是自治的,因为它的演…...
使用 CSS 的 `::selection` 伪元素来改变 HTML 文本选中时的背景颜色
定义 ::selection 伪元素: 在你的 CSS 文件中,添加 ::selection 伪元素,并设置 background-color 属性来改变选中文本的背景颜色。 示例代码: ::selection {background-color: yellow; /* 你可以根据需要更改颜色 */color: black…...
从0入门自主空中机器人-3-【环境与常用软件安装】
关于本课程: 本次课程是一套面向对自主空中机器人感兴趣的学生、爱好者、相关从业人员的免费课程,包含了从硬件组装、机载电脑环境设置、代码部署、实机实验等全套详细流程,带你从0开始,组装属于自己的自主无人机,并让…...
jmeter分布式启动
https://www.cnblogs.com/qtclm/p/11082081.html 1、代理机:输入“ipconfig”,找到IP地址,在Jmeter/bin/jmeter.properties设置remote host 启动jmeter server 1、控制机:输入“ipconfig”,找到IP地址,在J…...
【Linux】HTTP cookie与session
在登录B站时,有登录和未登录两种状态, 问题:B站是如何认识我这个登录用户的?问题:HTTP是无状态、无连接的,怎么能够记住我? HTTP协议是无状态、无连接的。比如客户端(浏览器&#…...
20. 【.NET 8 实战--孢子记账--从单体到微服务】--简易权限--补充--自动添加接口地址
在同学学习过程,部分同学向我反馈说每次新增接口都要在接口表里手动添加一条接口很麻烦,因此我把项目代码做了一个改动,使我们不需要手动添加,每次项目运行起来后就会自动把新的接口地址添加进去。 一、实现 首先,我…...
[Linux] 服务器CPU信息
(1)查看CPU信息(型号) cat /proc/cpuinfo | grep name | cut -f2 -d: | uniq -c输出:可以看到有128个虚拟CPU核心,型号是后面一串 128 Intel(R) Xeon(R) Platinum 8336C CPU 2.30GHz(2&…...
java_使用阿里云oss服务存储图片
什么情况下可以使用阿里云oss服务存储图片? 对图片的访问速度有高要求时使用,方便用户快速的(比如在网页页面中)访问到图像 参考:41 尚上优选项目-平台管理端-商品信息管理模块-阿里云OSS介绍_哔哩哔哩_bilibili 1.…...
Dali 1.1.4 | 解锁版AI图像生成器,无限生成
Dali是一款先进的AI图像生成器应用程序,能够根据您的描述生成不同风格的独特图像。它不仅限于生成艺术作品,还可以创建创新的纹身设计、独一无二的标志以及超写实照片。该软件使用尖端技术,将想象力转化为现实,提供迷人的数字艺术…...
快手视频不让下载怎么保存到相册
快手,作为国内领先的短视频平台之一,吸引了无数用户发布创意视频、分享生活点滴。随着短视频版权保护和用户隐私问题的日益严重,越来越多的视频内容在平台内都采取了“不让下载”的限制。面对这一情况,很多用户都希望能够保存自己…...
Linux环境下CUDA与对应版本CuDNN的安装指南
转载:Linux环境下CUDA与对应版本CuDNN的安装指南-百度开发者中心...
mybatisPlus打印sql配置
MyBatis-Plus 提供了方便的配置方式来打印 SQL 查询语句,以便进行调试和性能分析。可以通过配置 log 来输出 SQL 语句以及执行的参数。 方法 1:通过 application.properties 或 application.yml 配置打印 SQL 可以通过配置 application.properties 或 a…...
InstructGPT:基于人类反馈训练语言模型遵从指令的能力
大家读完觉得有意义记得关注和点赞!!! 大模型进化树,可以看到 InstructGPT 所处的年代和位置。来自 大语言模型(LLM)综述与实用指南(Amazon,2023) 目录 摘要 1 引言 …...
曾仕强解读《易经》
曾仕强对《易经》的解读内容丰富、深入浅出,以下是一些主要方面: 讲解《易经》基本原理 - 阴阳之道:曾仕强将阴阳比作白天与黑夜、男人与女人等,指出阴阳看似对立,实则相辅相成,强调为人处世要把握阴阳…...
http报头解析
http报文 http报文主要有两类是常见的,第一类是请求报文,第二类是响应报文,每个报头除了第一行,都是采用键值对进行传输数据,请求报文的第一行主要包括http方法(GET,PUT, POST&#…...