在伯克利教深度学习 - 李沐

本文来源:https://zhuanlan.zhihu.com/p/66062438

2019年春季我跟Alex Smola一起在加州大学伯克利分校(下面简称Berkeley)教了一门针对本科生的实验性质的深度学习课程,旨在探索如何有效的教授深度学习。连同两位助教(Rachel和Ryan)和一百来位学生一起度过了高强度的、痛并快乐的四个月。上周刚结束了课程项目的报告。二十个报告里有好几我特别喜欢,其中一两将投到下下周截稿的NeurIPS。更欣慰的是几个我一度觉得会失败的项目也找到自己的出路。

教学是一种形式上的创作。创作有着艺术性和个人性。我们的经验不足支撑写出一篇“如何高效的教深度学习”,但我们尝试的一些方法、走过的弯路、积累下来的材料也许对诸位感兴趣的老师和同学有所帮助。所以特意在这里分享出来。

动机

在过去三年中,我所在的亚马逊云服务(AWS)人工智能部门(AI)急速的扩张了好几十倍,侧面反映了行业的火爆。但我观察这个市场的扩张并没有想象中的那样发展迅猛。原因之一是AI的头号玩家深度学习的入门门槛依然很高。虽然它比机器学习更实用,但比起其他学科来说仍然很复杂。例如如果100个人学数据库(AWS的重要收入来源),可能50个人能用学到的知识来解决实际问题。但同样这些人来学深度学习,谨慎乐观估计10个学完后可以上手就很好了。如果AI一波热潮褪去,开发者学习热情降低,市场扩张随之变慢,那冬天就不远了。

我跟多位部门同事合作对亚马逊内部员工和AWS客户进行过培训(工作的副业之一),17年的时候跟Aston一起在国内做过十九期直播。去年年底的时候萌发录一个更加正式的质量更高的MOOC的想法。但担心线上课程不能实时得到反馈,所以难易度难以包括,所以想先面对面的教一遍积累些经验。

首选是去Stanford,因为离家近(踩单车15分钟)。但Stanford已经有几门深度学习课程同时在开。幸运的是Berkeley仍有空位,而且他们非常乐意我们去教。联系上Berkeley到AWS上层批准一周内就搞定,即使是我们明确说明了将会在半年之类每周两天不在办公室。这一点上我非常喜欢亚马逊,它推崇做一些有长远影响的项目,并鼓励快速尝试。

入职当天两个有意思的小插曲。一是被告知Berkeley是公立学校,职员算政府人员,需要宣誓战争发生时要为美国而战。我说这我没做好心理准备,要不不要发我工资,当我是个志愿者好了。这样我也安心去实验教学。

二是我去的伯克利统计系是世界上最好的统计系之一了吧(这里老师觉得就是世界第一,很像我读过的CMU计算机系和我老婆读过的MIT计算机和电子工程系,他们老师都觉得自己是世界第一😂)。楼上办公室坐着各大山头,Michael Jordan,Peter Bartlett,Bin Yu,一堆如雷贯耳名字。但统计系这栋楼是我去过的学校里面最破的,没有之一。以至于约助教见面时他们委婉的建议去隔壁楼的公共空间,那里更敞亮更现代。


Linux 查看发行版和内核参数

  1. 发行版

    cat /etc/issue
    
  2. 内核参数

    cat /proc/version
    

    image-20200203131645412

  3. 查看虚拟化方式

    1. dmidecode

      最初设计来显示系统 BIOS 和硬件组件的相关信息,不适用于容器

    2. systemd-detect-virt

      KVM、QEMU、VMware、Xen、Oracle VM、VirtualBox、UML 和基于容器的虚拟化技术(例如 LXC、Docker、OpenVZ)

    3. virt-what

      QEMU/KVM、VMware、Hyper-V、VirtualBox、OpenVZ/Virtuozzo、Xen、LXC、IBM PowerVM 以及 Parallels 等平台类型,可以用apt-get 或 yum 安装 virt-what


nginx 启用目录索引,显示文件列表,解决中文乱码问题

在nginx中,如果特定目录中没有index.html 文件,则默认会返回 404 Not Found 的错误。

但是,Nginx 自动索引模块—— ngx_http_autoindex_module 模块,提供了一种自动生成列表的方法,添加自动索引非常容易,使用 autoindex on 即可。下面的配置,将在访问特定请求时返回目录结构。

官方参考: http://nginx.org/en/docs/http/ngx_http_autoindex_module.html**

server {
        listen   80;
        ... ...
        
        location /somedir {
           autoindex on;
        }
}

除了简单地使用自动索引打开或关闭之外,还可以对其做其他的配置,包括:

  • autoindex_exact_size; 显示输出的确切文件大小,还是最接近的KB,MB或GB。默认为on,显示出文件的确切大小,单位是bytes。改为off后,显示出文件的大概大小,单位是kB或者MB或者GB。
  • autoindex_format; 该指令指定Nginx索引列表应以什么格式输出。该指令有4个选项:html/xml/json/jsonp。
  • autoindex_localtime; 显示的文件时间为GMT时间。 注意:改为on后,显示的文件时间为文件的服务器时间。

使用这几个配置后配置内容类似于如下内容:

location /somedirectory/ {
    root   /var/ftp/;  
    autoindex on;
    autoindex_exact_size off;
    autoindex_format html;
    autoindex_localtime on;
}

如果有中文目录的话会出现乱码问题,所以还需要在下面添加这一句:

charset utf-8,gbk;

参考资料


cron 每五分钟运行

如下,使用系统默认crontab,在头一个*后添/5即可。

*/5 * * * * /var/local/cron/every_five_minute.sh >> /var/local/log/cron/every_five_minute.log 2>&1 

什么是cron

以下内容来自:https://www.runoob.com/linux/linux-comm-crontab.html

Linux crontab是用来定期执行程序的命令。 当安装完成操作系统之后,默认便会启动此任务调度命令。 crond 命令每分锺会定期检查是否有要执行的工作,如果有要执行的工作便会自动执行该工作。

注意:新创建的 cron 任务,不会马上执行,至少要过 2 分钟后才可以,当然你可以重启 cron 来马上执行。

而 linux 任务调度的工作主要分为以下两类:

  • 1、系统执行的工作:系统周期性所要执行的工作,如备份系统数据、清理缓存
  • 2、个人执行的工作:某个用户定期要做的工作,例如每隔10分钟检查邮件服务器是否有新信,这些工作可由每个用户自行设置

语法

crontab [ -u user ] file

crontab [ -u user ] { -l | -r | -e }

说明:

crontab 是用来让使用者在固定时间或固定间隔执行程序之用,换句话说,也就是类似使用者的时程表。

-u user 是指设定指定 user 的时程表,这个前提是你必须要有其权限(比如说是 root)才能够指定他人的时程表。如果不使用 -u user 的话,就是表示设定自己的时程表。

参数说明

  • -e : 执行文字编辑器来设定时程表,内定的文字编辑器是 VI,如果你想用别的文字编辑器,则请先设定 VISUAL 环境变数来指定使用那个文字编辑器(比如说 setenv VISUAL joe)
  • -r : 删除目前的时程表
  • -l : 列出目前的时程表

时间格式如下:

f1 f2 f3 f4 f5 program
  • 其中 f1 是表示分钟,f2 表示小时,f3 表示一个月份中的第几日,f4 表示月份,f5 表示一个星期中的第几天。program 表示要执行的程序。
  • 当 f1 为 * 时表示每分钟都要执行 program,f2 为 * 时表示每小时都要执行程序,其馀类推
  • 当 f1 为 a-b 时表示从第 a 分钟到第 b 分钟这段时间内要执行,f2 为 a-b 时表示从第 a 到第 b 小时都要执行,其馀类推
  • 当 f1 为 */n 时表示每 n 分钟个时间间隔执行一次,f2 为 */n 表示每 n 小时个时间间隔执行一次,其馀类推
  • 当 f1 为 a, b, c,… 时表示第 a, b, c,… 分钟要执行,f2 为 a, b, c,… 时表示第 a, b, c…个小时要执行,其馀类推
*    *    *    *    *
-    -    -    -    -
|    |    |    |    |
|    |    |    |    +----- 星期中星期几 (0 - 7) (星期天 为0)
|    |    |    +---------- 月份 (1 - 12) 
|    |    +--------------- 一个月中的第几天 (1 - 31)
|    +-------------------- 小时 (0 - 23)
+------------------------- 分钟 (0 - 59)

使用者也可以将所有的设定先存放在文件中,用 crontab file 的方式来设定执行时间。

实例

每一分钟执行一次 /bin/ls:

* * * * * /bin/ls

在 12 月内, 每天的早上 6 点到 12 点,每隔 3 个小时 0 分钟执行一次 /usr/bin/backup:

0 6-12/3 * 12 * /usr/bin/backup

周一到周五每天下午 5:00 寄一封信给 alex@domain.name:

0 17 * * 1-5 mail -s "hi" alex@domain.name < /tmp/maildata

每月每天的午夜 0 点 20 分, 2 点 20 分, 4 点 20 分….执行 echo “haha”:

20 0-23/2 * * * echo "haha"

下面再看看几个具体的例子:

0 */2 * * * /sbin/service httpd restart  意思是每两个小时重启一次apache 

50 7 * * * /sbin/service sshd start  意思是每天7:50开启ssh服务 

50 22 * * * /sbin/service sshd stop  意思是每天22:50关闭ssh服务 

0 0 1,15 * * fsck /home  每月1号和15号检查/home 磁盘 

1 * * * * /home/bruce/backup  每小时的第一分执行 /home/bruce/backup这个文件 

00 03 * * 1-5 find /home "*.xxx" -mtime +4 -exec rm {} \;  每周一至周五3点钟,在目录/home中,查找文件名为*.xxx的文件,并删除4天前的文件。

30 6 */10 * * ls  意思是每月的1、11、21、31日是的6:30执行一次ls命令

注意:当程序在你所指定的时间执行后,系统会发一封邮件给当前的用户,显示该程序执行的内容,若是你不希望收到这样的邮件,请在每一行空一格之后加上 > /dev/null 2>&1 即可,如:

20 03 * * * . /etc/profile;/bin/sh /var/www/runoob/test.sh > /dev/null 2>&1 

脚本无法执行问题

如果我们使用 crontab 来定时执行脚本,无法执行,但是如果直接通过命令(如:./test.sh)又可以正常执行,这主要是因为无法读取环境变量的原因。

解决方法:

  • 1、所有命令需要写成绝对路径形式,如: /usr/local/bin/docker

  • 2、在 shell 脚本开头使用以下代码:

    #!/bin/sh
      
    . /etc/profile
    . ~/.bash_profile
    

    3、在 /etc/crontab 中添加环境变量,在可执行命令之前添加命令 . /etc/profile;/bin/sh,使得好几遍了生效,例如:

    20 03 * * * . /etc/profile;/bin/sh /var/www/runoob/test.sh
    

Nginx 日志按天分割

nginx 日志分割是比较常见的运维工作,关于这方面的文章也很多,通常无外乎两种做法:

  1. cron定期执行shell脚本对日志文件进行归档。
  2. 使用专门日志归档logrotate。

以上方式与nginx其实没有特别的关系。 从nginx 0.7.6 版本开始,access_log 的路径配置可以包含变量,我们以此进行日志分割。

同时我们基于nginx的 timeiso8601 内嵌变量来获取时间。time_iso8601格式如下:

2018-09-21T16:01:02+02:00

然后使用正则表达式来获取所需时间的数据。

http {
  log_format default_format '$remote_addr - $remote_user [$time_iso8601] "$request" '
      '$status $body_bytes_sent "$http_referer" '
      '"$http_user_agent" "$http_x_forwarded_for" "$request_body"';
      
      
      server {
          listen 443 ssl;
          ... ...
          if ($time_iso8601 ~ '(\d{4}-\d{2}-\d{2})') {
            set $tttt $1;
          }
          
          access_log /log/blog_access_$tttt.log;
      }
}

主要关注两个地方:

  1. 要在外方法的log_format上添加 $time_iso8601 ,将原来的time_local修改为time_iso8601。
  2. 在单个server中,通过正则表达式截取 $time_iso8601 生成时间戳。

最后插一个话题,你知道 $time_iso8601 为什么叫这个名字吗?

国际标准ISO 8601,是国际标准化组织的日期和时间的表示方法,全称为《数据存储和交换形式·信息交换·日期和时间的表示方法》。目前是2004年12月1日发行的第三版“ISO8601:2004”以替代1998年的第一版“ISO8601:1998”与2000年的第二版“ISO8601:2000”。


GoAccess:轻量级nginx日志分析工具

什么是GoAccess

GoAccess是一个开源的基于终端的快速日志分析器。 Github地址:https://github.com/allinurl/goaccess 官网地址: https://goaccess.io/ 说明文档: https://goaccess.io/man

中国站:https://goaccess.cc/

它既支持命令行界面,也可以输出html界面。

为什么选择 GoAccess

其实一开始我是锚定了 ELK/EFK 技术栈的,因为在公司已经在使用这一套了,效果确实也很好。但自己安装后发现,作为个人开发者,钱少+运维成本高,还是先暂时远离 elastic 这一套技术栈了,goaccess 开箱即用、够用就好。

GoAccess界面

1576306600313

1576306703602

使用

一般来说,我现在使用软件都不会再进行安装了,全部使用容器化方式运行,更好管理。

对goaccess我也是用容器化方式部署。

1 创建目录结构

goaccess/
├── data
│   └── goaccess.conf
├── docker-compose.yml
└── report
    └── a.html
└── logs
    └── a.log
    └── b.log

/goaccess.conf: goaccess 配置文件

/docker-compose.yml: docker-compose 配置

/logs/a.log: 待分析的 nginx 日志文件

/report/index.html: 分析出的报告文件,通过 nginx 访问

2 docker-compose.yml 文件

version: "3.2"
services:
  goaccess:
    image: kelvinblood/goaccess:v201912
    network_mode: bridge
    container_name: goaccess
#    command:
#    - goaccess
#    - --no-global-config
#    - --config-file=/srv/data/goaccess.conf
#    - --num-tests=0
    entrypoint: ["/bin/sh"]
    tty: true
    volumes:
    - ./data:/srv/data
    - ./report:/srv/report
    - ./logs:/srv/logs
    - /etc/localtime:/etc/localtime:ro
    - /etc/timezone:/etc/timezone:ro

有几点需要注意的:

  1. 通过时区设置,使导出的文件与本地时间相同。
  2. 我自己编译了一个 goaccess 版本,主要用于中文输出,在dockerfile中设置了 ENV LANG zh_CN.UTF-8 配置。
  3. 我设置了默认不运行,通过 docker exec 的方式导出分析文件,所以修改了默认的 entrypoint。

3. goaccess配置

在默认的配置文件后增加三行配置:

time-format %H:%M:%S
date-format %d/%b/%Y
log-format %h %^[%d:%t %^] "%r" %s %b "%R" "%u"

同时修改 nginx 的日志格式,参考这一篇文章——《Nginx 日志按天分割》 中log_format的部分。

goaccess默认配置文件可在容器中获得,位于/etc/goaccess/goaccess.conf,可以先运行容器后再copy出来:

docker-compose up -d
docker exec -it goaccess /bin/sh
cp /etc/goaccess/goaccess.conf /src/data

1576308588084

最后在配置文件末添加刚才提到的三行配置。

4. 根据日志文件导出分析结果

在这里血衫对日志做了按天分割,你有需求也可以依葫芦画瓢,或者把我日期部分忽略掉即可:

time=`date "+%Y-%m-%d"`

docker exec goaccess goaccess --no-global-config --config-file=/srv/data/goaccess.conf --output=/srv/report/a_$time.html --log-file=/srv/logs/a_$time.log

# 如果没有日期分割,使用以下命令
docker exec goaccess goaccess --no-global-config --config-file=/srv/data/goaccess.conf --output=/srv/report/a.html --log-file=/srv/logs/a.log

# 如果要同时分析多个文件:
docker exec goaccess goaccess --no-global-config --config-file=/srv/data/goaccess.conf --output=/srv/report/a.html -f a.log b.log c.log

结合 Linux 的定时任务,你还可以设计一个自动更新的统计。

参考资料