使用 SKLearn 的 Django 项目中的 Heroku 内存配额大大超出

Heroku Memory quota vastly exceeded in Django Project using SKLearn

我已经在 Heroku 上部署了一个 Django 应用程序,目的是让受信任的已知内部用户上传 CSV 文件,单击 "Run",在幕后,Django 应用程序:

  1. 加载保存的 sklearn 管道 .pkl 模型(假设大小为 120 MB)
  2. 使用Pandas
  3. 读取用户的 CSV 数据
  4. 使用 CSV 数据作为输入对模型调用 predict
  5. 使用 Django 存储将文件输出到 S3

这适用于小型 CSV 文件,但如果用户上传大型 CSV 文件,则会导致 Memory quota vastly exceeded...并且较大的 CSV 文件会增加内存消耗是有道理的。

我不确定在哪里调整。 我想知道是否有人在部署 sklearn 模型时遇到过类似的情况,他们是如何 "solved" 的?

我的想法是:

  1. 识别内存泄漏?甚至不知道从哪里开始。 Django DEBUG 设置为 False
  2. 更改我的 celery 任务以改为分块处理输入的文件?
  3. 用 joblib 以某种方式制作一个较小的 SKLearn 管道文件(我已经使用 compress=1)?
  4. 增加 Heroku 测功机?工人?

我的 django models.py 看起来像这样:

from django.db import models
from django.urls import reverse


class MLModel(models.Model):
    name = models.CharField(max_length=80)
    file = models.FileField(upload_to="models/")
    created = models.DateTimeField(auto_now_add=True)
    updated = models.DateTimeField(auto_now=True)

    def __str__(self):
        return self.name


class Upload(models.Model):
    name = models.CharField(max_length=100)
    mlmodel = models.ForeignKey(MLModel, on_delete=models.CASCADE)
    file = models.FileField(upload_to='data/')

    def __str__(self):
        return self.name

    def get_absolute_url(self):
        return reverse('edit', kwargs={'pk': self.pk})

我的 celery 任务是这样的:

@shared_task
def piparoo(id):
    instance = Upload.objects.get(id=id)
    model = joblib.load(instance.mlmodel.file.storage.open(instance.mlmodel.file.name))
    data = pd.read_csv(instance.file)
    data['Predicted'] = model.predict(data)

    buffer = StringIO()
    data.to_csv(buffer, index=False)
    content = buffer.getvalue().encode('utf-8')
    default_storage.save('output/results_{}.csv'.format(id), ContentFile(content))

Heroku 日志:

2018-04-12T06:12:53.592922+00:00 app[worker.1]: [2018-04-12 06:12:53,592: INFO/MainProcess] Received task: predictions.tasks.piparoo[f1ca09e1-6bba-4115-8989-04bb32d4f08e]
2018-04-12T06:12:53.737378+00:00 heroku[router]: at=info method=GET path="/predict/" host=tdmpredict.herokuapp.com request_id=ffad9785-5cb6-4e3c-a87c-94cbca47d109 fwd="24.16.35.31" dyno=web.1 connect=0
ms service=33ms status=200 bytes=6347 protocol=https
2018-04-12T06:13:08.054486+00:00 heroku[worker.1]: Error R14 (Memory quota exceeded)
2018-04-12T06:13:08.054399+00:00 heroku[worker.1]: Process running mem=572M(111.9%)
2018-04-12T06:13:28.026973+00:00 heroku[worker.1]: Error R15 (Memory quota vastly exceeded)
2018-04-12T06:13:28.026765+00:00 heroku[worker.1]: Process running mem=1075M(210.1%)
2018-04-12T06:13:28.026973+00:00 heroku[worker.1]: Stopping process with SIGKILL
2018-04-12T06:13:28.187650+00:00 heroku[worker.1]: Process exited with status 137
2018-04-12T06:13:28.306221+00:00 heroku[worker.1]: State changed from up to crashed

解决了我的问题的解决方案(以常识方式)。

不是一次将用户的 CSV 文件读入内存,而是使用 Pandas chunksize 参数分块处理它,然后在最后将数据帧列表连接成一个。我还删除了模型 (120 MB) 以尝试为将来的进程释放内存。

我的 celery 任务现在看起来像这样:

@shared_task
def piparoo(id):
    instance = Upload.objects.get(id=id)
    model = joblib.load(instance.mlmodel.file.storage.open(instance.mlmodel.file.name))

    final = []
    for chunk in pd.read_csv(instance.file, chunksize=5000):
        chunk['Predicted'] = model.predict(chunk)
        final.append(chunk)

    del model
    final = pd.concat(final)

    buffer = StringIO()
    final.to_csv(buffer, index=False)
    content = buffer.getvalue().encode('utf-8')
    default_storage.save('output/results_{}.csv'.format(id), ContentFile(content))