我在使用Keras的model.fit()和sklearn的model.fit()函数时发现了一个奇怪的差异。当在循环中调用model.fit()时,使用Keras顺序模型会得到不一致的预测结果,而使用sklearn模型则不会出现这种情况。请看以下示例代码以重现这一现象。
from numpy.random import seedseed(1337)import tensorflow as tftf.random.set_seed(1337)from sklearn.linear_model import LogisticRegressionfrom keras.models import Sequentialfrom keras.layers import Dense, Dropoutfrom keras.layers import InputLayerfrom sklearn.datasets import make_blobsfrom sklearn.preprocessing import MinMaxScalerimport numpy as npdef get_sequential_dnn(NUM_COLS, NUM_ROWS): # 模型代码if __name__ == "__main__": input_size = 10 X, y = make_blobs(n_samples=100, centers=2, n_features=input_size, random_state=1 ) scalar = MinMaxScaler() scalar.fit(X) X = scalar.transform(X) model = get_sequential_dnn(X.shape[1], X.shape[0]) # print(model.summary()) # model = LogisticRegression() for i in range(2): model.fit(X, y, epochs=100, verbose=0, shuffle=False) # model.fit(X, y) Xnew, _ = make_blobs(n_samples=3, centers=2, n_features=10, random_state=1) Xnew = scalar.transform(Xnew) # 进行预测 # ynew = model.predict_proba(Xnew)[:, 1] ynew = model.predict_proba(Xnew) ynew = np.array(ynew) # 显示输入和预测输出 print('--------------') for i in range(len(Xnew)): print("X=%s \n Predicted=%s" % (Xnew[i], ynew[i]))
运行结果如下
--------------X=[0.32799209 0.32682211 0.62699485 0.89987274 0.59894281 0.94662653 0.77125788 0.73345369 0.2153754 0.35317172] Predicted=[0.9931685]X=[0.60876924 0.33208319 0.24770841 0.11435312 0.66211608 0.17361879 0.12891829 0.25729677 0.69975833 0.73165292] Predicted=[0.35249507]X=[0.65154993 0.26153846 0.2416324 0.11793901 0.7047334 0.17706289 0.07761879 0.45189967 0.8481064 0.85092378] Predicted=[0.35249507]--------------X=[0.32799209 0.32682211 0.62699485 0.89987274 0.59894281 0.94662653 0.77125788 0.73345369 0.2153754 0.35317172] Predicted=[1.]X=[0.60876924 0.33208319 0.24770841 0.11435312 0.66211608 0.17361879 0.12891829 0.25729677 0.69975833 0.73165292] Predicted=[0.17942095]X=[0.65154993 0.26153846 0.2416324 0.11793901 0.7047334 0.17706289 0.07761879 0.45189967 0.8481064 0.85092378] Predicted=[0.17942095]
然而,如果我使用逻辑回归(取消注释相应的代码行),预测结果是一致的:
--------------X=[0.32799209 0.32682211 0.62699485 0.89987274 0.59894281 0.94662653 0.77125788 0.73345369 0.2153754 0.35317172] Predicted=0.929209043999009X=[0.60876924 0.33208319 0.24770841 0.11435312 0.66211608 0.17361879 0.12891829 0.25729677 0.69975833 0.73165292] Predicted=0.04643513037543502X=[0.65154993 0.26153846 0.2416324 0.11793901 0.7047334 0.17706289 0.07761879 0.45189967 0.8481064 0.85092378] Predicted=0.038716408758471876--------------X=[0.32799209 0.32682211 0.62699485 0.89987274 0.59894281 0.94662653 0.77125788 0.73345369 0.2153754 0.35317172] Predicted=0.929209043999009X=[0.60876924 0.33208319 0.24770841 0.11435312 0.66211608 0.17361879 0.12891829 0.25729677 0.69975833 0.73165292] Predicted=0.04643513037543502X=[0.65154993 0.26153846 0.2416324 0.11793901 0.7047334 0.17706289 0.07761879 0.45189967 0.8481064 0.85092378] Predicted=0.038716408758471876
我明白解决这一问题的一个明显方法是在循环前拟合模型,可能Keras模型在数据和标签拟合过程中具有很强的随机性,但在某些情况下,你需要使用循环来获得预测分数。例如,如果你想对训练数据进行10折交叉验证以获取AUC、灵敏度和特异性值。在这些情况下,这种随机性是不可接受的。
是什么导致了这种不一致性?解决方案是什么?
回答:
在尝试使用Keras获得可重现结果时,您的方法存在几个问题。
- 您在已经拟合的模型(当
i==0
时)上再次调用fit
(当i==1
时)。因此,优化器在两种情况下看到了不同的初始权重集,因此您最终会得到两个不同的模型。解决方案:每次都获取一个新的模型。Sklearn的情况并非如此,每次调用fit
时都会使用新初始化的权重开始。 model.fit
内部可能会使用当前阶段的随机数生成器。您在循环外设置了种子,因此当第二次调用fit
时,状态会有所不同。解决方案:在循环内设置种子。
存在问题的示例代码
# 这里是问题2tf.random.set_seed(1337)def get_model(): model = Sequential() model.add(Dense(4, input_dim=8, activation='relu')) model.add(Dense(1, activation='sigmoid')) model.compile(loss='binary_crossentropy', optimizer='adam') return modelX = np.random.randn(10,8)y = np.random.randn(10,1)# 这里是问题1model = get_model()results = []for i in range(10): model.fit(X, y, epochs=5, verbose=0, shuffle=False) results.append(np.sum(model.predict(X)))assert np.all(np.isclose(results, results[0]))
如您所见,断言失败了
修正后的代码
results = []for i in range(10): tf.random.set_seed(1337) model = get_model() model.fit(X, y, epochs=5, verbose=0, shuffle=False) results.append(np.sum(model.predict(X)))assert np.all(np.isclose(results, results[0]))