# Introduction

[Junyangz's documents](https://docs.junyangz.com)

Some simple tutorial documents or notes.

## Contact me

* [Mail](mailto:junyangz.iie@gmail.com)
* Phone: +1-9292651918 [1](#footnote1).
* Telegram: [Junyang](https://t.me/junyoungz)
* QQ/Wechat:

  ```bash
    dig TXT qq.cm.junyangz.com
    dig TXT wechat.cm.junyangz.com
  ```

1 SMS only.


# Ops

Ops or SRE?


# Linux-tips

Last updated: 2023-02-23

```bash
# sudo no password
echo "$(whoami)" ALL=(ALL) NOPASSWD:ALL >> /etc/sudoers
```

```bash
# copy ssh keys
ssh-copy-id -i ~/.ssh/id_rsa.pub user@host
ssh-copy-id -f -i ~/.ssh/id_rsa.pub user@host # force add without checking if key exists
# not fancy way
ssh user@host 'mkdir -p .ssh && cat >> .ssh/authorized_keys' < ~/.ssh/id_rsa.pub
```

VIM

```bash
# delete a world: `daw`
# delete a sentence: `das`
#  __d__elete, __y__ank, __c__hange, > (indent in)
# b for before, e for end, a for append, i for insert.
# caw - perform the c operator on the aw text object (ergo, change the "a word" that the cursor is currently on).
# dd for delete line, yy for copy line, cc for change line
# >> for align line right, << for align line left
# :%s/old/new/g for replace all
# :%s/old/new/gc for replace all with confirm
# :%s/old/new/gcI for replace all with confirm and ignore case
# BdW (go to first whitespace delete to next whitespace)
# bdw, back delete word.
# Scroll: Ctrl-u (up), Ctrl-d (down)
# Find: f{character}, t{character}, F{character}, T{character}
# find/to forward/backward {character} on the current line
# , / ; for navigating matches
# Search: /{regex}, n / N for navigating matches
# %s/foo/bar/g replace foo with bar globally in file
# To switch to the right window, press “Ctrl + w”, then “l”. To go to the left window, it's “Ctrl + w”, then “h”. If you did a horizontal split, then going up and down is necessary. For going up, press “Ctrl + w”, then “k”. For going down, press “Ctrl + w”, then “j”.
# set :nu for line numbers

```

BASH

```bash
# $_ - Last argument from the last command
# $! - PID of the last background command
# $@ - All arguments
# $* - All arguments, unquoted
# $? - Exit status of the last command
# $$ - PID of the current shell
# $0 - Name of the current script
# $# - Number of arguments passed to script
# !! - Entire last command, including arguments. A common pattern is to execute a command only for it to fail due to missing permissions; you can quickly re-execute the command with sudo by doing sudo !!
# ESC . - last argument from the previous command
# !:n - nth argument from the previous command

```

```bash
bash -c 'cat < "TEST"'
#bash: TEST: No such file or directory means that the shell is trying to read from a file called TEST
bash -c 'cat << "TEST"'
#bash: warning: here-document at line 0 delimited by end-of-file (wanted `TEST') means that the shell is trying to read from here-document 
bash -c 'cat <<< "TEST"'
#TEST means that the shell is trying to read from here-string
```

```bash
# process substitution
# <(command) - read from the output of command
# >(command) - write to the input of command
```

```bash
# bash coproc (> bash 4.0)
# coproc NAME { command; }
# $NAME_PID - PID of the coprocess
# ${NAME[@]} - file descriptor of the coprocess
# ${NAME[0]} - ouput file descriptor of the coprocess
# ${NAME[1]} - input file descriptor of the coprocess

set -e
set -x

coproc macaroons (echo $(date))

echo "The coprocess array: ${macaroons[@]}"
echo "The PID of the coprocess is ${macaroons_PID}"
# Read the output off the first file descriptor of the array.                                                                                
read -r output <&"${macaroons[0]}" # read -r -u "${macaroons[0]}" output
echo "The output of the coprocess is ${output}"
# https://copyconstruct.medium.com/bash-coprocess-2092a93ad912
```


# MySQL-5.7.20

Last edited by Junyangz AT 2018-05-18 19:40:27

## 准备安装包

> boost\_1\_59\_0.tar.gz cmake-2.8.12.tar.gz mysql-5.7.20.tar.gz

## 安装编译环境

```bash
yum install -y gcc gcc-c++ ncurses-devel perl
```

## 添加MySQL用户及用户组

```bash
groupadd mysql
useradd -r -g mysql mysql
mkdir /usr/local/mysql5.7
```

## 编译安装

```bash
tar zxvf boost_1_59_0.tar.gz
mv boost_1_59_0 /usr/local/boost

#cmake --version
tar zxvf cmake-2.8.12.tar.gz
cd cmake-2.8.12
./bootstrap
make && make install
cmake --version

cd ../
mkdir -p /usr/local/mysql-5.7.20
tar zxvf mysql-5.7.20.tar.gz
cd mysql-5.7.20
cmake -DCMAKE_INSTALL_PREFIX=/usr/local/mysql-5.7.20 -DWITH_BOOST=/usr/local/boost/
make && make install

#unlink /usr/local/mysql
ln -s /usr/local/mysql-5.7 /usr/local/mysql
chown -R mysql:mysql /usr/local/mysql /usr/local/mysql-5.7
cp /usr/local/mysql/support-files/mysql.server /etc/init.d/mysqld
```

## 添加环境变量

```bash
vim /etc/profile
export MYSQL_HOME=/usr/local/mysql
export PATH=$PATH:$MYSQL_HOME/bin

source /etc/profile
#echo $MYSQL_HOME
mysql -V
```

## 配置启动

### 配置MySQL

```
#/etc/my.cnf
[mysql]
default-character-set = utf8
socket=/data2/mysql5.7/run/mysql.sock

[mysqld]
basedir=/usr/local/mysql
datadir=/data1/mysql5.7
user=mysql
symbolic-links=0
lower-case-table_names=1
socket=/data2/mysql5.7/run/mysql.sock
log-error=/data2/mysql5.7/log/mysqld.log
pid-file=/data2/mysql5.7/log/mysqld.pid
max-allowed-packet=32M
open-files-limit=65535

server-id = 20
log-bin = /data2/mysql5.7/log/mysql-bin
auto-increment-increment = 2
auto-increment-offset = 2
log-slave-updates = 1
relay-log = /data2/mysql5.7/log/relay-bin
relay-log-purge = 1
read_only=0
```

### 开机自启&初始化及启动

```bash
chkconfig mysqld on
chkconfig mysqld --list

service mysql start
mysqld --initialize-insecure --user=mysql  --datadir={$datadir}
# service mysql restart
```

## Reference

* [Installing MySQL from Source](https://dev.mysql.com/doc/refman/5.7/en/source-installation.html)


# Upgrading MySQL

> Upgrade from MySQL 5.6 to 5.7

Last edited by Junyangz AT 2018-05-18 19:44:46

**If you plan to to upgrade using the data directory from your existing:**

1. MySQL installation:
2. Stop the old (MySQL 5.6) server
3. Upgrade the MySQL binaries in place (replace the old binaries with the new ones)
4. Start the MySQL 5.7 server normally (no special options)
5. Run mysql\_upgrade to upgrade the system tables
6. Restart the MySQL 5.7 server

## Installing MySQL from Source

```bash
# refer MySQL-5.7 installation-tutorial.
```

## Backup old MySQL

```bash
#tar zcvf mysql.tar.gz /usr/local/mysql
service mysqld stop
cd /usr/local/
mv mysql mysql5.6
```

## Replace the binaries

```bash
#mv /usr/local/mysql-5.7.19 /usr/local/mysql
#ln -s  /usr/local/mysql/bin /usr/local/bin/
#unlink /usr/local/mysql
ln -s mysql-5.7.19/ mysql
chown -R mysql:mysql mysql-5.7.19/
chown -R mysql:mysql mysql
# copy init.d file
cp mysql/support-files/mysql.server  /etc/init.d/mysqld
```

## Set MySQL PATH

```bash
vim /etc/profile
export MYSQL_HOME=/usr/local/mysql
export PATH=$PATH:$MYSQL_HOME/bin

source /etc/profile
#echo $MYSQL_HOME
```

## Check and Upgrade MySQL Tables

```bash
service mysqld start
mysql/bin/mysql_upgrade -uroot -p -S /data2/mysql5.6/run/mysql.sock

mysql -uroot -p
show databases;
#database sys is MySQL5.7 new add.
#service mysqld restart
```

## Reference

* [MySQL Upgrade Strategies](https://dev.mysql.com/doc/refman/5.7/en/upgrading-strategies.html)
* [mysql\_upgrade](https://dev.mysql.com/doc/refman/5.7/en/mysql-upgrade.html)
* [MySQL 5.6升级至MySQL 5.7--------版本升级最佳实战](http://blog.51cto.com/lisea/1941616)
* [MySQL从5.6升级到5.7的多种实战经验总结](http://www.fordba.com/mysql-upgrade-from-56-to-57.html)
* [记录一次MySQL升级的运维实践](https://github.com/Junyangz/Documents/tree/46d56dc7cd9c671859be4e1e78b94b0bf252a739/Documentation/www.yunweipai.com/archives/24315.html)


# Upgrade OpenSSH to 7.7p1 in CentOS 6

Last edited by Junyangz AT 2018-06-09 10:08:21

## Install telnet and basic environment

* Installation

```bash
yum -y install telnet-server* telnet
yum -y install gcc-c++,zlib,zlib-devel,openssl,openssl-devel,pam-devel
```

* Enable telnet service

```bash
# vi /etc/xinetd.d/telnet
# 将其中disable字段的yes改为no以启用telnet服务
# mv /etc/securetty /etc/securetty.old    #允许root用户通过telnet登录
# service xinetd start                    #启动telnet服务
# chkconfig xinetd on                     #使telnet服务开机启动，避免升级过程中服务器意外重启后无法远程登录系统
```

## Upgrade OpenSSL to 1.0.2.o

```bash
#!/bin/bash
# Copyright © 2018 Junyangz
cd
#mkdir ssh_upgrade && cd ssh_upgrade
#find / -name openssl
#find / -name "libssl*"
timestamp=$(date +%s)
#backup old OpenSSL
cp  /usr/lib64/libcrypto.so.10  /usr/lib64/libcrypto.so.10-${timestamp}
cp  /usr/lib64/libssl.so.10  /usr/lib64/libssl.so.10-${timestamp}
mv /usr/bin/openssl /usr/bin/openssl-${timestamp}
mv /usr/include/openssl /usr/include/openssl-${timestamp}
mv /usr/lib64/openssl/engines /usr/lib64/openssl/engines-${timestamp}
mv /usr/lib64/openssl /usr/lib64/openssl-${timestamp}

#remove old OpenSSL rpm package
rpm -qa |grep openssl|xargs -i rpm -e --nodeps {}

#compile and install new OpenSSL
tar zxvf openssl-1.0.2o.tar.gz && cd openssl-1.0.2o
./config --prefix=/usr/local/openssl --openssldir=/etc/ssl --shared zlib&& make && make test && make install
ln -s /usr/local/openssl/bin/openssl /usr/bin/openssl
ln -s /usr/local/openssl/include/openssl /usr/include/openssl

echo "/usr/local/openssl/lib">>/etc/ld.so.conf
ldconfig
mv  /usr/lib64/libcrypto.so.10-*  /usr/lib64/libcrypto.so.10
mv  /usr/lib64/libssl.so.10-*  /usr/lib64/libssl.so.10
#ldconfig -v # for check
echo "OpenSSl version upgrades as to lastest:" && openssl version
#openssl version -a
# OpenSSL 1.0.2o  27 Mar 2018
# built on: reproducible build, date unspecified
# platform: linux-x86_64
# options:  bn(64,64) rc4(16x,int) des(idx,cisc,16,int) idea(int) blowfish(idx)
# compiler: gcc -I. -I.. -I../include  -DOPENSSL_THREADS -D_REENTRANT -DDSO_DLFCN -DHAVE_DLFCN_H -Wa,--noexecstack -m64 -DL_ENDIAN -O3 -Wall -DOPENSSL_IA32_SSE2 -DOPENSSL_BN_ASM_MONT -DOPENSSL_BN_ASM_MONT5 -DOPENSSL_BN_ASM_GF2m -DRC4_ASM -DSHA1_ASM -DSHA256_ASM -DSHA512_ASM -DMD5_ASM -DAES_ASM -DVPAES_ASM -DBSAES_ASM -DWHIRLPOOL_ASM -DGHASH_ASM -DECP_NISTZ256_ASM
#OPENSSLDIR: "/usr/local/openssl/ssl"
#echo "New version upgrades as to lastest:" && $(ssh -V)
```

## Upgrade OpenSSH to 7.7p1

```bash
cd
timestamp=$(date +%s)
#backup old OpenSSH
cp -R /etc/ssh /etc/ssh-${timestamp}
cp /etc/init.d/sshd /etc/init.d/sshd-${timestamp}

rpm -qa | grep openssh
rpm -e --nodeps `rpm -qa | grep openssh`

tar zxvf openssh-7.7p1.tar.gz && cd openssh-7.7p1
./configure --prefix=/usr/local/openssh --sysconfdir=/etc/ssh \
--with-ssl-dir=/usr/local/openssl && make && make install

#ln -s /usr/local/openssh/sbin/sshd /usr/sbin/sshd  #or modify sshd file.
# 复制配置文件
cp ssh_config /etc/ssh/
cp sshd_config /etc/ssh/
cp moduli /etc/ssh/

# 复制启动脚本到/etc/init.d
# 根据安装路径情况，可能需要修改启动脚本中sshd的路径
cp contrib/redhat/sshd.init /etc/init.d/sshd
chmod +x /etc/init.d/sshd
/usr/sbin/sshd -t -f /etc/ssh/sshd_config # vim /etc/init.d/sshd

# 加入开机自启
chkconfig --add sshd
chkconfig sshd on
chkconfig sshd --list

# 开启root用户远程登录。
#vi /etc/ssh/sshd_config
sed -i 's/#PermitRootLogin yes/PermitRootLogin yes/g' /etc/ssh/sshd_config

# 开启SSH服务
# 千万不能restart。使用restart会造成连不上，需要登录控制台启动。
service sshd start
#service sshd restart

#mv /etc/securetty.old /etc/securetty ##disable telnet login
```

## RPM

最终批量更新使用RPM包的形式来进行[详情参考](https://github.com/Junyangz/upgrade-openssh-7.7p1-CentOS)。

### Build OpenSSH RPM on CentOS 6.5

```bash
yum install -y pam-devel rpm-build rpmdevtools zlib-devel openssl-devel krb5-devel gcc
mkdir -p ~/rpmbuild/SOURCES && cd ~/rpmbuild/SOURCES

wget -c http://ftp.openbsd.org/pub/OpenBSD/OpenSSH/portable/openssh-7.7p1.tar.gz
wget -c http://ftp.openbsd.org/pub/OpenBSD/OpenSSH/portable/openssh-7.7p1.tar.gz.asc
# # verify the file

# update the pam sshd from the one included on the system
# the default provided doesn't work properly on CentOS 6.5
tar zxvf openssh-7.7p1.tar.gz
cp /etc/pam.d/sshd openssh-7.7p1/contrib/redhat/sshd.pam
mv openssh-7.7p1.tar.gz{,.orig}
tar zcpf openssh-7.7p1.tar.gz openssh-7.7p1
cd
tar zxvf ~/rpmbuild/SOURCES/openssh-7.7p1.tar.gz openssh-7.7p1/contrib/redhat/openssh.spec
# edit the specfile
cd openssh-7.7p1/contrib/redhat/
sed -i -e "s/%define no_gnome_askpass 0/%define no_gnome_askpass 1/g" openssh.spec
sed -i -e "s/%define no_x11_askpass 0/%define no_x11_askpass 1/g" openssh.spec
sed -i -e "s/BuildPreReq/BuildRequires/g" openssh.spec
#if encounter build error with the follow line, comment it.
sed -i -e "s/PreReq: initscripts >= 5.00/#PreReq: initscripts >= 5.00/g" openssh.spec
rpmbuild -ba openssh.spec
```

### Batch update

### For CentOS 6.5

```bash
#!/bin/bash
# Copyright © 2018 Junyangz
# For CRS2-CentOS 6.5 with OpenSSH_5.3p1, OpenSSL 1.0.1e-fips 11 Feb 2013
cd
mkdir openssh && cd openssh
timestamp=$(date +%s)
if [ ! -f openssh-7.7p1-RPMs.tar.gz ]; then wget http://10.27.5.118/openssh-7.7p1-RPMs.tar.gz; fi;
tar zxvf openssh-7.7p1-RPMs.tar.gz
cp /etc/pam.d/sshd pam-ssh-conf-$timestamp
#rpm -e openssh-askpass-5.3p1-94.el6.x86_64
rpm -U *.rpm
#mv /etc/pam.d/sshd /etc/pamd.d/sshd_bak
yes | cp pam-ssh-conf-$timestamp /etc/pam.d/sshd
#sed -i 's/#PermitRootLogin yes/PermitRootLogin yes/g' /etc/ssh/sshd_config
/etc/init.d/sshd restart
echo "New version upgrades as to lastest:" && $(ssh -V)
```

```bash
#!/bin/bash
# Copyright © 2018 Junyangz
# fix error when openssh-askpass was installed.
if [ -f /etc/ssh/sshd_config.rpmnew ]; then
    echo "New version upgrades as to lastest:" && $(ssh -V)
    exit 0
fi

cd openssh
if [ ! -f openssh-7.7p1-RPMs.tar.gz ]; then wget http://${httpd-listen-ip}/openssh-7.7p1-RPMs.tar.gz; fi;
if [ ! -f pam-ssh-conf-* ]; then cp /etc/pam.d/sshd pam-ssh-conf-bak; fi;
if [ ! -f openssh-7.7p1-1.el6.x86_64.rpm ]; then tar zxvf openssh-7.7p1-RPMs.tar.gz; fi;

rpm -e --nodeps `rpm -qa | grep openssh-askpass`
rpm -U *.rpm
yes | cp pam-ssh-conf-* /etc/pam.d/sshd
/etc/init.d/sshd restart
#cd
#rm -rf openssh
echo "New version upgrades as to lastest:" && $(ssh -V)
```

### For CentOS 6.4

> Update openssl first for CentOS 6.4 (add openssl-1.0.1e-57.el6.x86\_64.rpm and openssl-devel-1.0.1e-57.el6.x86\_64.rpm for update)

```bash
#!/bin/bash
# Copyright © 2018 Junyangz
# For CRS1-CentOS 6.4 with OpenSSH_5.3p1, OpenSSL 1.0.0-fips 29 Mar 2010
if [ -f /etc/ssh/sshd_config.rpmnew ]; then
    echo "New version upgrades as to lastest:" && $(ssh -V)
    exit 0
fi
cd /tmp/
# ansible all -m copy -a "src=/root/openssh-update/openssh-7.7p1-RPMs.tar.gz dest=/tmp/openssh-7.7p1-RPMs.tar.gz force=yes"
if [ ! -f openssh-7.7p1-RPMs.tar.gz ]; then exit 1; fi;
timestamp=$(date +%s)
tar zxvf openssh-7.7p1-RPMs.tar.gz
cd openssh
rpm -e --nodeps `rpm -qa |grep openssl-devel`
# update openssl
rpm -U openssl/*.rpm
# backup sshd
cp /etc/pam.d/sshd pam-ssh-conf-$timestamp
rpm -e --nodeps `rpm -qa | grep openssh-askpass`
rpm -U *.rpm
yes | cp pam-ssh-conf-$timestamp /etc/pam.d/sshd
/etc/init.d/sshd restart
cd
rm -rf /tmp/openssh /tmp/openssh-7.7p1-RPMs.tar.gz
echo "New version upgrades as to lastest:" && $(ssh -V)
```

整个升级过程不会中断ssh连接，但这种升级方式会禁止root密码登录，如需开启需修改/etc/ssh/sshd\_config文件后重启sshd。

```bash
sed -i 's/#PermitRootLogin yes/PermitRootLogin yes/g' /etc/ssh/sshd_config
/etc/init.d/sshd restart
```

附PermitRootLogin参数解释：

```bash
PermitRootLogin yes                   #允许root用户以任何认证方式登录（貌似也就两种认证方式：用户名密码认证，公钥认证）
PermitRootLogin without-password      #只允许root用public key认证方式登录
PermitRootLogin no                    #不允许root用户以任何认证方式登录
```

## Summary

~~目前已批量更新了虚拟机集群，待测试稳定无问题后再升级物理机集群。~~ 已更新完集群所有机器。


# Linux PERSISTENT NAMING

Last edited by Junyangz AT 2018-07-10 20:03:49

> Linux管理多块磁盘时（以SATA盘为例）会按磁盘加载的顺序依次给磁盘命名为/dev/sda, /dev/sdb... 这种命名规则就会导致，在增减磁盘数量（热插拔）或磁盘不稳定以及系统重启后，盘符都有可能发生变化，会影响到一些依赖磁盘盘符工作的应用程序，比如fstab里按盘符名来挂载，最终导致挂载的分区不可用影响业务运行。

## 问题分析

> ​要解决磁盘盘符漂移问题，一劳永逸的方法就是将磁盘槽位与盘符名做绑定；

如果只针对磁盘挂载到问题，可通过按标签或UUID挂载的方式解决，下文将简单介绍下方案。 如下所示的fstab，系统启动时，会自动执行每一行挂载动作，将/dev/sda挂载到/data/disk1，其它依此类推。如果磁盘发生热插拔，第一块磁盘的盘符由原来的/dev/sda变成了/dev/sdc，那么fstab就不能正确挂载第一块磁盘。

```bash
/dev/sda /data/disk1 ext4 defaults,noatime 0 0
/dev/sdb /data/disk2 ext4 defaults,noatime 0 0
```

## 按照磁盘标签挂载

为了保证在发生盘符漂移时，磁盘仍能正常挂载，首先对fstab做如下改进，按磁盘标签来挂载；比如第一行的含义是，将标签为disk1的磁盘挂载到/data/disk1。

```bash
LABEL=disk1 /data/disk1    ext4    defaults,noatime 0 0
LABEL=disk2 /data/disk2    ext4    defaults,noatime 0 0
```

接下来的问题就是如何给磁盘设置标签，针对ext系列的文件系统，可通过e\*label来设置标签；也可在磁盘format时设置标签。

```bash
mke4fs /dev/sda -L disk1 #or
mke4fs /dev/sda; e4label /dev/sda disk1
```

通过上述设置后，磁盘/dev/sda就拥有了标签disk1，在fstab里挂载拥有disk1标签的磁盘，即挂载/dev/sda，即使这块磁盘的盘符发生了变化，由于其标签没变，fstab也能正确的将其挂载；通过mke4fs或e4label设置的标签，标签实际上是跟文件系统绑定的，是文件系统超级块的一部分，可通过tune4fs查询到。

## 按照UUID挂载

​设置标签后，如果磁盘上的文件系统被重新格式化，则其原来设置的标签也就不复存在了，这也正是标签机制不足的地方；

​如果要解决这个问题，可通过在fstab里按UUID来挂载磁盘，UUID对于磁盘来说是不变的，不论其盘符、标签是否变化；但使用UUID的缺陷在于灵活性不足，不利于大批量部署。

```bash
UUID=356fdf58-6923-43d5-9a09-349159c7c8a6 /data/disk1    ext4    defaults,noatime 0 0
UUID=3b93fbad-bea2-4cbb-9a76-b4885924d287 /data/disk1    ext4    defaults,noatime 0 0
```

## 集群部署方案

~~​待补充~~

```bash
#!/bin/bash
#By Junyangz AT 2018-07-16 16:58:15 for reconfigure fstab with uuid.

timestamp=$(date +"%Y-%m-%d")
cp /etc/fstab /etc/fstab-${timestamp}

for DEV in $( cat /etc/fstab | awk '{print $1}' | fgrep /dev/ )
do
    PUREDEV=$( echo $DEV | cut -d/ -f3- )
    UUIDIS=$( ls -l /dev/disk/by-uuid/ | fgrep $PUREDEV | awk '{print $9}' )
    UUID=$( echo UUID=${UUIDIS} )
    sed -i "s|$DEV|$UUID|g" /etc/fstab
done
```

## Reference

* [Linux盘符漂移问题](http://blog.chinaunix.net/uid-20196318-id-4009633.html)
* [Persistent Naming of a Block Device CentOS 6](https://www.linuxquestions.org/questions/linux-server-73/persistent-naming-of-a-block-device-centos-6-a-946495/)
* [PERSISTENT NAMING](https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/6/html/storage_administration_guide/persistent_naming)


# Use Kafka with Flume - CRS2

Create by Junyangz AT 2018-08-01 10:53:46 based on dmy's docs.

Last edited by Junyangz AT 2018-08-01 13:32:51.

## Flume

### Intoduction Flume

Flume is a distributed, reliable, and available service for efficiently collecting, aggregating, and moving large amounts of log data. It has a simple and flexible architecture based on streaming data flows. It is robust and fault tolerant with tunable reliability mechanisms and many failover and recovery mechanisms. It uses a simple extensible data model that allows for online analytic application.

![DevGuide\_image00.png](https://flume.apache.org/_images/DevGuide_image00.png)

### 安装

1. 安装前准备

   安装jdk
2. 下载

   <http://flume.apache.org/download.html>

   apache-flume-1.7.0-bin.tar.gz
3. 解压

```bash
tar -zxvf apache-flume-1.7.0-bin.tar.gz
```

### 配置及运行

Use the Kafka sink to send data to Kafka from a Flume source.[refer-doc](https://www.cloudera.com/documentation/kafka/2-0-x/topics/kafka_flume.html#concept_rsb_tyb_kv__section_zgc_tyb_kv)

#### 添加PATH环境变量

* conf/flume-env.sh

```bash
cp flume-env.sh.template flume-env.sh
export JAVA_OPTS="-Xms2048m -Xmx4096m -Dcom.sun.management.jmxremote"
```

* conf/spool1-kafka.properties 重要配置参数：

```
a1.sources.r1.spoolDir = /home1/flume/spool/dns/1   # 监听目录需要提前创建好
a1.sinks.k1.kafka.bootstrap.servers = hadoop-slave01:9092,hadoop-slave02:9092   # broker列表（部分）
a1.sinks.k1.kafka.topic = test   # topic名称
```

* conf/spool\[2-n]-kafka.properties同上

#### 启动

```bash
flume-ng agent -n a1 -c conf -f conf/spool1-kafka.properties &
flume-ng agent -n a2 -c conf -f conf/spool2-kafka.properties &
...
#start_flume.sh
```

## Kafka

### Intoduction Kafka

Kafka® is used for building real-time data pipelines and streaming apps. It is horizontally scalable, fault-tolerant, wicked fast, and runs in production in thousands of companies.

![kafka\_diagram](https://kafka.apache.org/images/kafka_diagram.png)

### Kafka安装

1. 安装前准备
   1. 安装jdk
   2. 启动zookeeper
2. 下载

   <http://kafka.apache.org/downloads>

   kafka\_2.10-0.10.0.0.tgz
3. 解压

```bash
tar -zxvf kafka_2.10-0.10.0.0.tgz
```

### 配置Kafka

1. 添加PATH环境变量
2. **config/server.properties**

   ```
    broker.id=0  #每一个boker都有一个唯一的id作为它们的名字，一般是从0开始，依次加1。当该服务器的IP地址发生改变时，broker.id没有变化，则不会影响consumers的消费情况
    delete.topic.enable=true #直接删除 topic
    auto.create.topics.enable=false  #默认为true，生产环境通常置为false
    auto.leader.rebalance.enable=true  #balancing leadership，默认即为 true
    listerners=PLAINTEXT: #client3:9092
    log.dirs=/opt/apps/kafka/logs  #kafka数据的存放地址，多个地址用逗号分割，多个目录分布在不同磁盘上可以提高读写性能
    default.replication.factor=3
    min.insync.replicas=2  #当producer设置 request.required.acks 为-1时， min.insync.replicas 指定 replicas 的最小数目（必须确认每一个 repicas 的写数据都是成功的），如果这个数目没有达到， producer 会产生异常（默认为1）
    queued.max.requests  #在网络线程停止读取新请求之前，可以排队等待I/O线程处理的最大请求个数（默认为 500）
    zookeeper.connect=slave10:2181,slave11:2181,slave12:2181  #指定zookeeper连接字符串，格式如hostname:port
   ```
3. **bin/kafka-server-start.sh**
   1. **添加以下代码，开启 JMX（便于监控）：**

      ```bash
       if [ "x$KAFKA_HEAP_OPTS" = "x" ]; then
       export KAFKA_HEAP_OPTS="-Xmx1G -Xms1G"
       export JMX_PORT="9999"
       fi
      ```
   2. **修改上面的 Java 设置：**

      测试机上目前的配置如下：

      > -Xmx6g -Xms6g -XX:PermSize=128m -XX:MaxPermSize=256m

      LinkedIn 的 Java 配置：

      > -Xmx6g -Xms6g -XX:MetaspaceSize=96m -XX:+UseG1GC -XX:MaxGCPauseMillis=20 -XX:InitiatingHeapOccupancyPercent=35 -XX:G1HeapRegionSize=16M -XX:MinMetaspaceFreeRatio=50 -XX:MaxMetaspaceFreeRatio=80

### 启动Kafka

Kafka 集群中的节点要**关闭防火墙**，不然会报如下错误：

> Error in fetch kafka.server.ReplicaFetcherThread$FetchRequest\@5b1413a8 (kafka.server.ReplicaFetcherThread) java.io.IOException: Connection to client3:9092 (id: 1 rack: null) failed

```bash
kafka-server-start.sh config/server.properties &
kafka-server-stop.sh
```

## Kafka Manager

A tool for managing Apache Kafka. <https://github.com/yahoo/kafka-manager>

1. 安装前准备
   * 安装 sbt，jdk8
   * 想要看到读取、写入速度，kafka 需要开启 JMX
2. 下载

   ```bash
    git clone https://github.com/yahoo/kafka-manager
   ```
3. 编译

   由于需要的环境是 **Java 8+**， 如果 java 不在环境变量中，在编译和运行时需要指定 Java 8+

   ```bash
    cd kafka-manager
    PATH=/home/hadoop-user/jdk1.8.0_131/bin:$PATH
    JAVA_HOME=/home/hadoop-user/jdk1.8.0_131
    sbt -java-home/home/hadoop-user/jdk1.8.0_131 clean dist
   ```
4. 解压

   编译好的包在 **kafka-manager/target/universal** 中，将其移动到指定目录进行解压。

   ```bash
    unzip kafka-manager-1.3.3.6.zip
   ```
5. 配置
   1. **conf/application.conf**

      ```
       kafka-manager.zkhosts="slave10:2181,slave11:2181,slave12:2181"
      ```
6. 启动
   1. 编写启动脚本：

      ```bash
       vim start.sh
       #nohup ./kafka-manager &
       #默认地，kafka manager 使用 9000 端口，可以添加以下参数进行修改：
       #nohup ./kafka-manager -Dconfig.file=/path/to/application.conf -Dhttp.port=8080 &
       #如果 java 8 不在环境变量中，增加 -java-home 参数：
       #nohup ./kafka-manager -java-home /home/hadoop-user/jdk1.8.0_131 &
      ```
   2. 启动：

      ```bash
       sh start.sh
      ```
7. 使用
   1. Web访问9000端口
   2. 创建 cluster
   3. 配置 cluster：输入Zookeeper Hosts，选择Kafka版本，打开JMX Polling

## Reference

1.[Using Kafka with Flume](https://www.cloudera.com/documentation/kafka/2-0-x/topics/kafka_flume.html#concept_rsb_tyb_kv__section_zgc_tyb_kv)

2.dmy


# Setup Chroot SFTP in CentOS

First version AT 2018-08-25 09:39:36 Updated AT 2019-11-08 14:36:29 for user separate syslog configuration.

Set up an account that will be used only to transfer files(and not to ssh to the system), you should setup SFTP Chroot Jail as explained in this article.

> If you want to give sftp access on your system to outside vendors to transfer files, you should not use standard sftp. Instead, you should setup Chroot SFTP Jail as explained below.

## Chroot SFTP Environment

`chroot` A chroot on Unix operating systems is an operation that changes the apparent root directory for the current running process and its children. Chroot SFTP means that the user can sftp to the system, and view only the directory that you’ve designated to perform sftp.

## Setup in CentOS

Test environment at CentOS 6.5.

### Create a New Group

```bash
groupadd sftpusers
```

### Create Users

`useradd -g sftpusers -s /sbin/nologin sftpuser`

`passwd sftpuser`

### Setup sftp-server Subsystem in sshd\_config

```bash
Subsystem  sftp  internal-sftp
Match Group sftpusers
    ChrootDirectory /sftp/%u
    X11Forwarding no
    AllowTCPForwarding no
    PasswordAuthentication yes
```

### Create sftp Home Directory

```bash
mkdir -p /sftp/sftpuser/home
chown sftpuser:sftpusers /sftp/sftpuser/home
```

### Restart sshd and Test Chroot SFTP

`service sshd restart`

## Script for Setup Chroot SFTP in CentOS 6.5

```bash
#!/bin/bash
#@Junyangz AT 2018-08-25 09:41:50 for configure sftp and add sftp users.

#@Configure part
#########################
sed -i "s/^Subsystem/#Subsystem/g" /etc/ssh/sshd_config
#sed -i "/^#Subsystem/aSubsystem sftp internal-sftp" /etc/ssh/sshd_config
groupadd sftpusers
#add follow to /etc/ssh/sshd_config
cat >>/etc/ssh/sshd_config <<EOF
Subsystem sftp internal-sftp
KexAlgorithms curve25519-sha256@libssh.org,ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521,diffie-hellman-group-exchange-sha256,diffie-hellman-group14-sha1,diffie-hellman-group-exchange-sha1,diffie-hellman-group1-sha1
Match Group sftpusers
        ChrootDirectory /data1/sftp/%u
        ForceCommand internal-sftp -f LOCAL0 -l INFO
        X11Forwarding no
        AllowTCPForwarding no
        PasswordAuthentication yes
EOF
service sshd restart
#@Add user part
#########################
# create an sftp user and jail them, using username, password and CHROOT path provided as script args.
if [ -z "$2" ]; then
    echo "Usage: add-sftp-user.sh username password /data1"
    echo "Create sftp user with chroot at /data1/sftp/username and work directory at home."
    exit 1
fi
username=$1 # get from script params
egrep "^$username" /etc/passwd >/dev/null
if [ $? -eq 0 ]; then
    echo "$username exists!"
    exit 1
else
password=$2 # get this from script params
#CHROOT_DIR=$3 # get this from script params
#useradd -g sftpusers -d $CHROOT_DIR/sftp/$username -s /sbin/nologin $username
useradd -g sftpusers -M -s /sbin/nologin $username
[ $? -eq 0 ] && echo "User has been added to system!" || echo "Failed to add a user!"
echo "$username:$password" | chpasswd
usermod -d /upload $username
mkdir -p /data1/sftp/$username/upload
chown -R $username:sftpusers /data1/sftp/$username/upload
#chmod 755 $CHROOT_DIR/sftp/$username/upload
fi
```

## Log internal-sftp chroot jailed users

* add to `/etc/ssh/sshd_config`

```
# Subsystem sftp internal-sftp -l VERBOSE -f LOCAL0
# only need in Match group block
ForceCommand internal-sftp -f LOCAL0 -l INFO
```

* add to `/etc/rsyslog.d/sftp.conf`

```
# add sftp chroot log
# %u represent sftp username
# local0.* /var/log/sftp.log
# $AddUnixListenSocket /data1/sftp/%u/dev/log

#===above is old config===#

input(type="imuxsock" HostName="sftp_username" Socket="/data1/sftp/sftp_username/dev/log" CreatePath="on")
if $fromhost == 'sftp_username' then /var/log/sftp/sftp_username.log
& stop
```

* mkdir for socket file and touch sftp log

```bash
#%u represent sftp username
mkdir /data1/sftp/%u/dev
chown %u:sftpusers /data1/sftp/%u/dev
mkdir /var/log/sftp/
touch /var/log/sftp/sftp_username.log
```

* restart sshd and rsyslog

```bash
service sshd restart
service rsyslog restart
```

## Reference

1. [How to Setup Chroot SFTP in Linux (Allow Only SFTP, not SSH)](https://www.thegeekstuff.com/2012/03/chroot-sftp-setup)
2. [SFTP chroot](https://wiki.archlinux.org/index.php/SFTP_chroot)
3. [How to configure an sftp server with restricted chroot users with ssh keys](https://access.redhat.com/solutions/2399571)
4. [SFTP Guide - IBM](ftp://public.dhe.ibm.com/software/commerce/doc/sb2bi/gis42/GIS42_SFTP.pdf)
5. [SFTP log](https://access.redhat.com/discussions/672633)


# Setup software RAID-5 in CentOS

```bash
mdadm -E /dev/sd[b-k]
#Partitioning the Disks for RAID
mdadm -E /dev/sd[b-k]1 # If no super-blocks detected, than we can move forward to create a new RAID 5 setup on these drives.
############
umount /data[1-5]
for DEV in `ls /dev/sd[b-f]`
parted /dev/sdb print
parted /dev/sdb mktable gpt
parted /dev/sdb rm 1
parted /dev/sdb mkpart primary 1 100%

mdadm -C /dev/md0 -l 5 -n 4 -x 1 /dev/sdb1 /dev/sdc1 /dev/sdd1 /dev/sde1 /dev/sdf1

umount /data[6-9] && umount /data10
for DEV in `ls /dev/sd[g-k]`
parted /dev/sdg print
parted /dev/sdg mktable gpt
parted /dev/sdg rm 1
parted /dev/sdg mkpart primary 1 100%

mdadm -C /dev/md1 -l 5 -n 4 -x 1 /dev/sdg1 /dev/sdh1 /dev/sdi1 /dev/sdj1 /dev/sdk1

##########
# mdadm --create /dev/md0 --level=5 --raid-devices=10 /dev/sdb1 /dev/sdc1 /dev/sdd1 /dev/sde1 /dev/sdf1 /dev/sdg1 /dev/sdh1 /dev/sdi1 /dev/sdj1 /dev/sdk1
# mdadm -C /dev/md0 -l 5 -n 10 /dev/sd[b-k]1
# mdadm -C /dev/md0 -l 5 -n 8  -x 2 /dev/sd[b-k]1 #add two Spare Drive.

cat /proc/mdstat
watch -n1 cat /proc/mdstat
mdadm --detail /dev/md0
mdadm -D /dev/md0
#----------------------#

mkfs.ext4 /dev/md0
mkfs.ext4 /dev/md1
mount /dev/md0 /data1 -o noatime
mount /dev/md1 /data2 -o noatime
###########
vim /etc/fstab
#/dev/md0                /data1              ext4    defaults,noatime        0 0
#/dev/md1                /data2              ext4    defaults,noatime        0 0

mount -av # check whether any errors in fstab entry.

# Step 5: Save Raid 5 Configuration
mdadm --detail --scan --verbose >> /etc/mdadm.conf

# Step 6: Adding Spare Drives
# What the use of adding a spare drive? its very useful if we have a spare drive, if any one of the disk fails in our array, this spare drive will get active and rebuild the process and sync the data from other disk, so we can see a redundancy here.
# For more instructions on how to add spare drive and check Raid 5 fault tolerance, read #Step 6 and #Step 7 in the following article.

# Add Spare Drive to Raid 5 Setup(https://www.tecmint.com/create-raid-6-in-linux/)
```

## In short

```bash
### make raid5
umount /data[1-9] && umount /data10 && for i in {b..k} ;do parted /dev/sd$i rm 1 && parted /dev/sd$i mkpart primary 1 100%;done
mdadm -C /dev/md0 -l 5 -n 4 -x 1 /dev/sdb1 /dev/sdc1 /dev/sdd1 /dev/sde1 /dev/sdf1 && mdadm -C /dev/md1 -l 5 -n 4 -x 1 /dev/sdg1 /dev/sdh1 /dev/sdi1 /dev/sdj1 /dev/sdk1 && watch -n1 cat /proc/mdstat
### mkfs
mkfs.ext4 -T largefile /dev/md0 && mkfs.ext4 -T largefile /dev/md1 &
### save config
mdadm --detail --scan --verbose >> /etc/mdadm.conf
### mount
mount /dev/md0 /data1 -o noatime && mount /dev/md1 /data2 -o noatime
### update fstab
sed -i '/data/d' /etc/fstab
cat >> /etc/fstab << EOF
/dev/md0                /data1              ext4    defaults,noatime        0 0
/dev/md1                /data2              ext4    defaults,noatime        0 0
EOF
```

## Raid10

```bash
umount /data[1-2]
mdadm -S /dev/md0
mdadm -S /dev/md1
for i in {b..k} ; do parted /dev/sd$i mktable gpt && parted /dev/sd$i mkpart primary 1 100%; done
mdadm --zero-superblock /dev/sd[b-k]1
mdadm -C /dev/md0 -l 10 -n 10 /dev/sdb1 /dev/sdc1 /dev/sdd1 /dev/sde1 /dev/sdf1 /dev/sdg1 /dev/sdh1 /dev/sdi1 /dev/sdj1 /dev/sdk1
```

## Reference

1. [Creating RAID 5 (Striping with Distributed Parity) in Linux – Part 4](https://www.tecmint.com/create-raid-5-in-linux/)
2. [使用parted对大于2TB的硬盘分区](http://blog.51cto.com/jackeyge/1878676)
3. [Linux 创建和管理软RAID实例](https://www.jianshu.com/p/f207c58642b0)
4. [RAID详解，Linux系统下使用mdadm程序实现常用软件RAID的各种配置](https://www.dwhd.org/20150522_103540.html)


# SSH-port-forwarding

```bash
# 本地转发（SERVER可以访问目标服务）本机监听forwardingPort
ssh -g -f -N -L forwardingPort:targetIP:targetPort user@SERVER
# 远程转发（SERVER具有公网IP,提供内网服务外网访问）SERVER监听forwardingPort
ssh -f -N -R forwardingPort:targetIP:targetPort user@SERVER
# 动态转发（创建了SOCKS代理服务，流量转发至SERVER）
ssh -p PORT -f -N -D *:20080 user@SERVER
```

## Reference

1. [IBM](https://www.ibm.com/developerworks/cn/linux/l-cn-sshforward/index.html)
2. [SSH Manual Page](https://man.openbsd.org/ssh)


# Elasticsearch In Production

## Install Elasticsearch with RPM manually

```bash
sudo rpm --install elasticsearch-6.6.0.rpm
sudo chkconfig --add elasticsearch
```

## Configure Elasticsearch

`/etc/elasticsearch/elasticsearch.yml`

`/etc/sysconfig/elasticsearch` #SysV init

`/etc/elasticsearch/jvm.options` #Xmx <= 32G

```bash
sed -n '/^#/!p' /etc/elasticsearch/elasticsearch.yml
####
cluster.name: crs2-es
node.name: ${HOSTNAME}
path.data: /data3/elasticsearch/data
path.logs: /data3/elasticsearch/log

bootstrap.system_call_filter: false #set to false as SecComp fails on CentOS 6
bootstrap.memory_lock: true
network.host: [10.0.0.1, 127.0.0.1]
http.port: 9200
discovery.zen.ping.unicast.hosts: ["10.0.0.1", "10.0.0.2", "10.0.0.3"]
#########################################

sed -n '/^#/!p' /etc/sysconfig/elasticsearch
####
JAVA_HOME=/opt/apps/java/jdk1.8.0_171
ES_PATH_CONF=/etc/elasticsearch
ES_STARTUP_SLEEP_TIME=5
MAX_LOCKED_MEMORY=unlimited
#########################################

sed -n '/^#/!p'  /etc/elasticsearch/jvm.options |egrep Xm[sx]
####
-Xms24g
-Xmx24g
```

```bash
mkdir -p  /data3/elasticsearch/{data,log}
chmod -R 775 /data3/elasticsearch/
chown elasticsearch:elasticsearch /data3/elasticsearch/{data,log}
```

## Run Elasticsearch

`sudo -i service elasticsearch start`

## Check status(cluster)

`curl -XGET 'localhost:9200/_cluster/state?pretty'`

## Reference

* [Elasticsearch Reference \[6.6\]](https://www.elastic.co/guide/en/elasticsearch/reference/current/rpm.html#rpm)
* [Elasticsearch In Production — Deployment Best Practices](https://medium.com/@abhidrona/elasticsearch-deployment-best-practices-d6c1323b25d7)


# ELK-simple-tutorial

v0.0.1 on 2019-02-21 18:52:34

v0.0.2 on 2019-11-08 15:56:02

本文简述elk三剑客的简单使用场景，不包括安装部分。 日志采集总体流程为：filebeat -> logstash -> elasticsearch -> Kibana/Grafana

## filebeat

filebeat配置文件为filebeat.yml,添加需要ship的日志文件路径

```bash
sed -n '/^#/!p' /opt/apps/filebeat/filebeat.yml|egrep -v "^$|#"
##################
filebeat.inputs:
- type: log
  enabled: false
  paths:
    - /var/log/sftp.log
  tags: ["SFTP"]
- type: log
  enabled: false
  paths:
    - /opt/apps/processDNS/logs/*.log
  tags: ["DNS"]
filebeat.config:
  inputs:
    enabled: true
    path: inputs.d/*.yml
    reload.enabled: true
    reload.period: 10s
filebeat.config.modules:
  path: ${path.config}/modules.d/*.yml
  reload.enabled: false
setup.template.settings:
  index.number_of_shards: 3
setup.kibana:
output.logstash:
  hosts: ["logstash-host:5044"]
processors:
  - add_host_metadata: ~
  - add_cloud_metadata: ~
```

1. 以上配置添加了/var/log/sftp.log和/opt/apps/processDNS/logs/文件下以.log结尾的文件并对应添加了相应的tags标签
2. 开启自动reload，修改配置文件后不需要重启filebeat,本例中只对filebeat.input配置块进行实时监听并reload。
3. 配置输出logstash的主机名

## logstash

logstash 主要对filebeat输入的日志进行筛选匹配等一系列操作。

```bash
# Sample Logstash configuration for creating a simple
# Beats -> Logstash -> Elasticsearch pipeline.

input {
  beats {
    port => 5044
    type => filebeat
  }
}

filter {
  if [type] == "filebeat" {
        if "written" in [message] {
                grok {
                  match => { "message" => "%{SYSLOGTIMESTAMP:syslog_timestamp} %{SYSLOGHOST:syslog_hostname} %{DATA:syslog_program}(?:\[%{POSINT:syslog_pid}\])?: close \"%{DATA:syslog_message}\" bytes read 0 written %{NUMBER:sftp_write:int}" }
                  remove_field => "message"
                }
                grok {
                    match => { "syslog_message" => "/(?<datedir>\d{4}\d{2}\d{2})/%{GREEDYDATA:filename}" }
                    remove_field => "syslog_message"
                }
                grok {
                    match => { "filename" => "(?<ab_code>\d{3})_%{DATA:source_ip}_%{DATESTAMP_EVENTLOG:file_timestamp}_" }
                }
                if [ab_code] == "100" {
                    mutate {
                        add_field => {
                            "sftp_username" => "BJ100"
                        }
                    }
                }

                date {
                  match => [ "syslog_timestamp", "MMM  d HH:mm:ss", "MMM dd HH:mm:ss" ]
                }
                date {
                    match => ["syslog_timestamp", "MMM  d HH:mm:ss", "MMM dd HH:mm:ss" ]
                    target => "syslog_timestamp"
                }
                date {
                    match => ["file_timestamp", "yyyyMMddHHmmss"]
                    target => "file_timestamp"
                }
                ruby {
                  init => "require 'time'"
                  code => "duration = (event.get('syslog_timestamp') - event.get('file_timestamp')) rescue nil; event.set('Upload_delay', duration); "
                }
        }
        if "processDNS" in [source] {

                grok {
                  match => { "source" => "/opt/apps/processDNS/logs/DNS-%{USERNAME:sftp_username}_2qS-putted-%{DATA:datedir}.log" }
                }

                # grok {
                #   match => { "message" => "%{DATESTAMP:event_timestamp} %{DATA:event_flag}: %{DATA:filename} was putted." }
                #   add_field => {
                #         "event_status" => "putted"
                #     }
                # }
                # date {
                #   match => [ "event_timestamp", "yyyy/MM/dd HH:mm:ss" ]
                # }
        }
    }
}


output {

  elasticsearch {
    hosts => ["es1:9200", "es2:9200", "es3:9200"]
    index => "logstash-%{+YYYY.MM.dd}"
  }

  elasticsearch {
    hosts => ["http://127.0.0.1:9200"]
    index => "logstash-%{+YYYY.MM.dd}"
  }

}
```

1. 使用filter插件对通过filebeat接受到的日志进行grok匹配相应字段(enrich)，便于后续统计分析。
2. output可以指定多个es输出源

## Kibana

用于分析可视化数据，具体使用参考官方文档。 如需非本机访问要将默认配置文件kibana.yml中监听地址改成`"0.0.0.0"`

## Grfana

设定数据源为es（支持多种数据源），然后编写query制图，可以设定alert。制图这一块和kibana极为相似。 相较于kibana的优势在于支持权限控制ACL及告警机制，制图也略胜一点。

## 白话文elk使用-CRS3

整个集群运行监控和维护的主要流程如下： 首先使用filebeat以及metricbeat采集系统的各项指标日志，这一块的内容kibana里有对应的指导安装部署的教程。 我们生产环境中使用这两个组件分别采集的日志是各sftp用户的syslog日志和系统日志，安装的过程可以直接使用rpm包的形式，十分方便。 目前管理机上有对应的内部源/etc/yum.repos.d/elasticstack.repo下发至待安装集群就可以使用ansible批量安装了。

`ansible host-group -m yum -a 'name=filebeat stats=latest'`

```ini
[elasticsearch-7.x]
name=Elasticsearch repository for 7.x packages
baseurl=http://10.252.208.189/repos/elasticstack/yum/elastic-7.x
gpgcheck=0
enabled=1
type=rpm-md
```

关于在同一台机器上启动多个filebeat实例有个小tips：

1. 将/usr/lib/systemd/system/filebeat.service拷贝一份例如/usr/lib/systemd/system/filebeat\@cdn.service
2. 建立一个新的配置文件目录/etc/filebeat2/, 从/etc/filebeat/复制一份配置文件过去，针对需求改动配置文件
3. 创建filebeat的数据存储目录和日志存储目录，然后修改service文件中对应的配置文件，存储目录等。
4. 执行`systemctl daemon-reload`然后用`systemctl start filebeat@cdn && systemctl enable filebeat@cdn`启动新的filebeat\@cdn服务以及设定开机自启动。

下面的配置文件是我的service文件，可以看到需要改动的地方不多。

```ini
[Unit]
Description=Filebeat sends log files to Logstash or directly to Elasticsearch.
Documentation=https://www.elastic.co/products/beats/filebeat
Wants=network-online.target
After=network-online.target

[Service]

Environment="BEAT_LOG_OPTS=-e"
Environment="BEAT_CONFIG_OPTS=-c /etc/filebeat2/filebeat.yml"
Environment="BEAT_PATH_OPTS=-path.home /usr/share/filebeat -path.config /etc/filebeat2 -path.data /var/lib/filebeat2 -path.logs /var/log/filebeat2"
ExecStart=/usr/share/filebeat/bin/filebeat $BEAT_LOG_OPTS $BEAT_CONFIG_OPTS $BEAT_PATH_OPTS
Restart=always

[Install]
WantedBy=multi-user.target
```

```yml
filebeat.inputs:
- type: log
  enabled: false
  paths:
     - /opt/apps/scripts/processCDN/logs/*.log
filebeat.config.modules:
  path: ${path.config}/modules.d/*.yml
  reload.enabled: true
  reload.period: 10s
setup.template.settings:
  index.number_of_shards: 3
setup.kibana:
  host: "nrgl-core-mpp-b-7:5601"
output.elasticsearch:
  hosts: ["http://nrgl-core-mpp-b-7:9200","http://nrgl-core-mpp-c-10:9200","http://nrgl-core-mpp-c-15:9200"]
processors:
  - add_host_metadata: ~
  - add_cloud_metadata: ~
```

以上的配置采集的是system日志，需要将filebeat配置文件/etc/filebeat/modules.d/system.yml.disabled改成/etc/filebeat/modules.d/system.yml开启这个模块。

```yml
filebeat.inputs:
- type: log
  enabled: true
  paths:
     - /var/log/sftp/*.log
filebeat.config.modules:
  path: ${path.config}/modules.d/*.yml
  reload.enabled: true
  reload.period: 10s
setup.template.settings:
  index.number_of_shards: 3
setup.kibana:
  host: "nrgl-core-mpp-b-7:5601"
output.logstash:
  hosts: ["nrgl-core-mpp-c-9:5044"]
processors:
  - drop_event:
      when:
        not:
          contains:
            message: "written"
  - add_host_metadata: ~
  - add_cloud_metadata: ~
```

以上配置文件采集的是sftp用户日志，在processors里进行了日志事件的过滤，只提取sftp日志里包含“written”字段的日志事件`Nov 3 03:41:16 xxx internal-sftp[75685]: close "/upload/filename.log.gz.tmp" bytes read 0 written 2671` 发送到logstash里进行filter、enrich和字段匹配grok等操作。

Logstash 部署在`nrgl-core-mpp-c-9`这台机器上，rpm包的形式安装。主要配置文件/etc/logstash/logstash-filebeat-input-out-es.conf

```
# Beats -> Logstash -> Elasticsearch pipeline.

input {
  beats {
    port => 5044
    type => filebeat_CDN
  }
}

filter {
  if [type] == "filebeat_CDN" {
        if "written" in [message] {
                grok {
                  # CN_access_2107775201014001_BCS-CDN-2075-660349_20190723T105001Z_1.log.gz.tmp
                  # CN_access_1975750101001_SCS-CDN-0002-CSX01_20190815153000_1.log.gz.tmp
                  match => { "message" => "%{SYSLOGTIMESTAMP:syslog_timestamp} %{USERNAME:cdn_sftp_username} %{DATA:syslog_program}(?:\[%{POSINT:syslog_pid}\])?: close \"%{DATA:syslog_message}\" bytes read 0 written %{NUMBER:cdn_sftp_written:int}" }
                  remove_field => "message"
                }
                grok {
                    match => { "syslog_message" => "/%{DATA:upload_dir}/%{GREEDYDATA:filename}" }
                    remove_field => "syslog_message"
                }
                grok {
                    match => { "filename" => "CN_%{DATA:cdn_log_Type}_%{DATA:cdn_log_NodeId}_%{DATA:cdn_log_DeviceId}_%{DATA:cdn_log_timestamp}_" }
                }
                date {
                    match => [ "syslog_timestamp", "MMM  d HH:mm:ss", "MMM dd HH:mm:ss" ]
                }
                date {
                    match => [ "syslog_timestamp", "MMM  d HH:mm:ss", "MMM dd HH:mm:ss" ]
                    target => "syslog_timestamp"
                }

                if "Z" in [cdn_log_timestamp] {
                    date {
                        match => [ "cdn_log_timestamp", "yyyyMMdd'T'HHmmssZ" ]
                        timezone => "UTC"
                        target => "cdn_log_timestamp"
                    }
                } else {
                    date {
                        match => [ "cdn_log_timestamp", "yyyyMMddHHmmss" ]
                        target => "cdn_log_timestamp"
                    }
                }

                ruby {
                    init => "require 'time'"
                    code => "duration = (event.get('syslog_timestamp') - event.get('cdn_log_timestamp')) rescue nil; event.set('cdn_log_upload_delay', duration); "
                }
        }

        if "_grokparsefailure" in [tags] {
            drop { }
        }
    }
}


output {
  elasticsearch {
    hosts => ["http://nrgl-core-mpp-b-7:9200","http://nrgl-core-mpp-c-10:9200","http://nrgl-core-mpp-c-15:9200"]
    index => "logstash-%{+YYYY.MM.dd}"
  }
}
```

这一块的配置文件需要了解logstash的配置文档，建议参考官方的文档针对需求有目的的学习。 以上配置主要是对filebeat发来的日志作各种正则匹配提取各项字段，以及通过文件名提取的日志打包时间戳和上传至服务器所产生的syslog日志时间戳计算时延并保存到字段`cdn_log_upload_delay`里。最后输出到elasticsearch供后续分析查询。

最后一块就是日志分析和展示了，这一块主要通过kibana和grafana里做，多使用以及多看别人是怎么用的就好了，每个人都有自己的风格和特点，不同的场景和需求下能出各式各样的图表，我也没啥值得拿出来的经验分享了。

整个过程是一个比较实际的应用场景，各个环节的调优和负载均衡都没有深入的研究，后续还有很多内容值得去探索发现。


# Ansible Playbooks for Apache Kafka in production

## 前言

参考了[confluentinc/cp-ansible](https://github.com/confluentinc/cp-ansible)playbook批量安装部署Apache Kafka 2.2.0. 已在[Github](https://github.com/Junyangz/ansible-kafka)上开源

> 注：集群的zookeeper集群已通过Cloudera Manager安装了，所以不包括Zookeeper的安装部分

## Apache Kafka & Systemd

### Requirements

* Ansible setup on your terminal
* rhel7/CentOS7
* Zookeeper cluster
* Ansible playbook([repo](https://github.com/Junyangz/ansible-kafka))
* offline dist([Apacher Kafka](https://kafka.apache.org/downloads))

### Source tree

![Source tree](https://img.junyangz.com/picGo/20190524102658.png)

> 注：仓库里不含Kafka发行包`kafka_2.12-2.2.0.tgz`,需要自行下载并放置在上图的位置中

* `hosts.yml` 里列出所有的主机角色，包括`Zookeeper`和`Kafka broker`
* `all.yml` 里列出对位于`broker`分组中的主机执行`task`任务

### 理解和使用

运行

```bash
#ansible-playbook -C all.yml #运行之前可以先检查下,检查没问题后再运行
ansible-playbook -i hosts.yml all.yml
```

`roles/kafka-broker/defaults/main.yml`

```
配置安装过程中的所需变量即Zookeeper端口，Kafka broker配置，Systemd等相关内容
```

`roles/kafka-broker/handlers/main.yml`

```
配置完Systemd服务后执行daemon-reload并重启Kafka broker
```

`roles/kafka-broker/tasks/main.yml`

```
任务执行流，创建Kafka用户及用户组，创建Kafka数据目录，分发Kafka发行包，更改权限，拷贝broker配置文件并修改，创建systemd文件并启动Kafka
```

`roles/kafka-broker/templates/`

```
该文件夹下放置了broker的配置文件和Systemd文件，执行的时候将defaults设定的变量写入到文件中
默认配置文件参考了confluent Kafka的配置，请自行修改成自己所需的配置。
```

部署后可以使用 `journalctl -xefu kafka`检查Kafka的运行日志,部署目录/opt/apps/kafka/logs下也有相应的日志文件，此外还可以在Syslog中自行配置Identifier=kafka的日志处理。例如传输到各种监控程序中。

## Reference

* [Ansible Playbooks for Confluent Platform](https://docs.confluent.io/current/tutorials/cp-ansible/docs/index.html)
* [Running Kafka in Production](https://docs.confluent.io/current/kafka/deployment.html#cp-production-parameters)
* [cp-ansible](https://github.com/confluentinc/cp-ansible)
* [Ansible playbooks for Kafka and Zookeeper](https://blog.insightdatascience.com/ansible-playbooks-for-kafka-and-zookeeper-with-ec2-dynamic-inventory-8f317d4d2bfc)
* [HowTo: Organize Ansible Playbook to install, uninstall, start and stop Kafka and Kafka Connect](https://medium.com/@mykidong/howto-organize-ansible-playbook-to-install-uninstall-start-and-stop-kafka-and-kafka-connect-e7250c5def9d)


# GitHub Actions depoly Hexo

> 前言： 最近换了Mac，之前在Windows上写博客是基于本地配置好的一个hexo渲染引擎，迁移到Mac着实花费了不少精力(主要是学习和回顾，好久没折腾hexo了)，结论是本地维护一个这样的渲染环境很麻烦也不易迁移。因而有了这篇文章从另外一个角度来实现hexo博客更快的部署和维护。

## GitHub Actions

* 概念
  * GitHub推出的[持续集成](https://en.wikipedia.org/wiki/Continuous_integration)服务(CI)
  * 持续集成包括：抓取代码、运行测试、登录远程服务器，发布到第三方服务（Actions）
  * 每个操作写成独立的脚本文件
  * 持续集成过程，变成 actions 的组合
* 术语
  * **workflow** （工作流程）：持续集成一次运行的过程，就是一个 workflow。
  * **job** （任务）：一个 workflow 由一个或多个 jobs 构成，含义是一次持续集成的运行，可以完成多个任务。
  * **step**（步骤）：每个 job 由多个 step 构成，一步步完成。
  * **action** （动作）：每个 step 可以依次执行一个或多个命令（action）。
* workflow 文件

  * 放置.github/workflows
  * YAML格式，后缀.yml
  * 配置文档 <https://help.github.com/en/articles/workflow-syntax-for-github-actions>
  * 基本字段：name、on、jobs、runs-on、steps

  ```yaml
  name: Greeting from Mona
  on: push

  jobs:
    my-job:
      name: My Job
      runs-on: ubuntu-latest
      steps:
      - name: Print a greeting
        env:
          MY_VAR: Hi there! My name is
          FIRST_NAME: Mona
          MIDDLE_NAME: The
          LAST_NAME: Octocat
        run: |
          echo $MY_VAR $FIRST_NAME $MIDDLE_NAME $LAST_NAME.
  ```

## Hexo迁移及部署至GitHub Pages

> 刚开始以为迁移Hexo博客很麻烦，实际上操作起来很方便没有想象中的那么困难

### 一、 迁移Hexo

1. 将Windows下的hexo博客目录整个打包下
2. 在macOS上解压，cd到博客目录下
3. （安装node）执行`npm -i -g hero-cli`, `npm -i`
4. 生成静态页面`hexo g` , 本地查看`hexo s`
5. 生成并部署至GitHub pages `hexo g -d`

### 二、 使用GitHub Actions自动化部署

1. 创建GitHub repository 存放源文件
2. 在repo设置界面里添加Secrets（本地生成一对公私钥ssh-keygen，这里填上私钥，命名为 `ACTION_DEPLOY_KEY`（可以任意命名，但要和Actions里的设定`${{ secrets.ACTION_DEPLOY_KEY }}`对应))
3. 在存放GitHub pages的repo设定Deploy keys为刚生成的公钥
4. 在根目录下创建GitHub Actions workflow文件

> `note-ci.yml`(.github/workflows/note-ci.yml)

```yaml
name: Build and Update Note.junyangz.com for github pages

on: push

jobs:
  build:
    runs-on: macOS-latest

    steps:
      - uses: actions/checkout@v1
      
      - name: Use Node.js 10.x
        uses: actions/setup-node@v1
        with:
          node-version: "10.x"

      - name: Setup Hexo env
        env:
          ACTION_DEPLOY_KEY: ${{ secrets.ACTION_DEPLOY_KEY }}
        run: |
          # set up private key for deploy
          mkdir -p ~/.ssh/
          echo "$ACTION_DEPLOY_KEY" > ~/.ssh/id_rsa
          chmod 600 ~/.ssh/id_rsa
          ssh-keyscan github.com >> ~/.ssh/known_hosts
          # set git infomation
          git config --global user.name 'Junyangz'
          git config --global user.email 'junyangz.iie@gmail.com'
          # install dependencies
          npm i -g hexo-cli
          npm i

      - name: Deploy
        run: |
          # generate and depoly
          hexo g -d
```

1. 移除不必要的文件夹node\_modules和public
2. 坑1: themes下面的主题文件material当时使用的git clone拉取的，在上层源目录添加的时候会被提示material已经存在一个repo要不要使用submoudle添加，具体详细信息后面查了下可以参考[这篇文章](https://blog.shopsys.com/how-to-maintain-multiple-git-repositories-with-ease-61a5e17152e0)
3. 坑2: 由于我使用的主题material文件是个git库，`.gitignore`文件里面有主题配置文件`_config.yml`,导致一开始的时候GitHub Action总是部署不成功，后来发现推到GitHub上的库里没有主题配置文件，手动`git add`提示被忽略，这才发现了问题，强制`git add -f`解决。

### 三、使用GitHub Actions部署本篇文章

1. 有了之前的实践后续就很简单了，首先将使用markdown写的本篇文章调整为hexo post的格式(在行首添加如下描述，可用`hexo new post`生成)

```ini
---
layout: pages
title: GitHub Actions部署Hexo博客
date: 2019-09-17 11:03:21
categories:
 - Tutorial
tags:
 - GitHub Actions
 - Hexo
---
```

1. `git add`, `git commit`, `git push`
2. Github Actions -> Github Pages -> 当前页面

## 参考文章

* [GitHub Actions 入门教程](http://www.ruanyifeng.com/blog/2019/09/getting-started-with-github-actions.html)
* [通过 GitHub Actions 自动部署 Hexo](https://gythialy.github.io/deploy-hexo-to-github-pages-via-github-actions/)
* [Github Actions 测试 - 自动部署 Hexo](https://xiaopc.org/2019/08/29/github-actions-测试-自动部署-hexo/)
* [尝试使用GitHub Actions自动部署Hexo](http://sinlapis.coding.me/2019/08/28/尝试使用GitHub-Actions自动部署Hexo/#尝试使用GitHub-Actions自动部署Hexo)


# Test HTTP3/QUIC docker

* Nginx conf <https://github.com/cloudflare/quiche/blob/master/extras/nginx/README.md>

```apacheconf
events {
    worker_connections  1024;
}

http {
    server {
        # Enable QUIC and HTTP/3.
        listen 443 quic reuseport;

        # Enable HTTP/2 (optional).
        listen 443 ssl http2;

        ssl_certificate      cert.crt;
        ssl_certificate_key  cert.key;

        # Enable all TLS versions (TLSv1.3 is required for QUIC).
        ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3;

        # Add Alt-Svc header to negotiate HTTP/3.
        add_header alt-svc 'h3-23=":443"; ma=86400';
    }
}
```

```bash
docker run --name test-quic -p 8180:80 -p 8143:443 --rm \
-v /var/www/html/:/usr/share/nginx/html:ro \
-v /etc/nginx/docker-nginx-conf/nginx.conf:/etc/nginx/nginx.conf \
-v /etc/nginx/docker-nginx-conf/certs/cert.crt:/etc/nginx/cert.crt \
-v /etc/nginx/docker-nginx-conf/certs/cert.key:/etc/nginx/cert.key \
ranadeeppolavarapu/nginx-http3:latest
```

* MacOS open Chrome with flag enabled

```bash
open -a /Applications/Google\ Chrome\ Canary.app --args \
--flag-switches-begin \
--enable-quic \
--quic-version=h3-23 \
--enable-features=EnableTLS13EarlyData \
--flag-switches-end
```

[docker-nginx-http3](https://github.com/RanadeepPolavarapu/docker-nginx-http3)


# Docker tutorial

Created on Wed, 02 Dec 2019, 06:00PM

Last changed on Wed, 04 Dec 2019, 05:58PM

> `Docker`常用命令手册

* 运行Nvidia-docker

```bash
sudo nvidia-docker run --runtime=nvidia -it -d -p 8888:8888 -p 6006:6006 -v $(pwd):/tf/ tensorflow/tensorflow:latest-gpu-py3-jupyter
--rm # 停止后即删除
```

```bash
sudo docker pull tensorflow/tensorflow:latest-gpu-py3-jupyter
# docker pull 
sudo docker images
docker run --help
docker run [-it] some-image # 创建某个镜像的容器。注意，同一个镜像可以通过这种方式创建任意多个container. 加上-it之后，可以创建之后，马上进入交互模式。
docker rm container-id # 删除某个容器
docker start [-i] container-id # 启动某个容器，必须是已经创建的。 加上-i 参数之后，可以直接进入交互模式：
docker attach container-id # 进入交互模式的另一种方式
# 进入交互模式之后，想退出但是保持容器运行，按CTRL+Q+P三个键
# 退出，并关闭停止容器，按CTRL+D或者输入exit再回车
```

* 容器备份

先通过`docker ps`或者`docker ps -a`来查看你想备份的容器的id， 然后通过：

```
docker commit -p [your-container-id] [your-backup-name]
```

来将id为your-container-id的容器创建成一个镜像快照。

接着，你通过`docker images`就可以查看到刚刚创建好的镜像快照了。 然后，通过：

```
docker save -o [path-you-want-to-save/your-backup-name.tar]] [your-backup-name]
```

把那个镜像打包成tar文件，保存到服务器上。 后面就可以把服务器上打包好的tar文件，下载到本地了。

恢复： `docker load -i your-backup-name.tar`

`docker run -d -p 80:80 your-backup-name`

* 其他

```bash
docker run \
    --interactive \
    --tty \
    --volume $(pwd):/federated \
    --workdir /federated \
    tensorflow_federated \
    bash
    
sudo nvidia-docker run --runtime=nvidia \
    --interactive \
    -p 8896:8888 \
    -p 6003:6006 \
    --tty \
    --volume $(pwd):/federated \
    --workdir /federated \
    tensorflow_federated:v0.11-py3-jupyter \
    bash
```


# SFTP-auth-pubkey

现有两台主机A、B。主机A使用用户名sftp-users通过sftp登录主机B上传文件。

配置公钥登录可以按照如下流程来操作：

1. 主机A生成公私钥对`id_rsa、id_rsa.pub` ｜可用命令`ssh-keygen -t rsa`生成
2. 主机B在sftp-users用户的主页目录下创建`~/.ssh/authorized_keys`文件
3. 主机B将主机A生成的`id_rsa.pub`的内容拷贝到上诉文件中，并将authorized\_keys文件owner属性设定为sftp-users，权限设定为600。｜可以尝试使用 `ssh-copy-id`进行密钥拷贝。
4. 主机A使用命令sftp登录主机B进行测试。

如果设定为仅密钥登录，主机B则可以在sshd\_config配置文件中屏蔽密码登录：

```bash
PasswordAuthentication yes

Match Group sftpusers
        ChrootDirectory /sftp/%u
        PasswordAuthentication no
```

使用`Match Group`可让某些选项只对该用户组生效，上诉配置设定了除了`sftpusers`用户组外，其他用户都可以用密码登录。

Debug小注解：如果使用公钥登录失败首先查看sftp-users的home目录是否设定正确，可通过/etc/passwd查看，如不正确可使用`usermod -d /sftp/sftpusers/home sftpusers`修改，必须要保证`.ssh/authorized_keys`的上级目录为sftp-users的home目录。示例中的认证文件绝对路径为`/sftp/sftpusers/home/.ssh/authorized_keys`

参考文档：

<https://wiki.archlinux.org/index.php/SFTP_chroot>


# Linux Process Substitution

Linux Process Substitution is a feature that allows the output of a command or process to be used as input to another command or process. This can be useful when working with commands that do not accept standard input or when you want to manipulate the output of a command before passing it on as input to another command.

Process substitution is performed using the <() or >() operators. The <() operator is used to pass the output of a command or process as standard input to another command, while the >() operator is used to pass the output of a command or process as a file to another command.

Here is an example of using process substitution to sort the output of the ls command and pass it as input to the wc command, which counts the number of lines, words, and characters in a file:

```bash
wc -l <(sort <(ls))
```

In this example, the output of the ls command is passed as input to the sort command, which sorts the output alphabetically. The sorted output is then passed as standard input to the wc command, which counts the number of lines in the input.

Process substitution can be used with any command that accepts standard input or a file as an argument. It is a powerful tool that can greatly simplify and streamline your workflow when working with the command line.

There are a few things to keep in mind when using process substitution:

* Process substitution is performed asynchronously, which means that the commands or processes inside the <() or >() operators are executed in parallel with the rest of the script. This can be useful for running long-running commands or processes in the background, but it can also lead to unexpected behavior if the commands inside the process substitution depend on each other.
* Process substitution is not supported by all shells. It is supported by the Bash shell and some other shells, but it may not work in all shells.
* The output of a command or process passed as input to another command using process substitution is treated as a file. This means that the commands inside the process substitution must produce output that is suitable for use as a file. For example, the output must be plain text, and it should not contain any special characters that would cause problems when interpreted as part of a filename.
* Process substitution can be nested, which means that you can use the output of one process substitution as input to another process substitution. This can be useful for chaining together multiple commands or processes in a complex workflow.

Overall, process substitution is a useful feature that can help you streamline your workflow and simplify complex command lines by allowing you to pass the output of one command or process as input to another. It is a powerful tool that is worth learning and using if you work with the command line on a regular basis.

## Example

```bash
# Merge the output of two commands:
cat <(command1) <(command2)
```

```bash
#Compare the contents of two files:
diff <(sort file1) <(sort file2)
```

**P.S. The above was generated by ChatGPT.**

[tldp: Process Substitution](https://tldp.org/LDP/abs/html/process-sub.html)


# Note

Record some manuals and quick-checking documents for my own use.


# Interview

## Tips

* [Tech Interview Handbook](https://yangshun.github.io/tech-interview-handbook/)
* [笔试面试知识整理](https://hit-alibaba.github.io/interview/index.html)

## Campus interview experience

`Reserved release`


# interview-prepare

## first section(自我介绍)

我叫xxx，xxx大学xxx专业毕业，我在学校获得xxx荣誉（或者证书）……这样的信息（基本信息介绍）其次，在工作方面，我在xxx公司实习（或者学校活动），我负责xx工作，为了完成这个工作，我做了xxx努力，最后取得xxx成果，结尾，还可以总结一下通过这次活动或者项目有什么收获。（如果是大佬，介绍可以更简单些。）

## 简历上的项目介绍

## 问答环节

## 反问环节

您觉得，这份工作所需的能力，我还有哪些不具备？需要在哪些方面加强？ 另外还有就是您觉得对于应届毕业生来说如何来快速的提升这些方面的能力？

## HR面

1. 缺点

   年轻经验不足，缺乏磨练、有些着急、对待效率低下的人缺乏耐心等 首先，我刚毕业，经验方面不足，我会在工作中积极完成工作，积累各方面经验其次，性子急，对待效率低下的人缺乏耐心，但是我平时和别人聊天的时候会控制自己语速和讲话，慢慢培养自己耐心，避免浮躁。（遵循一个原则避重就轻）Tips：利用你的优点改正你的缺点，比如，工作追求细节极致，导致项目无法按时完成，通过时间管理，得以解决。一定不能说对应聘岗位的硬伤的缺点，以及无法弥补的缺点。
2. 自我介绍

   首先，我叫xxx，xxx大学xxx专业毕业，我在学校获得xxx荣誉（或者证书）……这样的信息（基本信息介绍）其次，在工作方面，我在xxx公司实习（或者学校活动），我负责xx工作，为了完成这个工作，我做了xxx努力，最后取得xxx成果，结尾，还可以总结一下通过这次活动或者项目有什么收获。
3. 薪酬

   第一，每家单位都有自己的薪酬标准。第二，可以先提交一个薪酬区间，一旦被录用，人力资源部一定会有专人与您进行薪酬沟通，到时再友好协商也不晚。Tips：每个单位都有薪资宽带就最低最高界限,评估自己能力及自己生活所需，可以先提交一个薪酬区间，如果你能力强可以往上限靠，如果一般取中间值。
4. 兴趣爱好
5. 你未来3-5年的职业规划是怎样的？

   大部分面试官司都会问你是否有职业规划，这个问题的背后是了解你的求职动机和对自己中长期职业发展的思考。在回答这个问题之前，要对自己有个清晰的认识，知道自己想往哪个方向发展以及未来有什么计划，要给面试官一种积极向上，好学上进，有追求，有规划的感觉，面试官喜欢有规划的求职者。 回答范例：我希望从现在开始，1-2年之内能够在我目前申请的这个职位上沉淀下来，通过不断的努力后，最好能有晋升，希望3-5年内可以从开发做到架构师。同时我也希望自己能够在企业的平台上得到进一步的职业能力提升。 第一，介绍自己认真思考过这个问题，自己的规划是基于目前的实际情况来设计的。第二，在工作方面，突出自己打算通过积极完成工作任务，积累各方面的经验，让自己成为这个领域的专业人士，也希望有机会能够带领团队，成为优秀的管理者，为单位做出更大贡献，获得双赢。第三，在学习方面，打算在专业领域做进一步学习和研究，将实践经验与专业知识相结合，为自己的职业成长做好铺垫，打好基础。
6. 加班

   第一，任何一家单位都有可能要加班。第二，自身的工作任务没有完成，加班是理所当然的，当然，自己会不断提高专业技能，以尽量减少不必要的加班，之前也是这么做的。第三，如果遇到紧急任务或突发情况时，需要加班，自己会尽己所能，希望能够尽快顺利地完成团队面临的任务。Tips：表现出自己愿意牺牲自己的一部分个人时间，提升个人能力，为公司创造更多利益；明确岗位是否需要经常加班，表明自己态度。
7. 为何选择这个职位

   第一 ，是要突出个人经验和技能与该职位的匹配度相对比较高。 第二，提前做功课，仔细查阅用人单位的网站和视频资料，最好是要在应答中提到招聘单位的规模、品牌、知名度、规范性、愿景等等。 第三，强调用人单位是适合个人职业发展的平台。
8. 你有什么要问的吗？

   第一，可以问本职岗位工作要求、职责。例如，这个部门人员设置是怎么样的。第二，可以问公司、公司的业务、体系、行业、客户。eg：为了胜任该职位，需要我提前学习哪些技术知识？eg：贵公司业务及战略的未来发展？eg：团队、公司现在面临的最大挑战是什么？Tips：切忌纠缠薪资，如果回答没问题，HR会误会，你对岗位没有太大兴趣。

   该部门工作中的信息，如项目情况，开发技术再或者说贵公司的晋升机制是什么样的等。
9. 你遇到过最大的挫折是什么？
10. 多个Offer如何选择


# 2020-campus-recruiting

2019年是忙碌的一年，也是收获的一年，论文上的毫无进展只能通过找工作这一块来填补，期间投递简历几十家（估计大概在30多），笔面试十几家，写了18篇小记录（进入过面试的），大大小小共拿到11个offer。

## 简历准备

* 尽量控制在一页
* [STAR原则](https://en.wikipedia.org/wiki/Situation,_task,_action,_result)
* 注意排版和发布

## 笔试

* Leetcode刷题 【[推荐的题目](https://yangshun.github.io/tech-interview-handbook/best-practice-questions)】
* 算法数据结构
* 计算机网络
* 操作系统
* CSAPP

## 面试

* 简历的上的内容自己预演一遍（解释清楚）
* 简历上写过的内容要有原理层面的深入理解【大厂看中】
* 可以找小公司试试水（如果没面试经历）
* 自信应答但别不懂装懂
* 可以适当进行问题探讨，虚心请教
* 表现出自己的潜力以及对这份工作的热情【重要】

## 谈offer

* 了解市场行情【微信公众号校招薪水】
* 准确的定位以及一定的谈判筹码让HR和你同一战线进行薪资argue

## 其他

* 提前收集好各家笔面试日程
* 对自己应聘的职位有着清晰的认识
* 做好规划
* 问问自己到底想要的什么？（再去看看周围的同学）

## 参考

* <https://yangshun.github.io/tech-interview-handbook/>
* <https://hit-alibaba.github.io/interview/index.html>
* <https://github.com/jwasham/coding-interview-university>
* <https://github.com/CyC2018/Interview-Notebook>
* <http://www.ruanyifeng.com/blog/2020/01/technical-resume.html>
* <https://leetcode.com/contest/>
* <https://github.com/search?q=interview>

## 我的面试经历

> reserved.

* **腾讯**
* **华为**
* **美团**
* **IBM**
* **网易有道**
* **网易游戏**
* **58**
* **小米**
* **搜狗**
* **白山云**
* **作业帮**
* **阿里**
* **滴滴**


# Android Tips

## OxygenOS bus card

1. 安装最新版一加钱包`cn.oneplus.wallet_1.2.2-8_minAPI21(nodpi)_apkmirr.apk`
2. 系统设置->关于手机->多次点击`Build number`开启开发者选项
3. 在开发者选项里打开USB调试
4. 手机连接电脑进入`adb shell` 输入以下命令

```bash
am start -n cn.oneplus.wallet/cn.oneplus.wallet.activity.NewCardActivity
```

### Reference

* [氧 OS 也能使用公交卡](https://real-neo.me/OxygenOS-Bus-Card)
* [cn.oneplus.wallet\_1.2.2-8\_minAPI21(nodpi)\_apkmirr.apk](https://drive.google.com/open?id=1J-eZkQRP3tiNFXboecS5e1bg1ODHH2vo)

## 去除原生系统Wi-Fi及蜂窝网络叹号

```bash
adb shell "settings put global captive_portal_http_url http://www.google.cn/generate_204"
adb shell "settings put global captive_portal_https_url https://www.google.cn/generate_204"
```

其中captive\_portal的url也可以换成任意能生成204状态码的地址，例如<http://connect.rom.miui.com/generate_204>、<https://captive.v2ex.co/generate_204>等，小米MIUI的portal地址在中国大陆也很稳定。


# MacOS tips

Created on Sun, 06 Oct 11:32AM

Last changed on 2023-01-27 20:37:24

## Restart mac network with specific interface

```bash
sudo ifconfig en7 down
sudo ifconfig en7 up
```

## Preventing slee when close the lid

```bash
# 禁止自动睡眠
sudo pmset -b sleep 0; sudo pmset -b disablesleep 1
# 恢复关盖睡眠
sudo pmset -b sleep 5; sudo pmset -b disablesleep 0
```

* [nosleep](https://github.com/integralpro/nosleep)

## Sudo with Touch ID

```bash
# /etc/pam.d/sudo
sudo gsed -i '1aauth	   sufficient 	  pam_tid.so' /etc/pam.d/sudo
sudo sed -i '' -e '1a\
auth       sufficient     pam_tid.so' /etc/pam.d/sudo #sudo touch id
```

## Check listen port

```bash
sudo lsof -i -P | grep -i "listen"
```

## Nmap or nc check

```bash
nc -zv [host-name/ip] [port(s)]
```

## iTerm2 zmodem

```zsh
zsh <(curl -sSL https://github.com/Junyangz/iterm2-zmodem/raw/master/iterm2-zmodem.sh)
```

## Git user base on remote url

```properties
# ~/.gitconfig, need git version >= 2.36
[includeIf "hasconfig:remote.*.url:git@github.com*/**"]
# [includeIf "hasconfig:remote.*.url:https://github.com*/**"] # for https
	path = ~/.gitconfig-github
```

## MacOS remove quarantine

```bash
xattr -d -r com.apple.quarantine /Applications/xxx.app # or xxx binary
```

## Other assets

* <https://github.com/hzlzh/Best-App>
* <https://mubu.com/doc/oeP76-2GF>
* <https://github.com/jaywcjlove/awesome-mac>
* <https://lemon.qq.com/lab/>
* <https://sspai.com/tag/Mac>


# Secret knowledge

> a lot of useful information, idea from the-book-of-secret-knowledge.

## 4A

* 身份验证 Authentication
* 账号管理 Account
* 授权控制 Authorization
* 安全审计 Audit

## 数字签名

> ref \[1]

现在实用的数字签名机制一般利用公开密钥算法实现。具体的实现方式如下所述。

当A方打算发一条消息x给B方并签名时，首先用他的秘密密钥Kd对x加密，得到签名$$y=f\_{K\_{d}}(x)$$，然后发送有序信息对(x,y)。B方收到(x,y)后，用A方的公开密钥Ke对y解密，得到$$x^{\prime}=f\_{K\_{\mathrm{s}}}(y)$$。x'与x完全相同的条件是：

* 信息对(x,y)在传输过程中无任何变化，x或y的任何变化都会使x'与x不等。
* y确实是用Kd对x加密得到的，Kd的任何变化都会使x'与x不等。

因此，只要x'与x相等，就可以确定三件事：

* 消息x确实由A方发来。
* 签名y确实由A方生成。
* B方收到的消息是完整的。

由于只有A方知道他的秘密密钥Kd，所以通过上述签名和验证可以防止下面两种情况：

* A方否认他曾经发送消息x，或者否认B方收到的消息属实。因为只有他能生成y，而y与x是对应的。
* B方伪造消息。因为他得不到Kd，无法证明与伪造消息对应的签名是A方生成的。

为了防止B方事后否认收到消息x，A方可以要求B方提供收信回执，如"B从A处得到x"一段文字，并且要求B方用他的秘密密钥对这段文字签名，以防抵赖。

综上所述，数字签名可以验证消息的完整性，有效地对抗冒充、抵赖等威胁。

## Github budget

<https://img.shields.io/badge/Last_Updated-18--11--23-blue.svg?style=flat> <https://img.shields.io/badge/Words-769-red.svg?style=flat> <https://img.shields.io/badge/Read-3_min-ff69b4.svg?style=flat>

## Reference

\[0] [the-book-of-secret-knowledge](https://github.com/trimstray/the-book-of-secret-knowledge)

\[1]【清华计算机网络课程：计算机网络体系结构】


# GPG-Note

## Import/export key

```bash
gpg --list-keys
gpg --armor --output <public-key-file> --export <ID>
gpg --import <public-key-file>
gpg --keyserver hkp://keys.gnupg.net --search-keys <ID>
```

```bash
gpg --armor --output <private-key-file> --export-secret-keys <ID>
gpg --allow-secret-key-import --import <private-key-file>
gpg --list-secret-keys --keyid-format LONG
```

## Edit passphrase

```bash
gpg --edit-key <ID>
passwd
```

## Git setup gpg sign

> Github add gpg public key

```bash
git config --global commit.gpgsign true
git config --global gpg.program gpg
```

## gpg and git for MAC

```bash
# import keys
gpg2 --list-secret-keys
git config --global gpg.program /usr/local/bin/gpg2
git config --global user.signingkey 987BACA4 
git config --global commit.gpgsign true 
```

Refer [gists](https://gist.github.com/danieleggert/b029d44d4a54b328c0bac65d46ba4c65)


# ud185

## Tips

* 回归问题和二元分类问题经常使用均方损失
* 前向传播
* 计算损失
* 反向传播得到梯度
* 更新权重（使用优化器）

## 创建一个分类器

```python
from torch import nn, optim
import torch.nn.functional as F

class Classifier(nn.Module):
    def __init__(self):
        super().__init__()
        self.fc1 = nn.Linear(784, 256)
        self.fc2 = nn.Linear(256, 128)
        self.fc3 = nn.Linear(128, 64)
        self.fc4 = nn.Linear(64, 10)

    def forward(self, x):
        # make sure input tensor is flattened
        x = x.view(x.shape[0], -1)

        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        x = F.relu(self.fc3(x))
        x = F.log_softmax(self.fc4(x), dim=1)

        return x

#model = nn.Sequential(nn.Linear(784, 128),
#                      nn.ReLU(),
#                      nn.Linear(128, 64),
#                      nn.ReLU(),
#                      nn.Linear(64, 10),
#                       nn.LogSoftmax(dim=1))
```

## 验证分类器

```python
model = Classifier()
criterion = nn.NLLLoss()
optimizer = optim.Adam(model.parameters(), lr=0.003)

epochs = 30
steps = 0

train_losses, test_losses = [], []
for e in range(epochs):
    running_loss = 0
    for images, labels in trainloader:

        optimizer.zero_grad()

        log_ps = model(images)
        loss = criterion(log_ps, labels)
        loss.backward()
        optimizer.step()

        running_loss += loss.item()

    else:
        test_loss = 0
        accuracy = 0

        # Turn off gradients for validation, saves memory and computations
        with torch.no_grad():
            for images, labels in testloader:
                log_ps = model(images)
                test_loss += criterion(log_ps, labels)

                ps = torch.exp(log_ps)
                top_p, top_class = ps.topk(1, dim=1)
                equals = top_class == labels.view(*top_class.shape)
                accuracy += torch.mean(equals.type(torch.FloatTensor))

        train_losses.append(running_loss/len(trainloader))
        test_losses.append(test_loss/len(testloader))

        print("Epoch: {}/{}.. ".format(e+1, epochs),
              "Training Loss: {:.3f}.. ".format(running_loss/len(trainloader)),
              "Test Loss: {:.3f}.. ".format(test_loss/len(testloader)),
              "Test Accuracy: {:.3f}".format(accuracy/len(testloader)))
```

## 过拟合

* *早停法* (early stopping) 即：使用验证损失最低的模型，需要频繁保存模型。
* *丢弃*（dropout）即随机丢弃单元
  * `self.dropout = nn.Dropout(p=0.2)`

    ```python
    class Classifier(nn.Module):
        def __init__(self):
            super().__init__()
            self.fc1 = nn.Linear(784, 256)
            self.fc2 = nn.Linear(256, 128)
            self.fc3 = nn.Linear(128, 64)
            self.fc4 = nn.Linear(64, 10)

            # Dropout module with 0.2 drop probability
            self.dropout = nn.Dropout(p=0.2)

        def forward(self, x):
            # make sure input tensor is flattened
            x = x.view(x.shape[0], -1)

            # Now with dropout
            x = self.dropout(F.relu(self.fc1(x)))
            x = self.dropout(F.relu(self.fc2(x)))
            x = self.dropout(F.relu(self.fc3(x)))

            # output so no dropout here
            x = F.log_softmax(self.fc4(x), dim=1)

            return x
    ```

    验证的时候关闭`dropout`, 先使用`model.eval()`设定为推理模式，计算测试损失和精度后再开启模型训练`model.train()`启动`dropout`

    ```python
            # Turn off gradients for validation, saves memory and computations
            with torch.no_grad():
                model.eval() #set up to eval not use dropout
                for images, labels in testloader:
                    log_ps = model(images)
                    ...

            model.train() # set up to train with dropout
    ```

## 保存和加载网络模型

* `torch.save` 和 `torch.load`
* `PyTorch` 网络的参数保存在模型的 `state_dict` 中。状态字典包含每个层级的权重和偏差矩阵

```python
torch.save(model.state_dict(), 'checkpoint.pth')
state_dict = torch.load('checkpoint.pth')
model.load_state_dict(state_dict)
```

* 将状态加载到神经网络中需要执行 `model.load_state_dict(state_dict)`
  * 只有**模型结构和检查点的结构完全一样**时，状态字典才能加载成功
* 可以将模型的架构信息和状态字典都保存在检查点里，可以通过创建一个字典来实现

  ```python
  checkpoint = {'input_size': 784,
                'output_size': 10,
                'hidden_layers': [each.out_features for each in model.hidden_layers],
                'state_dict': model.state_dict()}

  torch.save(checkpoint, 'checkpoint.pth')

  def load_checkpoint(filepath):
      checkpoint = torch.load(filepath)
      model = fc_model.Network(checkpoint['input_size'],
                               checkpoint['output_size'],
                               checkpoint['hidden_layers'])
      model.load_state_dict(checkpoint['state_dict'])

      return model

  model = load_checkpoint('checkpoint.pth')
  ```
* 再次加载模型的时候使用`load_checkpoint`函数即可正确完成

## 加载图像数据

```python
data_dir = 'Cat_Dog_data/train' # 数据集所在目录
# 数据转换，缩放、裁剪，然后转换为张量
transform = transforms.Compose([transforms.Resize(255),
                                 transforms.CenterCrop(224),
                                 transforms.ToTensor()])
# 加载数据集
dataset = datasets.ImageFolder(data_dir, transform=transform)
# use the ImageFolder dataset to create the DataLoader
dataloader = torch.utils.data.DataLoader(dataset, batch_size=32, shuffle=True)

# 测试数据加载器
images, labels = next(iter(dataloader))
helper.imshow(images[0], normalize=False)
```

### 数据增强

> 训练神经网络的一个常见策略是在输入数据本身里引入随机性。例如，你可以在训练过程中随机地旋转、翻转、缩放和/或裁剪图像。这样一来，你的神经网络在处理位置、大小、方向不同的相同图像时，可以更好地进行泛化。

```python
train_transforms = transforms.Compose([transforms.RandomRotation(30),
                                       transforms.RandomResizedCrop(224),
                                       transforms.RandomHorizontalFlip(),
                                       transforms.ToTensor(),
                                       transforms.Normalize([0.5, 0.5, 0.5], 
                                                            [0.5, 0.5, 0.5])])
```

另外，还需要使用 `transforms.Normalize` 标准化图像。传入均值和标准偏差列表，然后标准化颜色通道

## 迁移学习

* 加载 [DenseNet](http://pytorch.org/docs/0.3.0/torchvision/models.html#id5) 等模型 `model = models.densenet121(pretrained=True)`

```python
# Freeze parameters so we don't backprop through them 冻结特征层
for param in model.parameters():
    param.requires_grad = False
```

* 测试GPU是否可用

  ```python
  device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
  ```

  > PyTorch 和其他深度学习框架一样，也使用 [CUDA](https://developer.nvidia.com/cuda-zone) 在 GPU 上高效地进行前向和反向运算。在 PyTorch 中，你需要使用 `model.to('cuda')` 将模型参数和其他张量转移到 GPU 内存中。你可以使用 `model.to('cpu')` 将它们从 GPU 移到 CPU，比如在你需要在 PyTorch 之外对网络输出执行运算时。

  ```python
  model.to(device)
  for epoch in range(epochs):
      for ii, (inputs, labels) in enumerate(trainloader):
          # Move input and label tensors to the default device
          inputs, labels = inputs.to(device), labels.to(device)
  ```

## Review

```python
%matplotlib inline
%config InlineBackend.figure_format = 'retina'

import matplotlib.pyplot as plt

import torch
from torch import nn
from torch import optim
import torch.nn.functional as F
from torchvision import datasets, transforms, models
from collections import OrderedDict

data_dir = 'Cat_Dog_data'

# Define transforms for the training data and testing data
train_transforms = transforms.Compose([transforms.RandomRotation(30),
                                       transforms.RandomResizedCrop(224),
                                       transforms.RandomHorizontalFlip(),
                                       transforms.ToTensor(),
                                       transforms.Normalize([0.485, 0.456, 0.406],
                                                            [0.229, 0.224, 0.225])])

test_transforms = transforms.Compose([transforms.Resize(255),
                                      transforms.CenterCrop(224),
                                      transforms.ToTensor(),
                                      transforms.Normalize([0.485, 0.456, 0.406],
                                                           [0.229, 0.224, 0.225])])

# Pass transforms in here, then run the next cell to see how the transforms look
train_data = datasets.ImageFolder(data_dir + '/train', transform=train_transforms)
test_data = datasets.ImageFolder(data_dir + '/test', transform=test_transforms)

trainloader = torch.utils.data.DataLoader(train_data, batch_size=64, shuffle=True)
testloader = torch.utils.data.DataLoader(test_data, batch_size=64)


device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
model = models.densenet121(pretrained=True)

for param in model.parameters():
    param.requires_grad = False

model.classifier = nn.Sequential(OrderedDict([
                          ('fc1', nn.Linear(1024, 500)),
                          ('relu', nn.ReLU()),
                          ('fc2', nn.Linear(500, 2)),
                          ('output', nn.LogSoftmax(dim=1))
                          ]))

criterion = nn.NLLLoss()
optimizer = optim.Adam(model.parameters(), lr=0.003)
model.to(device);

epochs = 30
steps = 0
running_loss = 0
print_every = 5
train_losses, test_losses = [], []

for epoch in range(epochs):

    for images, labels in trainloader:
        steps += 1

        images, labels = images.to(device), labels.to(device)

        optimizer.zero_grad() # clearing the gradients

        log_ps = model(images)
        loss = criterion(log_ps, labels)
        loss.backward()
        optimizer.step()

        running_loss += loss.item()

        if steps %% print_every == 0:
            model.eval() # set the network to evaluation mode
            test_loss = 0
            accuracy = 0
            for images, labels in testloader:

                images, labels = images.to(device), labels.to(device)

                logps = model(images)
                loss = criterion(logps, labels)
                test_loss += loss.item()

                # calculate our accuracy
                ps = torch.exp(logps)
                top_ps, top_class = ps.topk(1, dim=1)
                equality = top_class == labels.view(*top_class,shape)
                accuracy += torch.mean(equality.type(torch.FloatTensor)).item()
                running_loss = 0
                model.train() # back to training mode

            print("Epoch: {}/{}.. ".format(e+1, epochs),
                  "Training Loss: {:.3f}.. ".format(running_loss/len(trainloader)),
                  "Test Loss: {:.3f}.. ".format(test_loss/len(testloader)),
                  "Test Accuracy: {:.3f}".format(accuracy/len(testloader)))
```


# ud185-2

## Differential Privacy

* Type of Noise(Gaussian / Laplacian)
* Sensitivity of Query
* Desired Epsilon()
* Desired Delta()

Laplacian proof.

**Definition 1.1** (ℓ1-sensitivity). The ℓ1-sensitivity of a function f : $$\mathbb{N}^{|\mathcal{X}|} \rightarrow \mathbb{R}^{k}$$ is:

$$
\Delta f=\max *{|x-y|*{1}=1}|f(x)-f(y)|\_{1}
$$

The ℓ1 sensitivity of a function f captures the magnitude by which a single individual’s data can change the function $$f$$ in the worst case, and therefore, intuitively, the uncertainty in the response that we must introduce in order to hide the participation of a single individual. Indeed, we will formalize this intuition: the sensitivity of a function gives an upper bound on how much we must perturb its output to preserve privacy. One noise distribution naturally lends itself to differential privacy.

**Definition 1.2** (The Laplace Distribution). The Laplace Distribution(centered at 0) with scale b is the distribution with probability density function:

$$
\operatorname{Lap}(x | b)=\frac{1}{2b} \exp \left(-\frac{|x|}{b}\right).
$$

**Definition 1.3** (The Laplace Mechanism). Given any function $$f : \mathbb{N}^{|\mathcal{X}|} \rightarrow \mathbb{R}^{k}$$ , the Laplace mechanism is defined as:

$$
\mathcal{M}*{L}(x, f(\cdot), \varepsilon)=f(x)+\left(Y*{1}, \ldots, Y\_{k}\right)
$$

where $$Y\_{i}$$ are i.i.d. random variables drawn from $$\operatorname{Lap}(\Delta f / \varepsilon).$$

**Theorem 1.** The Laplace mechanism preserves (ε, 0)-differential privacy.

$$Proof.$$ Let $$x \in \mathbb{N}^{|\mathcal{X}|}$$ and $$y \in \mathbb{N}^{|\mathcal{X}|}$$ be such that $$||x-y||*{1} \leq 1$$, and let $$f(\cdot)$$ *be some function* $$f : \mathbb{N}^{|\mathcal{X}|} \rightarrow \mathbb{R}^{k}$$. Let $$p\_x$$ denote the probability density function of $$\mathcal{M}*{L}(x, f, \varepsilon)$$, and let $$p\_y$$ denote the probability density function of $$\mathcal{M}\_{L}(y, f, \varepsilon)$$. We compare the two at some arbitrary point $$z \in \mathbb{R}^{k}$$

$$
\begin{aligned} \frac{p\_{x}(z)}{p\_{y}(z)} &=\prod\_{i=1}^{k}\left(\frac{\exp \left(-\frac{\varepsilon\left|f(x)*{i}-z*{i}\right|}{\Delta f}\right)}{\exp \left(-\frac{\varepsilon\left|f(y)*{i}-z*{i}\right|}{\Delta f}\right)}\right) \ &=\prod\_{i=1}^{k} \exp \left(\frac{\varepsilon\left(\left|f(y)*{i}-z*{i}\right|-\left|f(x)*{i}-z*{i}\right|\right)}{\Delta f}\right) \ & \leq \prod\_{i=1}^{k} \exp \left(\frac{\varepsilon\left|f(x)*{i}-f(y)*{i}\right|}{\Delta f}\right) \ &=\exp \left(\frac{\varepsilon \cdot|f(x)-f(y)|\_{1}}{\Delta f}\right) \ & \leq \exp (\varepsilon) \end{aligned}
$$

where the first inequality follows from the triangle inequality, and the last follows from the definition of sensitivity and the fact that $$||x-y||*{1}\leq 1$$*. That\_ $$\frac{p\_{x}(z)}{p\_{y}(z)} \geq \exp (-\varepsilon)$$ follows by symmetry.

## Differential Privacy and Machine Learning

​ \[译]数据分析中最有用的任务之一是机器学习：自动查找简单规则以准确预测从未见过的数据的某些未知特征的问题。 许多机器学习任务可以在差分隐私的约束下执行。 事实上，隐私的约束并不一定与机器学习的目标相悖，两者都旨在从绘制数据的分布中提取信息，而不是从单个数据点提取信息。 在本节中，我们将调查一些关于私人机器学习的最基本结果，而不是试图完全覆盖这个大型领域。

​ 机器学习的目标通常与隐私数据分析的目标相似。学习者通常希望学习一些解释数据集的简单规则。但是，她希望这条规则能够”概括“ - 也就是说，她应该学习的规则不仅要正确描述她手边的数据，而且应该能够正确描述从中获取的新数据分布。通常，这意味着她希望学习一种规则，该规则捕获有关手头数据集的分布信息，其方式不会过于依赖任何单个数据点。当然，这正是隐私数据分析的目标 - 揭示有关私有数据集的分布信息，而不会过多地揭示数据集中的任何单个个体。毫无疑问，机器学习和隐私数据分析是紧密相连的。事实上，正如我们将要看到的，我们通常能够以几乎同样准确的方式执行隐私保护机器学习，几乎与我们可以执行非隐私保护机器学习的示例数量相同。

> “if adding, modifying, or removing any of its training samples would not result in a statistically significant difference in the model parameters learned. For this reason, learning with differential privacy is, in practice, a form of regularization.”


# Introducing Tensorflow Federated

> 作者: [Alex Ingerman](https://twitter.com/alex_ingerman)（产品经理）和 [Krzys Ostrowski](https://twitter.com/KrzysOstrowski)（研究科学家）译： Junyangz
>
> 注： Federated Learning 译作 联合学习 或 联盟(邦)学习，因Google中国翻译为联盟学习所以本文均译为联盟学习。
>
> 原文： <https://medium.com/tensorflow/introducing-tensorflow-federated-a4147aa20041>

​ 据统计，全世界约有[30亿部智能手机](https://newzoo.com/insights/trend-reports/newzoo-global-mobile-market-report-2018-light-version/)和[70亿部连接设备](https://iot-analytics.com/state-of-the-iot-update-q1-q2-2018-number-of-iot-devices-now-7b/)。 这些电话和设备不断产生新的数据。 传统的分析和机器学习需要在处理数据之前集中收集数据，以获得insights，ML模型以及最终产品的改进。 如果数据敏感或集中化代价昂贵，则这种集中式方法可能会出现问题。 如果我们能够在生成数据的设备上运行数据分析和机器学习，并且仍然能够将所学的内容汇总在一起，那不是更好吗？

​ Tensorflow Federated (TFF)是一个开源框架，用于对分散式数据进行机器学习和其他计算的实验。 它实现了一种称为[Federated Learning](https://ai.googleblog.com/2017/04/federated-learning-collaborative.html)(FL)的方法，允许许多参与的客户端训练共享的机器学习模型，同时保持它们的数据位于本地。 我们根据我们在谷歌开发Federated Learning技术的[经验](https://arxiv.org/abs/1902.01046)设计了 TFF，它为移动键盘预测和[on-device search](https://ai.google/stories/ai-in-hardware/)的机器学习模型提供了动力。 有了 TFF，我们很高兴能够将一个灵活、开放的框架放到所有 TensorFlow 用户的手中，用于本地模拟分散式计算。

![TensorFlow Federated enables developers to express and simulate federated learning systems. Pictured here, each phone trains the model locally (A). Their updates are aggregated (B) to form an improved shared model (C).](/files/-LiwCr-kZ_-Nz_Gmiiuh)

​ 为了说明 FL 和 TFF 的用法，让我们从最著名的图像数据集之一: MNIST 开始。 创建 MNIST 的原始 NIST 数据集包含81万个手写数字的图像，这些数字来自3600名志愿者，我们的任务是建立一个机器学习模型来识别这些数字。 传统的做法是将机器学习算法应用于整个数据集。 但是，如果我们不能把所有的数据结合起来呢? 例如，因为志愿者不同意将他们的原始数据上传到中央服务器？

​ 使用 TFF，我们可以编写我们选择的机器学习模型结构，然后跨所有数据拥有者提供的数据进行训练，同时保持各方的数据分散在各自本地。 我们将在下面介绍如何使用 TFF 的联盟学习(FL) API，使用由 Leaf 项目处理的 NIST 数据集的其中一个版本分隔每个志愿者所写的数字。

```python
# Load simulation data.
source, _ = tff.simulation.datasets.emnist.load_data()
def client_data(n):
  dataset = source.create_tf_dataset_for_client(source.client_ids[n])
  return mnist.keras_dataset_from_emnist(dataset).repeat(10).batch(20)

# Wrap a Keras model for use with TFF.
def model_fn():
  return tff.learning.from_compiled_keras_model(
      mnist.create_simple_keras_model(), sample_batch)

# Simulate a few rounds of training with the selected client devices.
trainer = tff.learning.build_federated_averaging_process(model_fn)
state = trainer.initialize()
for _ in range(5):
  state, metrics = trainer.next(state, train_data)
  print (metrics.loss)
```

​ 您可以在[联盟 MNIST 分类教程](https://www.tensorflow.org/federated/tutorials/federated_learning_for_image_classification)中看到其余部分。

​ 除了 FL API 之外，TFF 还提供了一组较低级别的原语，我们称之为联盟核心[(Federated Core，FC) API](https://www.tensorflow.org/federated/federated_core)。 这个 API 支持在分散的数据集上进行广义的计算。 使用联盟学习训练机器学习模型是联盟计算的一个例子; 在分散数据上对其进行评估是另一个例子。

​ 让我们用一个简单的例子来看一下 FC API。 假设我们有一系列的传感器来获取温度数据，并且想要计算这些传感器的平均温度，而不需要将它们的数据上传到中心位置。 使用 FC API，我们可以表示一个新的数据类型，指定它的底层数据(`tf.float32`)以及数据存在的位置(分散在客户端的设备上)。

`READINGS_TYPE = tff.FederatedType(tf.float32, tff.CLIENTS)`

然后在这个类型上指定一个联盟平均函数。

```python
@tff.federated_computation(READINGS_TYPE)
def get_average_temperature(sensor_readings):
  return tff.federated_average(sensor_readings)
```

​ 定义了联盟计算之后，TFF 以一种可以在分散式设置中运行的形式表示它。 TFF 的初始版本包括一个本地运行态，该运行态模拟跨一组持有数据的客户端执行的计算，每个客户端计算其本地贡献，集中的协调器聚合所有贡献。 但是，从开发人员的角度来看，联盟计算可以看作是一个普通的函数，它恰好有驻留在不同位置的输入和输出(分别位于单个客户端和协调服务器中)。

![An illustration of the get\_average\_temperature federated computation expression.](/files/-LiwCr-mgNk6nb2OGUFs)

[联盟平均算法](https://arxiv.org/abs/1602.05629)的一个简单变体也可以直接使用 TFF 的声明模型:

```python
@tff.federated_computation(
  tff.FederatedType(DATASET_TYPE, tff.CLIENTS),
  tff.FederatedType(MODEL_TYPE, tff.SERVER, all_equal=True),
  tff.FederatedType(tf.float32, tff.SERVER, all_equal=True))
def federated_train(client_data, server_model, learning_rate):
  return tff.federated_average(
      tff.federated_map(local_train, [
          client_data,
          tff.federated_broadcast(server_model),
          tff.federated_broadcast(learning_rate)]))
```

​ 通过TensorFlow Federated，我们朝着让更多的用户可以使用这项技术的方向迈出了一步，并且邀请社区在一个开放、灵活的平台上参与开发联盟学习及相应研究。 您可以在浏览器中尝试 TFF，只需点击几下，通过遍历[教程](https://www.tensorflow.org/federated/tutorials/federated_learning_for_image_classification)。 有很多方法可以参与进来: 你可以在你的模型上体验现有的 FL 算法，为 [TFF 仓库](https://github.com/tensorflow/federated)贡献新的联盟数据集和模型，添加新的 FL 算法的实现，或者用新的特性扩展现有的 FL 算法。

​ 随着时间的推移，我们希望 TFF 运行时能够适用于主要的设备平台，并集成其他有助于保护敏感用户数据的技术，包括用于联盟学习(与 [TensorFlow 隐私](https://github.com/tensorflow/privacy)集成)和[安全聚合](https://arxiv.org/abs/1611.04482)的[差分隐私](https://arxiv.org/abs/1710.06963)。 我们期待着与社区一起开发 TFF，并使每个开发人员都能使用联合技术。

​ 准备好开始了吗？ 请访问 <https://www.tensorflow.org/federated/> 并尝试 TFF！

## 鸣谢

​ 创建 TensorFlow Federated 是一项团队工作。 特别感谢 Brendan McMahan、 Keith Rush、 Michael Reneer 和 Zachary Garrett，他们都做出了重大贡献。


# Tensorflow Federated

***

By Junyangz AT IIE, CAS.

## Background

* Much of the data is born decentralized(phones & IoT devices)
* easy learn if data is in one place
* Centralization has disadvantages
  * user experience
    * latency
    * offline
  * resource limits
    * data caps
    * battery life
  * privacy concerns
    * sensitive data
* Learn without collect data on-device
  * often too little data per client
  * other clients aren't contributing
  * pre-training may help... sometimes
* Learn together --- Federated Learning

## Overview 总览

> TensorFlow Federated (TFF) is an open-source framework for machine learning and other computations on decentralized data.

TensorFlow Federated (TFF) is an open-source framework for machine learning and other computations on decentralized data. TFF has been developed to facilitate open research and experimentation with [Federated Learning (FL)](https://ai.googleblog.com/2017/04/federated-learning-collaborative.html), an approach to machine learning where a shared global model is trained across many participating clients that keep their training data locally. For example, FL has been used to train [prediction models for mobile keyboards](https://arxiv.org/abs/1811.03604) without uploading sensitive typing data to servers.

TensorFlow 联合(TFF)是一个开源框架，用于在分布式数据上进行机器学习和其他计算。开发TFF是为了促进联合学习的开放式研究和实验，联合学习是一种机器学习方法，在这种方法中，在许多参与的客户之间训练共享的全局模型，这些客户将他们的训练数据保存在本地。例如，FL已被用于训练移动键盘的预测模型，而无需将敏感的打字数据上传到服务器。

TFF enables developers to simulate the included federated learning algorithms on their models and data, as well as to experiment with novel algorithms. The building blocks provided by TFF can also be used to implement non-learning computations, such as aggregated analytics over decentralized data. TFF’s interfaces are organized in two layers:

TFF使开发人员能够在他们的模型和数据上模拟联合学习算法，并尝试新的算法。TFF提供的构建模块也可用于实现非学习计算，例如对分散数据的聚合分析。TFF的接口分为两层:

> **Federated Learning (FL) API** This layer offers a set of high-level interfaces that allow developers to apply the included implementations of federated training and evaluation to their existing TensorFlow models.
>
> 该层提供了一组高级接口，允许开发人员将所包含的联合训练和评估实现应用于现有的TensorFlow模型

> **Federated Core (FC) API** At the core of the system is a set of lower-level interfaces for concisely expressing novel federated algorithms by combining TensorFlow with distributed communication operators within a strongly-typed functional programming environment. This layer also serves as the foundation upon which we've built Federated Learning.
>
> 该系统的核心是一组低级接口，用于通过在强类型函数编程环境中结合TensorFlow 和分布式通信操作符来简洁地表达新的联合算法。这一层也是我们建立联合学习的基础。

* Federated Learning (FL) API layer `tff.learning`

一组更高级别的接口，可用于执行常见类型的联合学习任务，例如联合训练，针对已在TensorFlow中实现的用户提供的模型。

## 准备输入数据

* TFF仓库里自带了一些数据集，包括CMU制作的针对联合学习benchmark的数据集[Leaf](https://github.com/TalwalkarLab/leaf)，由于每个作者都有一个独特的风格，这个数据集展示了非i.i.d的类型。这也是联合数据集的预期情况。

```python
#@test {"output": "ignore"}
emnist_train, emnist_test = tff.simulation.datasets.emnist.load_data()
emnist_train.output_types, emnist_train.output_shapes
#(OrderedDict([(u'label', tf.int32), (u'pixels', tf.float32)]), OrderedDict([(u'label', TensorShape([])), (u'pixels', TensorShape([28, 28]))]))
```

```python
example_dataset = emnist_train.create_tf_dataset_for_client(
    emnist_train.client_ids[0])

example_element = iter(example_dataset).next()

example_element['label'].numpy()
```

* 将MNIST数据集中的28x28图像压缩成784个元素数组
* 数据已经是`tf.data.Dataset`，因此可以使用数据集转换完成预处理。
* 将各个示例混合，将它们组织成批，然后将像素和标签的特征重命名为x和y，以便与Keras一起使用。 我们还重复数据集以运行多个周期。

```python
NUM_EPOCHS = 10
BATCH_SIZE = 20
SHUFFLE_BUFFER = 500


def preprocess(dataset):

  def element_fn(element):
    return collections.OrderedDict([
        ('x', tf.reshape(element['pixels'], [-1])),
        ('y', tf.reshape(element['label'], [1])),
    ])

  return dataset.repeat(NUM_EPOCHS).map(element_fn).shuffle(
      SHUFFLE_BUFFER).batch(BATCH_SIZE)
```

```python
def make_federated_data(client_data, client_ids):
  return [preprocess(client_data.create_tf_dataset_for_client(x))
          for x in client_ids]
```

```python
#@test {"output": "ignore"}
NUM_CLIENTS = 3

sample_clients = emnist_train.client_ids[0:NUM_CLIENTS]

federated_train_data = make_federated_data(emnist_train, sample_clients)
```

## 使用Keras创建模型

* 优化器和学习率不同于在i.i.d.数据集上的选择

```python
def create_compiled_keras_model():
  model = tf.keras.models.Sequential([
      tf.keras.layers.Dense(
          10, activation=tf.nn.softmax, kernel_initializer='zeros', input_shape=(784,))])
 
  model.compile(
      loss=tf.keras.losses.SparseCategoricalCrossentropy(),
      optimizer=tf.keras.optimizers.SGD(learning_rate=0.02),
      metrics=[tf.keras.metrics.SparseCategoricalAccuracy()])
  return model
```

* 将任何模型应用于TFF，需要使用`tff.learning.Model`重新包装下

```python
def model_fn():
  keras_model = create_compiled_keras_model()
  return tff.learning.from_compiled_keras_model(keras_model, sample_batch)
```

## 在联合数据集上训练模型

* 联合平均算法

```python
#@test {"output": "ignore"}
iterative_process = tff.learning.build_federated_averaging_process(model_fn)
```

TFF构建了一对联合计算并将它们打包成`tff.utils.IterativeProcess`，其中这些计算可作为一对初始化和下一个属性使用。

```python
state = iterative_process.initialize()
```

调用`initialize`计算来构造服务器状态

联合计算中的第二个函数`next`表示单轮联合平均（Federated Averaging），其包括将服务器状态（包括模型参数）推送到客户端，对其本地数据进行设备上训练，收集和平均模型更新，并在服务器上生成新的更新模型。可以类比成以下过程

```
SERVER_STATE, FEDERATED_DATA -> SERVER_STATE, TRAINING_METRICS
```

`next()`不仅是一个在server上运行的函数，而是作为整个分布式计算的声明性函数表示，某些输入由server（SERVER\_STATE）提供，但每个参与的设备都提供其自己的本地数据集。

```python
#@test {"timeout": 600, "output": "ignore"}
state, metrics = iterative_process.next(state, federated_train_data)
print('round  1, metrics={}'.format(metrics))
```

单轮训练

```python
#@test {"skip": true}
for round_num in range(2, 11):
  state, metrics = iterative_process.next(state, federated_train_data)
  print('round {:2d}, metrics={}'.format(round_num, metrics))
```

多轮训练

***

## 自定义训练参数

* 定义一个数据结构`namedtuple`用于保存训练指标

```python
MnistVariables = collections.namedtuple(
    'MnistVariables', 'weights bias num_examples loss_sum accuracy_sum')
```

* 创建mnist数据结构变量的函数

```python
def create_mnist_variables():
  return MnistVariables(
      weights = tf.Variable(
          lambda: tf.zeros(dtype=tf.float32, shape=(784, 10)),
          name='weights',
          trainable=True),
      bias = tf.Variable(
          lambda: tf.zeros(dtype=tf.float32, shape=(10)),
          name='bias',
          trainable=True),
      num_examples = tf.Variable(0.0, name='num_examples', trainable=False),
      loss_sum = tf.Variable(0.0, name='loss_sum', trainable=False),
      accuracy_sum = tf.Variable(0.0, name='accuracy_sum', trainable=False))
```

* 通过模型参数和累积统计信息的变量，定义计算损失，执行预测和更新单批输入数据的累积统计数据的正向传递方法

```python
def mnist_forward_pass(variables, batch):
  y = tf.nn.softmax(tf.matmul(batch['x'], variables.weights) + variables.bias)
  predictions = tf.cast(tf.argmax(y, 1), tf.int32)

  flat_labels = tf.reshape(batch['y'], [-1])
  loss = -tf.reduce_mean(tf.reduce_sum(
      tf.one_hot(flat_labels, 10) * tf.log(y), reduction_indices=[1]))
  accuracy = tf.reduce_mean(
      tf.cast(tf.equal(predictions, flat_labels), tf.float32))

  num_examples = tf.cast(tf.size(batch['y']), tf.float32)

  tf.assign_add(variables.num_examples, num_examples)
  tf.assign_add(variables.loss_sum, loss * num_examples)
  tf.assign_add(variables.accuracy_sum, accuracy * num_examples)

  return loss, predictions
```

* 返回本地指标方法，需要正确加权来自不同用户的贡献

```python
def get_local_mnist_metrics(variables):
  return collections.OrderedDict([
      ('num_examples', variables.num_examples),
      ('loss', variables.loss_sum / variables.num_examples),
      ('accuracy', variables.accuracy_sum / variables.num_examples)
    ])
```

* `aggregate_mnist_metrics_across_clients`聚合本地指标数据的方法
* 是一个TFF联合学习的计算表达式
* 输入`metrics`是上一个方法`get_local_mnist_metrics`返回的，但值已不再是`tf.Tensors`类型，而是经过`Boxed`成为了`tff.Value`，改变量不能再使用TensorFlow来修改，只能用TFF的联合操作例如：`tff.federated_mean`和`tff.federated_sum`
* 函数返回的字典结果定义了服务端可用的参数集合

```python
@tff.federated_computation
def aggregate_mnist_metrics_across_clients(metrics):
  return {
      'num_examples': tff.federated_sum(metrics.num_examples),
      'loss': tff.federated_mean(metrics.loss, metrics.num_examples),
      'accuracy': tff.federated_mean(metrics.accuracy, metrics.num_examples)
  }
```

* 模型使用的所有状态必须为TensorFlow变量，因为TFF在运行时不使用Python
* 模型应描述它接受的数据形式（input\_spec），因为通常，TFF是一种强类型环境，并且希望确定所有组件的类型签名。 声明模型输入的格式是其中不可或缺的一部分
* 虽然技术上不需要，但建议将所有TensorFlow逻辑（正向传递，度量计算等）包装为`tf.functions`，因为这有助于确保TensorFlow可以序列化，并且不需要显式的控制依赖

```python
class MnistModel(tff.learning.Model):

  def __init__(self):
    self._variables = create_mnist_variables()

  @property
  def trainable_variables(self):
    return [self._variables.weights, self._variables.bias]

  @property
  def non_trainable_variables(self):
    return []

  @property
  def local_variables(self):
    return [
        self._variables.num_examples, self._variables.loss_sum,
        self._variables.accuracy_sum
    ]

  @property
  def input_spec(self):
    return collections.OrderedDict([('x', tf.TensorSpec([None, 784],
                                                        tf.float32)),
                                    ('y', tf.TensorSpec([None, 1], tf.int32))])

  @tf.function
  def forward_pass(self, batch, training=True):
    del training
    loss, predictions = mnist_forward_pass(self._variables, batch)
    return tff.learning.BatchOutput(loss=loss, predictions=predictions)

  @tf.function
  def report_local_outputs(self):
    return get_local_mnist_metrics(self._variables)

  @property
  def federated_output_computation(self):
    return aggregate_mnist_metrics_across_clients
```

* 本地训练模型的设定

```python
class MnistTrainableModel(MnistModel, tff.learning.TrainableModel):

  @tf.function
  def train_on_batch(self, batch):
    output = self.forward_pass(batch)
    optimizer = tf.train.GradientDescentOptimizer(0.02)
    optimizer.minimize(output.loss, var_list=self.trainable_variables)
    return output
```

* 使用新模型进行联合训练

```python
iterative_process = tff.learning.build_federated_averaging_process(
    MnistTrainableModel)
state = iterative_process.initialize()
#@test {"timeout": 600, "output": "ignore"}
state, metrics = iterative_process.next(state, federated_train_data)
print('round  1, metrics={}'.format(metrics))
#@test {"skip": true}
for round_num in range(2, 11):
  state, metrics = iterative_process.next(state, federated_train_data)
  print('round {:2d}, metrics={}'.format(round_num, metrics))
```

## 评估

* 在联合数据集上进行评估需要需要使用构造器`tff.learning.build_federated_evaluation`,传入模型构造函数
* 评估不需要梯度下降所以传入`MnistModel`已足够，不像联合平均那样需要传入一个可训练的模型`MnistTrainableModel`
* 在试验或者出于研究目的情况下，当中心化的测试数据集可用时，可将联合学习的模型参数应用到标准的Keras模型上，然后在中心化的数据集上直接调用`tf.keras.models.Model.evaluate()`来进行评估。

```python
evaluation = tff.learning.build_federated_evaluation(MnistModel)
str(evaluation.type_signature)
#'(<<trainable=<weights=float32[784,10],bias=float32[10]>,non_trainable=<>>@SERVER,{<x=float32[?,784],y=int32[?,1]>*}@CLIENTS> -> <accuracy=float32@SERVER,loss=float32@SERVER,num_examples=float32@SERVER>)'
```

* 在最终状态调用评估函数，通过server状态来提取模型只需要简单的访问`.model`成员

```python
#@test {"output": "ignore"}
train_metrics = evaluation(state.model, federated_train_data)
```

```python
#@test {"output": "ignore"}
str(train_metrics)
#'<accuracy=0.465455,loss=1.47458,num_examples=2750.0>'
```

## 相关结论

* 一般来说在数据量足够的情况下，迁移学习效果不如完全重新训练，对于[联合迁移学习](https://arxiv.org/pdf/1902.04885.pdf)来说，如果不是受限与计算能力和训练时长一般不会用到。另外Google并未提到过相关概念。我的理解是联合学习本身就是利用到了海量的数据集，并能在资源状态很好的情况下进行训练，从而能够达到一个全局最优模型。P.S. Q YANG也是炒个概念。（个人见解）

## 参考文献

* [TensorFlow Federated (TFF): Machine Learning on Decentralized Data (TF Dev Summit ‘19)](https://www.youtube.com/watch?v=1YbPmkChcbo)
* <https://www.tensorflow.org/federated>


# Expert Python Programing

## cheatsheet

1. [mementopython3-english.pdf](https://github.com/Junyangz/Documents/blob/master/assets/mementopython3-english.pdf)
2. [python-cheat-sheet-v1.pdf](https://github.com/Junyangz/Documents/blob/master/assets/python-cheat-sheet-v1.pdf)

## Chapter1

> 待补充

## Chapter2

* 列表推导 \[]
* enumerate
* {推导} -> set集合
* collections类
  1. deque 类似列表(list)的容器，实现了在两端快速添加(append)和弹出(pop)
  2. namedtuple 创建命名元组子类的工厂函数
  3. OrderedDict 字典的子类，保存了他们被添加的顺序
  4. defaultdict
     1. defaultdict类的初始化函数接受一个类型作为参数，当所访问的键不存在的时候，可以实例化一个值作为默认值 `defaultdict(list)`
     2. 该类除了接受类型名称作为初始化函数的参数之外，还可以使用任何不带参数的可调用函数，例如: `defaultdict(lambda: 0)`
  5. [collections官方文档](https://docs.python.org/zh-cn/3/library/collections.html#module-collections)
* 迭代器

  * \_\_next\_\_
  * \_\_iter\_\_
  * yield

  ```python
  def fibonacci():
      a, b = 0, 1
      while True:
          yield b
          a, b = b, a + b
  ```

  * send

  ```python
  import time

  def consumer():
      r = ''
      while True:
          n = yield r
          if not n: return
          print('[CONSUMER] Consuming %s...' % n)
          time.sleep(.1)
          r = '200 OK'
  def produce(c):
      next(c)
      n = 0
      while n < 5:
          n = n + 1
          print('[PRODUCER] Producing %s...' % n)
          r = c.send(n)
          print('[PRODUCER] Consumer return: %s' % r)
      c.close()

  if __name__=='__main__':
      c = consumer()
      produce(c)
  ```
* 装饰器
  * 实现了自己\_\_call\_\_方法的类的实例
  * 语法糖
  * 使用functools模块内置wraps()装饰器打造的装饰器可以保存函数元数据如函数文档字符串
  * 用于 参数检查 缓存 代理 上下文提供者（锁）

    ```python
    from time import time

    def timer(function):
        def wrapper(*args, **kwargs):
            before = time()
            result = function(*args, **kwargs)
            after = time()
            print('Run time is %s' % (after - before))
            return result
        return wrapper

    @timer
    def add_dec(x, y=10):
        return x + y

    @timer
    def sub_dec(x, y=10):
        return x - y
    ```
* with语句

  1. Context Manager

  ```python
  from sqlite3 import connect
  from contextlib import contextmanager

  @contextmanager
  def temptable(cur):
      cur.execute('create table points(x int, y int)')
      print('created table')
      try:
          yield
      finally:
          cur.execute('drop table points')
          print('dropped table')

  with connect('test.db') as conn:
      cur = conn.cursor()
      with temptable(cur):
          cur.execute('insert into points (x, y) values(1, 1)')
          cur.execute('insert into points (x, y) values(1, 2)')
          cur.execute('insert into points (x, y) values(2, 1)')
          cur.execute('insert into points (x, y) values(2, 2)')
          for row in cur.execute("select x, y from points"):
              print(row)
  ```
* **del is the fastest method for removing a key from a Python dictionary**

## Chapter4

> 在Python中，使用二进制按位运算来合并选项是很常见的。使用OR（`|`）运算符可以将多个选项合并到一个整数中，而使用AND（`&`）运算符则可以检查该选项是否在整数中

## Chapter13

* 并发
  1. 多线程multithreading
  2. 多进程multiprocessing
  3. 异步编程
     1. 协程Coroutines
     2. Tasklets
     3. Green trheads
* [refer](https://medium.com/@bfortuner/python-multithreading-vs-multiprocessing-73072ce5600b)

## 异步编程

* async和await


# What happens when zh\_CN

一个古老的面试问题：当你在浏览器中输入 google.com 并且按下回车之后发生了什么？

## 按下"g"键

接下来的内容介绍了物理键盘和系统中断的工作原理，但是有一部分内容却没有涉及。当你按下“g”键，浏览器接收到这个消息之后，会触发自动完成机制。浏览器根据自己的算法，以及你是否处于隐私浏览模式，会在浏览器的地址框下方给出输入建议。大部分算法会优先考虑根据你的搜索历史和书签等内容给出建议。你打算输入 "google.com"，因此给出的建议并不匹配。但是输入过程中仍然有大量的代码在后台运行，你的每一次按键都会使得给出的建议更加准确。甚至有可能在你输入之前，浏览器就将 "google.com" 建议给你。

\===========================

键盘中断

事件传递

解析URL

\===========================

## 检查HSTS列表

* 浏览器检查自带的“预加载 HSTS（HTTP严格传输安全）”列表，这个列表里包含了那些请求浏览器只使用HTTPS进行连接的网站
* 如果网站在这个列表里，浏览器会使用 HTTPS 而不是 HTTP 协议，否则，最初的请求会使用HTTP协议发送
* 注意，一个网站哪怕不在 HSTS 列表里，也可以要求浏览器对自己使用 HSTS 政策进行访问。浏览器向网站发出第一个 HTTP 请求之后，网站会返回浏览器一个响应，请求浏览器只使用 HTTPS 发送请求。然而，就是这第一个 HTTP 请求，却可能会使用户受到 [downgrade attack](https://en.wikipedia.org/wiki/Downgrade_attack) 的威胁，这也是为什么现代浏览器都预置了HSTS 列表。

  > 降级攻击：故意使系统放弃新式、安全性高的工作方式（如加密连接），反而使用为向下兼容而准备的老式、安全性差的工作方式（如明文通讯）。
  >
  > Chrome HSTS列表 <chrome://net-internals/#hsts>

## DNS查询

* 浏览器检查域名是否在缓存当中（要查看 Chrome 当中的缓存， 打开 <chrome://net-internals/#dns>）。
* 如果缓存中没有，就去调用 `gethostbyname` 库函数（操作系统不同函数也不同）进行查询。
* `gethostbyname` 函数在试图进行DNS解析之前首先检查域名是否在本地 Hosts 里，Hosts 的位置 [不同的操作系统有所不同](https://en.wikipedia.org/wiki/Hosts_\(file\)#Location_in_the_file_system)
  * **Linux/Unix**: /etc/hosts
  * **Microsoft Windows**: [%SystemRoot%](https://www.wikiwand.com/en/Environment_variable#Windows)\System32\drivers\etc\hosts
* 如果 `gethostbyname` 没有这个域名的缓存记录，也没有在 `hosts` 里找到，它将会向 DNS 服务器发送一条 DNS 查询请求。DNS 服务器是由网络通信栈提供的，通常是本地路由器或者 ISP 的缓存 DNS 服务器。
* 查询本地 DNS 服务器
* 如果 DNS 服务器和我们的主机在同一个子网内，系统会按照下面的 ARP 过程对 DNS 服务器进行 ARP查询
* 如果 DNS 服务器和我们的主机在不同的子网，系统会按照下面的 ARP 过程对默认网关进行查询

## ARP 过程

要想发送 ARP（地址解析协议）广播，我们需要有一个目标 IP 地址，同时还需要知道用于发送 ARP 广播的接口的 MAC 地址。

* 首先查询 ARP 缓存，如果缓存命中，我们返回结果：目标 IP = MAC

如果缓存没有命中：

* 查看路由表，看看目标 IP 地址是不是在本地路由表中的某个子网内。是的话，使用跟那个子网相连的接口，否则使用与默认网关相连的接口。
* 查询选择的网络接口的 MAC 地址
* 我们发送一个二层（ [OSI 模型](https://en.wikipedia.org/wiki/OSI_model) 中的数据链路层）ARP 请求：

  `ARP Request`:

  ```
  Sender MAC: interface:mac:address:here
  Sender IP: interface.ip.goes.here
  Target MAC: FF:FF:FF:FF:FF:FF (Broadcast)
  Target IP: target.ip.goes.here
  ```

根据连接主机和路由器的硬件类型不同，可以分为以下几种情况：

直连：

* 如果我们和路由器是直接连接的，路由器会返回一个 `ARP Reply` （见下面）。

集线器：

* 如果我们连接到一个集线器，集线器会把 ARP 请求向所有其它端口广播，如果路由器也“连接”在其中，它会返回一个 `ARP Reply` 。

交换机：

* 如果我们连接到了一个交换机，交换机会检查本地 CAM/MAC 表，看看哪个端口有我们要找的那个 MAC 地址，如果没有找到，交换机会向所有其它端口广播这个 ARP 请求。
* 如果交换机的 MAC/CAM 表中有对应的条目，交换机会向有我们想要查询的 MAC 地址的那个端口发送 ARP 请求
* 如果路由器也“连接”在其中，它会返回一个 `ARP Reply`

  `ARP Reply`:

  ```
  Sender MAC: target:mac:address:here
  Sender IP: target.ip.goes.here
  Target MAC: interface:mac:address:here
  Target IP: interface.ip.goes.here
  ```

现在我们有了 DNS 服务器或者默认网关的 IP 地址，我们可以继续 DNS 请求了：

* 使用 53 端口向 DNS 服务器发送 UDP 请求包，如果响应包太大，会使用 TCP 协议

  > DNS 查询响应报文大于 512 字节时。
  >
  > DNS 主、辅助服务器之间，进行区域传送时。（辅服务器从主服务器同步信息的动作）
  >
  > 客户端主动发起TCP的DNS查询请求
* 如果本地/ISP DNS 服务器没有找到结果，它会发送一个递归查询请求，一层一层向高层 DNS 服务器做查询，直到查询到起始授权机构，如果找到会把结果返回。

## 使用套接字

当浏览器得到了目标服务器的 IP 地址，以及 URL 中给出来端口号（http 协议默认端口号是 80， https 默认端口号是 443），它会调用系统库函数 `socket` ，请求一个 TCP流套接字，对应的参数是 `AF_INET/AF_INET6` 和 `SOCK_STREAM` 。

* 这个请求首先被交给传输层，在传输层请求被封装成 TCP segment。目标端口会被加入头部，源端口会在系统内核的动态端口范围内选取（Linux下是ip\_local\_port\_rang, default: 32768 60999)
* TCP segment 被送往网络层，网络层会在其中再加入一个 IP 头部，里面包含了目标服务器的IP地址以及本机的IP地址，把它封装成一个IP packet。
* 这个 TCP packet 接下来会进入链路层，链路层会在封包中加入 frame 头部，里面包含了本地内置网卡的MAC地址以及网关（本地路由器）的 MAC 地址。像前面说的一样，如果内核不知道网关的 MAC 地址，它必须进行 ARP 广播来查询其地址。

到了现在，TCP 封包已经准备好了，可以使用下面的方式进行传输：

* [以太网](http://en.wikipedia.org/wiki/IEEE_802.3)
* [WiFi](https://en.wikipedia.org/wiki/IEEE_802.11)
* [蜂窝数据网络](https://en.wikipedia.org/wiki/Cellular_data_communication_protocol)

对于大部分家庭网络和小型企业网络来说，封包会从本地计算机出发，经过本地网络，再通过调制解调器把数字信号转换成模拟信号，使其适于在电话线路，有线电视光缆和无线电话线路上传输。在传输线路的另一端，是另外一个调制解调器，它把模拟信号转换回数字信号，交由下一个 [网络节点](https://en.wikipedia.org/wiki/Computer_network#Network_nodes) 处理。节点的目标地址和源地址将在后面讨论。

大型企业和比较新的住宅通常使用光纤或直接以太网连接，这种情况下信号一直是数字的，会被直接传到下一个 [网络节点](https://en.wikipedia.org/wiki/Computer_network#Network_nodes) 进行处理。

最终封包会到达管理本地子网的路由器。在那里出发，它会继续经过[自治区域](https://www.wikiwand.com/zh-hans/傳輸層安全性協定)(autonomous system, 缩写 AS)的边界路由器，其他自治区域，最终到达目标服务器。一路上经过的这些路由器会从IP数据报头部里提取出目标地址，并将封包正确地路由到下一个目的地。IP数据报头部 time to live (TTL) 域的值每经过一个路由器就减1，如果封包的TTL变为0，或者路由器由于网络拥堵等原因封包队列满了，那么这个包会被路由器丢弃。

上面的发送和接受过程在[TCP](https://www.wikiwand.com/zh-hans/%E4%BC%A0%E8%BE%93%E6%8E%A7%E5%88%B6%E5%8D%8F%E8%AE%AE)连接期间会发生很多次(SYN, *SYN-ACK*, ACK)：

* 客户端选择一个初始序列号(Initial sequence numbers, ISN)，将设置了 SYN 位的封包发送给服务器端，表明自己要建立连接并设置了初始序列号
* 服务器端接收到 SYN 包，如果它可以建立连接：
  * 服务器端选择它自己的初始序列号
  * 服务器端设置 SYN 位，表明自己选择了一个初始序列号
  * 服务器端把 (客户端ISN + 1) 复制到 ACK 域，并且设置 ACK 位，表明自己接收到了客户端的第一个封包
* 客户端通过发送下面一个封包来确认这次连接：
  * 自己的序列号+1
  * 接收端 ACK+1
  * 设置 ACK 位
* 数据通过下面的方式传输：
  * 当一方发送了N个 Bytes 的数据之后，将自己的 SEQ 序列号也增加N
  * 另一方确认接收到这个数据包（或者一系列数据包）之后，它发送一个 ACK 包，ACK 的值设置为接收到的数据包的最后一个序列号
* 关闭连接时(FIN, ACK -- FIN, ACK)：
  * 要关闭连接的一方发送一个 FIN 包
  * 另一方确认这个 FIN 包，并且发送自己的 FIN 包
  * 要关闭的一方使用 ACK 包来确认接收到了 FIN

## TLS 握手

* 客户端发送一个 `ClientHello` 消息到服务器端，消息中同时包含了它的 [Transport Layer Security (TLS)](https://www.wikiwand.com/zh/%E5%82%B3%E8%BC%B8%E5%B1%A4%E5%AE%89%E5%85%A8%E6%80%A7%E5%8D%94%E5%AE%9A) 版本，可用的加密算法和压缩算法。
* 服务器端向客户端返回一个 `ServerHello` 消息，消息中包含了服务器端的TLS版本，服务器所选择的加密和压缩算法，以及数字证书认证机构（Certificate Authority，缩写 CA）签发的服务器公开证书，证书中包含了公钥。客户端会使用这个公钥加密接下来的握手过程，直到协商生成一个新的对称密钥
* 客户端根据自己的信任CA列表，验证服务器端的证书是否可信。如果认为可信，客户端会生成一串伪随机数，使用服务器的公钥加密它。这串随机数会被用于生成新的对称密钥
* 服务器端使用自己的私钥解密上面提到的随机数，然后使用这串随机数生成自己的对称主密钥
* 客户端发送一个 `Finished` 消息给服务器端，使用对称密钥加密这次通讯的一个散列值
* 服务器端生成自己的 hash 值，然后解密客户端发送来的信息，检查这两个值是否对应。如果对应，就向客户端发送一个 `Finished` 消息，也使用协商好的对称密钥加密
* 从现在开始，接下来整个 TLS 会话都使用对称秘钥进行加密，传输应用层（HTTP）内容

## HTTP 协议

如果浏览器是 Google 出品的，它不会使用 HTTP 协议来获取页面信息，而是会与服务器端发送请求，商讨使用 [SPDY](https://www.wikiwand.com/zh/SPDY) 协议(HTTP/2前身)。

如果浏览器使用 HTTP 协议而不支持 SPDY 协议，它会向服务器发送这样的一个请求:

```
GET / HTTP/1.1
Host: google.com
Connection: close
[其他头部]
```

“其他头部”包含了一系列的由冒号分割开的键值对，它们的格式符合HTTP协议标准，它们之间由一个换行符分割开来。（这里我们假设浏览器没有违反HTTP协议标准的bug，同时假设浏览器使用 `HTTP/1.1` 协议，不然的话头部可能不包含 `Host` 字段，同时 `GET` 请求中的版本号会变成 `HTTP/1.0` 或者 `HTTP/0.9` 。）

HTTP/1.1 定义了“关闭连接”的选项 "close"，发送者使用这个选项指示这次连接在响应结束之后会断开。例如：

> Connection:close

不支持持久连接的 HTTP/1.1 应用必须在每条消息中都包含 "close" 选项。

在发送完这些请求和头部之后，浏览器发送一个换行符，表示要发送的内容已经结束了。

服务器端返回一个响应码，指示这次请求的状态，响应的形式是这样的:

```
200 OK
[响应头部]
```

然后是一个换行，接下来有效载荷(payload)，也就是 `www.google.com` 的HTML内容。服务器下面可能会关闭连接，如果客户端请求保持连接的话，服务器端会保持连接打开，以供之后的请求重用。

如果浏览器发送的HTTP头部包含了足够多的信息（例如包含了 Etag 头部），以至于服务器可以判断出，浏览器缓存的文件版本自从上次获取之后没有再更改过，服务器可能会返回这样的响应:

```
304 Not Modified
[响应头部]
```

这个响应没有有效载荷，浏览器会从自己的缓存中取出想要的内容。

在解析完 HTML 之后，浏览器和客户端会重复上面的过程，直到HTML页面引入的所有资源（图片，CSS，favicon.ico等等）全部都获取完毕，区别只是头部的 `GET / HTTP/1.1` 会变成 `GET /$(相对www.google.com的URL) HTTP/1.1` 。

如果HTML引入了 `www.google.com` 域名之外的资源，浏览器会回到上面解析域名那一步，按照下面的步骤往下一步一步执行，请求中的 `Host` 头部会变成另外的域名。

## HTTP 服务器请求处理

HTTPD(HTTP Daemon)在服务器端处理请求/响应。最常见的 HTTPD 有 Linux 上常用的 Apache 和 nginx，以及 Windows 上的 IIS。

* HTTPD 接收请求
* 服务器把请求拆分为以下几个参数：
  * HTTP 请求方法(`GET`, `POST`, `HEAD`, `PUT`, `DELETE`, `CONNECT`, `OPTIONS`, 或者 `TRACE`)。直接在地址栏中输入 URL 这种情况下，使用的是 GET 方法
  * 域名：google.com
  * 请求路径/页面：/ (我们没有请求google.com下的指定的页面，因此 / 是默认的路径)
* 服务器验证其上已经配置了 google.com 的虚拟主机
* 服务器验证 google.com 接受 GET 方法
* 服务器验证该用户可以使用 GET 方法(根据 IP 地址，身份信息等)
* 如果服务器安装了 URL 重写模块（例如 Apache 的 mod\_rewrite 和 IIS 的 URL Rewrite），服务器会尝试匹配重写规则，如果匹配上的话，服务器会按照规则重写这个请求
* 服务器根据请求信息获取相应的响应内容，这种情况下由于访问路径是 "/" ,会访问首页文件（你可以重写这个规则，但是这个是最常用的）。
* 服务器会使用指定的处理程序分析处理这个文件，假如 Google 使用 PHP，服务器会使用 PHP 解析 index 文件，并捕获输出，把 PHP 的输出结果返回给请求者

## 浏览器背后的故事

当服务器提供了资源之后（HTML，CSS，JS，图片等），浏览器会执行下面的操作：

* 解析 —— HTML，CSS，JS
* 渲染 —— 构建 DOM 树 -> 渲染 -> 布局 -> 绘制

## Reference

* [**what-happens-when-zh\_CN**](https://github.com/skyline75489/what-happens-when-zh_CN)


# TILGC

## “一统体制与有效治理”矛盾的应对机制

作为所有权力集于一身的中央政府，主要需要完成两项基本任务：

* 一是为广大百姓提供基本的公共服务，维持政权的长期稳定；
* 二是保证下放给行政代理人的权力不被滥用，中央的政令能够畅通无阻。

### 应对机制之一：决策一统性与执行灵活性之间的动态关系

***

> 政策实施过程中表现出各种变通现象
>
> 地方政府有效治理的能力在很大程度上体现在解决实际问题 时诉诸地方性、社会性、非正式性的种种话语和做法。
>
> 中国政府治理过程中存在着决策一统性与执行灵活性、象征性国家与基层治理之间的矛盾
>
> 基层政府间共谋行为在很大程度上缓和了一统体制与有效治理间的矛盾，缓和了自上而下的政策在各地实施过程中产生“共振反应”所 可能带来的震荡

### 应对机制之二：政治教化的礼仪化

***

* 这一仪式制度不是从认知上建立了共享观念，而是在象征性符号和动 员机制上建立和制度化了一整套程序规则。
* 这些仪式性活动并不仅仅是象 征性的，它们对于维系一统体制还有实质性意义。当人们“认认真 真走过场”时，这些行为本身就是对这一体制的顺从和接受。
* 这些仪式化活动在日常生活中不断地维系、强化了人们相互 间对中央权威的意识和认可。
* 这些仪式性活动产生 了民众顺从权威的共享知识和同步启动的效果，这正是权力的基础。

### 应对机制之三：运动型治理机制

***

> 从整顿金融市场混乱、整治小金库，到安全生产、 整治市容、反腐运动等各个领域。
>
> 特点： 暂时叫停原官僚制常规过程，以政治动员过程替代之，以便超越官僚制 度的组织失败，达到纠偏、规范边界的意图。

* 中央政府针对官僚制度失败和地方性或局部性偏差的一个重要应对手段是运动型治理机制，即通过运动式的政治动员来贯彻落实自上而下的政策意图。
* 运动型治理机制是调节一统体制与有效治理间矛盾关系的一个 重要机制。
* 一旦超越了某种临界度，触动一统体制的神经，**灵活性**就演变成为偏离甚至对抗。
* 通过间或的运动来规范这些灵活性的边界，从而在一统体制和有效治理间保持一个动态的平衡。

> 学术研究着眼于“是什么”和“为什么”的 问题，这并不意味着所谓“为学术而学术”的学风；恰恰相反，这 是对知识的尊重。在我看来，如果没有在扎实研究基础上对现实中 “是什么”和“为什么”的问题做出满意的回答，那么关于“应该 怎么做”的所谓研究只能是空中楼阁水中月，使得学术研究工作流 于空洞无物的清谈说教或成为哗众取宠的道具。


# VScode keyboard shortcuts

<https://code.visualstudio.com/shortcuts/keyboard-shortcuts-macos.pdf>

| Keybinding | Command                              |
| ---------- | ------------------------------------ |
| ⌥⇧F        | format content                       |
| F2         | rename refactoring                   |
| ⇧⌘K        | del the entire line                  |
| ⌥↓         | move line down                       |
| ⌥↑         | move line up                         |
| ⇧⌥↓        | copy line above the current position |
| ⇧⌥↑        | copy line below the current position |
| ⌥⌘\[       | fold code                            |
| ⌥⌘]        | unfold code                          |
| F8         | navigate errors and warnings         |
| ⌘K ⌘S      | Keyboard Shortcuts                   |


# Abseil Python

Abseil Quickstart (Python) <https://abseil.io/docs/python/quickstart.html>

* `from absl import app`
* `from absl import flags`
* 创建Flags = flags.FLAGS
* 定义flag，`flags.DEFINE_string/DEFINE_integer/DEFINE_boolean("flag_name", default_value, "help")`

  ```python
  DEFINE = _defines.DEFINE
  DEFINE_flag = _defines.DEFINE_flag
  DEFINE_string = _defines.DEFINE_string
  DEFINE_boolean = _defines.DEFINE_boolean
  DEFINE_bool = DEFINE_boolean  # Match C++ API.
  DEFINE_float = _defines.DEFINE_float
  DEFINE_integer = _defines.DEFINE_integer
  DEFINE_enum = _defines.DEFINE_enum
  DEFINE_enum_class = _defines.DEFINE_enum_class
  DEFINE_list = _defines.DEFINE_list
  DEFINE_spaceseplist = _defines.DEFINE_spaceseplist
  DEFINE_multi = _defines.DEFINE_multi
  DEFINE_multi_string = _defines.DEFINE_multi_string
  DEFINE_multi_integer = _defines.DEFINE_multi_integer
  DEFINE_multi_float = _defines.DEFINE_multi_float
  DEFINE_multi_enum = _defines.DEFINE_multi_enum
  DEFINE_multi_enum_class = _defines.DEFINE_multi_enum_class
  DEFINE_alias = _defines.DEFINE_alias
  ```
* 设定必选项flag `flags.mark_flag_as_required("flag_name")`
* 应用`Flags.flag_name`

***

完整的示例：

```python
from absl import app
from absl import flags

Flags = flags.FLAGS
flags.DEFINE_integer("num_times", 1, "Number of print times")
flags.DEFINE_string("name", None, "Your name")

# Required flag
flags.mark_flag_as_required("name")

def main(argv):
    del argv # Unused
    for _ in range(0, Flags.num_times):
        print('Hello %s, from absl' % Flags.name)
    
if __name__ == "__main__":
    app.run(main)

# test run python3 absl-hello.py --name=test
```

## [Programming Guides](https://abseil.io/docs/python/guides/)

### app.py

`app.py`是Abseil python应用的通用入口，是与一般python应用主要不同的地方，一般情况下运行e.g., `$ python my_app.py` 。当通过bazel运行的时候，Bazel会通过寻找`app.run()`确认入口点。

当程序启动， `app.run()` 处理flags, 打印一个用法信息和错误信息如果指定了非法flags。

### Flags

`absl.flags`定义了一个分布式命令行系统，取代了诸如`getopt()`，`optparse`和手动参数处理之类的系统。 每个应用程序都不必定义在`main()`或其附近的所有标志，而由每个Python模块定义对其有用的标志。 当一个Python模块导入另一个模块时，便可以访问另一个模块的标志。 （通过使所有模块共享包含所有标志信息的公共全局注册表对象来实现此行为。）

Abseil标志库包括定义标志类型（布尔值，浮点数，整数，列表），自动生成帮助（以人类和机器可读格式）以及从文件中读取参数的功能。 它还包括从帮助标志自动生成手册页的功能。

标志是通过使用`DEFINE_ *`函数定义的（标志的类型用于定义值）。

> Flag names are globally defined! So in general, we need to be careful to pick names that are unlikely to be used by other libraries. If there is a conflict, we'll get an error at import time.

#### Example Usage

```python
from absl import app
from absl import flags

FLAGS = flags.FLAGS

# Flag names are globally defined!  So in general, we need to be
# careful to pick names that are unlikely to be used by other libraries.
# If there is a conflict, we'll get an error at import time.
flags.DEFINE_string('name', 'Jane Random', 'Your name.')
flags.DEFINE_integer('age', None, 'Your age in years.', lower_bound=0)
flags.DEFINE_boolean('debug', False, 'Produces debugging output.')
flags.DEFINE_enum('job', 'running', ['running', 'stopped'], 'Job status.')


def main(argv):
  if FLAGS.debug:
    print('non-flag arguments:', argv)
  print('Happy Birthday', FLAGS.name)
  if FLAGS.age is not None:
    print('You are %d years old, and your job is %s' % (FLAGS.age, FLAGS.job))


if __name__ == '__main__':
  app.run(main)
  
```

#### Flag 类型

* DEFINE\_string\`: takes any input and interprets it as a string.
* `DEFINE_bool` or `DEFINE_boolean`: typically does not take an argument: pass `--myflag` to set `FLAGS.myflag` to `True`, or `--nomyflag` to set `FLAGS.myflag` to `False`. `--myflag=true` and `--myflag=false` are also supported, but not recommended.
* `DEFINE_float`: takes an input and interprets it as a floating point number. This also takes optional arguments `lower_bound` and `upper_bound`; if the number specified on the command line is out of range, it raises a `FlagError`.
* `DEFINE_integer`: takes an input and interprets it as an integer. This also takes optional arguments `lower_bound` and `upper_bound` as for floats.
* `DEFINE_enum`: takes a list of strings that represents legal values. If the command-line value is not in this list, it raises a flag error; otherwise, it assigns to `FLAGS.flag` as a string.
* `DEFINE_list`: Takes a comma-separated list of strings on the command line and stores them in a Python list object.
* `DEFINE_spaceseplist`: Takes a space-separated list of strings on the commandline and stores them in a Python list object. For example: `--myspacesepflag "foo bar baz"`
* `DEFINE_multi_string`: The same as `DEFINE_string`, except the flag can be specified more than once on the command line. The result is a Python list object (list of strings), even if the flag is only on the command line once.
* `DEFINE_multi_integer`: The same as `DEFINE_integer`, except the flag can be specified more than once on the command line. The result is a Python list object (list of ints), even if the flag is only on the command line once.
* `DEFINE_multi_enum`: The same as `DEFINE_enum`, except the flag can be specified more than once on the command line. The result is a Python list object (list of strings), even if the flag is only on the command line once.

#### Special Flags

Some flags have special meanings:

* `--help`: prints a list of all key flags (see below).
* `--helpshort`: alias for `--help`.
* `--helpfull`: prints a list of all the flags in a human-readable fashion.
* `--helpxml`: prints a list of all flags, in XML format. *Do not* parse the output of `--helpfull` and `--helpshort`. Instead, parse the output of `--helpxml`.
* `--flagfile=filename`: read flags from file *filename*.
* `--undefok=f1,f2`: ignore unrecognized option errors for *f1*,*f2*. For boolean flags, you should use `--undefok=boolflag`, and `--boolflag` and `--noboolflag` will be accepted. Do not use `--undefok=noboolflag`.
* `--`: as in getopt(). This terminates flag-processing.

### [Logging](https://abseil.io/docs/python/guides/logging)

​ Abseil has its own library for logging in Python. It is implemented on top of the standard logging module in Python (described in [PEP282](https://legacy.python.org/dev/peps/pep-0282/)), which is good if you’re already familiar with that library. This section mentions the basics of Abseil’s logging library. See the [source](https://github.com/abseil/abseil-py/blob/master/absl/logging/__init__.py) for more details.

**Dependencies:**

```python
from absl import logging
```

**Example code:**

```python
logging.info('Interesting Stuff')
logging.info('Interesting Stuff with Arguments: %d', 42)

logging.set_verbosity(logging.INFO)
logging.log(logging.DEBUG, 'This will *not* be printed')
logging.set_verbosity(logging.DEBUG)
logging.log(logging.DEBUG, 'This will be printed')

logging.warning('Worrying Stuff')
logging.error('Alarming Stuff')
logging.fatal('AAAAHHHHH!!!!')  # Process exits
```

**Log levels:**

* `logging.FATAL`
* `logging.ERROR`
* `logging.WARNING`
* `logging.INFO`
* `logging.DEBUG`

**Functions:**

* `fatal(msg, *args)`
* `error(msg, *args)`
* `warning(msg, *args)`
* `info(msg, *args)`
* `debug(msg, *args)`
* `vlog(level, msg, *args)`
* `exception(msg, *args)`

### [Testing](https://abseil.io/docs/python/guides/testing)

​ Abseil Python’s testing library is similar to Python’s standard `unittest` module (sometimes referred to as PyUnit) but offers some additional useful features on top of the standard library, such as interfacing with Abseil Flags.

To use the Abseil testing library, do the following in your unit tests:

* import the `absltest` module
* import the `flags` module, which gives you access to the variables `FLAGS.test_srcdir` and `FLAGS.test_tmpdir`.
* call `absltest.main()` instead of `unittest.main()`

[absl-py’s own tests](https://github.com/abseil/abseil-py/blob/master/absl/tests/app_test.py)


# Latex Note

Created on Thu, 05 Dec 2019, 08:07PM

Last changed on Tue, 25 Feb 2020, 12:44AM

## 基础公式

### 基础数学公式

*LaTeX*的数学公式主要有两种，即行内公式（Inline Formulas）和块级公式（Display Formulas）。行内公式内嵌于正文文本中间，与正文文字行高相等；块级公式则单独成行。

#### 行内公式

用`$...$`符号包围的*LaTeX*代码：

```latex
这样的代码可以生成如$$x^n+y^n=z^n$$这样的行内公式。
```

> eg. 这样的代码可以生成如$$x^n+y^n=z^n$$这样的行内公式。

#### 块级公式

用`$$...$$`符号包围的*LaTeX*代码：

```latex
这样的代码可以生成如$$x^n+y^n=z^n$$这样的块级公式。
```

> eg. 这样的代码可以生成如
>
> $$x^n+y^n=z^n$$
>
> **这样的块级公式。**

#### 块级公式的编号

直接使用块级代码`$$x^n+y^n=z^n$$`不会生成编号，而使用`\tag{...}`标签就可以生成对应的编号。

```latex
这样的代码可以生成如`$$x^n+y^n=z^n \tag{1.1}$$`的编号块级公式。
```

> eg. 这样的代码可以生成如

$$x^n+y^n=z^n \tag{1.1}$$

> 的编号块级公式。

{% hint style="info" %}
除了手动编号之外*LaTeX*还支持对公式的自动编号，对于需要自动编号的公式，需要使用`\begin{equation}...\end{equation}`将代码快包围起来。
{% endhint %}

```latex
这样的代码可以生成如$$\begin{equation}x^n+y^n=z^n\end{equation}$$的自动编号块级公式。
```

> eg. 这样的代码可以生成如

$$
\begin{equation} x^n+y^n=z^n \end{equation}
$$

> 的自动编号块级公式。

因为自动编号的代码较为复杂，而且不易扩展，所以不太建议使用自动编号，手动编号更易维护。

公式引用时候可以直接用`$编号$`即可。 对于`$$a^2+b^2=c^2 \tag {1.2}$$`由公式`$(1.2)$`即可得到结论。

> eg. 对于

$$a^2+b^2=c^2 \tag {1.2}$$

> 由公式$$(1.2)$$即可得到结论。

#### 单个公式换行

单个公式很长的时候需要换行，但仅允许生成一个编号时，可以用`split`标签包围公式代码，在需要转行的地方使用`\\`，每行需要使用1个`&`来标识对齐的位置，结束后可使用`\tag{...}`标签编号。

在用`equation`的时候内部可以用`aligned`来将单个公式换行

```latex
$$
\begin{split}
a &= b \\
c &= d \\
e &= f
\end{split}\tag{1.3}
$$
```

> eg.

$$
\begin{split} a &= b \ c &= d \ e &= f \end{split}\tag{1.3}
$$

{% hint style="warning" %}
注意：每行只允许出现一个`&`，使用`split`标签后，编号会**上下居中**显示。
{% endhint %}

```latex
\begin{equation}
  \begin{aligned}
    \mathrm{E}_{B}\left[\mathcal{L}\left(w_{B}^{n_{p}}\right)-\mathcal{L}\left(w^{*}\right)\right] \leq \frac{\alpha^{2} L M}{2 \gamma c} \frac{(B-1)}{B_{d}}+\left(1-\frac{(B-1)}{B_{d}}\right) \frac{L D^{2}}{2} \\
    +\frac{1}{B_{d}} \sum_{l=1}^{B-1}(1-\gamma c)^{l n_{p}}\left[\frac{L D^{2}}{2}-\frac{\alpha^{2} L M}{2 \gamma c}\right]  
  \end{aligned}
\end{equation}
```

$$
\begin{equation} \begin{aligned} \mathrm{E}*{B}\left\[\mathcal{L}\left(w*{B}^{n\_{p}}\right)-\mathcal{L}\left(w^{\*}\right)\right] \leq \frac{\alpha^{2} L M}{2 \gamma c} \frac{(B-1)}{B\_{d}}+\left(1-\frac{(B-1)}{B\_{d}}\right) \frac{L D^{2}}{2} \ +\frac{1}{B\_{d}} \sum\_{l=1}^{B-1}(1-\gamma c)^{l n\_{p}}\left\[\frac{L D^{2}}{2}-\frac{\alpha^{2} L M}{2 \gamma c}\right] \end{aligned} \end{equation}
$$

#### 多行的独立公式

有时候需要罗列多个公式，可以用`eqnarray*`标签包围公式代码，在需要转行的地方使用`\\`，每行需要使用2个`&`来标识对齐位置，两个`&...&`号之间的是公式间对齐的位置，每行公式后可使用`\tag{...}`标签编号：

```latex
$$
\begin{eqnarray*}
x^n+y^n &=& z^n \tag{1.4} \\
x+y &=& z \tag{1.5}
\end{eqnarray*}
$$
```

$$
\begin{eqnarray\*} x^n+y^n &=& z^n \tag{1.4} \ x+y &=& z \tag{1.5} \end{eqnarray\*}
$$

以上就是常见的Markdown工具环境下的*LaTeX*公式排版最简单的、最常用的代码命令。

{% hint style="success" %}
LaTex 数学公式完。
{% endhint %}

## Latex中的空格

$$
\verb|a,b| a,b \ \verb|$a,b$| $a,b$ \ \verb|a\thinspace b| a\thinspace b \ \verb|$a\thinspace b$| $a\thinspace b$ \ \verb|$a!b$| $a!b$ \ \verb|$a>b$| $a>b$ \ \verb|$a;b$| $a;b$ \ \verb|$a:b$| $a:b$ \ \verb|a\enspace b| a\enspace b \ \verb|$a\enspace b$| $a\enspace b$ \ \verb|a\quad b| a\quad b \ \verb|$a\quad b$| $a\quad b$ \ \verb|a\qquad b| a\qquad b \ \verb|$a\qquad b$| $a\qquad b$ \ \verb|a\hskip 1em b| a\hskip 1em b \ \verb|$a\hskip 1em b$| $a\hskip 1em b$ \ \verb|a\kern 1pc b| a\kern 1pc b \ \verb|$a\kern 1pc b$| $a\kern 1pc b$ \ \verb|a\hspace{35pt}b| a\hspace{35pt}b \ \verb|$a\hspace{35pt}b$| $a\hspace{35pt}b$ \ \verb|axyzb| axyzb \ \verb|a\hphantom{xyz}b| a\hphantom{xyz}b \ \verb|$axyzb$| $axyzb$ \ \verb|$a\hphantom{xyz}b$| $a\hphantom{xyz}b$ \ \verb|a{ }b| a{ }b \ \verb|$a{ }b$| $a{ }b$ \ \verb|a\space b| a\space b \ \verb|$a\space b$| $a\space b$ \ \verb|a\ b| a\ b \ \verb|$a\ b$| $a\ b$ \ \verb|a~~b| a~~b \ \verb|$a~~b$| $a~~b$ \\
$$

## Beamer slide

* 主题选择
* `\begin{frame}` 一张slide

  ```latex
  \begin{frame}[t]
    \frametitle{章节标题}
  \end{frame}
  ```
* `\section` `\subsection` 标题 子标题

  ```
  \section[章缩写标题]{主标题}\label{sec:1}
  \subsection[节缩写标题]{节主标题}\label{subsec:1-1}
  ```
* etc
* **UCAS-Beamer**
* 字体大小

```latex
\tiny x
\scriptsize x
\footnotesize x
\small x
\normalsize x
\large x
\Large x
\LARGE x
\huge x
\Huge x
```

* 分栏

```latex
\begin{columns}
  \begin{column}{0.55\textwidth}
    ColumnContent1
  \end{column}
  \begin{column}{0.45\textwidth}
    ColumnContent2
  \end{column}
\end{columns}
```

## Reference

\[1] [Markdown下LaTeX公式、编号、对齐](https://www.zybuluo.com/fyywy520/note/82980)

\[2] [latex:公式的序号](https://www.cnblogs.com/suerchen/p/4817711.html)

\[3] [UCAS-Beamer](https://github.com/icgw/ucas-beamer/)


# Git Cheatsheet

Created on Fri, 03 Jan 2020, 05:20PM

Last changed on Fri, 03 Jan 2020, 05:20PM

​ 之前用`git` 的时候时不时会遇到一些非`add, commit, push`的情况，也查过不少相关的高级用法，但往往就用一次之后就忘记了，最近看了篇[文章](https://dev.to/maxpou/git-cheat-sheet-advanced-3a17)，顺着这篇文章也捋一下`git`的高级用法以备后用。

## 不那么高级却很有用的Tips

* `git log --oneline` # 简介明了的git日志, 一行就很明了的日志打印形式
* `git checkout -` # 回到切换分支之前的分支, 类似于`cd -` 切换回之前的工作目录
* `git log --all --grep='homepage'` # 在所有提交日志中搜索包含「homepage」的提交
* `git log --author="Maxence"` # 获取某人的提交日志
* `git commit --amend -m "更新后的提交日志"` # 编辑上次提交的message
* `git add . && git commit --amend --no-edit` # 在上次提交中附加一些内容，保持提交日志不变
* `git commit --allow-empty -m "chore: re-trigger build"` # 空提交 —— 可以用来重新触发 CI 构建
* `git reflog` # 获取所有操作历史
* `git reset HEAD@{4}` # 重置到相应提交
* `git reset --hard <'commit-hash'>` # ……或者……
* `git diff master..my-branch`
* `git fetch origin`
* `git checkout master`
* `git reset --hard origin/master`

## Advanced cheat sheet

### squash 提交

比方说我想要 rebase 最近 3 个提交：

1. `git rebase -i HEAD~3`
2. 保留第一行的 `pick`，剩余提交替换为 `squash` 或 `s`
3. 清理提交日志并保存（vi 编辑器中键入 `:wq` 即可保存）

```git
pick 64d26a1 feat: add index.js
s 45f0259 fix: update index.js
s 8b15b0a fix: typo in index.js
```

### Fixes

比方说想在提交 `fed14a4c` 加上一些内容。

```
git add .

git commit --fixup HEAD~1
# 或者也可以用提交的哈希值（fed14a4c）替换 HEAD~1

git rebase -i HEAD~3 --autosquash
# 保存并退出文件（VI 中输入 `:wq`）
```

...未完待续...

## Reference

\[1] [git 高级用法小抄](https://nextfe.com/git-cheatsheet-advanced/)

\[2] [Git: Cheat Sheet (advanced)](https://dev.to/maxpou/git-cheat-sheet-advanced-3a17)


# Study Smarter Not Harder

Created on Mon, 06 Jan 2020, 03:22PM

Last changed on Mon, 06 Jan 2020, 03:33PM

> A summary in part of a lecture titled "Study. Less Study Smart" by Dr. Marty Lobdell, Former Psychology Professor at Pierce College in Washington State

1. **Plan time in your schedule to study.** Make studying an important part of each day. It's also equally important to attend class, attend on time and be prepared for class when you arrive.
2. **Break you studying down into chucked sessions of 25-30 minutes.** Your ability to study diminishes after this time period. Take 5 minute break after each 30 minute interval to do something you enjoy. After you have completed your entire study session, reward yourself with a big treat. Things that are reinfored we trend to do more of. The things that are punished or ignored, we tend to do less of.
3. **Create a dedicated study area.** The context provided by your environment largely determines your behavior. Design your study area to encourage actual studying.
4. **Study actively.** There is a difference between actual recollection and simple recognition. Recongnition requires a cue or trigger and you don't get that in a test. So study by quizzing yourself, instead of just looking over highlighted sections of your books or notes.
5. **Take smart notes in class.** Expand on them as soon as possible(ASAP) after class to boost your initial learning.
6. **Summarize or teach what you learn.** It will help you pen point gaps in your understanding because you're unable to gloss over things.
7. **Use your textbook effectively.**
   1. **Survey**
   2. **Question**
   3. **Read**
   4. **Recite**
   5. **Review**
8. **Use mnemonics, acronyms, coined sayings, and image associations to study facts.** These strategies can help you remember information easier than note taking.

References:

1. Lobdell, M. (Producer). (2011, July 22). Study Less Study Smart. Video retrieved from <https://www.youtube.com/watch?v=IlU-zDU6aQ0>
2. Frank, T. (Producer). (2015, January 29). Study Less Study Smart: A 6-Minute Summary of Marty Lobdell's Lecture. College Info Geek. Video retrieved from <https://www.youtube.com/watch?v=23Xqu0jXlfs>
3. [Study Smarter Not Harder.pdf](https://www.uapb.edu/sites/www/Uploads/SSC/Study%20Smarter%20Not%20Harder.pdf)


# Machine Learning Interviews

> Machine Learning Systems Design

@chipro [huyenchip.com](https://github.com/Junyangz/Documents/blob/master/note/huyenchip.com)

## Introduction

Hour-long interview, you might have time to go over only one or two questions about design a machine learning system to **solve practical problems.**

Interviewer generally agree that even if you can't get to a working solution, as long as you **communicate your thinking process to show that you understand different constraints, trade-offs, and concerns of your system, it's good enough.**

These questions often **both love and hate**, as they are **fun, practical, flexible, and require the least amount of memoization**.

Open-ended question often lack evaluation guidelines but expects only one right answer -- the answer that the interviewer is familiar with.

These questions are **ambiguous**. You drive the interview and choose what to focus on.

"Most candidates know the model classes (linear, decision trees, LSTM, convolutional neural networks) and memorize the relevant information, so for me the interesting bits in machine learning systems interviews are data cleaning, data preparation, logging, evaluation metrics, scalable inference, feature stores (recommenders/rankers)." -- Dmitry Kislyuk

**No much know about the model classes...**

**Looks for the ability to divide and conquer the problem.**

"When I ask such questions, what I am looking for is the following. 1. Can the candidate break down the open ended problem into simple components (building blocks) 2. Can the candidate identify which blocks require machine learning and which do not." -- Ravi Ganti

1. **building blocks**
2. **Identify which blocks require machine learning.**
3. **TO sum as ML-system engineer**

"I think this \[the machine learning systems design] is the most important question. **Can a person define the problem, identify relevant metrics, ideate on data sources and possible important features, understands deeply what machine learning can do.** Machine learning methods change every year, solving problems stays the same." --Illia Polosukhin

1. **Define the problem**
2. **Identify relevant metrics**
3. **Ideate on data sources and features**
4. **Understand what machine learning can do**

> it aims to provide a framework for approaching those questions. -- the book

### Research vs production

The fundamental diffeerneces between machine learning in an academic setting and machine learning in production.

Academic care more about training

Production care more about serving.

Candidates often make the mistake of focusing entirely on training without of how it would be used.

#### Preformance requirements

Research -> **State-of-the-art(SOTA)** results on benchmarking tasks.

Edge out a **small increase** in prerformance that make **too complex** to be useful.

A technique .. is [**ensembling**](https://www.wikiwand.com/en/Ensemble_learning): combining "multiple learning algorithms to obtain better predictive performance than could be obtained from any of the constituent learning algorithms alone."

**A few precentage point increase** in performance, but make system more **complex**, much **more time** to develop and train, can **costs more**.

Research -> leaderboard(a few precentage points), but not for users.(95% or 96%)

#### Compute requirements

exponentially more compute power and exponentially more data to train.

Accodring to OpenAI, "the amount of compute used in the largest AI training runs has doubled every 3.5 months."

**The goals of research are very different from the goals of production.**

## Design a machine learning system

Designing a machine learning system is an iterative process.

* Project setup
* Data pipeline
* Modeling(selecting, training, debugging)
* Serving(testing, deploying, maintaining)

!\[image-20191231105100700]\(/Users/junyangz/Library/Application Support/typora-user-images/image-20191231105100700.png)

### Project setup

* Goals
  * What do you want to achieve with the problem?
* User experience
  * Setp walkthrough of how end users are supposed to use the system.
* Preformance constraints
  * How fast/good
  * What's more important: precision or recall?
  * What's more costly: FN or FP
* Evaluation
  * Training and inferencing
  * Users' reactions. etc
* Personalizaion
  * One model for all or for a group or for each user individually
  * Train a base model then finetune it for target users.
* Project constraints
  * Real world may less during interviews.(time, compute power, system, talents work, etc.)

### Data pipeline

* Data availiability and collection
* User data
* Storage
* Data reprocessing & representation
* Challenges
* Privacy
* Biases

### Modeling

Training

Debugging

Scaling

### Serving


# 深度学习中的优化

## 引言

   机器学习中的算法涉及诸多的优化问题，典型的就是利用梯度下降法(gradient descent)求使损失函数$$J(\theta)$$下降的模型参数 $$\theta$$。在深度学习，尤其是深度神经网络的训练和预测中，大的模型往往要花上数天甚至是数月的训练时间，因此虽然模型的优化费事费力，仍然是一个高回报的步骤， 因为好的模型和优化方法可以极大的加速深度学习模型的训练。在本章中，将主要介绍神经网络优化这一特定问题：寻找神经网络的参数 $$\theta$$，可以显著的降低由训练集误差项和正则化项组成的代价函数 $$J(\theta)$$。

## 本章结构

* 深度学习优化与纯优化的异同
* 优化神经网络的挑战
* 神经网络优化的基本算法
* 优化参数初始化
* 自适应学习率优化
* 二阶近似的利用
* 优化策略和元算法

## 1. 深度学习优化与纯优化的异同

   在机器学习的问题中，我们关心的是算法的性能度量 $$P$$, 它通常定义于测试集之上而且往往是不可解的，因此，我们采取的策略是间接地优化 $$P$$ ：我们希望通过降低代价函数 $$J(\theta)$$ 的方式来提高 $$P$$。而传统的纯优化就是单纯的优化目标 $J$ 本身。深度学习中，代价函数通常用训练集上损失函数的期望或者平均$$(1.1)$$：

$$J(\theta)=E\_{(x,y)\sim \hat{p}\_{data}} L(f(\boldsymbol{x}; \boldsymbol{\theta}), y) \tag{1.1}$$

其中，$$L$$ 是每个样本的损失函数（loss function），$$f(\boldsymbol{x}; \boldsymbol{\theta})$$ 是输入$$x$$时候的预测的输出，$$\hat{p}\_{data}$$ 是样本的经验分布，在监督学习中，$$y$$ 是目标输出。上式定义了训练集上的目标函数，而我们真正希望最小化的是基于数据生成分布 $$p\_{data}$$ 的期望$$(1.2)$$:

$$J^\*(\theta)=E\_{(x,y)\sim p\_{data}} L(f(\boldsymbol{x}; \boldsymbol{\theta}), y) \tag{1.2}$$

### 1.1 经验风险的最小化

   机器学习算法真正目标是想降低 $$(1.2)$$ 方程表示的期望误差，这样的一个量也叫做风险（risk）。需要注意的是，该式所计算的值是取自数据的真实潜在分布 $$p\_{data}$$ 。如果我们知道了真实的数据分布，那么这将变成一个可以被优化算法优化的问题。但是，通常我们只有训练集的样本。因此，机器学习优化问题在这里可以转化为求最小化训练集上的误差期望，也就是说用已知的经验分布 $$\hat{p}\_{data}$$ 代替未知的真实分布 $$p\_{data}$$。这样一来，我们转为最小化经验风险（emperical risk）$$(1.3)$$:

$$E\_{(x,y) \sim \hat{p}*{data} } L(f(\boldsymbol{x}; \boldsymbol{\theta}), y)=\frac{1}{m}\sum*{i=1}^{m} L(f(\boldsymbol{x^{(i)}}; \boldsymbol{\theta}), y^{(i)}) \tag{1.3}$$

上式中 $$m$$ 表示训练样本的数目。基于这种评价训练误差的训练过程被称为经验风险最小化（empirical risk minimization）。总结起来，我们并没有直接优化风险，而是优化经验风险，希望能够很大程度上的降低风险。单纯依靠最小化经验风险可能导致过拟合现象，而且在很多的情形下，减小经验风险并不可行，所以在深度学习中，我们很少使用经验风险最小化，而使用另外一不同的方法。

### 1.2 代理损失函数和提前终止

   有时候我们的真正损失函数，比如 0-1 分类误差并无法被有效的优化，此时我们会使用[代理损失函数](http://fa.bianp.net/blog/2014/surrogate-loss-functions-in-machine-learning/)（surrogate loss function）来作为原来目标的替代，而且会带来好处。比如，正确分类类别的负对数似然通常用作 0-1 损失的替代，负对数似然允许模型估计给定样本的类别的条件概率，能够输出期望最小分类误差所对应的类型。有些情况下，代理损失函数可以比原损失函数学到更多的东西，比如对数似然代替 0-1 分类误差函数时，当训练集上的误差达到0之后，测试集上的误差还可以持续下降，也就是说此时模型可以继续学习以拉开不同类别直接的距离以提高分类的鲁棒性。也就是说，代理损失函数从训练数据中学到了更多的东西。

   另外一个一般优化算法和机器学习训练算法不同的地方是，训练算法不会停在局部极小值点，而且通常机器学习的算法会提前设置终止条件，当条件满足时（通常在过拟合之前）算法就停止，虽然此时代理损失函数仍然有较大的导数。对于纯优化来说，终止时导数非常小。

### 1.3 批量算法和小批量算法

   机器学习算法的目标函数通常可以分解为训练样本上的求和。机器学习中的优化算法在计算参数的每一次更新时，通常仅仅使用整个代价函数的一部分项来估计代价函数的期望值。例如$$(1.4)$$：

$$\theta\_{ML}=\underset{\theta} {argmax} \sum\_{1}^{m} L(f(\boldsymbol{x^{(i)}}; \boldsymbol{\theta}), y^{(i)}) \tag{1.4}$$

最大化这个总和等价于最大化训练集在经验分布上的期望$$(1.5)$$：

$$J(\theta)=E\_{(x,y)\sim \hat{p}*{data} } log*{p\_{model}}(\boldsymbol{x}, y; \boldsymbol{\theta}) \tag{1.5}$$

优化算法用到的目标函数 $$J$$ 中用到的大多数属性也是训练集上的期望。例如常用的梯度期望$$(1.6)$$：

$$abla\_{\theta} J(\theta)=E\_{(x,y)\sim \hat{p}*{data}} \nabla*{\theta} log\_{p\_{model}} (\boldsymbol{x}, y; \boldsymbol{\theta}) \tag{1.6}$$

   准确的计算这个期望的代价非常大，因为需要在训练集的每个数据上进行以上的计算，计算量非常大。在真正的实践中，我们通常会随机采样少量的样本，然后计算这些样本上的平均值。简单的论证如下：$$n$$ 个样本的平均值的方差是 $$\frac{\sigma}{\sqrt{n}}$$，其中 $$\sigma$$ 是样本真实的标准差。这个公式表明，当样本量增大100倍时，相应地只能得到10倍的误差减小，也就是说回报是低于线性的。如果能够快速的计算出梯度的估计值，而不是缓慢的计算所有梯度的准确值，大多数算法会收敛的更快。另外，训练集的冗余也使得我们考虑使用小数目的部分样本进行模型训练。

   使用整个训练集的优化方法被称为批量(batch) 或确定性（deterministic）梯度算法，他们会在每次更新参数时计算所有样本。通常，“批量梯度下降”指使用全部训练集，而“批量”单独出现时，指一组样本。每次只使用部分样本的方法被称为随机（stochastic）或者在线（online）算法。在线通常是指从连续产生的数据流（stream）中提取样本，而不是从一个固定大小的样本中遍历多次采样的情形。大多数深度学习算法介于两者之间，使用一个以上但不是全部的训练样本，传统上称这种方法为小批量（minibatch）或者小批量随机（minibatch stochastic）方法，现在统称为随机（stochastic）方法。

  小批量大小由以下因素决定：

* 更大批量产生更精准梯度，但是回报低于线性
* 太小的批量无法充分利用多核架构
* 如果可并行处理，那么内存消耗和批量大小成正比
* 特定大小数组运行在某些硬件上，运行更快
* 小批量引入噪声，具有正则化的效果

  不同的算法使用不同的方法从小批量中提取信息，有些表现好，有些表现不好，原因可能是无法在小批量上面获取有用信息，或者是放大了小批量上面的误差噪声。

  小批量是随机抽取的这一点非常重要，以防连续遍历时数据之间的相关性导致梯度估计的相关性。从一组样本中计算出梯度期望的无偏估计要求这些样本之间是互相独立的。通常的做法是在训练之前，将数据随机打乱一次后，再按照顺序一直往下取 minibatch 就可以了。每个独立的模型每次遍历数据时都会使用我们已经提前打乱的数据，这种理论上偏离随机采样的方法并没有给模型训练带来怎样的影响。 小批量随机梯度下降的一个有趣的事实是，只要没有重复使用样本，它将遵循着真实泛化误差的梯度。在线学习最好体现了随机梯度下降是最小泛化误差的原因：所有数据都是新的独立产生的，而不是原来固定大小的训练集，这种情况下每次更新的样本是直接从分布 $$p\_{data}$$ 中采样获得的无偏样本。

  除非数据量非常之大，通常最好是多次遍历数据集，如果只在第一次随机打乱数据顺序后不再改变顺序，只有第一次遍历数据符合泛化误差梯度的无偏估计，额外的训练仍然可以继续减小训练误差而获得好处。随着数据集的不断增大，往往一个数据只会使用一次，甚至只是使用部分训练集，这时过拟合不再是问题，欠拟合和计算效率成为我们需要考虑的问题。

## 2. 优化神经网络的挑战

   优化通常是一个非常困难的任务，传统的机器学习会想办法设计目标函数和约束，使得目标函数是凸函数，从而避免优化过程中出现非凸函数的问题。然而，即使是凸函数，也会遇到优化的问题，本节将主要探讨深度学习优化问题中会遇到的挑战。

### 2.1 病态情况

   在优化凸函数的时候，会遇到Hessian矩阵 $$\boldsymbol{\textit{H}}$$ 病态的情况。病态情况一般被认为广泛存在于神经网络的训练过程中，体现在随机梯度下降会“卡”在某些特殊的情况，此时即使很小的更新步长也会增加代价函数。回顾之前的代价函数的二阶泰勒展开预测梯度下降的 $$-\epsilon\boldsymbol{\textit{g}}$$ 会增加：

$$\frac{1}{2}\epsilon^2\boldsymbol{\textit{g}}^{\top}\boldsymbol{\textit{H}}\boldsymbol{\textit{g}}-\epsilon\boldsymbol{\textit{g}}^{\top}\boldsymbol{\textit{g}} \tag{2.1}$$

当 $$\frac{1}{2}\epsilon^2\boldsymbol{\textit{g}}^{\top}\boldsymbol{\textit{H}}\boldsymbol{\textit{g}}$$ 超过 $$\epsilon\boldsymbol{\textit{g}}^{\top}\boldsymbol{\textit{g}}$$时，梯度的病态会成为问题，很多情况下，梯度的范数不会再训练中显著的减小，但是$$\boldsymbol{\textit{g}}^{\top}\boldsymbol{\textit{H}}\boldsymbol{\textit{g}}$$ 的增长会超过一个数量级。结果是，尽管梯度很强，但是学习率必须收缩以弥补更强的曲率，因此学习变得非常缓慢。 有些适合于解决其他情况中的病态的技术并不适用于深度神经网络。比如牛顿法在解决带有病态条件的Hessian矩阵的凸优化问题时，是有效的方法，但是运用到神经网络时需要很大的改动。

### 2.2 局部极小值

   在凸优化问题中，优化问题可以简化为寻找一个局部极小值点，因为任何的局部极小值就是全局最小值。虽然有些凸函数底部是一个很大的平坦区域，并非单一的极值点，但是应用过程中实际上该区域中每一个极小值点都是一个可以接受的点。所以说，对于凸优化问题来说，找到任何形式的临界点，就是找到了一个不错的可行解。而对于非凸函数问题，比如神经网络问题，可能会存在很多的局部极小值点。

  如果一个足够大的模型可以唯一确定一组模型的参数，那么我们说该模型是可以辨认的。带有潜变量的模型往往是不可辨认的，因为互相交换潜变量，可以得到等价的模型。神经网络代价函数具有非常多甚至是无限多的局部极小值点，而且，由于不可辨识性问题而产生的局部极小值都有相同的代价函数，因此局部极小值点并非是非凸带来的问题。如果局部极小值点相比全局最小值点有很大的代价，那么局部极小值点会带来很大的问题。对于实际使用的神经网络，是否存在很多代价很大的局部极小值点，优化算法是否会碰到这些极小值点都是尚未解决的公开问题。学者们现在猜想，对于足够大的神经网络而言，大部分局部极小值都具有很小的代价函数，我们能不能找到全局最小点并不重要，重要的是能够在参数空间里找到一个代价很小的**可以接受**的点。

### 2.3 高原、鞍点和平坦区域

  很多高维非凸函数而言，局部极值远少于另一类梯度为零的点：鞍点。在一个鞍点附近，有些方向有更大的代价函数，有些方向有更小的代价函数。在鞍点处，Hessian矩阵同时具有正负特征值，位于正特征值对应的特征向量方向的点比鞍点有更大的代价，负特征值对应的特征向量方向的点比鞍点有更小的代价。鞍点是某个方向的横截面的极大值点，也是另一个方向截面的极小值点。

![saddle example](https://raw.githubusercontent.com/exacity/simplified-deeplearning/master/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E4%B8%AD%E7%9A%84%E4%BC%98%E5%8C%96/img/Saddle.png)

（图片截取自[Wikipedia](https://en.wikipedia.org/wiki/Saddle_point)）

   在低维空间中，局部极值很长常见，而在高维空间中，鞍点则很常见。理论上已经证明，不具有非线性的浅层自编码器只有全局极小值和鞍点，没有代价比全局极小值更大的局部极小值点。试验中发现，梯度下降在很多情况下可以逃离鞍点。对于牛顿法而言，鞍点是一个问题，因为梯度下降旨在朝着“下坡”方向移动，而非明确寻找梯度为0的点。如果不经修改，牛顿法就会跳进一个鞍点。而在高维空间中，鞍点激增，所以以牛顿法为代表的二阶方法无法成功取代梯度下降。

### 2.4 悬崖和梯度爆炸

   多层神经网络因为有大量的因子相乘，典型的比如循环神经网络，因为长的时间序列会有大量因子相乘，这中情况下存在像悬崖一样的斜率较大的区域，当遇到这种悬崖结构时，梯度更新会很大程度的改变参数的值，进而跳过这样的区域。不管是从上还是从下接近悬崖，都会产生不好的结果。如果采用启发式梯度截断可以避免严重的后果。基本想法在于梯度只是指明了移动的最佳方向，并没有指明最佳步长，因此启发式梯度截断会减小步长，使得梯度下降不太可能一步走出最陡下降方向的悬崖区域。

![gradient cliff](https://raw.githubusercontent.com/exacity/simplified-deeplearning/master/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E4%B8%AD%E7%9A%84%E4%BC%98%E5%8C%96/img/gradientCliff.png)

（图片由[英文原书](http://www.deeplearningbook.org/)内基于Pascanu等人2013论文《On the difficulty of training recurrent neural networks 》修改 ）

### 2.5 长期依赖

   在一些算中，当计算图变得非常深的时候，会面临一个长期依赖的问题。深层的计算图不仅存在于前馈网络，也存在于循环神经网络中，因为循环神经网络要在很长的时间序列的各个时刻重复应用相同操作来构建非常深的计算图。举个例子，假如某个计算图包含一条反复与矩阵 $$\boldsymbol{\textit{W}}$$ 相乘的路径，那么 $$t$$ 步之后，相当于乘以 $$\boldsymbol{\textit{W}^t}$$。假设$$\boldsymbol{\textit{W}}$$ 有特征分解：

$$\boldsymbol{\textit{W}}=(\boldsymbol{\textit{V}} diag(\lambda)\boldsymbol{\textit{V}}^{-1})^t=\boldsymbol{\textit{V}} diag(\lambda)^t\boldsymbol{\textit{V}}^{-1} \tag{2.2}$$

当特征值 $$\lambda\_i$$ 不在 1 附近时，若大于1，则会爆炸，若小于1，则会消失。梯度消失与爆炸问题（vanishing and exploding gradient problem）是指该计算图上面的梯度会因 $$diag(\lambda)^t$$ 发生大幅度的变化。梯度消失使得我们不知道参数朝那个方向移动可以快速改进代价函数，而梯度爆炸会使得学习不稳定。循环网络中使用的相同的矩阵$$\boldsymbol{\textit{W}}$$并没有在前馈网络中使用，因此即使使用非常深的前馈网络，也能避免梯度消失于爆炸问题。

### 2.6 非精确梯度

   大多数优化算法先决条件是我们知道精确的梯度或者是Hessian矩阵，然而在实践中，往往都是有偏的估计，几乎所有的深度学习算法都需要基于采样的估计，比如小批量数据计算更新梯度。有些情况下，我们希望最小化的目标函数实际上是难以处理的，所以此时我们只能使用近似梯度。大多数神经网络算法的设计都考虑到了梯度估计的缺陷，所以选择比真实损失函数更容易估计的代理损失函数来避免这个问题。

### 2.7 局部与全局的弱对应

   以上讨论的大都是单点的性质，如果利用梯度下降在某个方向上损失函数改进很大，但是并没有指向全局代价更低的遥远区域，那么单点处表现很好，但是全局表现不佳。有学者认为大部分训练的运行时间取决于到达最终解的路径长度，大多数优化研究难点集中于训练是否找到了全局最小点，局部最小点，鞍点，但是在实践中神经网络并不会达到其中任何一种。

  梯度下降和几乎所有可以有效训练神经网络的方法，都是基于局部较小更新。以上的内容都是集中于为何这些局部范围更新的正确方向难以计算，但是难以确定局部下降是否定义通向有效解的足够短的路径。目标函数可能有诸如病态条件或不连续梯度的问题，使得梯度为目标函数提供较好近似的区间非常小。有些情况下，局部下降或许可以定义通向解的路径，但是该路径包含很多次更新，因此遵循该路径会带来很大的计算代价；还有些情况下，局部下降完全无法定义通向解的路径；还有些情况下，局部移动太过贪心，朝着下坡的方向移动，但是却和所以可行解越来越远。

![local vs all](https://raw.githubusercontent.com/exacity/simplified-deeplearning/master/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E4%B8%AD%E7%9A%84%E4%BC%98%E5%8C%96/img/local_all.png)

   许多现有的研究方法在研究求解具有困难全局结构的问题时，着力于寻找良好的初始点，而不是在局部范围内更新的算法，因为前者实现目标更加可行。

### 2.8 优化的理论限制

   一些理论研究表明，我们为任何神经网络设计的任何优化算法都有性能限制，但是，这样的理论上的性能限制并不影响神经网络在实践中的应用。寻找一个给定规模的网络的可行解是困难的，但在现实情况中，我们可以通过选择更大的网络，设置更多的参数，轻松找到可以接受的解。另外，在神经网络中实践中，我们不关注某个函数的精确的极小值点，只要求损失减到足够小以获得可以接受的泛化误差即可。理论研究优化算法的性能上界需要学术界更多的努力。

## 3. 神经网络优化的基本算法

   以上内容已经讲解了神经网络优化的理论指导思想，使用梯度下降和随机梯度下降，可以很大程度上加速模型的训练，代价函数会沿着随机挑选的小批量数据的梯度方向下降。

### 3.1 随机梯度下降

  随机梯度下降（SGD）及其变种是一般机器学习中应用最多的优化算法，尤其是在深度学习中。按照数据生成分布 $$p\_{data}$$ 随机抽取 $$m$$ 个小批量（独立同分布，iid）的样本，通过计算它们梯度的均值，得到梯度的无偏估计。下图展示了这个算法的过程：

![algorithm8.1](https://raw.githubusercontent.com/exacity/simplified-deeplearning/master/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E4%B8%AD%E7%9A%84%E4%BC%98%E5%8C%96/img/algorithm8.1.png) （算法图片截取自[中文翻译](https://github.com/exacity/deeplearningbook-chinese)）

  SGD算法中的一个关键参数是学习率 $$\epsilon\_k$$，在此之前我们介SGD都是使用的固定的学习率，在实践中，随着梯度的降低，有必要逐步减小学习率。因此以上伪代码中将第$$k$$步的学习率用 $$\epsilon\_k$$ 来表示。由于SGD中随机采样 minibatch 会引入噪声源，因此在极小点处梯度并不会消失。而批量梯度下降使用全量数据更新梯度，在接近极小值点时，梯度很小并逐步变为0，因此，批量梯度下降可以使用固定学习率。与之对比，SGD收敛的一个充分条件是：

$$
\begin{split} \sum\_{k=1}^{\infty}\epsilon\_k = \infty​ \ \sum\_{k=1}^{\infty}\epsilon\_k^2 < \infty​ \end{split} \tag{3.1}
$$

在实践中，一般会选择线性衰减学习率直到第 $$\tau$$ 次迭代：

$$\epsilon\_k = (1-\alpha)\epsilon\_0 + \alpha\epsilon\_{\tau} \tag{3.2}$$

其中 $$\alpha= \frac{k}{\tau}$$。在第 $$\tau$$次迭代之后，一般使 $$\epsilon$$ 保持常数。学习率可以通过实验误差的演变曲线来选取，如果初始 $$\epsilon\_0$$选取的过大，则会出现损失上升和震荡，而如果$$\epsilon\_0$$选取的过小，学习过程会过于缓慢。

   SGD及其相关的小批量，或者是更广义的基于梯度优化的在线学习算法，一个重要的性质是每一步更新的计算时间不依赖与总的训练样本的多少。即使总的数据集很大，它也能收敛，而且SGD往往在处理完整个训练集之前就收敛到可接受的误差范围之内。    研究算法的收敛率，一般会衡量额外误差（excess error） $$J(\theta)-min\_{\theta}J(\theta)$$，也即当前代价函数超出最低可能代价的量。SGD应用于凸问题时，$$k$$ 步迭代之后误差的量级是$$O(\frac{1}{\sqrt{k}})$$，在强凸情况下误差量级是$$O(\frac{1}{k})$$。在没有额外假设和辅助信息之下，以上的界限不能进一步改进。批量梯度下降理论上SGD有更好的收敛率，然而有学者研究指出，泛化误差的下降速度不会快于$$O(\frac{1}{k})$$，因此对于机器学习算法而言，不值得探索收敛快于$$O(\frac{1}{k})$$的优化算法，因为往往过快的收敛对应着过拟合。对于大数据集，SGD只需要少量的样本计算梯度从而实现初始快速更新。但是由SGD损失了常数倍$$O(\frac{1}{k})$$的渐进分析，我们可以在学习中逐渐增大小批量的batch大小，以此权衡并充分利用批量梯度下降和随机梯度下降两者的优点。

### 3.2 动量

   SGD是最受欢迎的优化算法，但是其学习过程有时会有点缓慢，动量方法旨在加速学习过程，特别是处理高曲率，小但一致的梯度，或者是带有噪声的梯度。动量算法积累了之前梯度指数级衰减的移动平均，并且继续沿该方向移动。从形式上看，动量算法引入了变量$$v$$ 充当速度角色-代表参数在参数空间移动的方向和速率，速度被认为是负梯度的指数衰减平均。动量这个名词的引入也是为了类比物理当中的动力概念。超参数 $$\alpha$$ 决定了之前的梯度贡献衰减得有多快，更新规则如下：

$$\begin{split} \boldsymbol{\textit{v}} &= \alpha \boldsymbol{\textit{v}} - \epsilon \nabla\_{\theta}(\frac{1}{m}\sum\_{i=1}^{m}L(f(\boldsymbol{x^{(i)}}; \boldsymbol{\theta}), y^{(i)})) \ \boldsymbol{\theta} &= \boldsymbol{\theta} + \boldsymbol{\textit{v}} \end{split} \tag{3.3}$$

速度$$v$$ 累积了梯度元素 $$abla\_{\theta}(\frac{1}{m}\sum\_{i=1}^{m}L(f(\boldsymbol{x^{(i)}}; \boldsymbol{\theta}), y^{(i)}))$$，相对于$$\epsilon$$，$$\alpha$$越大，之前梯度对现在方向的影响也越大。

![sgd with momentum](https://raw.githubusercontent.com/exacity/simplified-deeplearning/master/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E4%B8%AD%E7%9A%84%E4%BC%98%E5%8C%96/img/sgd_momentum.png)

带动量的SGD算法的伪代码如下：

![alg\_png](https://raw.githubusercontent.com/exacity/simplified-deeplearning/master/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E4%B8%AD%E7%9A%84%E4%BC%98%E5%8C%96/img/algorithm8.2.png)

（算法图片截取自[中文翻译](https://github.com/exacity/deeplearningbook-chinese)）

在之前的SGD或者批量梯度下降中，步长只是梯度范数乘以学习率，现在，步长取决于梯度序列的大小和排列，当许多连续的梯度指向相同的方向时，步长最大，如果动量算法始终观察到梯度 $$\boldsymbol{\textit{g}}$$，那么它会在 $$-\boldsymbol{\textit{g}}$$ 的方向上不断的加速。其中，步长大小为：

$$\frac{\epsilon \left | \boldsymbol{\textit{g}} \right |} {1-\alpha} \tag{3.4}$$

因此将动量方法的超参数视为 $$\frac{\epsilon}{1-\alpha}$$ 有助于SGD类比理解。比如，$$\alpha=0.9$$ 对应着最大速度10倍于梯度下降算法。在实践中，一般 $$\alpha$$ 会取值为0.5， 0.9和0.99，和学习率一样，$$\alpha$$ 也会随着时间不断地调整。一开始取比较小的值，逐渐变大，随着训练的深入，调整 $$\alpha$$ 没有收缩 $$\epsilon$$重要。

### 3.3 Nesterov 动量

  受Nesterov加速梯度算法的启发，Sutskever等人提出了动量算法的一个变种，更新规则如下：

$$\begin{split} \boldsymbol{\textit{v}} &= \alpha \boldsymbol{\textit{v}} - \epsilon \nabla\_{\theta}(\frac{1}{m}\sum\_{i=1}^{m}L(f(\boldsymbol{x^{(i)}}; \boldsymbol{\theta}+\alpha \boldsymbol{\textit{v}}), y^{(i)})) \ \boldsymbol{\theta} &= \boldsymbol{\theta} + \boldsymbol{\textit{v}} \end{split} \tag{3.5}$$

参数 $$\alpha$$ 和 $$\epsilon$$ 发挥了和标准动量方法中类似的作用，Nesterov动量和标准动量之间的区别在于梯度的计算上。Nesterov动量中，梯度计算在施加当前速度之后，可以理解为Nesterov 动量往标准动量方法中添加了一个校正因子。在凸优化问题使用批量梯度下降的情况下，Nesterov 动量将 $$k$$ 步之后额外误差收敛率从$$O(\frac{1}{k})$$ 提高到$$O(\frac{1}{k^2})$$，对SGD没有改进收敛率。完整的 Nesterov动量算法如下所示：

![alg\_png](https://raw.githubusercontent.com/exacity/simplified-deeplearning/master/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E4%B8%AD%E7%9A%84%E4%BC%98%E5%8C%96/img/algorithm8.3.png)

（算法图片截取自[中文翻译](https://github.com/exacity/deeplearningbook-chinese)）

## 4. 优化参数初始化

   有些算法本质上是非迭代的，只是求解一个点。而有些其他优化算法本质上是迭代的，应用这类优化问题时，能在可接受的时间内收敛到可接受的解，并且收敛值与初始值无关。深度学习的模型通常是迭代的，因此要求使用者制定一些开始迭代的初始点。另外，深度学习模型又是一个很复杂的问题，以至于大部分算法的结果收到初始值的影响。初始点能决定算法是否收敛，有些初始点十分不稳定，使得算法会遭遇数值困难，并完全失败。在收敛的情形下，初始点可以决定学习收敛的有多快，以及是否收敛到一个代价高或者低的点。另外，差不多代价的点可以导致区别极大的泛化误差，初始点可以影响泛化。

  现代机器学习乃至深度学习和神经网络的初始化策略是简单和启发式的，改进初始化是一项困难的任务。神经网络的优化到目前都没有被很好的理解。有些初始化策略在神经网络初始化时具有很好的效果，然而我们并没有理解哪些性质可以在初始化之后得以保持。进一步，有些初始化从优化的观点是有利的，但是从泛化的角度看是不利的。

  目前完全确信的唯一特性是初始参数需要在不同的单元之间“破坏对称性”。比如，如果具有相同激活函数的两个隐藏单元连接到相同的输入，那么这些单元必须具有不同的初始化参数。如果他们具有相同的参数，那么应用到确定性损失和模型的确定性学习算法之上时将会一直以相同的方式更新这两个单元。通常来说，最好还是初始化每个单元使其和其他单元计算不同的函数，这或许有助于确保没有输入模式丢失在前向传播的零空间中，也没有梯度丢失在反向传播的零空间中。每个单元计算不同的函数的目标促使了参数的随机初始化。

  我们几乎总是初始化模型的权重为高斯或者均匀分布中随机抽取的值，两者似乎没有很大的区别，然而初始分布的大小确实对优化过程的结果和网络的泛化能力都有很大的影响。

  更大的初始权重具有更强的破坏对称性的作用，有助于避免冗余的单元，也有助于避免在每层线性成分的前向或反向传播中丢失信号。如果权重初始太大，那么会在前向或者反向中产生爆炸的值。对于初始化网络，正则化和优化有着不同的观点：优化观点建议权重应该足够大以成功传播信息，正则化则希望参数小一点以降低模型复杂度。

  很多启发式的方法可用于选择权重的初始大小。一种初始化 $$m$$ 个输入和 $$n$$ 个输出的全连接层的权重启发式方法是从分布 $$U(-\frac{1}{\sqrt{m}},\frac{1}{\sqrt{m}})$$ 中采样权重，而 Glorot 和 Bengio 建议使用标准初始化(normalized initialization)：

$$W\_{i, j} \sim U(-\sqrt{\frac{6}{m+n}},\sqrt{\frac{6}{m+n}}) \tag{4.1}$$

后一种启发式方法初始化所有的层，目的在于使其处于具有相同激活方差和使其具有相同的梯度方差之间。虽然这种假设网络是不含有非线性的链式矩阵乘法，现实的神经网络会违反这个假设，但是很多设计给线性模型的算法在非线性模型上面都有很好的效果。

   如果计算资源允许，将每层权重的初始值范围设定为一个超参数通常是一个好主意，使用额超参数搜索算法，比如随机搜索来进行参数挑选。是否选择使用密集或者稀疏初始化也可以设置为一个超参数，当然我们也可以选择手动搜索最优初始范围。以上都是关注权重的初始化，其他参数的初始化通常是更加容易的。初始化偏置的方法必须和初始化权重的方法相协调，通常大部分情况下将偏置设置为0是可行的方案。当然，存在以下这些需要将初始化偏置为非0的情形：

* 偏置作为输出单元，初始化偏置以获得正确的输出边缘统计通常是有利的
* 有时需要选择偏置以避免初始化引起太大饱和
* 有时一个单元会控制其他单元能否参与到等式中

  上面这些初始化模型参数为常数或者随机算法，实践中还可以利用机器学习来初始化网络参数。在深度学习这本书的第三部分，一个常用的策略是使用无监督模型训练出来的参数来初始化监督模型。 即使是在一个不相关的问题上运行监督训练，往往也会得到一个比随机初始化更快收敛的初始值（类似于迁移学习）。这些新式的初始化策略有时能够得到更好的泛化误差和更快的收敛速度，因为它们编码了模型初始参数的分布信息。之前的其他初始化策略效果也不错的原因主要在于设置了正确的参数范围，或者是设置不同单元计算互相不同的参数。

## 5. 自适应学习率优化

   学习速率对神经网络的性能有着显著的影响，损失通常高度敏感于参数空间的某些方向，而对其他因素不敏感。动量算法可以一定程度上缓解这个问题，但代价是引入了另一个超参数。如果我们相信方向敏感度在某种程度是轴对齐的，那么给每个参数设置不同的学习率，在模型学习训练过程中自动适应这些学习率是有道理的。早期的一个模型训练时候的启发式算法 Delta-bar-delta算法，基于简单的想法：如果损失对于某个给定模型参数的偏导符号保持不变，那么学习率应该增大，如果对于该参数的偏导的方向发生了变化，那么学习率应该减小。这种方法只适用于全批量优化中。本节将介绍最近提出的几种基于小批量的算法来自适应模型参数的学习率。

### 5.1 AdaGrad 算法

   AdaGrad算法，如下图所示，独立地适应所有模型参数的学习率，缩放每个参数反比于其所有梯度历史平方值总和和平方根，具有损失最大偏导的参数相应有一个快速下降的学习率，而具有小偏导的参数在学习率上有相对较小的下降。总的效果是在参数空间中更为平缓的倾斜方向会取得更大的进步。对于训练深度神经网络而言，从训练开始积累梯度平方会导致有效学习率过早和过量的减小。

![alg\_png](https://raw.githubusercontent.com/exacity/simplified-deeplearning/master/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E4%B8%AD%E7%9A%84%E4%BC%98%E5%8C%96/img/algorithm8.4.png)

（算法图片截取自[中文翻译](https://github.com/exacity/deeplearningbook-chinese)）

### 5.2 RMSProp 算法

   RMSProp由Hinton于2012年提出，用于修改AdaGrad以在非凸设定下效果更好，将梯度积累改变为指数加权的移动平均。AdaGrad设计以让凸问题能够快速的收敛。当应用于非凸函数训练神经网络时，学习轨迹可能穿过了很多不同的结构，最终到达一个局部是凸碗的结构。AdaGrad根据平方梯度的整个历史来收缩学习率，学习率很可能在到达这样的凸碗结构之前就变得太小。而RMSProp使用指数衰减平均，丢弃遥远过去的历史，使其能够在找到凸碗结构后快速收敛，该算法等效于一个初始化与该碗状结构的AdaGrad算法。实践中和经验上，RMSProp已经被证明是是一种有效而且实用的深度神经网络优化算法，目前是深度学习从业者经常采用的优化方法之一。

   RMSProp的标准形式和结合Nesterov动量的形式如下图所示，相比AdaGrad，引入了一个新的超参数 $$ho$$，用来控制移动平均的长度范围。

![alg\_png](https://raw.githubusercontent.com/exacity/simplified-deeplearning/master/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E4%B8%AD%E7%9A%84%E4%BC%98%E5%8C%96/img/algorithm8.5.png)

![alg\_png](https://raw.githubusercontent.com/exacity/simplified-deeplearning/master/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E4%B8%AD%E7%9A%84%E4%BC%98%E5%8C%96/img/algorithm8.6.png)

（算法图片截取自[中文翻译](https://github.com/exacity/deeplearningbook-chinese)）

### 5.3 Adam 算法

   Adam (adaptive moments)，在早期算法的背景下，最好被看成结合RMSProp和具有一些重要区别的动量的变种。首先，在Adam中动量直接并入了梯度一阶矩（指数加权）的估计。将动量加入RMSProp最直观的方法是将动量应用于缩放后的梯度。其次，Adam包括偏置修正，修正从原点初始化的一阶矩（动量项）和二阶矩（非中心项）。Adam通常被认为对超参数的选择相当鲁棒，尽管学习率有时需要从建议的默认值修改。

![alg\_png](https://raw.githubusercontent.com/exacity/simplified-deeplearning/master/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E4%B8%AD%E7%9A%84%E4%BC%98%E5%8C%96/img/algorithm8.7.png)

（算法图片截取自[中文翻译](https://github.com/exacity/deeplearningbook-chinese)）

### 5.4 选择正确的优化算法

  本节以上部分讨论了一系列通过自适应每个模型参数的学习率以解决优化深度模型中的难题，究竟在实践中该如何选择并没有定论。目前来说，最流行并且使用率很高的优化算法包括SGD，有动量的SGD，RMSProp，有动量的RMSProp，AdaDelta 和 Adam，选择哪一个算法主要取决于使用者对特定算法的熟悉程度以便调节超参数。

  算法总结：基于动量和基于自适应学习率的优化算法都是从梯度下降SGD演化而来，算法的细节之处对比如下图：

![alg\_png](https://raw.githubusercontent.com/exacity/simplified-deeplearning/master/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E4%B8%AD%E7%9A%84%E4%BC%98%E5%8C%96/img/relation.png)

（图片截取自[blog](https://blog.slinuxer.com/category/machine-learning/page/2)）

   为比较和各个算法的特点和更好的理解基于梯度下降算法的工作原理，以下两张动图可供参考。左图：损失函数的等高图和不同算法的演化迭代收敛时间。右图：鞍点附近的各个算法的工作图，注意到红色的SGD路径在鞍点处花了很长时间才找到下降的方向，而RMSProp很快就找到了更快下降的方向。

![](https://raw.githubusercontent.com/exacity/simplified-deeplearning/master/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E4%B8%AD%E7%9A%84%E4%BC%98%E5%8C%96/img/opt2.gif) ![](https://raw.githubusercontent.com/exacity/simplified-deeplearning/master/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E4%B8%AD%E7%9A%84%E4%BC%98%E5%8C%96/img/opt1.gif)

（图片来自作者[Alec Radford](https://twitter.com/alecrad)）

*    以上基于SGD的6个算法的Python演示代码如下 [SGD\_optimization\_demos](https://github.com/exacity/simplified-deeplearning/blob/master/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E4%B8%AD%E7%9A%84%E4%BC%98%E5%8C%96/src/SGD_optimizatioin_demo.ipynb)

## 6. 二阶近似的利用

   本节会讨论用于训练深度神经网络的二阶方法。为简单起见，只讨论目标函数为经验风险（暂时不考虑正则化项）$$(6.1)$$：

$$J(\theta) = E\_{(x,y)\sim \hat{p}*{data} } L(f(\boldsymbol{x}; \boldsymbol{\theta}), y)=\frac{1}{m} \sum*{1}^{m} L(f(\boldsymbol{x^{(i)}}; \boldsymbol{\theta}), y^{(i)}) \tag{6.1}$$

### 6.1 牛顿法

  与一阶方法相比，二阶方法使用二阶导数改进了优化，最广泛使用的是牛顿法。牛顿法是基于二阶泰勒级数展开在某点 $$\theta\_0$$ 附近来近似 $$J(\theta)$$的方法，忽略了更高级的导数$$(6.2)$$：

$$J(\theta) \approx J(\theta\_0) + (\theta-\theta\_0)^{\top}\nabla\_{\theta}J(\theta\_0)+\frac{1}{2}(\theta-\theta\_0)^{\top}\boldsymbol{\textit{H}}(\theta-\theta\_0) \tag{6.2}$$

其中 $$\boldsymbol{\textit{H}}$$ 是 $$J$$ 相对于 $$\theta$$ 的Hessian矩阵在 $$\theta\_0$$处的估计。如果我们基于上式求解这个函数的临界点，得到牛顿参数的更新规则$$(6.3)$$：

$$\theta^\* = \theta\_0 - \boldsymbol{\textit{H}}^{-1}\nabla\_{\theta}J(\theta\_0) \tag{6.3}$$

因此，对于局部的具有正定 $$\boldsymbol{\textit{H}}$$ 的二次函数，用 $$\boldsymbol{\textit{H}}^{-1}$$调整梯度，牛顿法会直接跳到极小值，如果目标函数是凸的但非二次，该更新将是迭代的，得到的相关算法如下。对于非二次的表面，只要Hessian矩阵保持正定，牛顿法就能够迭代应用。

![alg\_png](https://raw.githubusercontent.com/exacity/simplified-deeplearning/master/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E4%B8%AD%E7%9A%84%E4%BC%98%E5%8C%96/img/algorithm8.8.png)

（算法图片截取自[中文翻译](https://github.com/exacity/deeplearningbook-chinese)）

   牛顿法只适用于Hessian矩阵是正定的情况，而在深度学习中，目标函数的表面通常是非凸的，因此使用牛顿法是有问题的，这种情况下可以通过正则化Hessian矩阵来避免，常用的方法是在Hessian矩阵对角线上增加常数 $$\alpha$$：

$$\theta^\* = \theta\_0 - \[\boldsymbol{\textit{H}(f(\theta\_0))+\alpha \boldsymbol{\textit{I}}} ]^{-1}\nabla\_{\theta}J(\theta\_0) \tag{6.4}$$

   这个正则化策略用于牛顿法的近似，只要Hessian矩阵的负特征值仍然相对接近0，效果就会很好。在曲率方向更极端的情况下，$$\alpha$$ 的值必须足够大，以抵消负特征。但是如果 $$\alpha$$ 变得太大，Hessian矩阵会变成由对角矩阵 $$\alpha \boldsymbol{\textit{I}}$$ 为主导，通过牛顿法选择的方向会收敛到普通梯度除以 $$\alpha$$。如果有很强的负数曲率存在时，$$\alpha$$ 需要特别大，导致牛顿法比选择合适学习率的梯度下降的步长更小。

   除了目标函数带来的挑战，牛顿法在大型神经网络训练中还受限制于庞大的计算负担。如果有 $$k$$ 个参数，那么牛顿法需要计算 $$k\times k$$的矩阵的逆，计算的额复杂度是 $$O(k^3)$$。另外由于每次训练迭代都要计算Hessian矩阵及其逆矩阵，所以只有参数很少的网络才能在实际中使用牛顿法。至于用于更大规模的网络训练，本节以下将讨论一些保持牛顿法优点，同时减小计算量的替代算法。

### 6.2 共轭梯度

  共轭梯度是一种通过迭代下降的共轭方向，来有效避免Hessian矩阵求逆的算法。这种方法来源于对最速下降法弱点的研究和改进。在共轭梯度法中，我们寻求一个和先前搜索方向共轭（conjugate）的搜索方向，也即它不会撤销该方向上的进展，在训练迭代第 $$t$$ 步时，下一步的搜索方向 $$\boldsymbol{\textit{d}}\_t$$的形式如下：

$${\boldsymbol{\textit{d}}}*t = \nabla*{\theta} J(\theta) + {\beta}*t {\boldsymbol{\textit{d}}}*{t-1} \tag{6.5}$$

其中 $$\beta\_t$$的大小控制着我们应该沿方向 $$\boldsymbol{\textit{d}}\_{t-1}$$ 上加回多少道当前搜索方向上。在这里，如果:

$${\boldsymbol{\textit{d}}}*t^{\top} {\boldsymbol{\textit{H}}} {\boldsymbol{\textit{d}}}*{t-1} = 0 \tag{6.6}$$

那么我们说两个方向 $$\boldsymbol{\textit{d}}\_t$$ 和 $$\boldsymbol{\textit{d}}\_{t-1}$$ 是共轭的。有两种计算 $$\beta\_t$$ 的方法，一种是 Fletcher-Reeves方法，另一种是 Polak-Ribiere 方法。对于二次曲面而言，共轭方向确保梯度沿着前一个方向大小不变。

![alg\_png](https://raw.githubusercontent.com/exacity/simplified-deeplearning/master/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E4%B8%AD%E7%9A%84%E4%BC%98%E5%8C%96/img/algorithm8.9.png)

（算法图片截取自[中文翻译](https://github.com/exacity/deeplearningbook-chinese)）

### 6.3 BFGS

   BFGS算法（Broyden-Fletcher-Goldfarb-Shanno）算法具有牛顿法的有点，但是没有牛顿法的计算负担。BFGS算法使用了一个更直接的方法近似牛顿更新，回顾公式$$(6.3)$$，BFGS算法使用矩阵 $$\boldsymbol{\textit{M}\_t}$$近似逆，迭代地低秩更新精度以更好地近似 $$\boldsymbol{\textit{H}^{-1}}$$。更多关于BFGS算法的内容，请参见相关参考资料。

  当Hessian逆近似 $$\boldsymbol{\textit{M}\_t}$$ 更新时，下降方向 $$ho\_t$$ 为 $$ho\_t = \textit{M}\_t \textit{g}\_t$$，在该方向上的线搜索用于决定该方向上的步长 $$\epsilon^\*$$，参数更行为：

$$\theta\_{t+1}=\theta\_t + \epsilon^\* \rho\_t \tag{6.7}$$

  和共轭梯度法类似BFGS算法迭代一系列线搜索，其方向含有二阶信息。而和共轭梯度不同的是，该方法的成功并不依赖于线搜索寻找该方向上和真正极小值很近的一点。优点方面，相比于共轭梯度，BFGS花费较少时间改进每个线搜索。缺点方面，BFGS算法必须存储Hessian逆矩阵$$\boldsymbol{\textit{M}}$$，需要 $$O(n^2)$$的存储空间，使得BFGS不适用于具有百万级参数的大规模现代深度学习模型。作为改进，存储受限的BFGS（或者称为L-BFGS）通过避免存储完整的Hessian逆近似$$\boldsymbol{\textit{M}}$$，使得存储代价显著降低。

## 7. 优化策略和元算法

   有很多的优化技术并不是真正具体的算法，而是更为一般化的模板和思想，它们既可以产生特定的算法，也可以并入到很多的算法之中。

### 7.1 批标准化

  批标准化是优化深度神经网络的重要创新之一。实际上它并不是一个优化算法，而是一个自适应的重参数的方法，以期解决训练非常深的模型的困难。重参数化显著减少了多层之间协调更新的问题，可以用于网络的任何输入层或者是隐藏层。设 $$\boldsymbol{\textit{H}}$$ 是需要标准化的某层的小批量激活函数，排布为设计矩阵，每个样本的激活层出现在矩阵的每一行中。为了标准化 $$\boldsymbol{\textit{H}}$$，我们将其替换为：

$$
\boldsymbol{\textit{H}}'= \frac{\boldsymbol{\textit{H}}-\boldsymbol{\mu}}{\boldsymbol{\sigma}} \tag{7.1}
$$

其中 $$\mu$$ 是包含每个单元均值的向量，$$\sigma$$ 是包含每个单元标准差的向量。在训练的阶段:

$$
\begin{split} \mu = \frac{1}{m} \sum\_{i} {\boldsymbol{\textit{H}}}*i \ {\boldsymbol{\sigma}} = \sqrt{\delta+\frac{1}{m} \sum*{i} (\boldsymbol{\textit{H}}-{\boldsymbol{\mu})}\_i^2} \end{split} \tag{7.2}
$$

其中 $$\delta$$是一个很小的正值，比如 $$10^{-8}$$。我们反向传播这些操作，计算均值和标准差，并应用它们来标准化 $$\boldsymbol{\textit{H}}$$。在测试阶段，$$\mu$$ 和 $$\sigma$$ 可以被替换为训练阶段收集的运行均值，使得模型可以对单一样本进行评估。批标准化使得模型更容易学习。

### 7.2 坐标下降

  在某些情况下，将一个优化问题分解成几个部分，可以更快速地解决原问题。比如，如果我们相对于某个单一变量 $$x\_i$$ 最小化 $$f(\boldsymbol{\textit{x}})$$，然后相对于另一个变量 $$x\_j$$ 等等，这样反复循环所有变量，会保证达到局部极小值点。这种做法叫做坐标下降（coordinate descent），因为我们一次优化一个坐标。进一步地，块坐标下降（block coordinate descent）指对某个子集的变量同时最小化。

### 7.3 Polyak 平均

   Polyak 平均会平均优化算法在参数空间访问轨迹中的几个点。如果 $$t$$ 次迭代梯度下降访问了点 $$\boldsymbol{\theta}^{(1)}... \theta^{(t)}$$，那么Polyak平均算法输出的是：

$$\hat{\theta}^{(t)} = \frac{1}{t}\sum\_i \theta^{(i)} \tag{7.3}$$

在梯度下降应用于某些问题，比如凸问题时，这种方法具有较强的收敛保证。当Polyak平均于非凸问题时，通常会用指数衰减计算平均值：

$$\hat{\theta}^{(t)} = \alpha \hat{\theta}^{(t-1)} + (1-\alpha) \theta^t \tag{7.4}$$

### 7.4 监督预训练

  有时如果模型太过复杂难以优化或者任务非常困难，直接训练模型的挑战非常之大，有时训练一个较为简单的问题，然后使得模型逐渐复杂会更有效。训练模型先求解一个简化的问题，然后转移到最后的问题，有时也会更有效些。这种在训练最终模型之前训练简单模型求解简化问题的方法统称为预训练（pretraining）。

  贪心算法（greedy algorithm）将问题分解为许多部分，然后独立地在每个部分求解最优值，往往结合各个最佳部分并不能保证得到一个最优解，但是这种贪心算法计算比求解联合最优解的算法高效很多，并且贪心算法的结果即使不是最优解，往往也是可以接受的。贪心算法之后可以紧随一个精调（fine-tunning）阶段，联合优化算法搜索全问题的最优解。所以，使用贪心算法初始化联合优化算法，可以极大地加速算法，并提高寻找到的解的质量。

  预训练算法，特别是贪心预训练，将监督学习问题分解成其他简化的监督学习问题的预训练算法，叫做贪心监督预训练（greedy supervised pretraining）。这种方法有助于更好的指导深层结构的中间层的学习，且在一般情况下，预训练对于优化和泛化都是有帮助的，它实际上扩展了迁移学习的想法。

### 7.5 设计有助于优化的模型

  改进优化的最好方法并不总是改进优化算法，相反，在深度学习中的许多改进来自设计易于优化的模型。在实践中，选择一族容易优化的模型比使用一个强大的优化算法更重要。神经网络学习在过去30年的大多数进步主要来自改变模型族，而并非改变优化过程。

  现代神经网络的设计选择体现在层之间的线性变换，几乎处处可导的激活函数，和大部分定义域都有明显的梯度，特别是创新的模型，比如LSTM，整流线性单元和maxout单元都比先前的模型，比如sigmoid单元的深度网络，使用更多的线性函数，使得这些模型都有简化优化的作用。现代神经网络的设计方案旨在使其局部梯度信息合理地对应着移动向一个遥远的解。

### 7.6 延拓法和课程学习

  许多优化的挑战都来自于代价函数的全局结构，不能仅仅通过局部更新方向上更好的估计来解决。解决这个问题的主要方法是尝试初始化参数到某种区域内，该区域可以通过局部下降很快连接到参数空间中的解。

  延拓法（continuation method）是一族通过挑选初始点使得优化更容易的方法，以确保局部优化花费大部分时间在表现良好的区域。延拓法的基本思想是构造一系列具有相同参数的目标函数，这些函数难度逐渐提高。传统上，延拓法主要被用来客服局部极小值的问题，它被设计用来在有很多局部极小值的情况下，求解一个全局最小点。这些连续的方法会通过“模糊”原来的代价函数来构造更容易的代价函数，有些非凸函数在模糊之后就变成了近似凸函数，而且这种模糊保留了关于全局极小值的足够信息以供算法学习。尽管局部极小值问题已不再是神经网络优化的主要问题了，延拓法仍然有所帮助。

  Bengio 提出被称为课程学习（curriculum learning）或者塑造（shaping）的方法也可以被解释为延拓法。课程学习基于规划学习过程的想法，首先学习简单的概念，然后逐步学习依赖于这些简单概念的复杂概念。比如：教师通常会先展示更容易更典型的实例给学生，然后慢慢过渡到复杂的实例，在人类教学上，课程学习的策略比基于样本均匀采样的策略更为有效。

***

原文来自<https://github.com/exacity/simplified-deeplearning/blob/master/深度学习中的优化/深度学习中的优化.md>，有部分修正和改动。


# Beej's Guide to Network Programming Note

The <https://beej.us/guide/bgnet/html/> is a great tutorial for network programming.


# ch4

1. use `getaddrinfo()` to get all the struct sockaddr info
2. Change `AF_INET` to `AF_INET6`. Change `PF_INET` to `PF_INET6`.
3. Change `INADDR_ANY` assignments to `in6addr_any` assignments
4. Instead of `inet_aton()` or `inet_addr()`,use `inet_pton()`
5. `inet_ntoa()`,use `inet_ntop()`
6. etc...


# ch5

## getaddrinfo()

```c
/*
** showip.c -- show IP addresses for a host given on the command line
*/

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <netinet/in.h>

int main(int argc, char *argv[])
{
	struct addrinfo hints, *res, *p;
	int status;
	char ipstr[INET6_ADDRSTRLEN];

	if (argc != 2) {
	    fprintf(stderr,"usage: showip hostname\n");
	    return 1;
	}

	memset(&hints, 0, sizeof hints);
	hints.ai_family = AF_UNSPEC; // AF_INET or AF_INET6 to force version
	hints.ai_socktype = SOCK_STREAM;

	if ((status = getaddrinfo(argv[1], NULL, &hints, &res)) != 0) {
		fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(status));
		return 2;
	}

	printf("IP addresses for %s:\n\n", argv[1]);

	for(p = res;p != NULL; p = p->ai_next) {
		void *addr;
		char *ipver;

		// get the pointer to the address itself,
		// different fields in IPv4 and IPv6:
		if (p->ai_family == AF_INET) { // IPv4
			struct sockaddr_in *ipv4 = (struct sockaddr_in *)p->ai_addr;
			addr = &(ipv4->sin_addr);
			ipver = "IPv4";
		} else { // IPv6
			struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)p->ai_addr;
			addr = &(ipv6->sin6_addr);
			ipver = "IPv6";
		}

		// convert the IP to a string and print it:
		inet_ntop(p->ai_family, addr, ipstr, sizeof ipstr);
		printf("  %s: %s\n", ipver, ipstr);
	}

	freeaddrinfo(res); // free the linked list

	return 0;
}
```

## socket()

```c
int s;
struct addrinfo hints, *res;

// do the lookup
// [pretend we already filled out the "hints" struct]
getaddrinfo("www.example.com", "http", &hints, &res);

// again, you should do error-checking on getaddrinfo(), and walk
// the "res" linked list looking for valid entries instead of just
// assuming the first one is good (like many of these examples do).
// See the section on client/server for real examples.

s = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
```

## bind()

```c
#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, struct sockaddr *my_addr, int addrlen);
// sockfd is the socket descriptor returned by socket()
// my_addr is a pointer to a struct sockaddr
```

```c
1 struct addrinfo hints, *res;
2 int sockfd;
3
4 // first, load up address structs with getaddrinfo(): 5
6 memset(&hints, 0, sizeof hints);
7 hints.ai_family = AF_UNSPEC; // use IPv4 or IPv6, whichever
8 hints.ai_socktype = SOCK_STREAM;
9 hints.ai_flags = AI_PASSIVE; // fill in my IP for me
10
11 getaddrinfo(NULL, "3490", &hints, &res);
12
13 // make a socket:
14
15 sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol); 
16
17 // bind it to the port we passed in to getaddrinfo():
18
19 bind(sockfd, res->ai_addr, res->ai_addrlen);
```

## connect()

```c
#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd, struct sockaddr *serv_addr, int addrlen);
// serv_addr is a pointer to a struct sockaddr containing the destination port and IP address.
```

```c
1 struct addrinfo hints, *res;
2 int sockfd;
3
4 // first, load up address structs with getaddrinfo(): 
5
6 memset(&hints, 0, sizeof hints);
7 hints.ai_family = AF_UNSPEC;
8 hints.ai_socktype = SOCK_STREAM;
9
10 getaddrinfo("www.example.com", "3490", &hints, &res); 11
12 // make a socket:
13
14 sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol); 
15
16 // connect!
17
18 connect(sockfd, res->ai_addr, res->ai_addrlen);
19 // return -1 on error.
20 // we don't care about our local port number, only care the remote port, so we didn't call bind().
```

## listen()

```c
int listen(int sockfd, int backlog);
// backlog is the maximum number of connections that can be queued for this socket.
```

```c
```

```c
1 getaddrinfo();
2 socket();
3 bind();
4 listen();
5 /* accept() goes here */
```

## accept()

Their connection will be queued up waiting to be accept()ed. You call accept() and you tell it to get the pending connection. It’ll return to you a brand new socket file descriptor to use for this single connection! The original one is still listening for more new connections, and the newly created one is finally ready to send() and recv().

```c
#include <sys/types.h>
#include <sys/socket.h>

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
```

```c
1 #include <string.h>
2 #include <sys/types.h>
3 #include <sys/socket.h>
4 #include <netdb.h>
5
6 #define MYPORT "3490" // the port users will be connecting to
7 #define BACKLOG 10 // how many pending connections queue will hold
8
9 int main(void)
10 {
11 struct sockaddr_storage their_addr;
12 socklen_t addr_size;
13 struct addrinfo hints, *res;
14 int sockfd, new_fd;
15
16 // !! don't forget your error checking for these calls !! 17
18 // first, load up address structs with getaddrinfo():
19
20 memset(&hints, 0, sizeof hints);
21 hints.ai_family = AF_UNSPEC; // use IPv4 or IPv6, whichever
22 hints.ai_socktype = SOCK_STREAM;
23 hints.ai_flags = AI_PASSIVE; // fill in my IP for me
24
25 getaddrinfo(NULL, MYPORT, &hints, &res);
26
27 // make a socket, bind it, and listen on it: 28
29 sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
30 bind(sockfd, res->ai_addr, res->ai_addrlen);
31 listen(sockfd, BACKLOG);
32
33 // now accept an incoming connection: 34
35 addr_size = sizeof their_addr;
36 new_fd = accept(sockfd, (struct sockaddr *)&their_addr, &addr_size);
37
38 // ready to communicate on socket descriptor new_fd!
39 .
40 .
41 .
```

## send() and recv()

```c
int send(int sockfd, const void *msg, int len, int flags);
int recv(int sockfd, void *buf, int len, int flags);
// stream sockets use send() and recv() to send and receive data.
```

## sendto() and recvfrom()

Since datagram sockets aren’t connected to a remote host, guess which piece of information we need to give before we send a packet? That’s right! The destination address!

```c
int sendto(int sockfd, const void *buf, int len, int flags, const struct sockaddr *dest_addr, int addrlen);
int recvfrom(int sockfd, void *buf, int len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
```

## close() and shutdown()

```c
int close(int sockfd);
// free socket descriptor sockfd.
int shutdown(int sockfd, int how);
// shutdown() is used to close a socket in a graceful way.
// how is one of the following:
// SHUT_RD   = 0,  // shut down the reading side of the socket
// SHUT_WR   = 1,  // shut down the writing side of the socket
// SHUT_RDWR = 2   // shut down both sides of the socket
```

## getpeername() and gethostname()

```c
#include <sys/types.h>
#include <sys/socket.h>

int getpeername(int sockfd, struct sockaddr *addr, int *addrlen);
// getpeername() returns the address of the remote host to which the socket is connected.
```

```c
#include <unistd.h>

int gethostname(char *hostname, size_t len);
// gethostname() returns the hostname of the machine.
```


# ch6

C-S pairts: telnet/telnetd, ftp/ftpd, Chrome/Nginx etc. Often, there will only be one server on a machine, and that server will handle multiple cliens use fork(). Server wailt a connection, accept() it, and fork() a child process to handle it.

## A Simple Stream Server

```c
/*
** server.c -- a stream socket server demo.
*/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <signal.h>

#define PORT "3490"  // the port users will be connecting to

#define BACKLOG 10 // how many pending connections queue will hold

void sigchld_handler(int s)
{   
    // waitpid() might overwrite errno, so we save and restore it:
    int saved_errno = errno;

    while(waitpid(-1, NULL, WNOHANG) > 0);

    errno = saved_errno;
}

// get sockaddr, IPv4 or IPv6:
void *get_in_addr(struct sockaddr *sa)
{
    if (sa->sa_family == AF_INET) {
        return &(((struct sockaddr_in*)sa)->sin_addr);
    }

    return &(((struct sockaddr_in6*)sa)->sin6_addr);
}

int main(void)
{
    int sockfd, new_fd;  // listen on sockfd, new connection on new_fd
    struct addrinfo hints, *servinfo, *p;
    struct sockaddr_storage their_addr; // connector's address information
    socklen_t sin_size;
    struct sigaction sa;
    int yes=1;
    char s[INET6_ADDRSTRLEN];
    int rv;

    memset(&hints, 0, sizeof hints);
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_flags = AI_PASSIVE; // use my IP

    if ((rv = getaddrinfo(NULL, PORT, &hints, &servinfo)) != 0) {
        fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv));
        return 1;
    }

    // loop through all the results and bind to the first we can
    for(p = servinfo; p != NULL; p = p->ai_next) {
        if ((sockfd = socket(p->ai_family, p->ai_socktype,
                p->ai_protocol)) == -1) {
            perror("server: socket");
            continue;
        }

        if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes,
                sizeof(int)) == -1) {
            perror("setsockopt");
            exit(1);
        }

        if (bind(sockfd, p->ai_addr, p->ai_addrlen) == -1) {
            close(sockfd);
            perror("server: bind");
            continue;
        }

        break;
    }

    freeaddrinfo(servinfo); // all done with this structure

    if (p == NULL)  {
        fprintf(stderr, "server: failed to bind\n");
        exit(1);
    }

    if (listen(sockfd, BACKLOG) == -1) {
        perror("listen");
        exit(1);
    }

    sa.sa_handler = sigchld_handler; // reap all dead processes
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = SA_RESTART;
    if (sigaction(SIGCHLD, &sa, NULL) == -1) {
        perror("sigaction");
        exit(1);
    }

    printf("server: waiting for connections...\n");

    while(1) {  // main accept() loop
        sin_size = sizeof their_addr;
        new_fd = accept(sockfd, (struct sockaddr *)&their_addr, &sin_size);
        if (new_fd == -1) {
            perror("accept");
            continue;
        }

        inet_ntop(their_addr.ss_family,
            get_in_addr((struct sockaddr *)&their_addr),
            s, sizeof s);
        printf("server: got connection from %s\n", s);
        if (!fork()) { // this is the child process
            close(sockfd); // child doesn't need the listener
            if (send(new_fd, "Hello, world!\n", 13, 0) == -1)
                perror("send");
            close(new_fd);
            exit(0);
        }
        close(new_fd);  // parent doesn't need this
    }

    return 0;
}
```

## A Simple Stream Client

```c
/*
** client.c -- a stream socket client demo
*/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>

#include <arpa/inet.h>

#define PORT "3490"  // the port client will be connecting to

#define MAXDATASIZE 100 // max number of bytes we can get at once

// get sockaddr, IPv4 or IPv6:
void *get_in_addr(struct sockaddr *sa)
{
    if (sa->sa_family == AF_INET) {
        return &(((struct sockaddr_in*)sa)->sin_addr);
    }

    return &(((struct sockaddr_in6*)sa)->sin6_addr);
}

int main(int argc, char *argv[])
{
    int sockfd, numbytes;  
    char buf[MAXDATASIZE];
    struct addrinfo hints, *servinfo, *p;
    int rv;
    char s[INET6_ADDRSTRLEN];

    if (argc != 2) {
        fprintf(stderr, "usage: client hostname\n");
        exit(1);
    }
    
    memset(&hints, 0, sizeof hints);
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    
    if ((rv = getaddrinfo(argv[1], PORT, &hints, &servinfo)) != 0) {
        fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv));
        return 1;
    }
    
    // loop through all the results and connect to the first we can
    for(p = servinfo; p != NULL; p = p->ai_next) {
        if ((sockfd = socket(p->ai_family, p->ai_socktype,
                p->ai_protocol)) == -1) {
            perror("client: socket");
            continue;
        }
        
        if (connect(sockfd, p->ai_addr, p->ai_addrlen) == -1) {
            close(sockfd);
            perror("client: connect");
            continue;
        }
        
        break;
    }
    
    if (p == NULL) {
        fprintf(stderr, "client: failed to connect\n");
        return 2;
    }
    
    inet_ntop(p->ai_family, get_in_addr((struct sockaddr *)p->ai_addr),
            s, sizeof s);
    printf("client: connecting to %s\n", s);
    
    freeaddrinfo(servinfo); // all done with this structure
    
    if ((numbytes = recv(sockfd, buf, MAXDATASIZE-1, 0)) == -1) {
        perror("recv");
        exit(1);
    }

    buf[numbytes] = '\0';
    printf("client: received '%s'\n", buf);

    close(sockfd);

    return 0;
}
```

## Datagram Sockets

talker.c and listener.c

```c
/*
** listener.c -- a datagram sockets "server" demo
*/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>

#include <arpa/inet.h>

#define MYPORT "4950"  // the port users will be connecting to

#define MAXBUFLEN 100

// get sockaddr, IPv4 or IPv6:
void *get_in_addr(struct sockaddr *sa)
{
    if (sa->sa_family == AF_INET) {
        return &(((struct sockaddr_in*)sa)->sin_addr);
    }

    return &(((struct sockaddr_in6*)sa)->sin6_addr);
}

int main(void)
{
    int sockfd;
    struct addrinfo hints, *servinfo, *p;
    int rv;
    int numbytes;
    struct sockaddr_storage their_addr;
    char buf[MAXBUFLEN];
    socklen_t addr_len;
    char s[INET6_ADDRSTRLEN];

    memset(&hints, 0, sizeof hints);
    hints.ai_family = AF_INET6; // set to AF_INET to use IPv4
    hints.ai_socktype = SOCK_DGRAM;
    hints.ai_flags = AI_PASSIVE; // use my IP

    if ((rv = getaddrinfo(NULL, MYPORT, &hints, &servinfo)) != 0) {
        fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv));
        return 1;
    }

    // loop through all the results and bind to the first we can
    for(p = servinfo; p != NULL; p = p->ai_next) {
        if ((sockfd = socket(p->ai_family, p->ai_socktype,
                p->ai_protocol)) == -1) {
            perror("listener: socket");
            continue;
        }

        if (bind(sockfd, p->ai_addr, p->ai_addrlen) == -1) {
            close(sockfd);
            perror("listener: bind");
            continue;
        }

        break;
    }

    if (p == NULL) {
        fprintf(stderr, "listener: failed to bind socket\n");
        return 2;
    }

    freeaddrinfo(servinfo);

    printf("listener: waiting to recvfrom...\n");

    addr_len = sizeof their_addr;
    if ((numbytes = recvfrom(sockfd, buf, MAXBUFLEN-1 , 0,
            (struct sockaddr *)&their_addr, &addr_len)) == -1) {
        perror("recvfrom");
        exit(1);
    }

    printf("listener: got packet from %s\n",
            inet_ntop(their_addr.ss_family,
                get_in_addr((struct sockaddr *)&their_addr),
                s, sizeof s));
    printf("listener: packet is %d bytes long\n", numbytes);
    buf[numbytes] = '\0';
    printf("listener: packet contains \"%s\"\n", buf);

    close(sockfd);

    return 0;
}
```

```c
/*
** talker.c -- a datagram sockets "client" demo
*/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>

#include <arpa/inet.h>

#define SERVERPORT "4950" // the port users will be connecting to

int main(int argc, char *argv[])
{
    int sockfd;
    struct addrinfo hints, *servinfo, *p;
    int rv;
    int numbytes;

    if (argc != 3) {
        fprintf(stderr, "usage: talker hostname message\n");
        exit(1);
    }

    memset(&hints, 0, sizeof hints);
    hints.ai_family = AF_INET6; // set to AF_INET to use IPv4
    hints.ai_socktype = SOCK_DGRAM;

    if ((rv = getaddrinfo(argv[1], SERVERPORT, &hints, &servinfo)) != 0) {
        fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv));
        return 1;
    }

    // loop through all the results and make a socket
    for(p = servinfo; p != NULL; p = p->ai_next) {
        if ((sockfd = socket(p->ai_family, p->ai_socktype,
                p->ai_protocol)) == -1) {
            perror("talker: socket");
            continue;
        }

        break;
    }

    if (p == NULL) {
        fprintf(stderr, "talker: failed to create socket\n");
        return 2;
    }

    if ((numbytes = sendto(sockfd, argv[2], strlen(argv[2]), 0,
            p->ai_addr, p->ai_addrlen)) == -1) {
        perror("talker: sendto");
        exit(1);
    }

    freeaddrinfo(servinfo);

    printf("talker: sent %d bytes to %s\n", numbytes, argv[1]);
    close(sockfd);
    
    return 0;
}

```

talker calls connect() and specifies the listener's address. then talker can simply use send() and recv()


# ch7

## Blocking

Nothing here yet.


# \[Share]


# What to do after what to do

WUSANING August 17, 2019

作者：<https://www.douban.com/people/gninasuw/>

可能仅仅十年前的我们都很难想象到，二十一世纪直到今天的世界政治会在集体主义和民粹主义的道路上走得如此之远，并内在地成为了一种精神主流，在这个层面上这种变动就好比宗教改革分裂了天主教会却将新教根深蒂固植入人们血液。毫无疑问目前这条路上中国、中国人，首当其冲。

今天我们面对的是一个相比过去更加财大气粗、手段更精细、更善于伪装的大他者。在一种垄断的话语权力里，科学技术和资本也被收编或招安，成为巩固这个结构的道具。这个从历史唯物主义的信念出发的群体，却不断热衷并成功地消抹于自身不利的历史，从而架构出至高不容置疑的神圣权力，很自然地获得大量生物性的簇拥，但也从此陷入了一种彻底的历史虚无。

这种虚无也体现在了今天中文网络里不同立场的声音越发极端和难以交流，这两个月里所有有关HK游行的社交网络空间里（微博和微信公众号为主）的景象几乎惨不忍睹。那篇二十九年前《稳定压倒一切》的社论今天依然奏效。官方煽动民族主义情绪式的片面式宣传报道成功激起了自发的集体主义运动，民间媒体与自媒体已经习惯自我阉割和审查，而任何其他没有被算法自动屏蔽过滤的反主流话语都将遭受人肉的以正义为名义的羞辱谩骂，扣帽子式的集体批斗。在这个情形下，任何温和的交流探讨都已然成为不可能，从一个自由派的立场：我在提出改革和寻找与你更好共存的方案，而你一心只想让我消失，禁言，删号，乃至牢狱。而此时真正拥有家国天下格局的民族主义者早已被淹没在乌合之众统一的话术与呐喊狂欢中。

有时我顿感恍惚，在二战之后西方政治议题对象已经开始覆盖同性恋者、精神病人、罪犯，各种能想到的特殊群体人权的几十年后，今天我们这个猛然崛起的世界第二大经济体怎么却如同时空错位般开始一意孤行，再一次封闭起自己，并好似无视了一切二十世纪世界政治上出现过的惨痛悲剧和反思教训、普世价值。现在它更愿意注视自己，舔舐一个世纪前的民族伤口。但再退后一步看，新奥斯曼主义的土耳其、伊斯兰革命的伊朗、甚至make america great again的川普政府，又何尝不是这种保守主义复辟，在漫无边际的资本和消费主义、科学和民主的激励下不断往前冲的人类终于大面积地触碰到了虚无。军备大赛，星球大战，越做爱越要造反的五月风暴，中导条约，而科学匠人们能想象的终极除了对造物本身模仿的人工智能还能是什么呢。人类恐惧毁灭，比死亡更虚无的毁灭，恐惧意义缺席，所以要往反动的地方回去，时间的线性将被取消，即使回不到故乡田园牧歌的地方，也不能再僭越那些神的禁区。毁灭是不可能的，是底线。我们必须存在，这是这个繁复的星空下唯一的意义，让我们用雾霾和霓虹灯把这星空盖起来好吗，他们太耀眼，太本质了。回到母体的人们，紧闭着活在自己里面，不说无法抵达他者，甚至只是粗略地想象都是困难的。保守地去生活，不需要改变，就像没有尽头一样。也不要难民，因为我高贵的生活不想看见那些让我良心不安的任何存在。

在这样的时代轰鸣到你眼里的世界已经开始失真的时候，人甚至丢失了确认自我本质的能力，我们还如何进行精神独立的思考？而一个依然相信自由并相信美的价值的中国人现在该如何战斗，他/她还有可能激进并义无反顾地去爱上敌人、拆迁的推土机、垃圾场和高铁吗？

那些集体与国家主义者们往往透露着对男性气质与权力的崇拜，从而获得着摆脱自身命运局限的宏大感，他们就好像那些不解风情却胡乱臆断着女性心思的直男，渴望得到爱，但又恐惧爱。这甚至是这个大他者的一个影射，我们常常不解他们在恐惧的是什么，因为怎么也想不到他们恐惧的居然是爱，爱里面的无常，所以必须通过性别阶级符号隔离开来，保持对禁忌的依赖。因为真正的爱是先抵达彻底的孤独，是放掉你紧紧攥着的东西，放掉你的身体，放掉真实。如同一个人站在蹦极台前身体巨大的本能排斥，只有在这个时候你才有可能抵达爱，真正和世界联结在一起，这才是我认为巴迪欧语境里的坠入爱河。即使六十年代浩浩荡荡的嬉皮运动早已在西方都沦为无关痛痒的文化商品，左派集体陷入革命失败的忧郁之中，但今天在中国，我们依然还有很巨大奋斗的空间，绝不该那像在爱里受伤就摒弃爱的李莫愁。在我们连看乐队的夏天这种试图面向大众的综艺里的弹幕时都要心里暗暗骂傻逼的时候，我们已经沦为自己反对的对象，符号和标签的奴隶，离那个通透的爱、迷幻的爱也还有十万八千里。朋友，战斗很长，首先，不要放弃美和爱。

**8.12 更新**

上周五下午网路冲浪时一时兴起写了些粗略的近期感受和不成章的思考，不料变成爆款文一则的景象，周末无暇也无心应付回复，不得不把手机提醒关掉，方才翻看了评论转发。

引来这么多注意实非我本意，但是大字报我不反对的，也不甚介意自己被迫成为被群众审视对象，因为在今天任何公共空间的非一言堂式的讨论都已经显得弥足珍贵。即便有一个很明了的事实摆在那里：任何文字载体的输出都无法避免充斥着大量误读误解，无论转发晒傻逼嘲笑讥讽者，还是支持称赞感同身受者。我有意无意的笼统表述都增加了这个误读的空间，一是这几年越来越没有耐心对具体事物写展开细致讨论的文本，逻辑的缜密在不断自我怀疑解构和感受力的轰炸下不再举足轻重或轻易呈现，二是为了躲避算法的关键词审查，三是原本预想的读者只是语境思路相近的友邻圈子，无需赘言来将话说得周全。

因此可以说此文本出发点并非在讨论或指出政治层面的建设，一定要拎出一个主旨的话，不过是在对每个活生生的个体讲一个朴素的观点和呼吁，即在当下时代洪流里，不要轻易被话语权力裹挟，也不要对令自己失望的现状投降而变得犬儒。

至于文末提到的令人或激动或感觉陈词滥调、“自由派的软弱”的那个关键词“爱与美”也并非空泛而陈腐之辞。就今天的中文网络空间而言，交流变得越发不可能。在一个信息断层的封闭系统里，舆论被一个媒体架构出来的叙事彻底掌控，而这种叙事的情绪煽动性与其暗示的绝对正义性都令受其影响的个体无法再接受任何其他模式的叙事，并一神论式地将自己认为的真相奉为绝对标准，从而必须消灭任何与其相悖的叙事以保持自身的正当性。略有传媒常识的人应该都明白不同的叙事建构对于一个事件的阐释和呈现将天差地别，而我国今天媒体叙事的单一程度不言而喻，他正打着一场不战而胜的媒体战，自由度连最近的胡温时代都已远远不及。相反，一个多元论者要面对的是一个更复杂得多的世界，他遭遇的问题也将更难解甚至无解，即使在他的叙事体系中甚至包含着那个官方版本，但这无妨那个极端化的集体机器还是要将其同化或毁灭。面对这些无差别的毁灭性的铁拳，多元论者无论出于自我保全还是对公共正义的追寻都最终无法避免卷入这场战斗。即使从方法论的角度出发，对付比你拳头和嗓门都更大得多的敌人时，与其拼拳拼鲁莽都绝不是明智的选择，更具可能性的方式是将敌人拖入你的世界观和眼界中，以共情的能力将符号化的世界边界融化，而这种法术般的迷幻力量往往就是我们表述为爱的那个道不明的隐喻。这场战役的最终胜利不是为了消灭“敌人”，恰恰是为了说明这个世上在终极意义上没有胜利可言。这并不是什么新颖的言说或战略方向，但对于每一个相信希望却在压力和日复一日的绝望中正在变得孱弱的生命而言，不断告诫、激励和确认自我并确认爱的力量是绝对必要的。这个战斗或许只是从感化掉你父母或朋友一个他们陈年的偏见开始，即便跨出这一步对许多人可能都已经很艰难。不要放弃爱、艺术的同时，也不要放弃语言，毕竟这几乎已经是人类最高效的交流工具。

那些偏执地坚信那套叙事的正当性，或者坚信是自己的独立思考和理性将自己导向了无条件坚定拥护国家主义的人们，他们是几乎不需要这些反复的自我确认的，因为他们有一种自动的排他性。对于一些事物绝对正当性的预设令其世界观坚固不可动摇，一切与其拥护的观点相左的论据和事实都被其顺理成章地过滤或嗤之以鼻，请问当你无视那些活生生血淋淋的新闻而抛出一句“政治远比你想象得复杂”时，到底谁才是纸上谈兵、泛泛空想之人？这些绝对正当性事物的一些具象的话术表现包括了经济/军事必然凌驾于文化之上，社会稳定是一切的基础，发展才是亘古不变的硬道理等等，每一个枝节都需要大量的时间精力去议论展开，我也只能抛砖引玉，可悲但也值得奋斗的是现在的话语权力下这种议论的空间都需要谨小慎微又无所畏惧地争取。我个人从未忽视过这个国家和人民，特别是近代史，的苦难，几度掉过眼泪，甚至对于这个政党所经历过的辉煌和挫败我敢言自己比99%在网上叫嚣着爱党爱国者更清楚，知晓一切既得都来之不易，我自然也绝非个例，但这不能成为我们保守沉默的理由。

另外，任何左右阶级派别主义等词汇的征用无非是为了更方便有效地阐述部分问题，而不该用来对于任何个体简单粗暴地扣帽子定性以简化问题，每个个体都是不该被随意物化的生命。


# Truman is everywhere

> <https://www.qdaily.com/articles/64605.html>

> 世界总会在某个瞬间露出马脚。

人对「不能做自己」有着漫长的恐惧史。

270 多年前，卢梭在《论人类不平等的起源》里指出人的异化处境：人类脱离自己原有的天性，在社会的形成过程中逐渐扭曲变形，原本平等的人类动物界，因为私有制的产生而逐渐充斥着不平等和压迫。因为技术的发展，人得以过上舒适的生活，而人一旦习惯了一种舒适，就再也不觉得它有多舒适了，相反没有它就会觉得难受。于是，人类无止境地追求更好的享受开始了，人也在这种追求中，从自然动物被「异化」成「啥都要拿来比较」的文明人。

后来黑格尔和马克思把「异化」的概念发扬光大，人的异化逐渐变成这样一种状态：原本人类发明了各种制度、工具、准则，但在使用的过程中，人逐渐背离了自身的目的，开始改变自己去服务那些制度和工具。而那些工具，则反客为主开始控制人类：

原本「考试」（客体）是为了检测「知识」（主体）掌握情况，后来人为了考试而去钻研如何考试。

原本「钱」是为了「物品」的流通和交换，后来人不惜消耗自己的健康和生命去赚取更多的钱，再把赚到的钱作为数字（可能到死都）囤在账户里。

原本「道德」是为了让「人」在公共领域里变得更向好，后来道德变成网络权利，人为了“维护道德”肆意辱骂和羞辱别人。

原本「知识」是为了让人更清楚地了解世界，用知识来更好地指导生活，知识付费下的如今，人们仅仅为了有“获取知识感”而“获取”知识。

原本「时钟」是为了让人更好的掌控时间，如今人们强迫自己遵守很多违反自然法则被时钟奴役（比如没有任何事需要处理仍然得等到下班时间的打卡，比如为了即时回复老板和甲方 24 小时微信待命就算凌晨也不耽搁）。

……

![img](http://img.qdaily.com/uploads/201909262204260cXeQ6t51mslCD3E.jpg-WebpWebW640)

异化代表一个人面对他人和外界时的一种「无力感」和「无归属感」，一个异化的人没法积极地去影响世界，而是作为一个被动受到外界影响的客体。放在如今自称“韭菜、肥宅、咸鱼”的当代 996 年轻人身上，可以说异化感强烈了。

在社会生产力早已经远远足够保证人类吃喝拉撒的生存需求后，原本人类劳动是为了创造价值，获得快乐，如今没有多少年轻人觉得劳动是快乐的。原本工作是为了更好的生活，如今工作本身仅仅成为生活的手段。每天的 8 个小时都粘在一个 1mX1m 的狭小工位上，大多数时间里一只手粘在键盘上，另一只手粘在手机上，整个思维都在工作报告和 todolist 上来回滚动，直到当天的工时耗尽。年轻人工作是为了未来可以不工作，不工作的周末和假期也仅仅作为进入下一周工作的充电阶段，人在工作中成为耗材。就像马尔库塞讲的：“人们并不是在过自己的生活，而是在履行某种事先确认的功能…占据极大部分个体生活时间的劳动时间是痛苦的时间，因为异化劳动毫无满足感，是对快乐原则的否定…个体绝大部分时间从事着同自己的机能和需要根本不协调的活动。”

每个时代的人都需要一个出口来为糟糕的生活做出解释，上世纪五六十年代的美国知识分子间就已经很流行用「异化」来批判社会，劳动带来的异化、消费带来的异化、技术带来的异化。即便到了现在，稍微盘点一下我们身边那些异化都会让人产生无尽的脱力感：

让·鲍德里亚在《消费社会》里系统地描绘了一个全面商品化的世界。在以女性为主要消费主体的如今，女性看似有诸多选择，选择爱马仕包、选择斩男色口红、选择山本耀司裙裤，选择加 pro 后缀的苹果大手机，但是不能选择「过气的」奶奶辈碎花裙，不能选择不选择手机，不能选择不选择护肤品。如今“女人之所以进行自我消费是因为她跟自己的关系是由符号和表达维持的……女人对自己的眼光，对自己的皮肤都没有自信，属于她自己的东西丝毫不能给她带来自信”。在商业社会里，人对物品的需求不是对物品本身的需求，而是对差异的需求。在以往，人和人建立关系能让整体变得更加丰富，而现在，每个社会关系都在增添个体的不足，因为任何拥有的东西都在跟别人的比较里被相对化了，不是你的耳机更贵就是他的鞋更限量款，不是你看的书更有深度就是他的追偶像更酷更小众。这也导致了，在无处没有鄙视链的社交网络里，每个「被异化」的个人，都在变成变成反对自己的人，我这鼻子不好看得修，我这衣服过时了得换，我这肉太松了得办卡练练，我这头发太少了得植。于是所有人都永远处于面向未来的“自我提升”中，当下永远不够好，当下永远准备迭代升级、永远充满焦虑。而这种升级，几乎又特指消费升级。追求苗条/肌肉/健康无非是一种特定的消费观，当代的审美也更多的是基于消费的审美，穿什么衣服、买什么书、用什么眼影、喷什么香水。社交网络上的独立女性，翻译过来就是有消费能力的女性，所谓的女子力，也差不多就等同于买买买的能力。在这点上，广告和消费温和又隐蔽地压制了「自我」，「我」在其中不断地被商家制造出新的需求，再不断地被满足，在自由选择的假象里，永远处于被满足的前置状态。结果就是，社会整体变得更好了，东西变得更精美更有格调更有内涵，但是人越来越能感受到自己的「不足」，大多数的「普通人」不是作为主体去参与其中，而是作为被动接受的客体，被时代（无数改变世界的明星企业家的新闻、社交网络的精致生活）裹挟着滚滚向前。

![img](http://img.qdaily.com/uploads/201909262204439Mn1CFNhSalRT7V6.jpg-WebpWebW640)

居伊·德波在《[景观社会](http://www.qdaily.com/articles/59766.html)》里认为，以视觉生活为主导的景观社会会妨碍人们去认识这个世界带来的苦难，一切都像一部供人消遣的电视剧。对苦难的忽视和娱乐化，又会进一步阻止人们去严肃思考改进社会的任何可能，麻木不仁又乐此不疲，景观让真实世界和影像分离，人们总是注视着下一步会发生什么但从来不行动，所有人都成为观众。而观众的异化在于，他对从社交网络里的表演期待得越多，他生活得就越少，他对整个社会环境提供的影像认同得越多，他对自己的生活和真实欲望就理解得越少。在某种程度上，视觉化的环境塑造了虚假个性，而把真正的主体性交给了技术、社区法规和营销广告。

尼尔·波兹曼则担忧对技术的过度依赖会导致人的异化。他认为“技术造就的文化将是没有道德根基的文化，它将瓦解人的精神活动和社会关系，于是人生价值将不复存在。”如今我们生活在一个被技术包围的世界，技术几乎霸占了人和人之间交流的媒介。人和人不再直接接触：说话通过通讯设备，向店家买东西通过网购，帮助别人通过网络捐款。起初是人掌握技术，人使用工具来实现目的，如今技术开始为人下定义，人被点赞量、粉丝数、留言量来指导行为，被动地观看数量庞大的「跟自己没关系，看了也不会做什么行动，不看也不会造成任何影响」的信息。在技术的裹挟下，人不可避免地成为「用户」，只能照着别人定下的平台规则来行动。

而这种技术带来的异化又是无解的。哈贝马斯认为，人要成为自主的人、要决定自己的生活，在「技术」上是不可能的。因为技术担负着扩大舒适生活和提高劳动生产率的功能，这种功能的完成势必导致人对技术设备的屈从。

这些异化的处境很容易让人想到一切都被安排好了的「楚门的世界」：你的一举一动看似都是自由的选择，但其实你的生活被自上而下地脚本化了，你周围的每一个人都被动地在某种程度上成为演员，扮演一个符号，一个标签，一段流量，一个商品，一个社会代码编程的 NPC。为了证明我们其实就是生活在楚门的世界里，我们找了 23 个证据。

（我们无意于阴谋化一切，仅仅想通过一些「楚门化」的现象，温柔地提醒大家对我们身处的世界保持一种警觉。）

### 人人都是演员：原本身份是用来描述人的，如今身份成为人的行为指导，人人都在表演身份

1.现实世界里医生是那种能治病救人的人，在这里，能够熟练背诵“上火、抑郁症、你这个病很严重”台词的，就是医生。

2.在社交网络这个世界里，所有的程序员都是穿格子衫冲锋衣秃头直男，并狂热地爱着机械键盘。

3.大概是由于演员人手不够的缘故，所以看起来所有的偶像都长着同一张脸。

4.除了角色，人和人之间没有区别。渣男=渣男，优衣库男=优衣库男，咪蒙粉丝=咪蒙粉丝，抖音用户=抖音用户，三和大神=三和大神。

5.所有人都在扮演你的老师。

6.在你的角色里，当大家给你唱生日歌，你必须老老实实地坐着摆出一副开心的样子。

![img](http://img.qdaily.com/uploads/20190926220509fzpX93NGcO5gkA4J.jpg-WebpWebW640)

### 你出不去：你被包裹在这样一个世界里，这里的一切都是有人安排好了的

7.“我们要供房，我们要供车，怎么丢得下？”怎么去斐济。

8.接到陌生电话，对方一字不差地报出你的名字。

9.新装了 5 个应用软件，每个 app 都猜得出你喜欢啥，和你可能认识谁。

11.刚跟朋友聊天聊了发际线困扰，打开社交网络就迎面推送来植发广告。

12.网络世界，表达即表演，但不表达他们就不存在了。

\13. 社会由科技和商业推进，但很显然，你在的这个世界是由名词和动词推进的，这些名词和动词主要由他们来写。

14.你不知道他们是谁，但他们永远知道你是谁。

15.根据相关法律法规，不跟人比就活不下去。

### 世界在这瞬间露出马脚：生活中时不时会出现很多反自然的 bug

16.USB 接口第一次一定无法插入，把接口旋转 180 度后仍然无法插入，再旋转 180 度又可以插入了。

17.当你上班堵车，经过一个点后突然就不堵了，前几分钟明明密密麻麻都是车流，过了这个点就全消失不见。因为前面的贴片动画还没加载好，导演需要一场堵车作为过场动画。

18.导演经常会把你拍得不好的片段剪掉，这就是为什么你拿起手机点开屏幕的时候，常常会突然忘了自己拿手机是要做什么了。

19.突然之间你觉得这个场景曾经经历过，但无论如何想不起更多线索，仿佛是技术在格式化你的存档剧情时没清除干净。

20.当你认识了一个新名词，接下来的几天里，这个词一定会重复出现，仿佛剧组刚过完购物节添加了新道具。

21.你总能看到两个完全没关系的人长得一模一样。当然，他们不会向你承认字自己是一人扮演多角。

![img](http://img.qdaily.com/uploads/201909262205336wmgoVv03apuPfqy.jpg-WebpWebW640)

### 整个演出最终都是为了卖广告：人和物品一样都成为商品

22.街上有随处可见穿着巨大 logo 衣服的人，作为人形移动广告奔走相告。

23.你的注意力是他们要卖的商品，你看什么他们就卖什么。

***

题图、插图来自：林小妖

原文出自好奇心日报，链接见上文。


# Way2outer

Update AT 2022-10-25

## 服务搭建

这部分略过, 如有需求请邮件联系`i#junyangz.com`(将#换成@)

## 使用手册

1. 下载最新v2ray客户端(<https://github.com/v2ray/v2ray-core/releases>) 根据系统下载对应版本。如果是Windows点击[链接](https://github.com/v2fly/v2ray-core/releases/download/v4.45.2/v2ray-windows-64.zip)下载, 备份[下载地址](https://drive.junyangz.com/share/trV4bYvd), 密码为当前页面标题
2. 解压下载的压缩包至特定位置
3. 下载我提供的配置文件[`config.json`](https://asset.junyangz.com/public/config.json)到解压后的目录下(P.S.右键另存为，选择覆盖默认的`config.json`文件)
4. 点击`wv2ray.exe`(后台运行）或者`v2ray.exe`（前台运行）启动， 使用`wv2ray.exe`启动后请在任务管理器里结束任务退出。

其他的一些图形化工具：

1. Clash <https://github.com/Fndroid/clash_for_windows_pkg/releases> 备份[下载地址](https://drive.junyangz.com/share/dLWdwSAE), 密码为当前页面标题
2. v2rayN <https://github.com/2dust/v2rayN/releases>

{% hint style="info" %}
**Clash配置文件**导入：Profiles -> Download from a URL: `aHR0cHM6Ly9hc3NldC5qdW55YW5nei5jb20vcC9wdWJsaWMueW1s` (BASE64 DECODE)
{% endhint %}

## 浏览器使用

### Chrome浏览器

方法一：

让Chrome使用Socks5代理 常规设置下,貌似Chrome只能采用和IE一样的代理设置.其实也是可以支持socks5代理的.随意创建一枚Chrome的快捷方式,右键点击,打开”属性”，在”目标”后加上 `--proxy-server="SOCKS5://127.0.0.1:1080"` 注意最前面要有一个空格，1080则是socks5代理端口。修改完以后，如果有Chrome的实例在运行，务必保证所有实例退出以后双击此快捷方式方能生效

方法二： 下载这个插件拖到Chrome安装（下方的 CRX 安装包可用于 Chromium. 关于下载后如何安装，您可以谷歌一下，或者看看[这里](https://www.jianshu.com/p/bb51dc91b93a)怎么说。） <https://github.com/FelisCatus/SwitchyOmega/releases/download/v2.5.20/SwitchyOmega_Chromium.crx> 后面按照教程添加代理服务器（协议Protocol: socks5, 地址Server: 127.0.0.1 端口Port: 1080) 即可。[常见问题](https://github.com/FelisCatus/SwitchyOmega/wiki/%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98)

### Firefox

点菜单 -> 选项 -> 高级 -> 设置 -> 手动代理设置，在 SOCKS Host 填上 127.0.0.1，后面的 Port 填 1080，再勾上使用 SOCKS v5 时代理 DNS (这个勾选项在旧的版本里叫做远程 DNS)

如果使用的是其它的浏览器，请自行在网上搜一下怎么设置 SOCKS 代理。


# 未来十五年

本书前几章节基于当前世界的发胀状况主要分析未来几十年来人类社会将会遇到的各种灾难和挑战，整理来说是十分具有悲观的感情色彩的，这些预言或将会在接下来的社会发展中应验或和玛雅预言的那样在万众关注的情况下证伪之后被世人慢慢的遗忘，但人类社会的发展离不开这些思想的前行者，他们当下提出的问题是值得我们去思考和行动的。（事实证明在人类能够提前预测出这些灾难性的结局时才能有所行动，防微杜渐）

书的目录章节

* 今日世界的狂潮
* 对全球危机的归因
* 愤怒的爆发
* 美好的世界

我个人觉得这本书最有价值的是最后一章，对美好世界的建立所提出的各项建议和行动策略，所以对此做下详细的读书笔记以提醒自己。

## 最后一章 1 从自己做起

一如既往，重大的集体变革发生总是从个人的变化开始的：从自己做起，在世界仍适宜人类居住的时候采取行动。当每个人开始改变自己时，本身就是在改变世界。

仁慈和无私，而非世俗的的欢愉，为自己和后人的生活赋予一种意义。

认清世间万物终将走向死亡的，生命是很奇妙的存在，向死而生的觉悟，始终捍卫我们的价值观，其中最重要的是对自由、民主的向往和坚持。

在面对重大危机时总能奏效--“只有过最高尚的生活才能拯救世界”

对他人充满爱是一个人幸福的关键。意味着我们必须意识到采取行动的必要性和紧迫性。头脑清醒的促进利他主义的发展。

首先要充分的感受、控制并逐渐消化愤怒的情绪，学会与它保持距离并反思，控制自己的情绪，然后才能实现良好的自我控制。

1. **意识到个体的死亡的必然性。**

开始设想这些场景勇敢的迈出第一步，万物之始都建立在赋予死亡一定意义的基础上。我们应牢记生命的唯一性和每时每刻的独特性，意识到成为更好的自己的必要性。

> 我们应该把当下的每一刻都**当成生命的最后一刻**，并保持着这份**神圣的愤怒**去反对任何**贬低自我，侮辱自我，限制自我和吞没自我**的事物。我们应**竭尽全力**在这个世界留下**绚丽的一笔**，因为我们也曾为了使这个世界变得**更加美好**而做出**自己的努力**。

1. **尊重自己，认真对待自己。**

每个人存在于这个世界的时间如此短暂，我们必须分秒必争、物尽其用。并不意味着我们不能留给自己任何娱乐和休息的时间。若想拥有一个充实的人生，我们需要保证身体和心灵的健康，不去中无意义的事情。

因此，对每个目标都应该有较高的要求，这些目标需要有存在的必要性，不能一股脑的都做，无论何时都应该保持自己的独特性。每个人都可能具备某种形式的天赋和能力，我们要挖掘出这种天赋并将其发扬光大。找到每个人独有的天赋是成就人生的重要前提之一。

1. **找到自己不变的特质。**

社会多元，但依然存在某些不变的事物，即那些每个人都应该珍视的价值观，它们应高于一切事物。是无论发生什么都会坚守的信念。这种价值观会为每个人营造一个心理舒适区，在这个舒适区内我们会觉得轻松自在，在这种价值观的指导下，人们永远不会有违背自我意志的感觉。

对我们来说，最重要的是去挖掘内心深处的信念并用自己的方式呈现出来，让他人意识到这些信念的价值。

这些信念会变成我们身上最真实的东西，它们会成为我们不惜牺牲生命也要捍卫的事物。当有人想要摧毁我们坚守的信念时，我们的坚持就会显得非常重要。

1. **对他人的行为和未来世界形成自己的见解，不断提出质疑并积极修正错误。**

保持好奇心和警惕心，不能先入为主抱有成见，要不断的从不同的角度来分析和认识这个世界。

要达到这个目的，同理心非常重要（也就是说要具备设身处地为他人着想，不轻易下判断，体恤他人难处的能力）。同理心也意味着我们需要尝试去理解他人的价值观念，看到他人好的品质，理解他们追求的事物以及他们的行为方式。有时他人的行为在我们看来也许是自私的、不忠的甚至是充满敌意的。

需要意识到这个世界正在走向穷途末路，若我们不想如此，就需要江我们的愤怒用于抵制即将发生的事情，需要把怒火转换为行动的动力，怀着满腔的热情去成就更好的自己，同时更好地了解自我和他人的关系。

1. **个人的幸福建立在他人的幸福之上。**

需要注意到个体和世界的紧密联系，个人的不幸往往源于每个个体在面对他人的不幸时熟视无睹或委曲求全的态度。若我们不能乐人之乐，忧人之忧，我们也终将一事无成，尤其当我们对后代也秉持这种利他主义的精神时，也是在为自己谋求福利。

积极的为他人着想都对自己有益处，我们才会欣然接受利他主义。帮助他人，尤其是为后代尽一份力，对我们自己来说也是一种恩惠，对我们每个人都是有利的。

只有所有人都意识到以上的事实，我们才能真正事项从追求个体的自由到帮助他人实现自由的转变，也只有这样我们才能避免使自己的满腔怒火变成暴力行动。

这种转变是人类文明得以延续的前提条件，这一转变实现的必须依靠践行利他主义，而非强加于人的方式。这应是自发的，被深刻认知和理解的，无论从理智和情感角度，都是每个人内心深处真心希望的结果。

1. **做好同时过上或相继过上多重生活的准备。**

拥抱变化，要有规划我们多重生活的准备以及付出实践的勇气，尽可能的做出新颖的、有操作性的、符合自己特性的人生规划。通过不同的追求自我实现的方式，塑造出不同的人生。

1. **随时做好应对危机、威胁、批评、失望和失败的准备。**

奋起反击的勇气，失败中汲取教训，永远不要让自己丧失与屈辱、挫败和混乱斗争的勇气和动力。帮助他人建立独立判断的能力，时刻尊重他人的意见和他们选择的生活方式，即使这可能会危及自己的利益。这才是真正的利他主义。

> 我们要学会在悲伤中存活下来，不要因为自己是一场自然灾难、一场事故或一场恐怖袭击的幸存者而有负罪感，不要因为一时的情感失败或事业受挫而一蹶不振。一切的挫败都是为了实现最终的目的：成就自我。

1. **一切皆有可能。**

> 对一切看似不可能的事都要抱有一线希望，按照自己的想法付诸实践。任何形式的设想，不论它被证实有多遥不可以，我们都不能轻易放弃（除非有不可辩驳的科学论证，或是不符合道德或法律规范）。
>
> 要知道，当我们面对两件看似无法实现的事，其中一件事往往有可能促成另一件事成为现实。
>
> 我们要准备好不断学习并改变自己，只有这样我们才能真正实现自己的人生规划.

1. **怀着谦卑之心，多听取他人的意见，理智地分析现状，更好地践行对自我实现具有重要意义的人生规划。**

   人生规划要尽可能的具体、大胆并且具有一定的现实意义。
2. **最后，时刻准备为改变世界而行动。**

   > 鉴于上文所述的内容，我们绝不应任由自己变成“屈服的申诉者”，每天满足于抱怨世道的不公，却从不想着为了改变自己或是让世界变得更加美好而努力，这样的人把自己当作一个无力的旁观者，事实上从未为改变现状做出丝毫努力。
   >
   > 我们需要认清一个事实，一旦这个世界陷入危机，所有人的人生规划都会变成无稽之谈，因此从现在开始我们应该为了改变世界拼尽全力。
   >
   > 有鉴于此，在做出努力的同时，我们应谨记切勿以追逐财富或功名作为经世立身的准则，我们应有限地参与各个政党或组织的活动，同时也要避免在政治上出现独裁的局面。
   >
   > 一旦我们对世界有了一个全面的认识，意识到我们需要从当下做起来改变世界，那么现在需要做的就是付诸行动。当我们向这个世界施以善意，并让后代传承这种行为，这个世界方能改变。只有汇集数十亿具有利他主义精神的“自我”才能改变这个世界。这并不意味着我们无须制订一个改变历史进程的全球性规划，也不意味着我们无须践行这个规划。就如播种一样，除了投放种子，还需要制订一个系统的灌溉计划。
   >
   > 最初可能只有几千人，然后是数百万人，很快就会有数十亿人明白这个道理。这些人就是给我们带来希望的星星之火。他们对这个世界有清晰的认识，并选择成为自己希望的样子，他们也懂得一切必须以利他主义为前提。这些人可能是教师、医生、农民、干部、护士、企业家，也可能是普通的工人、学生或是从事其他职业的人。他们在以不同的方式看待这个世界的同时，也在不断地问自己一个问题，这个问题也可作为我们前文的一个总结：“为了世人的幸福，我可以做些什么？”他们终将找到这个问题的答案，到那时人们将会收获无限的喜悦和幸福。他们的行为会慢慢引起他人的注意，他们会颠覆一个旧世界，就像当初资本主义推翻了封建主义一样，到那时我们将真正迎来一个美好的世界。

对自己和世界有着清晰的认识，并努力的成为自己希望的样子，这个世界将会更加的美好。

## 最后一章 2 为世界行动起来

> 在追求自我实现的过程中，人们会不断有新收获，在新的发现中不断提出新质疑，每个人都可以去战斗、斗争、反思，思考如何在前人的理论和经验的基础上提出一个更加具体有效的方案。

1. 在教育大纲和法律条文中倡导利他主义精神，要求人们学会宽容与正直。
2. 以联合国大会为核心成立以下3个机构：
   1. 反映当今世界局势的安全理事会。
   2. 组建一个新议会，汇集来自全球各地30岁以下的年轻人，听取他们对影响后代福祉的国际决策的看法。这样一个组织可以使用多种统计形式和预测方式，也可以获得全球所有的公共数据。它将有权要求安全理事会听取他们的意见，并享有协商的特权。
   3. 组建一个致力于环境保护的国际法庭，推行国际协议及多种法案，以人道主义为准则，明确当代人肩负的使命。
3. 与一切可能引发全球冲突的潜在威胁做斗争。
4. 加强法制建设，强化司法对暴力行为的震慑力，尤其是对妇女和儿童的暴力行为。
5. 协调全球经济发展。
6. 在区块链的基础上发行一种世界货币。
7. 在全球范围内，制定统一的土地财产保护制度。
8. 设立一个全球积极经济基金会，促进利他主义的发展，鼓励人们积极加入造福后代的活动中。
9. 在全世界范围内推动对科学技术的应用。
10. 最后，在客观数据的基础上评价企业、城市、地区、国家和整个世界的发展。

后文作者还对法国（作者所属的国家）提出了10条建议。

> 现在，已经到了决定人类命运的紧要关头，只有全人类联合起来，世界才有希望，今天仍有许多人持有这种信念。任何人都不应该再假装无视我们可以预见的一切。**话尽于此，无须多言。**


# Quote

## zh\_CN

1. > 不积跬步，无以至千里。不积小流，无以成江海。 《劝学篇》—— 荀子
2. > 菩提本无树，明镜亦非台，本来无一物，何处惹尘埃。 《六祖坛经》 —— 惠能
3. > 天之道，损有余而补不足；人之道则不然，损不足以奉有余。民之饥，以其上食税之多；民之轻死，以其上求生之厚；民不畏死，奈何以死惧之。 —— 老子
4. > 世间好物不坚牢，彩云易散琉璃脆。

## en\_US

1. > "Be curious. Read widely. Try new things." – Aaron Swartz
2. > “We live on a planet well able to provide a decent life for every soul on it, which is all ninety-nine of a hundred human beings ask. Why in the world can’t we have it?” – Jack Finney, 1970
3. > “The price of reliability is the pursuit of the utmost simplicity.” – C.A.R. Hoare, Turing Award lecture
4. > "The world is everything that is the case." – Ludwig Wittgenstein, Tractatus Logico-Philosophicus, 1922
5. > "All happy families are alike; each unhappy family is unhappy in its own way." – Leo Tolstoy, Anna Karenina, 1878
6. > "This above all: to thine own self be true, And it must follow, as the night the day, Thou canst not then be false to any man. Farewell, my blessing season this in thee!" – From a monologue delivered by the character Polonius in Act I Scene III of Hamlet by William Shakespeare.
7. > "You can fool some of the people all of the time, and all of the people some of the time, but you can not fool all of the people all of the time." – Abraham Lincoln, 1856

## 王尔德

1. 我认为，上帝造人有点过高估计了自己的能力。（I think that God, in creating man, somewhat overestimated his ability.）
2. 永远原谅敌人，没有什么能比这个让他们更恼火。（Always forgive your enemies; nothing annoys them so much.）
3. 我什么都能抗拒，除了诱惑。（I can resist everything except temptation.）
4. 为了赢回我的青春，我什么都愿意做，除了锻炼、早起、做个对社会有用的人。（To win back my youth, there is nothing I wouldn't do - except take exercise, get up early, or be a useful member of the community.）
5. 年轻的时候，我以为钱是人生最重要的，现在我老了，我懂了，确实如此。（When I was young I thought that money was the most important thing in life; now that I am old I know that it is.）
6. 人生中只有一件事比被人议论更糟糕，那就是：无人议论你。（There is only one thing in life worse than being talked about, and that is not being talked about.）
7. 时装是一种让人无法忍受的丑陋，所以我们必须每六个月换一次。（Fashion is a form of ugliness so intolerable that we have to alter it every six months.）
8. 我们都在阴沟里，但有些人在仰望星空。（"We are all in the gutter but some of us are looking at the stars". ）


