python 算法模型 热加载_Python功能点实现:数据热更新

关键词:热更新 | 热重载 | 定时更新 | 即时更新 | 缓存 | functools | cachetools | LRU | TTL

假设应用需要加载一个配置文件config.txt,一般的做法类似于:

with open('config.txt') as f:

parameters = f.read()

接下来parameters中存储的数据就可以被其他代码使用,但是这样写的话程序每次启动后,数据是固定死的,无法动态地自我更新,每次要修改配置/模型只能重启整个应用。

本文中热更新的意思是在应用运行时内,从外部(如文件、数据库、REST API)中获得数据并更新应用内的Python对象。应用场景一般是应用作为服务(有对外的API),需要在不重启的前提下更新自己的配置参数或者算法模型。

热更新可以分为两种:定时更新(periodic update)和即时更新(on-call update),前者周期性地、主动地执行更新,后者则是被动地等待,直到接收到某种来自应用外部的信号才会执行更新。本文将通过内存缓存(Memory Cache)和装饰器(Decorator)技术来实现两种热更新。

内存缓存

先来说明一下内存缓存。缓存中的数据一般以键值对(key-value pair)的形式存在,value中放数据本身,key中放数据的某种描述名。缓存的容量决定了其最大可容纳的数据条数,当容量已满时再向缓存中存入新的数据,缓存就会采取开始清理行为:清除掉已存的部分数据,从而为新数据腾地方。清理的策略(判定何时需要清理、具体如何清理数据)有很多种,决定了缓存的不同类型。

Python中缓存常被写成装饰器的形式,缓存数据的key里放的是被装饰原函数的参数值组合(key的生成方法可以不同,后面还会提到),value里放的则是原函数的返回值。这样当函数被调用时,程序会先去缓存数据里找是不是已经有相同的参数值,如果有就直接返回已缓存的返回值,不重复进行原函数内的计算。

注意缓存的使用有一个隐含前提:函数本身是无状态的。假如函数内引用了全局变量,或者存在闭包,那同样的参数值不一定必然计算出相同的返回值。这样缓存的返回值和实际期望的返回值就不一定一致了。

定时热更新

定时更新的实现使用了来源于第三方库cachetools的TTLCache,TTL(Time-to-Live)指存在时长策略。这种缓存为每一条存入的数据记录其存在的时长。每次调用都会检查是否存在超过某个设定时长阈值的数据,如果有就会开始清理行为:所有超时数据都会被清除掉;如果没有超时数据,缓存将会换用LRU策略,使缓存不超出容量大小(下一部分会提到LRU的具体策略)。

当我们将TTL缓存的容量设为1时、且用于加载数据的原函数参数不变的情况下,逻辑就变成了定时更新:

未超过时长:缓存保留,每次调用都使用缓存数据

超过时长,缓存清空(只有一条数据),程序重新计算(在这里即重新加载数据)

示例代码如下(运行需要安装cachetools,并在文首链接里下载完整的项目):

import time

import cachetools

from utils import change_conf_file

ROTATE = 5

@cachetools.cached(cachetools.TTLCache(1, ROTATE))

def reload():

print('Cache cleared, reloading config...')

with open('config.txt') as f:

parameters = f.read()

return parameters

class Model():

def log(self):

self.model = reload()

print(self.model)

if __name__ == '__main__':

# Reload automatically every [ROTATE] seconds

model = Model()

while True:

time.sleep(2)

change_conf_file() # change data

model.log()

即时热更新

即时更新的实现使用了来源于Python内置库functools的lru_cache,LRU(Least Recently Used)指最少使用策略。这种缓存为每一条存入的数据记录其被使用的次数,每次调用都会检查缓存大小是否超出容量,如超出就会开始清理行为:会从使用次数最少的数据开始清理,直到缓存大小处于容量以内。

当我们将LRU缓存的容量设为1、且用于加载数据的原函数参数不变的情况下,原函数只有在第一次被调用时才会发生计算,之后调用都会直接返回缓存中的数据,到这里与一般的读取效果上并无区别。当我们需要热更新数据的时候,只需要主动清空缓存。如下例中Getter.getModel.cache_clear()。其中Getter.getModel是装饰后的函数,其中带有用于清理缓存的函数cache_clear()。有了这个扳机,我们只需要额外开发一个API(比如REST下的GET)来触发它,这样通过外部即时call API就可以进行热更新了。

示例代码如下(运行需要在文首链接里下载完整的项目):

import time

from functools import lru_cache

from utils import change_conf_file

class Getter:

@staticmethod

@lru_cache(1)

def getModel():

with open('config.txt') as f:

model = f.read()

return model

class Model():

def log(self):

self.model = Getter.getModel()

print(self.model)

if __name__ == '__main__':

# Reload only when cache_clear() is called

model = Model()

while True:

model.log()

time.sleep(2)

change_conf_file() # change data

Getter.getModel.cache_clear()

print('Cache cleared, reloading config...')

model.log()

这里补充一个细节,上面的示例中被缓存装饰器装饰的原函数getModel是一个无参数的函数,这种情况下lru_cache是如何运作的呢?lru_cache的实现中使用函数functools._make_key来生成缓存的key。在Python中,当原函数无参数时,默认参数args可认为是空元组(),可选参数可认为是空字典{},在这种情况下生成的key将会是空列表[](注意列表是不可hash的,不可直接作为字典的key,functools里的实际数据结构较为复杂,这里没有深入)。上一部分提到的第三方库cachetools实现了类似的方法keys.typedkey,两者生成的key存在区别,但是结合其他方法,行为在大部分情况是一样的,包括本文中的无参数函数情况。

import time

from functools import _make_key

from cachetools.keys import typedkey

if __name__ == '__main__':

print(_make_key((), {}, False)) # []

print(typedkey((), {}, False)) # ((), {}, , )

扩展问题

多线程情况:本文的热更新方法均基于缓存,而由于缓存涉及到读写操作,在多线程环境下我们需要考虑其正确性。functools.lru_cache和cachetools.TTLCache里均有使用到锁的机制,再考虑到Python的GIL锁,本文所述的热更新在线程安全上应该算是有保障的,但目前未经试验无法完全下断言。更正:functools.lru_cache的文档中提到多线程环境下hit和miss的计数只是近似值,而Cachetool的文档中则明确提到cachetools.TTLCache这样的类是非线程安全的,需要额外提供锁以实现同步。最开始写这篇文章的时候我对GIL的理解有误,按我现在的理解,GIL宽泛地讲只会阻止多线程调用多个CPU,但同一个CPU下的多线程仍然是有效的(不然还要多线程干嘛),所以同步的问题还是要考虑清除。

import:假如我们在一个模块(module)里更新model,而另一个模块import这个model,那么当原模块的model热更新后,import得到的model并不会更新,这种行为可能与Python自身的module cache有关。要实现所有module的热更新,能考虑的一个办法是让数据自己成为一个模块,使其变得可以在模块之间共享。

吐槽:这篇写得我浑身无力啊,本来觉得很简单,就平时用的小东西拿来拎拎清,没想到越拎越深...很多时候我们很happy是因为我们站在冰山的最上面,不用面对水下的魔鬼细节...作为搞技术的,我们还是不能光看脸,也要多盯裆(≖_≖)✧

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,我们可以使用Python中的NumPy库来实现神经网络学习,并使用Matplotlib库进行可视化。 首先,我们需要准备数据集并对其进行预处理。这里我们使用一个简单的手写数字识别数据集,包含训练集和测试集,每张图片大小为28x28像素。我们将每张图片展开成一维向量,并进行归一化处理。 ```python import numpy as np import matplotlib.pyplot as plt # 加载数据集 train_data = np.load('train_data.npy') test_data = np.load('test_data.npy') # 对数据进行预处理 train_X = train_data[:, :-1] / 255.0 train_y = train_data[:, -1] test_X = test_data[:, :-1] / 255.0 test_y = test_data[:, -1] # 将标签转换为独编码 train_Y = np.zeros((train_y.shape[0], 10)) train_Y[np.arange(train_y.shape[0]), train_y] = 1 test_Y = np.zeros((test_y.shape[0], 10)) test_Y[np.arange(test_y.shape[0]), test_y] = 1 ``` 接下来,我们定义一个三层神经网络模型,包含一个输入层、一个隐藏层和一个输出层。我们使用交叉熵损失函数和L2正则化,同时使用随机梯度下降算法进行优化。 ```python class NeuralNetwork: def __init__(self, input_size, hidden_size, output_size, learning_rate, reg_lambda): self.input_size = input_size self.hidden_size = hidden_size self.output_size = output_size self.learning_rate = learning_rate self.reg_lambda = reg_lambda # 初始化权重 self.W1 = np.random.randn(input_size, hidden_size) self.b1 = np.zeros((1, hidden_size)) self.W2 = np.random.randn(hidden_size, output_size) self.b2 = np.zeros((1, output_size)) def forward(self, X): # 前向传播 self.z1 = np.dot(X, self.W1) + self.b1 self.a1 = np.tanh(self.z1) self.z2 = np.dot(self.a1, self.W2) + self.b2 exp_scores = np.exp(self.z2) self.probs = exp_scores / np.sum(exp_scores, axis=1, keepdims=True) return self.probs def compute_cost(self, X, y): # 计算损失函数 num_samples = X.shape[0] data_loss = -np.sum(y * np.log(self.probs)) / num_samples reg_loss = 0.5 * self.reg_lambda * (np.sum(np.square(self.W1)) + np.sum(np.square(self.W2))) return data_loss + reg_loss def backward(self, X, y): # 反向传播 delta3 = self.probs - y dW2 = np.dot(self.a1.T, delta3) db2 = np.sum(delta3, axis=0, keepdims=True) delta2 = np.dot(delta3, self.W2.T) * (1 - np.power(self.a1, 2)) dW1 = np.dot(X.T, delta2) db1 = np.sum(delta2, axis=0) # 加上L2正则化的梯度 dW2 += self.reg_lambda * self.W2 dW1 += self.reg_lambda * self.W1 # 更新权重和偏置 self.W1 -= self.learning_rate * dW1 self.b1 -= self.learning_rate * db1 self.W2 -= self.learning_rate * dW2 self.b2 -= self.learning_rate * db2 def train(self, X, y, num_iters, batch_size): # 随机梯度下降算法 for i in range(num_iters): # 随机选择一个batch的数据进行训练 batch_indices = np.random.choice(X.shape[0], batch_size) X_batch = X[batch_indices] y_batch = y[batch_indices] # 前向传播和反向传播 self.forward(X_batch) self.backward(X_batch, y_batch) # 每隔100次迭代输出一次损失函数 if i % 100 == 0: cost = self.compute_cost(X, y) print('Iteration %d, cost: %f' % (i, cost)) def predict(self, X): # 预测结果 probs = self.forward(X) return np.argmax(probs, axis=1) ``` 接下来,我们实例化一个神经网络模型并进行训练。我们使用2000次迭代,每次随机选择300个样本进行训练,并设置L2正则化参数为0.1。 ```python # 实例化神经网络模型并进行训练 nn = NeuralNetwork(input_size=784, hidden_size=50, output_size=10, learning_rate=0.01, reg_lambda=0.1) nn.train(train_X, train_Y, num_iters=2000, batch_size=300) ``` 最后,我们使用测试集评估模型的性能,并可视化模型在测试集上的结果。 ```python # 在测试集上评估模型 test_pred = nn.predict(test_X) accuracy = np.mean(test_pred == test_y) * 100 print('Test accuracy: %.2f%%' % accuracy) # 可视化模型在测试集上的结果 fig, axes = plt.subplots(5, 5, figsize=(10, 10)) for i, ax in enumerate(axes.flat): ax.imshow(test_X[i].reshape((28, 28)), cmap='gray') ax.set_title('True: %d, Pred: %d' % (test_y[i], test_pred[i])) ax.axis('off') plt.show() ``` 运行完整的代码后,可以得到模型在测试集上的准确率,并可视化模型在测试集上的预测结果。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值