文章目录

  • ​​0、推荐​​
  • ​​1、背景​​
  • ​​2、起因​​
  • ​​3、耗内存的原因​​
  • ​​4、解决方案​​
  • ​​5、效果​​

0、推荐

无意中发现了一个巨牛的人工智能教程,忍不住分享一下给大家。教程不仅是零基础,通俗易懂,而且非常风趣幽默,像看小说一样!觉得太牛了,所以分享给大家。

1、背景

这阵子一直在研究机器学习和深度学习的算法,昨天碰到了一个棘手的问题。当使用支持向量机算法时,训练样本数量达到100000条的时候,操作系统直接用“OOM Killer”将程序杀死了。我用的是CentOs系统,通常触发 Linux 内核里的 Out of Memory (OOM) killer,是因为某时刻应用程序大量请求内存导致系统内存不足造成的。OOM killer 会杀掉某个进程以腾出内存留给系统用,不致于让系统立刻崩溃。解决这个问题最简单的办法就是增加内存,后续我会一点点讲如何用最简单的方式去解决。

2、起因

内核检测到系统内存不足、挑选并杀掉某个进程的过程,可以参考内核源代码
​​​https://github.com/torvalds/linux/blob/master/mm/oom_kill.c​

通过源码我们可以看到,当系统内存不足的时候,out_of_memory() 被触发,然后调用 select_bad_process() 选择一个 “bad” 进程杀掉。如何判断并选择一个 “bad” 进程呢,总不能随机选吧?挑选的过程由 oom_badness() 决定,挑选的算法和想法都很简单很朴实:最“bad”的那个进程就是那个最占用内存的进程。

值得关注的核心模块:

/**
* oom_badness - heuristic function to determine which candidate task to kill
* @p: task struct of which task we should calculate
* @totalpages: total present RAM allowed for page allocation
*
* The heuristic for determining which task to kill is made to be as simple and
* predictable as possible. The goal is to return the highest value for the
* task consuming the most memory to avoid subsequent oom failures.
*/
unsigned long oom_badness(struct task_struct *p, struct mem_cgroup *memcg,
const nodemask_t *nodemask, unsigned long totalpages)
{
long points;
long adj;

if (oom_unkillable_task(p, memcg, nodemask))
return 0;

p = find_lock_task_mm(p);
if (!p)
return 0;

adj = (long)p->signal->oom_score_adj;
if (adj == OOM_SCORE_ADJ_MIN) {
task_unlock(p);
return 0;
}

/*
* The baseline for the badness score is the proportion of RAM that each
* task's rss, pagetable and swap space use.
*/
points = get_mm_rss(p->mm) + p->mm->nr_ptes +
get_mm_counter(p->mm, MM_SWAPENTS);
task_unlock(p);

/*
* Root processes get 3% bonus, just like the __vm_enough_memory()
* implementation used by LSMs.
*/
if (has_capability_noaudit(p, CAP_SYS_ADMIN))
adj -= 30;

/* Normalize to oom_score_adj units */
adj *= totalpages / 1000;
points += adj;

/*
* Never return 0 for an eligible task regardless of the root bonus and
* oom_score_adj (oom_score_adj can't be OOM_SCORE_ADJ_MIN here).
*/
return points > 0 ? points : 1;
}

3、耗内存的原因

通常情况下,我们会把总样本2-8分成测试集和训练集,通常也是在执行训练和测试的时候耗时的。每种算法中,占用内存是不相同的,拿CNN来说吧。

作为案例研究,让我们更详细地分析VGGNet。 整个VGGNet由CONV层组成,它们用步幅1和垫1执行3x3卷积,POOL层用步长2执行2x2最大池化(并且没有填充)。 我们可以在处理的每一步写出表示的大小,并跟踪表示大小和权重总数:

INPUT: [224x224x3]        memory:  224*224*3=150K   weights: 0
CONV3-64: [224x224x64] memory: 224*224*64=3.2M weights: (3*3*3)*64 = 1,728
CONV3-64: [224x224x64] memory: 224*224*64=3.2M weights: (3*3*64)*64 = 36,864
POOL2: [112x112x64] memory: 112*112*64=800K weights: 0
CONV3-128: [112x112x128] memory: 112*112*128=1.6M weights: (3*3*64)*128 = 73,728
CONV3-128: [112x112x128] memory: 112*112*128=1.6M weights: (3*3*128)*128 = 147,456
POOL2: [56x56x128] memory: 56*56*128=400K weights: 0
CONV3-256: [56x56x256] memory: 56*56*256=800K weights: (3*3*128)*256 = 294,912
CONV3-256: [56x56x256] memory: 56*56*256=800K weights: (3*3*256)*256 = 589,824
CONV3-256: [56x56x256] memory: 56*56*256=800K weights: (3*3*256)*256 = 589,824
POOL2: [28x28x256] memory: 28*28*256=200K weights: 0
CONV3-512: [28x28x512] memory: 28*28*512=400K weights: (3*3*256)*512 = 1,179,648
CONV3-512: [28x28x512] memory: 28*28*512=400K weights: (3*3*512)*512 = 2,359,296
CONV3-512: [28x28x512] memory: 28*28*512=400K weights: (3*3*512)*512 = 2,359,296
POOL2: [14x14x512] memory: 14*14*512=100K weights: 0
CONV3-512: [14x14x512] memory: 14*14*512=100K weights: (3*3*512)*512 = 2,359,296
CONV3-512: [14x14x512] memory: 14*14*512=100K weights: (3*3*512)*512 = 2,359,296
CONV3-512: [14x14x512] memory: 14*14*512=100K weights: (3*3*512)*512 = 2,359,296
POOL2: [7x7x512] memory: 7*7*512=25K weights: 0
FC: [1x1x4096] memory: 4096 weights: 7*7*512*4096 = 102,760,448
FC: [1x1x4096] memory: 4096 weights: 4096*4096 = 16,777,216
FC: [1x1x1000] memory: 1000 weights: 4096*1000 = 4,096,000

TOTAL memory: 24M * 4 bytes ~= 93MB / image (only forward! ~*2 for bwd)
TOTAL params: 138M parameters

详情请看:​​http://cs231n.github.io/convolutional-networks/#case​

4、解决方案

目前,大多数操作系统都使用了虚拟内存。如 Windows的“虚拟内存”;Linux的“交换空间”。其实所谓的虚拟内存,说白了。就是内存条的空间不够了,为了能让应用程序认为它拥有连续可用的内存(一个连续完整的地址空间),我们匀出一部分硬盘空间来充当内存使用的。在windows上,虚拟内存在硬盘上其实就是为一个硕大无比的文件,文件名是PageFile.Sys。在Linux上,通常使用Swap分区来实现。

还记得大学的时候,给Linux硬盘分区时分的Swap,一直不知道他有什么实际用途,现在才搞明白。Swap空间的作用可简单描述为:当系统的物理内存不够用的时候,就需要将物理内存中的一部分空间释放出来,以供当前运行的程序使用。那些被释放的空间可能来自一些很长时间没有什么操作的程序,这些被释放的空间被临时保存到Swap空间中,等到那些程序要运行时,再从Swap中恢复保存的数据到内存中。这样,系统总是在物理内存不够时,才进行Swap交换。 其实,Swap的调整对Linux服务器,特别是Web服务器的性能至关重要。通过调整Swap,有时可以越过系统性能瓶颈,节省系统升级费用。

我们在Linux上开发就应该直接增加Swap分区就可以了:
1、创建swap分区 :

dd if=/dev/zero of=/swapfile  bs=1M count=20000

2、激活swap分区 :

# 将swap文件设置为swap分区文件  
mkswap swapfile
#(由于此文件也会占用磁盘空间 , 最好找一个比较大的磁盘存放)
#激活swap,启用分区交换文件
swapon swapfile
#注意:insecure permissions 0644, 0600 suggested.
chmod 600 swapfile
# 停用虚拟内存
# swapoff -v swapfile

3、设置允许开机启用swap分区 : 修改 /etc/fstab

vim /etc/fstab
#第一栏磁盘装置文件名或该磁盘的lable
#第二栏挂载点
#第三栏磁盘分区槽的文件系统
/dev/mapper/fedora-root / ext4 defaults 1 1
/dev/mapper/fedora-home /home ext4 defaults 1 2
/opt/swapfile swap swap defaults 0 0

5、效果

增加完了后,查看内存使用情况:

SVM、NN等统计学算法爆内存的解决方案_SVM


或者直接用top命令来查看:

SVM、NN等统计学算法爆内存的解决方案_算法_02


增加虚拟内存后,算法可以正常使用了。

以上是linux增加虚拟内存的方案,windows的可以这样做:​​https://jingyan.baidu.com/article/63acb44a2c7c7461fcc17edd.html​