Predicting Battery Lifetime with CNNs | by Hannes Knobloch | Towards Data Science

This article was written by Hannes Knobloch, Adem Frenk, and Wendy Chang.You can find the source code of this project on GitHub:
github.com
Lithium-ion batteries power almost every electronic device in our lives, including phones and laptops. They’re at the heart of renewable energy and e-mobility. For years companies have tried to predict how many charging cycles a battery will last before it dies. Better predictions would enable more accurate quality assessment and improve long-term planning.
But that’s difficult, because every battery ages differently, depending on its usage and conditions during manufacturing. A recent paper, called Data-driven prediction of battery cycle life before capacity degradation, by Kristen A. Severson et al., claims to have found the key to solve this problem by “combining comprehensive experimental data and artificial intelligence”. Even though their results are ahead of traditional methods, the team focused more on their domain knowledge in electrical engineering than on the machine learning part.
The good news: The researchers have made the dataset, the largest of its kind, publicly available! Although the dataset is limited to measurements from new batteries in a lab setting, it is still the most comprehensive of its kind. We used a more sophisticated machine learning approach to build a more versatile and accurate model for predicting battery lifetime (under these circumstances). Here you can see it in action: www.ion-age.org/example . To understand fully what the model does, read on!
This article describes our research process from start to finish. Additionally, we describe our workflow and tools to give beginner data scientists inspiration for their own projects.
The authors of the paper focused on brand-new batteries and predicted their lifetime. That’s impressive, but if you want to diagnose a battery that’s already been in use, you’re out of luck. Instead we want to predict the remaining cycle life of any battery, used and new.
Another caveat is the amount of data needed for a prediction. The researchers used data from the first and the hundredth charging cycle for the prediction. Our goal is to get accurate results with measurements from only 20 consecutive charging cycles, making the model much more applicable in the real world. On top of that, it would be useful to know the current age of a battery.
This leads us to the following problem definition:
Given measurements during a limited amount of charging cycles, how many cycles has a battery cell lived through and how many cycles will it last before it breaks?
Let’s walk through the steps to build a model and predict this!
The authors of the original paper assembled 124 lithium-ion battery cells to measure data from. Each cell is charged and discharged according to one of many predetermined policies, until the battery reaches 80 percent of its original capacity (meaning the battery has become too unreliable for normal use and is considered “broken”). The number of cycles (charging fully, then discharging fully) until this state is reached is called the battery cycle life, one of our targets. Within our dataset this number varies wildly from 150 to 2,300.
The data for each cell is presented in a nested structure, with some features only measured once per cycle and others multiple times. Over a full cycle, we have more than a thousand measurements for capacity, temperature, voltage, and current, but only one scalar measurement for other metrics such as internal resistance of the cell or the total cycle time.
Since a big chunk of the measurements is taken during the experimentally controlled charging policy (which varies from cell to cell), we crop the data to the discharging period (which is comparable across all cells). This brings us to the next step...
Raw measurement data can be extremely noisy. Distances between measurements are not always equal, data that’s supposed to decrease monotonically increases unexpectedly and sometimes the hardware just shuts off and continues measuring at a random point in time. So we took special care that the data was clean and in the correct format before feeding it to a model.
We remove cycles that had time gaps, small outliers, or other inconsistencies. One particular useful thing we found for smoothing out noise is the savitzky golay filter. This helped us recover some data samples that had measuring issues during the experiments.
Another problem in the data was time. The different charging policies meant that some cycles were finished quicker than others and the time measurements of charge and temperature couldn’t be compared as they were. Instead we resampled the data in a similar fashion as the original paper did:
Take the voltage range during discharging as the reference instead of time!For this cell model, 3.6V and 2.0V always correspond to fully charged and discharged. This range stays constant, even when time doesn’t.Interpolate charge and temperature over voltage.Resample charge and temperature at 1000 equidistant voltage steps.
Take the voltage range during discharging as the reference instead of time!For this cell model, 3.6V and 2.0V always correspond to fully charged and discharged. This range stays constant, even when time doesn’t.
Interpolate charge and temperature over voltage.
Resample charge and temperature at 1000 equidistant voltage steps.
Done! All measurements now had the same length for every cell and cycle, but we still had some features with 1000 steps and others only as scalars. How do we avoid a shape mismatch when feeding array features and scalar features into our model at the same time? One solution is to feed the data into the model at different entry points and bring everything together later. This trick will become clearer when we talk about the model in detail. There wasjust one more thing we needed to do.
To be able to detect a trend, we took multiple consecutive charging cycles as input. These groups of cycles we call windows. There should always be just one target for the whole window, but every cycle has a “current cycle” and “remaining cycles” value. Which is why we defined the values from the last cycle to be the targets for the whole window.
Let’s finally move to the (more) fun part!
Before we could dive into the data and create cool models, we needed to think about our setup. We wanted to use TensorFlow 2.0 from start to finish to profit from integrated functionality like tensorboard, the dataset API, and hyperparameter tuning.
After choosing the framework, we decided what platform we should run our training jobs on. Instead of overheating our own laptops, we went with AI Platform from Google Cloud. AI Platform allowed us to run several training jobs at the same time, label them easily and monitor the process.
This requires some setup. This can take quite some time to do it right the first time, so we won’t go into all the details in this article. Here’s a summary:
Create an account and install the google cloud sdk on your machineUpload your data to a google cloud bucketWrite one central python script that runs a job (load the data, load the model, train the model, save the results)Make sure your project and folder structure are properly set up for AI Platform
Create an account and install the google cloud sdk on your machine
Upload your data to a google cloud bucket
Write one central python script that runs a job (load the data, load the model, train the model, save the results)
Make sure your project and folder structure are properly set up for AI Platform
Now we were able start a training job from the command line with the option to modify almost everything on the fly. We could adjust things like number of epochs, batch size, shuffling, checkpoint saving and even switch between model architectures easily, by adding a flag after the command. This allowed us to iterate fast, test different theories, and burn through a lot of (free) credits.
We’ built our model with tf.Keras using the functional API. We feed the array and scalar features into the model at separate entry points, so we can do different things to them before bringing them back together.
The array features in each window we concatenate along their short side to make them a 3D matrix with shape (window size, length, number of features). We can then pass this matrix through three Conv2D layers with MaxPooling to extract relevant information from it while keeping the sequential nature of the window. The Conv2D acts on the “number of features” dimension as if these were the number of color channels in an image. This works because the array features all share the same voltage range and thus are highly correlated (just like RGB channels in an image). After the convolutions we flatten the data to a 1D array.
We concatenate the scalar features in a similar fashion in the window direction to produce an array with shape (window size, number of features), before passing it through two Conv1D layers with one MaxPooling and flatten it in the end.
We now have two flat arrays with feature mappings which we can easily put together and feed it into a fully connected dense network to produce our result.
After we had built our model, it was time to train it. We wrote a script to call the GCP API in a simple command-line interface, so while you’re in the project’s main directory, starting a training job in the cloud becomes super easy:
./train.sh
If, for example, we wanted to modify the number of training epochs and the number of samples per window, our script would allow us to do this with simple flags:
./train.sh -e 70 -w 10
During training we tracked three metrics across both train (orange) and validation (blue) set: The loss and mean absolute error (MAE) of current cycle, as well as remaining cycles. After a few minutes we can look at the results in TensorBoard. Let’s see what the loss looks like:
It’s going in the right direction, but we were not happy with the gap between train and validation loss. To decrease that gap, dropout is a popular tool, so we added it to the model. We also needed to tune our hyperparameters, that’s why we used gridsearch over different setups. To keep track of these setups, we used the hparams module which from TensorFlow 2.0 on you can find in tensorboard.plugins. Now we were able to compare different runs and pick the parameters that work best.
Since the correct predictions for “current” and “remaining cycles” should always be bigger than zero, we tried ReLU as an activation function for the output layer to reduce the search space for our model during the training process. Additionally our model heavily relies on CNNs, so we also tried different kernel sizes. Finally, we tested two different learning rates, and we measured the MAE of current cycles and remaining cycles of the setups.
With the best model setup we obtained from hyperparameter tuning, and by setting the number of training epochs to 1000, we ended up with a model that’s at 90 MAE for the current and 115 MAE for the remaining cycles:
It’s still not perfect, but we were pretty happy with the results for the application we had in mind (and we finally got some food to celebrate!).
When we looked at the training curve for our best setup, we could see that the lowest loss wasn’t at the end of training, but somewhere around three quarters through training. How do we use the model as it was at that point for our predictions? We had to implement checkpoints that allow us to restore a saved model at certain times during training.
Once we have a model, we can serve it with TensorFlow Serving or a web-framework such as Flask. At the time Google Cloud Platform didn’t support TF2 Serving, so we decided to build the app completely in Flask and host it on an AWS EC2 instance.
You can see the results again here: www.ion-age.org/example
After loading a random sample file, you can preview the data that we’re going to predict on and you’ll find our two linear and three scalar features. Hitting the predict button produces a graph that shows you our two targets: current and remaining cycles.
That’s it! That’s all you need for an algorithm that can accurately predict the age and expected lifetime of any lithium-ion battery, given the proper measurement data.
If you have any suggestions or questions or you found a bug, feel free to leave a comment. We are happy to answer questions!
We created this project during Data Science Retreat, a three-month Data Science bootcamp in Berlin.