原创博客,转载请注明出处,谢谢!

代码下载地址:https://github.com/XiuzeZhou/NASA

1. 需求

最近一段时间,趁着新能源的热度,开启了我的锂电池研究计划。大部分有关锂电池寿命预测的论文都会用到两个数据集:NASA 和 CALCE。NASA 是美国宇航局 NASA 埃姆斯研究中心提供的锂电池老化实验数据,CALCE 是马里兰大学高级生命周期工程中心的电池循环测试数据集。

最近几个月,我会陆续把经典的神经网络框架都写出来,预测锂电池的寿命。先从最简单的 MLP 开始入手。

2. NASA 数据集

I. 测试条件

温度:室温 24 度

充电:以 1.5A 的恒定电流(CC)模式进行充电,直到电池电压达到 4.2V,然后以恒定电压(CV)模式充电,直到充电电流降至 20mA。

放电:以 2A 的恒定电流(CC)模式进行放电,直到电池 5、6、7 和 18 的电压降到 2.7V,2.5V,2.2V 和 2.5V。

终止条件:当电池达到寿命终止(End Of Life, EOF)标准——额定容量下降到它的30%,即电池 5、6、7 和 18 的额定容量从 2Ahr 到 1.4Ahr。

II. 数据集介绍及预处理

NASA 原始数据集的数据格式是 mat 的,需要先进行预处理。首先读取 mat 文件,然后从中筛选出关键信息:容量,电流和电压。

这部分详细的内容,请看我的博客: https://snailwish.com/395/

加载数据完毕后,我们定义函数来查看锂电池随充放电次数的变化曲线:

fig, ax = plt.subplots(1, figsize=(12, 8))
color_list = ['b:', 'g--', 'r-.', 'c:']
c = 0
for name,color in zip(Battary_list, color_list):
    df_result = Battery[name]
    ax.plot(df_result[0], df_result[1], color, label=name)

ax.set(xlabel='Discharge cycles', ylabel='Capacity (Ah)', 
       title='Capacity degradation at ambient temperature of 24°C')
plt.legend()

3. 训练集和测试集

I. 序列生成

锂电池容量是一个整体减小趋势的序列。我们采用一个长度为 window_size 的滑动窗口,从序列头部到尾部,每次移动 1 个数据,来截取我们需要的训练数据。比如原始序列是 [1, 2, 3, 4, 5],window_size=3,那么 (x, y) 对应的训练数据和标签就是 ([1, 2, 3], 4),([2, 3, 4], 5)。

def build_sequences(text, window_size):
    #text:list of capacity
    x, y = [],[]
    for i in range(len(text) - window_size):
        sequence = text[i:i+window_size]
        target = text[i+1:i+1+window_size]

        x.append(sequence)
        y.append(target)

    return np.array(x), np.array(y)

II. 生成训练集和测试集

可以采用留一评估,即一个数据作为测试集,其他所有数据作为训练集。这样做比较简单。因此,我们把三组锂电池的全部数据为训练集,剩余一组数据为测试集。

# 留一评估:一组数据为测试集,其他所有数据全部拿来训练
def get_train_test(data_dict, name, window_size=8, train_ratio=0.):
    data_sequence=data_dict[name][1]
    train_data, test_data = data_sequence[:window_size+1], data_sequence[window_size+1:]
    train_x, train_y = build_sequences(text=train_data, window_size=window_size)
    for k, v in data_dict.items():
        if k != name:
            data_x, data_y = build_sequences(text=v[1], window_size=window_size)
            train_x, train_y = np.r_[train_x, data_x], np.r_[train_y, data_y]

    return train_x, train_y, list(train_data), list(test_data)

4. MLP 网络

I. 网络定义

MLP 的定义很简单,主要的参数就是隐含层的层数堆叠。

class Net(nn.Module):
    def __init__(self, feature_size=8, hidden_size=[16, 8]):
        super(Net, self).__init__()
        self.feature_size, self.hidden_size = feature_size, hidden_size
        self.layer0 = nn.Linear(self.feature_size, self.hidden_size[0])
        self.layers = [nn.Sequential(
            nn.Linear(self.hidden_size[i], self.hidden_size[i+1]), nn.ReLU()) 
                       for i in range(len(self.hidden_size) - 1)]
        self.linear = nn.Linear(self.hidden_size[-1], 1)

    def forward(self, x):
        out = self.layer0(x)
        for layer in self.layers:
            out = layer(out)
        out = self.linear(out) 
        return out

II. 评估方法

a. 均方根差 (Root Mean Square Error,缩写 RMSE)

b. 平均绝对误差 (Mean Absolute Error,缩写 MAE)

c. 剩余充放电次数相对误差 (Relative Error,缩写 RE)

III. 训练模型

训练函数定义:LR:学习率,feature_size:也能本特征数目,hidden_size:隐含层结构,weight_decay:正则项系数,window_size:滑动宽口大小,EPOCH:训练次数,seed:随机数种子

def tain(LR=0.01, feature_size=8, hidden_size=[16,8], weight_decay=0.0, 
         window_size=8, EPOCH=1000, seed=0):
    mae_list, rmse_list, re_list = [], [], []
    result_list = []
    for i in range(4):
        name = Battary_list[i]
        train_x, train_y, train_data, test_data = get_train_test(Battery, name, window_size)
        train_size = len(train_x)
        print('sample size: {}'.format(train_size))

        setup_seed(seed)
        model = Net(feature_size=feature_size, hidden_size=hidden_size)
        if torch.cuda.is_available():
            model = model.cuda()

        optimizer = torch.optim.Adam(model.parameters(), lr=LR, weight_decay=weight_decay)
        criterion = nn.MSELoss()

        test_x = train_data.copy()
        loss_list, y_ = [0], []
        for epoch in range(EPOCH):
            X = np.reshape(train_x/Rated_Capacity, (-1, feature_size)).astype(np.float32)
            y = np.reshape(train_y[:,-1]/Rated_Capacity,(-1,1)).astype(np.float32)

            X, y = torch.from_numpy(X), torch.from_numpy(y)
            output= model(X)
            loss = criterion(output, y)
            optimizer.zero_grad()              # clear gradients for this training step
            loss.backward()                    # backpropagation, compute gradients
            optimizer.step()                   # apply gradients

            if (epoch + 1)%100 == 0:
                test_x = train_data.copy() #每100次重新预测一次
                point_list = []
                while (len(test_x) - len(train_data)) < len(test_data):
                    x = np.reshape(np.array(test_x[-feature_size:])/Rated_Capacity, 
                                   (-1, feature_size)).astype(np.float32)
                    x = torch.from_numpy(x)
                    pred = model(x) 
                    next_point = pred.data.numpy()[0,0] * Rated_Capacity
                    test_x.append(next_point)#测试值加入原来序列用来继续预测下一个点
                    point_list.append(next_point)#保存输出序列最后一个点的预测值
                y_.append(point_list)#保存本次预测所有的预测值
                loss_list.append(loss)
                mae, rmse = evaluation(y_test=test_data, y_predict=y_[-1])
                re = relative_error(
                    y_test=test_data, y_predict=y_[-1], threshold=Rated_Capacity*0.7)
                print('epoch:{:<2d} | loss:{:<6.4f} | MAE:{:<6.4f} | RMSE:{:<6.4f} | \
                RE:{:<6.4f}'.format(epoch, loss, mae, rmse, re))
            if (len(loss_list) > 1) and (abs(loss_list[-2] - loss_list[-1]) < 1e-5):
                break

        mae, rmse = evaluation(y_test=test_data, y_predict=y_[-1])
        re = relative_error(
            y_test=test_data, y_predict=y_[-1], threshold=Rated_Capacity*0.7)
        mae_list.append(mae)
        rmse_list.append(rmse)
        re_list.append(re)
        result_list.append(y_[-1])
    return re_list, mae_list, rmse_list, result_list

5. 实验结果

I. 定量评估

定义好参数后,采用留一评估,不同的种子下运行 10 次,即设置 10 个不同的随机种子,然后取均值。

window_size = 8
EPOCH = 1000
LR = 0.01    # learning rate
feature_size = window_size
hidden_size = [16,8]
weight_decay = 0.0
Rated_Capacity = 2.0

MAE, RMSE, RE = [], [], []
for seed in range(10):
    re_list, mae_list, rmse_list, _ = tain(LR, feature_size, hidden_size, 
                                           weight_decay, window_size, EPOCH, seed)
    RE.append(np.mean(np.array(re_list)))
    MAE.append(np.mean(np.array(mae_list)))
    RMSE.append(np.mean(np.array(rmse_list)))
    print('------------------------------------------------------------------')

print('RE: mean: {:<6.4f} | std: {:<6.4f}'.format(
    np.mean(np.array(RE)), np.std(np.array(RE))))
print('MAE: mean: {:<6.4f} | std: {:<6.4f}'.format(
    np.mean(np.array(MAE)), np.std(np.array(MAE))))
print('RMSE: mean: {:<6.4f} | std: {:<6.4f}'.format(
    np.mean(np.array(RMSE)), np.std(np.array(RMSE))))
print('------------------------------------------------------------------')
print('------------------------------------------------------------------')
MAE RMSE RE
0.0852 0.0959 0.4185

II. 定性评估

接下来,查看预测的实验效果:查看每组电池的拟合曲线。

seed = 0
_, _, _, result_list = tain(LR, feature_size, hidden_size, 
                            weight_decay,window_size, EPOCH, seed)
for i in range(4):
    name = Battary_list[i]
    train_x, train_y, train_data, test_data = get_train_test(Battery, name, window_size)

    aa = train_data[:window_size+1].copy() # 第一个输入序列
    [aa.append(a) for a in result_list[i]] # 测试集预测结果

    battery = Battery[name]
    fig, ax = plt.subplots(1, figsize=(12, 8))
    ax.plot(battery[0], battery[1], 'b.', label=name)
    ax.plot(battery[0], aa, 'r.', label='Prediction')
    plt.plot([-1,170],[Rated_Capacity*0.7, Rated_Capacity*0.7], c='black', lw=1, ls='--')
    ax.set(xlabel='Discharge cycles', ylabel='Capacity (Ah)', 
           title='Capacity degradation at ambient temperature of 24°C')
    plt.legend()




III. 总结

采用留一评估,从实验结果上看,在 NASA 这个数据集上,MLP 的效果真心不好。大概是因为有两点:

(1) 电池的容量序列中存在大量跳跃点(容量重生现象),特别是曲线刚开始阶段,导致模型很难从电池历史记录中学习到好的趋势来电池的寿命;

(2) 这些数据序列偏差比较大,如 B0007 没有 1.4Ah 后的数据,B0018 数据很短、波动很严重,这导致模型很难学到共性;

注意:如果容量曲线差距较小,波动较缓,MLP 是可以获得不错效果的,详细请查看 MLP 在马里兰大学锂电池数据集 CALCE 上的实验效果。

更多内容

1. NASA 锂电池数据集,基于 Python 的锂电池寿命预测: https://snailwish.com/395/

2. 马里兰大学锂电池数据集 CALCE,基于 Python 的锂电池寿命预测: https://snailwish.com/437/

3. 马里兰大学锂电池数据集 CALCE,基于 Python 的 MLP 锂电池寿命预测: https://snailwish.com/

4. NASA 和 CALCE 锂电池数据集,基于 Pytorch 的 RNN、LSTM、GRU 寿命预测: https://snailwish.com/497/

5. 基于 Pytorch 的 Transformer 锂电池寿命预测: https://snailwish.com/555/

6. 锂电池研究之七——基于 Pytorch 的高斯函数拟合时间序列数据: https://snailwish.com/576/