Table of Contents

Introduction 1.1

A0-Mathmatics 1.2

Matrix_Calculus 1.2.1

attractor.py 1.2.2

A1-Computer_Science 1.3

Algorithm 1.3.1

Bit_Manipulation 1.3.1.1

Calculator_AST.py 1.3.1.2

DFS-BFS 1.3.1.3

Diffie_Hellman_Key_Exchange 1.3.1.4

Dynamic_Programming 1.3.1.5

Eight_Queen.py 1.3.1.6

FullBinaryTreeHeap.py 1.3.1.7

Trajetory_Search.py 1.3.1.8

bisect.cc 1.3.1.9

sort.cc 1.3.1.10

sort.py 1.3.1.11

sum 1.3.1.12

swap 1.3.1.13

Network 1.3.2

OSI 1.3.2.1

Programming 1.3.3

Front_End 1.3.3.1

HTML 1.3.3.1.1

split_panes_html 1.3.3.1.1.1

热力图 1.3.3.1.2

FrontEnd_html 1.3.3.1.2.1

百度api获取城市坐标.py 1.3.3.1.2.2

Language 1.3.3.2

C_or_C++ 1.3.3.2.1

Gramma 1.3.3.2.1.1

argv.cc 1.3.3.2.1.1.1

inherit.cc 1.3.3.2.1.1.2

STL 1.3.3.2.1.2

heap.cc 1.3.3.2.1.2.1

debug 1.3.3.2.1.3

gdb 1.3.3.2.1.3.1

pthread 1.3.3.2.1.4

Python 1.3.3.2.2

DevTools 1.3.3.2.2.1

crash_on_ipy.py 1.3.3.2.2.1.1

jupyter_hide_code.py 1.3.3.2.2.1.2

Example 1.3.3.2.2.2

sendmail 1.3.3.2.2.2.1

attachment.py 1.3.3.2.2.2.1.1

pic.py 1.3.3.2.2.2.1.2

qq_smtp.py 1.3.3.2.2.2.1.3

sendmail.py 1.3.3.2.2.2.1.4

argparse_example.py 1.3.3.2.2.2.2

create_epubook.py 1.3.3.2.2.2.3

ctypes 1.3.3.2.2.2.4

cython 1.3.3.2.2.2.5

keyboard-mouse 1.3.3.2.2.2.6

logging 1.3.3.2.2.2.7

ncurses_example.py 1.3.3.2.2.2.8

pandas 1.3.3.2.2.2.9

qrcode_example.py 1.3.3.2.2.2.10

socket 1.3.3.2.2.2.11

动态修改幻灯片内容 1.3.3.2.2.2.12

Regular_Expression 1.3.3.2.3

Software_Engineering 1.3.3.3

Design 1.3.3.3.1

Factory_Pattern 1.3.3.3.1.1

Learning 1.3.3.4

A2-Machine_Learning 1.4

Algorithms 1.4.1

NatrualLanguageProcessing 1.4.1.1

Attention 1.4.1.1.1

1_Nadaraya_Watson_Kernel_Regression 1.4.1.1.1.1

2_attention 1.4.1.1.1.2

transformer 1.4.1.1.2

word2vec 1.4.1.1.3

NeuralNetwork 1.4.1.2

nn_mnist.py 1.4.1.2.1

nn_mnist_static.py 1.4.1.2.2

ReinforcementLearning 1.4.1.3

Deep_Reinforcement_Learning 1.4.1.3.1

Basics 1.4.1.3.1.1

Q-Learning 1.4.1.3.1.1.1

Value_Based 1.4.1.3.1.1.2

Variance 1.4.1.3.1.1.3

Policy_Gradient 1.4.1.3.1.2

karpathy-pg-pong.py 1.4.1.3.1.2.1

kvfrans-ddpg.py 1.4.1.3.1.2.2

my_simple_PG.py 1.4.1.3.1.2.3

replay_buffer.py 1.4.1.3.1.2.4

Fine_Tuning 1.4.1.3.1.3

OU-process 1.4.1.3.1.4

cheatsheet.py 1.4.1.3.1.5

step_by_step_q_learning 1.4.1.3.1.6

bandit 1.4.1.3.2

Thinking 1.4.1.3.3

SupportVectorMachine 1.4.1.4

SMO_Simple 1.4.1.4.1

LDA.py 1.4.1.5

Optimizers 1.4.2

Problems 1.4.3

Imbalance 1.4.3.1

Tools 1.4.4

PyTorch 1.4.4.1

Example 1.4.4.1.1

SciKit-Learn 1.4.4.2

tsne.py 1.4.4.2.1

TensorFlow 1.4.4.3

TensorBoard 1.4.4.3.1

mnist_with_summaries.py 1.4.4.3.1.1

tensorboard_advanced.py 1.4.4.3.1.2

1-TensorFlow_Cheatsheet 1.4.4.3.2

Distributed_tensorflow.py 1.4.4.3.3

Duplicate_a_tensorflow_graph 1.4.4.3.4

A3-Unix-Like 1.5

Bash 1.5.1

bash的高级技巧 1.5.1.1

Server_Maintenance 1.5.2

scripts 1.5.2.1

backup_server_data.py 1.5.2.1.1

charles-adduser.sh 1.5.2.1.2

mv2cron.bash 1.5.2.1.3

sendlog.py 1.5.2.1.4

AUTO_UPGRADE 1.5.2.2

QUOTA 1.5.2.3

SECURITY 1.5.2.4

Tools 1.5.3

ffmpeg 1.5.3.1

git 1.5.3.2

lnav 1.5.3.3

netcat 1.5.3.4

tmux 1.5.3.5

Arch_Linux_Installation 1.5.4

macOS 1.5.5

A4-Control_Theory 1.6

OptimalControl_AA203 1.6.1

Lecture_01 1.6.1.1

Lecture_02 1.6.1.2

B1-Management 1.7

HumanResource 1.7.1

Curriculum_Vitae 1.7.1.1

2022 1.7.1.1.1

2022.en 1.7.1.1.1.1

2022.zh 1.7.1.1.1.2

Interview 1.7.1.2

2018ST实习收获 1.7.1.2.1

2020面试总结 1.7.1.2.2

Inwerviewer 1.7.1.2.3

ManagementByObject 1.7.2

OKR 1.7.2.1

SMART 1.7.2.2

Self-Control 1.7.2.3

B2-Economics 1.8

Principle 1.8.1

Quantitative_Investment 1.8.2

PB_PE 1.8.2.1

基本概念 1.8.2.2

C-Traffic 1.9

1_Traffic_Control 1.9.1

Objects 1.9.1.1

Terms 1.9.1.2

2_Autopilot 1.9.2

2_0_Planning 1.9.2.1

2_1_Search 1.9.2.2

2_2_Sampling 1.9.2.3

X-Photography 1.10

1-纽约摄影学院三原则 1.10.1

Y-Academic 1.11

论文写作 1.11.1

论文阅读 1.11.2

Z-Sundry 1.12

DPT-RP1_Config 1.12.1

Introduction

CharlesNotes

This is my notes, powered by GitBook, GitHub Pages, Travis CI.

Book Source: CharlesNotes

Online Reading: notes.pythonic.life

Download Offline Version

EPUB

MOBI

How to Write Notes Likes This?

You can read my blog: 最好的电子笔记本：GitBook + Typora.

Matrix_Calculus

矩阵微积分

分子记法：结果的形状与分子维度相同，与分母相反；分母记法反之。下面的例子都是分子记法。

标量求导

标量对标量求导: 1×11×1→1×1\frac{1 \times 1}{1 \times 1} \rightarrow {1 \times 1}1×11×1→1×1

dydx\frac{dy}{dx}dxdy

向量求导

向量对标量求导: m×11×1→m×1\frac{m \times 1}{1 \times 1} \rightarrow {m \times 1}1×1m×1→m×1

∂[y1y2…ym]T∂x=[∂y1∂x∂y2∂x…∂ym∂x]T\frac{\partial\begin{bmatrix} y_1 & y_2 & \ldots & y_m\end{bmatrix}^T}{\partial x}=\begin{bmatrix}\frac{\partial y_1}{\partial x} & \frac{\partial y_2}{\partial x} & \ldots & \frac{\partial y_m}{\partial x}\end{bmatrix}^T∂x∂[y1y2…ym]T=[∂x∂y1∂x∂y2…∂x∂ym]T

标量对向量求导（梯度）: 1×1n×1→1×n\frac{1 \times 1}{n \times 1} \rightarrow {1 \times n}n×11×1→1×n

∇uf=(∂f∂x)⊤u{\displaystyle \nabla _{\mathbf {u} }f=\left({\frac {\partial f}{\partial \mathbf {x} }}\right)^{\top }\mathbf {u} }∇uf=(∂x∂f)⊤u

∂y∂[x1x2…xn]T=[∂y∂x1∂y∂x2…∂y∂xn]\frac{\partial y}{\partial \begin{bmatrix} x_1 & x_2 & \ldots & x_n\end{bmatrix}^T} = \begin{bmatrix}\frac{\partial y}{\partial x_1} & \frac{\partial y}{\partial x_2} & \ldots & \frac{\partial y}{\partial x_n}\end{bmatrix}∂[x1x2…xn]T∂y=[∂x1∂y∂x2∂y…∂xn∂y]

向量对向量求导: m×1n×1→m×n\frac{m \times 1}{n \times 1} \rightarrow {m \times n}n×1m×1→m×n

d f(v)=∂f∂vd v{\displaystyle d\,\mathbf {f} (\mathbf {v} )={\frac {\partial \mathbf {f} }{\partial \mathbf {v} }}d\,\mathbf {v} }df(v)=∂v∂fdv

∂[y1y2…ym]T∂[x1x2…xn]T=[∂y1∂x1∂y1∂x2⋯∂y1∂xn∂y2∂x1∂y2∂x2⋯∂y2∂xn⋮⋮⋱⋮∂ym∂x1∂ym∂x2⋯∂ym∂xn]\frac{\partial\begin{bmatrix} y_1 & y_2 & \ldots & y_m\end{bmatrix}^T}{\partial \begin{bmatrix} x_1 & x_2 & \ldots & x_n\end{bmatrix}^T}=\begin{bmatrix} \frac{\partial y_1}{\partial x_1} & \frac{\partial y_1}{\partial x_2} & \cdots & \frac{\partial y_1}{\partial x_n}\\ \frac{\partial y_2}{\partial x_1} & \frac{\partial y_2}{\partial x_2} & \cdots & \frac{\partial y_2}{\partial x_n} \\ \vdots & \vdots & \ddots & \vdots \\ \frac{\partial y_m}{\partial x_1} & \frac{\partial y_m}{\partial x_2} & \cdots & \frac{\partial y_m}{\partial x_n} \end{bmatrix}∂[x1x2…xn]T∂[y1y2…ym]T=∂x1∂y1∂x1∂y2⋮∂x1∂ym∂x2∂y1∂x2∂y2⋮∂x2∂ym⋯⋯⋱⋯∂xn∂y1∂xn∂y2⋮∂xn∂ym

矩阵求导

矩阵对标量求导

∂Y∂x=[∂y11∂x∂y12∂x⋯∂y1n∂x∂y21∂x∂y22∂x⋯∂y2n∂x⋮⋮⋱⋮∂ym1∂x∂ym2∂x⋯∂ymn∂x]{\displaystyle {\frac {\partial \mathbf {Y} }{\partial x}}={\begin{bmatrix}{\frac {\partial y_{11}}{\partial x}}&{\frac {\partial y_{12}}{\partial x}}&\cdots &{\frac {\partial y_{1n}}{\partial x}}\\{\frac {\partial y_{21}}{\partial x}}&{\frac {\partial y_{22}}{\partial x}}&\cdots &{\frac {\partial y_{2n}}{\partial x}}\\\vdots &\vdots &\ddots &\vdots \\{\frac {\partial y_{m1}}{\partial x}}&{\frac {\partial y_{m2}}{\partial x}}&\cdots &{\frac {\partial y_{mn}}{\partial x}}\\\end{bmatrix}}}∂x∂Y=∂x∂y11∂x∂y21⋮∂x∂ym1∂x∂y12∂x∂y22⋮∂x∂ym2⋯⋯⋱⋯∂x∂y1n∂x∂y2n⋮∂x∂ymn

标量对矩阵求导

∂y∂X=[∂y∂x11∂y∂x21⋯∂y∂xp1∂y∂x12∂y∂x22⋯∂y∂xp2⋮⋮⋱⋮∂y∂x1q∂y∂x2q⋯∂y∂xpq]{\displaystyle {\frac {\partial y}{\partial \mathbf {X} }}={\begin{bmatrix}{\frac {\partial y}{\partial x_{11}}}&{\frac {\partial y}{\partial x_{21}}}&\cdots &{\frac {\partial y}{\partial x_{p1}}}\\{\frac {\partial y}{\partial x_{12}}}&{\frac {\partial y}{\partial x_{22}}}&\cdots &{\frac {\partial y}{\partial x_{p2}}}\\\vdots &\vdots &\ddots &\vdots \\{\frac {\partial y}{\partial x_{1q}}}&{\frac {\partial y}{\partial x_{2q}}}&\cdots &{\frac {\partial y}{\partial x_{pq}}}\\\end{bmatrix}}}∂X∂y=∂x11∂y∂x12∂y⋮∂x1q∂y∂x21∂y∂x22∂y⋮∂x2q∂y⋯⋯⋱⋯∂xp1∂y∂xp2∂y⋮∂xpq∂y

标量函数f(X)关于矩阵X在方向Y的方向导数可写成 ∇Yf=tr(∂f∂XY){\displaystyle \nabla _{\mathbf {Y} }f= {tr} \left({\frac {\partial f}{\partial \mathbf {X} }}\mathbf {Y} \right)}∇Yf=tr(∂X∂fY)

常用结论

如下结论按照分母记法给出。

∂xTA∂x=∂ATx∂x=A\frac{\partial \mathbf{x}^T \mathbf{A}}{\partial \mathbf{x}} = \frac{\partial \mathbf{A}^T\mathbf{x}}{\partial \mathbf{x}}=\mathbf{A}∂x∂xTA=∂x∂ATx=A

∂xTx∂x=x\frac{\partial\mathbf{x}^T\mathbf{x}}{\partial\mathbf{x}}=\mathbf{x}∂x∂xTx=x

对于方阵B\mathbf{B}B，有∂xTBx∂x=(B+BT)x\frac{\partial\mathbf{x}^T\mathbf{B}\mathbf{x}}{\partial\mathbf{x}}=\left(\mathbf{B}+\mathbf{B}^T\right)\mathbf{x}∂x∂xTBx=(B+BT)x

attractor.py

import MATLAB  # pyMATLABstyle
import math

class Particle(object):
    def __init__(self, xyz=(0, 0, 0), dxyz=(0, 0, 0), step_size=0.001):
        self.x, self.y, self.z = xyz
        self.dx, self.dy, self.dz = dxyz
        self.step_size = step_size

    def move(self):
        self.x += self.dx * self.step_size
        self.y += self.dy * self.step_size
        self.z += self.dz * self.step_size

    def update(self):
        raise NotImplementedError

    @property
    def coordinate(self):
        return (self.x, self.y, self.z)

    def step(self):
        self.move()
        self.update()
        return self.coordinate

class Lorenz(Particle):
    # For small values of ρ, the system is stable and evolves to one of two fixed point attractors.
    # When ρ is larger than 24.74, the fixed points become repulsors
    # and the trajectory is repelled by them in a very complex way.
    def __init__(self, sigma=10, rho=28, beta=8 / 3, xyz=(0, -2, 0), dxyz=(0, 0, 0), step_size=0.001):
        super(Lorenz, self).__init__(xyz, dxyz, step_size=step_size)
        self.sigma = sigma
        self.rho = rho
        self.beta = beta

    def update(self):
        self.dx = self.sigma * (self.y - self.x)
        self.dy = self.x * (self.rho - self.z) - self.y
        self.dz = self.x * self.y - self.beta * self.z

class Rossler(Particle):
    # Rössler studied the chaotic attractor with a = 0.2, b = 0.2, and c = 5.7,
    # though properties of a = 0.1, b = 0.1, and c = 14 have been more commonly used since.
    # Another line of the parameter space was investigated using the topological analysis.
    # It corresponds to b = 2, c = 4, and a was chosen as the bifurcation parameter.
    def __init__(self, a=0.1, b=0.1, c=14, xyz=(0, -2, 0), dxyz=(0, 0, 0), step_size=0.001):
        super(Rossler, self).__init__(xyz, dxyz, step_size=step_size)
        self.a = a
        self.b = b
        self.c = c

    def update(self):
        self.dx = -self.y - self.z
        self.dy = self.x + self.a * self.y
        self.dz = self.b + self.z * (self.x - self.c)

class Multiscroll(Particle):
    def __init__(self, a=40, b=3, c=28, f=None, xyz=(-0.1, 0.5, -0.6), dxyz=(0, 0, 0), step_size=0.001):
        super(Multiscroll, self).__init__(xyz, dxyz, step_size=step_size)
        self.a = a
        self.b = b
        self.c = c

        if f is not None:
            self.f = f

    def update(self):
        f = self.f((self.x, self.y, self.z))
        self.dx = self.a * (self.y - self.x)
        self.dy = (self.c - self.a) * self.x - self.x * f + self.c * self.y
        self.dz = self.x * self.y - self.b * self.z

    @staticmethod
    def f(xyz, g=1, h=25):
        x, y, z = xyz
        return g * z - h * math.sin(z)

if __name__ == "__main__":

    def plot_attractor(attractor):
        fig, ax = MATLAB.fig3d()
        p = attractor()
        data = [p.step() for i in range(500000)]
        ax.plot3D(*(zip(*data)), linewidth=0.5)
        MATLAB.tight_layout()

    for attractor in (Lorenz, Rossler, Multiscroll):
        plot_attractor(attractor)

    MATLAB.show()

Bit_Manipulation

去掉最右位：x & (x - 1)

只要最右位：x & ~(x - 1)

加法：

Result: XOR

Carry: AND

Calculator_AST.py

"""Calculator, worte in AST in python
"""
# This could be implemented as string-based big-number calcaulation
from ast import literal_eval
from math import inf

def ADD(x, y):
    return str(literal_eval(x) + literal_eval(y))

def SUB(x, y):
    return str(literal_eval(x) - literal_eval(y))

def POW(x, y):
    return str(literal_eval(x) ** literal_eval(y))

def MUL(x, y):
    return str(literal_eval(x) * literal_eval(y))

def DIV(x, y):
    return str(literal_eval(x) / literal_eval(y))

operations = {
    # 'op': [priority, function]
    '+': [1, ADD],
    '-': [1, SUB],
    '*': [2, MUL],
    '/': [2, DIV],
    '^': [3, POW]
}
parens = {
    '(': 1,
    ')': -1
}

class TreeNode(object):
    __slots__ = ('val', 'left', 'right')

    def __init__(self, val=None, left=None, right=None):
        self.val, self.left, self.right = val, left, right

    def __repr__(self):
        left, right = ' ', ' '
        if self.left is not None:
            left = repr(self.left) + ' <- '
        if self.right is not None:
            right = ' -> ' + repr(self.right)
        return '(' + left + repr(self.val) + right + ')'

def lexer(string):
    # removing useless parens
    result = list()
    if not string:
        return result
    head, tail = 0, 0  # head and tail are used for slice number out

    numeric = False  # Stores previous scanned atom type. If numeric changes, the successor atom type will change
    while tail < len(string):
        # if '+' '-' are operators
        if string[tail] in {'+', '-'} and numeric:
            numeric = False
            result.append(('SYMB', string[tail]))
        # elif numbers or '+' '-' are numbers' symbol
        elif (string[tail].isdecimal() or string[tail] == '.') or (string[tail] in {'+', '-'} and not numeric):
            numeric = True
            head = tail
            tail += 1
            while tail < len(string) and (string[tail].isdecimal() or string[tail] == '.'):
                tail += 1
            num = string[head:tail]
            if num in {'+', '-'}:
                num += '1'
            result.append(('NUM', num))
            tail -= 1
        # elif operators or parentheses
        elif string[tail] in {'*', '/', '^', '(', ')'}:
            symb = 'PAREN' if string[tail] in parens else 'SYMB'
            if numeric and string[tail] == '(':
                result.append(('SYMB', '*'))
            numeric = True if string[tail] == ')' else False
            result.append((symb, string[tail]))
        tail += 1
    return result

def parser(tokens):
    # Remove useless parens
    remove_enclosing_parans_flag = True
    while remove_enclosing_parans_flag:
        if len(tokens) == 0:
            return
        remove_enclosing_parans_flag = remove_enclosing_parans_flag and (tokens[0][1] == '(' and tokens[-1][1] == ')')
        depth = 0
        for TYPE, TOKEN in tokens[:-1]:
            depth += parens.get(TOKEN, 0)
            if depth == 0:
                remove_enclosing_parans_flag = remove_enclosing_parans_flag and (depth != 0)
                break
        if remove_enclosing_parans_flag:
            tokens = tokens[1:-1]

    depth = 0
    min_priority = inf
    min_priority_index = None

    # find the operator out of parentheses with smallest priority
    for i, (TYPE, TOKEN) in enumerate(tokens):
        # judge weather in parentheses or not
        if TYPE == 'PAREN':
            depth += parens[TOKEN]

        elif TYPE == 'SYMB' and depth == 0:
            if operations[TOKEN][0] < min_priority:
                min_priority_index, min_priority = i, operations[TOKEN][0]

    if min_priority_index is None:
        if tokens[0][1] in parens or tokens[0][1] in operations:
            return
        else:
            return TreeNode(tokens[0][1])

    left = parser(tokens[:min_priority_index])
    right = parser(tokens[min_priority_index + 1:])
    return TreeNode(tokens[min_priority_index][1], left, right)

def solve(ast_root, DEBUG=False):
    """Traverse the AST and do calculation
    """
    if ast_root is None:
        raise ValueError('AST cannot be None')
    if ast_root.left is ast_root.right is None:
        return ast_root.val
    else:
        if DEBUG:
            left = solve(ast_root.left, DEBUG)
            right = solve(ast_root.right, DEBUG)
            result = operations[ast_root.val][1](left, right)
            print('Calculating:', ast_root.val, ast_root.left, '|', ast_root.right, '=>', left, ast_root.val, right, '=', result)
            return result
        else:
            return operations[ast_root.val][1](solve(ast_root.left), solve(ast_root.right))

def calculator(string):
    return solve(parser(lexer(string)))

def main():
    DEBUG = False
    prompt = ['Expression > ', 'DEBUG > ']
    try:
        while 1:
            string = input(prompt[DEBUG])
            if string.upper().strip() == 'DEBUG':
                DEBUG = not DEBUG
                continue

            if DEBUG:
                print('Atoms      :', list(zip(*lexer(string)))[1])
                print('Tree       :', repr(parser(lexer(string))))
                print('PyResult   :', eval(string.replace('^', '**')))
                print('MyResult   :', solve(parser(lexer(string)), DEBUG))
            else:
                print('Result:', calculator(string))
    except (EOFError, KeyboardInterrupt):
        exit()

if __name__ == '__main__':
    main()

DFS-BFS

from collections import deque
from dsr import BinaryTreeNode as Node

def dfs(root):
    s = list()

    s.append(root)

    while len(s) > 0:
        r = s.pop()
        if r is None:
            continue
        print(r.value, end=' ')
        s.append(r.right)
        s.append(r.left)
    print()

def bfs(root):
    q = deque()

    q.append(root)

    while len(q) > 0:
        r = q.pop()
        if r is None:
            continue
        print(r.value, end=' ')
        q.appendleft(r.left)
        q.appendleft(r.right)
    print()

def main():
    root = Node(
        1,
        Node(
            2,
            Node(4),
            Node(5)
        ),
        Node(
            3,
            Node(6),
            Node(7)
        )
    )
    print('tree:', root, sep='\n')
    print('dfs:')
    dfs(root)
    print('bfs:')
    bfs(root)

if __name__ == "__main__":
    main()

Results:

tree:
            (1)
           /   \
    (2)             (3)
   /   \           /   \
(4)     (5)     (6)     (7)
dfs:
1 2 4 5 3 6 7
bfs:
1 2 3 4 5 6 7

Diffie_Hellman_Key_Exchange

mod = 53  # large prime number
base = 5  # usually 2 to 5
index_a = 30  # large number
index_b = 25  # large number

class Diffie_Hellman_Key_Exchanger(object):
    def __init__(self, base=None, mod=None, _private_index=None, external_message=None):
        self.base = base
        self.mod = mod
        self._private_index = _private_index
        self.external_message = None
        self.public_message = None
        self._key = None

    def get_public_message(self):
        self.public_message = self.base ** self._private_index % self.mod
        return self.public_message

    def get_key(self):
        self._key = self.external_message ** self._private_index % self.mod
        return self._key

Alice = Diffie_Hellman_Key_Exchanger(base, mod, index_a)
Bob = Diffie_Hellman_Key_Exchanger(None, None, index_b)

# Alice compute public message
Alice.get_public_message()

# Alice send base, mod, public message -> Bob
Bob.base, Bob.mod, Bob.external_message = Alice.base, Alice.mod, Alice.public_message

# Bob receive base, mod to compute public message, send to Alice
Bob.get_public_message()
Alice.external_message = Bob.public_message

# compute key from external public message with private index and mod
print(Alice.get_key(), Bob.get_key())

Dynamic_Programming

Dynamic Programming

Longest common substring:

If the selected character equals, then the count of this dp[i][j] = dp[i-1][j-1] + 1

else, the count of this dp equals zero.

The dp means that the length of longest common substring which ends with current coordinate.

#include <bits/stdc++.h>
using namespace std;

vector<string> longest_common_substr(string a, string b) {
    vector<vector<int>> dp(a.size() + 1, vector<int>(b.size() + 1, 0));
    int max_size = 0;
    vector<string> result;
    for (int i = 1; i <= a.size(); i++) {
        for (int j = 1; j <= b.size(); j++) {
            if (a[i - 1] == b[j - 1]) {
                dp[i][j] = dp[i - 1][j - 1] + 1;
                if (dp[i][j] > max_size) {
                    max_size = dp[i][j];
                }
            }
        }
    }
    if (max_size == 0) {
        return result;
    }
    for (int i = 1; i <= a.size(); i++) {
        for (int j = 1; j <= b.size(); j++) {
            if (dp[i][j] == max_size) {
                result.push_back(string(a.begin() + i - max_size, a.begin() + i));
            }
        }
    }

    return result;
}

int main() {
    string a = "ueoaoeu", b = "aoeueoa";
    for (auto i : longest_common_substr(a, b)) {
        cout << i << endl;
    }
    return 0;
}

Returns:

ueoa
aoeu

Eight_Queen.py

from copy import deepcopy

SIZE = 8

def print_board(chessboard, board=True, numeric=True):
    if board:
        print('+' + '-' * 2 * SIZE + '+')
        for line in chessboard:
            print('|', *map(lambda x: '  ' if x == 0 else '<>', line), sep='', end='|\n')
        print('+' + '-' * 2 * SIZE + '+')
    if numeric:
        print('BOARD:', *[line.index(1) + 1 for line in chessboard])

def check_board(chessboard, xy):
    x, y = xy
    x -= 1
    while x >= 0:
        if chessboard[x][y]:
            return False
        x -= 1
    x, y = xy
    x, y = x - 1, y + 1
    while 0 <= x < SIZE and 0 <= y < SIZE:
        if chessboard[x][y]:
            return False
        x, y = x - 1, y + 1
    x, y = xy
    x, y = x - 1, y - 1
    while 0 <= x < SIZE and 0 <= y < SIZE:
        if chessboard[x][y]:
            return False
        x, y = x - 1, y - 1
    return True

def eight_queen():
    chessboard = [[0 for i in range(SIZE)] for j in range(SIZE)]
    result = list()

    def helper(i=0):
        if i == SIZE:
            print_board(chessboard)
            result.append(deepcopy(chessboard))
        for j in range(SIZE):
            if check_board(chessboard, (i, j)):
                chessboard[i][j] = 1
                helper(i + 1)
                chessboard[i][j] = 0

    helper()
    return result

if __name__ == "__main__":
    print('NUMS OF SOLUTIONS:', len(eight_queen()))

FullBinaryTreeHeap.py

from dsr import BinaryTreeNode

class FullBinaryTreeHeap(object):
    def __init__(self, data=None, is_largest=True):
        self.__is_largest = is_largest
        if is_largest:
            self.__judgement = lambda x, y: x < y
        else:
            self.__judgement = lambda x, y: x > y
        if data is None:
            self.__heap = list()
        else:
            self.__heap = [i for i in data]
            self.heapify()

    def shift_down(self, i):
        child = 2 * i + 1
        T = self.__heap[i]
        while child < len(self.__heap):
            if child + 1 < len(self.__heap) and self.__judgement(
                    self.__heap[child],
                    self.__heap[child + 1]):
                child += 1
            if self.__judgement(T, self.__heap[child]):
                self.__heap[i] = self.__heap[child]
                i = child
                child = 2 * i + 1
            else:
                break
        self.__heap[i] = T

    def shift_up(self, i):
        parent = (i + 1) // 2 - 1
        T = self.__heap[i]
        while parent >= 0:
            if self.__judgement(self.__heap[parent], T):
                self.__heap[i] = self.__heap[parent]
                i = parent
                parent = (i + 1) // 2 - 1
            else:
                break
        self.__heap[i] = T

    def push(self, data):
        self.__heap.append(data)
        self.shift_up(len(self.__heap) - 1)

    def pop(self):
        if len(self.__heap) == 0:
            raise IndexError('No enough data to pop')
        ret = self.__heap[0]
        self.__heap[0] = self.__heap[-1]
        self.__heap.pop()
        if len(self.__heap) > 0:
            self.shift_down(0)
        return ret

    def replace(self, i, value):
        original_value = self.__heap[i]
        self.__heap[i] = value
        if self.__judgement(original_value, value):
            self.shift_up(i)
        else:
            self.shift_down(i)

    def heapify(self):
        for i in range(len(self.__heap) // 2 - 1, -1, -1):
            self.shift_down(i)

    @property
    def data(self):
        return self.__heap.copy()

    @property
    def tree(self):
        return self.__tree()

    def __tree(self, i=None):
        if not self.__heap:
            return BinaryTreeNode()
        if i is None:
            i = 0
        root = BinaryTreeNode(self.__heap[i])
        if 2 * i + 1 < len(self.__heap):
            root.left = self.__tree(2 * i + 1)
        if 2 * i + 2 < len(self.__heap):
            root.right = self.__tree(2 * i + 2)
        return root

    def copy(self):
        return FullBinaryTreeHeap(self.__heap, self.__is_largest)

    def __repr__(self):
        return repr(self.tree)

    def __len__(self):
        return self.__heap.__len__()

    def __iter__(self):
        iterator = self.copy()
        while len(iterator) > 0:
            yield iterator.pop()

Trajetory_Search.py

import time
from collections import deque
from copy import deepcopy

import termcolor

class Map(object):
    def __init__(self, map_data):
        self.map_data = map_data
        self.x_size = len(map_data)
        self.y_size = len(map_data[0])

    def __repr__(self):
        result = list()
        for line in self.map_data:
            for item in line:
                elem = f"{item}".rjust(2)
                if isinstance(item, int) and item != 0:
                    elem = termcolor.colored(elem, 'red')
                if isinstance(item, str):
                    elem = termcolor.colored(elem, 'green')
                result.append(elem)
            result.append('\n')
        return ''.join(result)

    def __getitem__(self, pos):
        return self.map_data[pos[0]][pos[1]]

    def __setitem__(self, pos, value):
        self.map_data[pos[0]][pos[1]] = value

    def neighbor(self, pos):
        x, y = pos
        for dx in [-1, 0, 1]:
            xi = x + dx
            if 0 <= xi < self.x_size:
                for dy in [-1, 0, 1]:
                    yi = y + dy
                    if dx == dy == 0:
                        continue
                    if 0 <= yi < self.y_size:
                        if self[xi, yi] == 0:
                            yield (xi, yi), {0: 0, 1: 1, 2: 1.41}[abs(dx) + abs(dy)]

def distance_manhatton(p1, p2):
    return sum(abs(i - j) for i, j in zip(p1, p2))

def distance_eular(p1, p2):
    return sum((i - j)**2 for i, j in zip(p1, p2))

def breadth_first_search(problem: Map, departure, destination):
    queue = deque()
    queue.appendleft((departure, 0, ()))
    visited = set()
    loops = 0
    while queue:
        top, distance, traj = queue.pop()
        loops += 1
        if top == destination:
            return distance, traj, loops, visited
        visited.add(top)
        for n, d in problem.neighbor(top):
            if n not in visited:
                queue.appendleft((n, distance + d, traj + (top,)))
    return -1, (), loops, visited

def best_first_search(problem: Map, departure, destination, distance_metric=distance_manhatton):
    queue = deque()
    queue.appendleft((departure, 0, (), distance_metric(departure, destination)))
    visited = set()
    loops = 0
    while queue:
        best = min(queue, key=lambda x: x[-1])
        queue.remove(best)
        top, distance, traj, score = best
        loops += 1
        if top == destination:
            return distance, traj, loops, visited
        visited.add(top)
        for next_pos, ds in problem.neighbor(top):
            if next_pos not in visited:
                queue.appendleft((next_pos, distance + ds, traj + (top,), distance_metric(next_pos, destination)))
    return -1, (), loops, visited

def a_star_search(problem: Map, departure, destination, distance_metric=distance_manhatton):
    queue = deque()
    queue.appendleft((departure, 0, (), distance_metric(departure, destination)))
    visited = set()
    loops = 0
    while queue:
        best = min(queue, key=lambda x: x[-1])
        queue.remove(best)
        top, distance, traj, score = best
        loops += 1
        if top == destination:
            return distance, traj, loops, visited
        visited.add(top)
        for next_pos, ds in problem.neighbor(top):
            if next_pos not in visited:
                queue.appendleft((next_pos, distance + ds, traj + (top,), distance + ds + distance_metric(next_pos, destination)))
    return -1, (), loops, visited

if __name__ == '__main__':
    destination = (13, 10)
    departure = (8, 5)
    map_data = [
        [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
        [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
        [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1],
        [1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
        [1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
        [1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1],
        [1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
        [1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1],
        [1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1],
        [1, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
        [1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
        [1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
        [1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1],
        [1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
        [1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
        [1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
        [1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
        [1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
        [1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
        [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
    ]

    for search_method in (
        breadth_first_search,
        best_first_search,
        a_star_search
    ):
        problem = Map(deepcopy(map_data))
        start = time.time()
        distance, traj, loops, visited = search_method(problem, departure, destination)
        end = time.time()
        for i, p in enumerate(traj):
            problem[p] = str(i)
        problem[departure] = 'DP'
        problem[destination] = 'DS'
        print(problem)
        print(search_method.__name__, f"{distance=}, {len(traj)=}, {loops=}, {end-start=:0.2f}")

bisect.cc

Bisect in C++

#include <iostream>
#include <vector>

using namespace std;

namespace algorithm {

template <typename T>
int bisect_right(const vector<T>& arr, const T& value) {
    int lo = 0, hi = arr.size(), mid;
    while (lo < hi) {
        mid = (lo + hi) / 2;
        if (arr[mid] <= value)
            lo = mid + 1;
        else
            hi = mid;
    }
    return lo - 1;
}

template <typename T>
int bisect_left(const vector<T>& arr, const T& value) {
    int lo = 0, hi = arr.size(), mid;
    while (lo < hi) {
        mid = (lo + hi) / 2;
        if (arr[mid] < value) {
            lo = mid + 1;
        } else {
            hi = mid;
        }
    }
    return lo;
}

template <typename T>
int bisect_right_rev(const vector<int>& arr, const T& value) {
    int lo = 0, hi = arr.size(), mid;
    while (lo < hi) {
        mid = (lo + hi) / 2;
        if (value <= arr[mid])
            lo = mid + 1;
        else
            hi = mid;
    }
    return lo - 1;
}

template <typename T>
int bisect_left_rev(const vector<int>& arr, const T& value) {
    int lo = 0, hi = arr.size(), mid;
    while (lo < hi) {
        mid = (lo + hi) / 2;
        if (value < arr[mid])
            lo = mid + 1;
        else
            hi = mid;
    }
    return lo;
}

}  // namespace algorithm

int main() {
    vector<int> arr{5, 4, 4, 4, 4, 4, 1};
    cout << algorithm::bisect_left_rev(arr, 4) << endl;
}

sort.cc

Sort in C++

MergeSort

#include <iostream>
#include <vector>

using namespace std;

namespace show {

template <typename T>
void show(vector<T> data, bool ln = true) {
    cout << '[';
    for (auto item : data) cout << item << ", ";
    cout << ']';
    if (ln) cout << endl;
}

}  // namespace show

namespace algorithm {

template <typename T>
vector<T> merge(const vector<T>& arr1, const vector<T> arr2) {
    vector<T> result;
    int i = 0, j = 0;
    while (i < arr1.size() && j < arr2.size())
        if (arr1[i] <= arr2[j])
            result.push_back(arr1[i++]);
        else
            result.push_back(arr2[j++]);

    while (i < arr1.size()) result.push_back(arr1[i++]);
    while (j < arr2.size()) result.push_back(arr2[j++]);

    return result;
}

template <typename T>
vector<T> mergesort(const vector<T>& arr, int lo = 0, int hi = -1) {
    if (hi == -1) hi = arr.size();
    if (hi - lo <= 1) return arr;
    int mid = (hi + lo) / 2;
    vector<T> left(arr.begin() + lo, arr.begin() + mid), right(arr.begin() + mid, arr.begin() + hi);
    vector<T> sorted_left = mergesort(left), sorted_right = mergesort(right);
    return merge(sorted_left, sorted_right);
}

template <typename T>  // inplace sort
void quicksort(vector<T>& arr, int lo = 0, int hi = -1) {
    if (hi == -1) hi = arr.size();
    if (hi - lo <= 1) return;
    int lo_ = lo, hi_ = hi;
    hi--;
    T pivot = arr[lo];
    while (lo < hi) {
        while (lo < hi && arr[hi] >= pivot) hi--;
        arr[lo] = arr[hi];
        while (lo < hi && arr[lo] <= pivot) lo++;
        arr[hi] = arr[lo];
    }
    arr[hi++] = pivot;
    quicksort(arr, lo_, lo);
    quicksort(arr, hi, hi_);
}

}  // namespace algorithm

int main() {
    vector<int> arr{9, 1, 4, 2, 3, 8, 4, 1, 8, 4, 6};
    cout << "Original data: ";
    show::show(arr);
    cout << "MergeSort: ";
    show::show(algorithm::mergesort(arr));
    cout << "QuickSort(inplace): ";
    algorithm::quicksort(arr);
    show::show(arr);
    return 0;
}

Result:

Original data: [9, 1, 4, 2, 3, 8, 4, 1, 8, 4, 6, ]
MergeSort: [1, 1, 2, 3, 4, 4, 4, 6, 8, 8, 9, ]
QuickSort(inplace): [1, 1, 2, 3, 4, 4, 4, 6, 8, 8, 9, ]

sort.py

Sort in Python

QuickSort and MergeSort

Code

import random
import sys
import time

sys.setrecursionlimit(1000000)

def quicksort(data, lo, hi):
    if lo >= hi:
        return
    else:
        x = data[lo]
        i, j = lo, hi
        while i < j:
            while x <= data[j] and i < j:
                j -= 1
            data[i] = data[j]
            while x >= data[i] and i < j:
                i += 1
            data[j] = data[i]
        data[i] = x
        quicksort(data, lo, i)
        quicksort(data, i + 1, hi)

def mergesort(data, lo, hi):
    pass
    if lo >= hi - 1:
        return
    else:
        mid = (lo + hi) // 2
        mergesort(data, lo, mid), mergesort(data, mid, hi)
        # merge(data, lo, mid, hi)
        if not lo < mid < hi:
            return
        else:
            result = list()
            i, j = lo, mid
            while i < mid and j < hi:
                if data[i] <= data[j]:
                    result.append(data[i])
                    i += 1
                else:
                    result.append(data[j])
                    j += 1
            data[lo:hi] = result + data[i:mid] + data[j:hi]

data = [random.randint(0, 1000000) for i in range(20000)]
d1, d2 = (data.copy() for i in range(2))
start = time.time()
quicksort(d1, 0, len(data) - 1)
print('quicksort', time.time() - start)
start = time.time()
mergesort(d2, 0, len(data))
print('mergesort', time.time() - start)
start = time.time()
sorted(data)
print('timsort in C', time.time() - start)

Result

quicksort 0.06924867630004883
mergesort 0.10277152061462402
timsort in C 0.0060214996337890625

BinaryTreeSort

Code

import random
from dsr import BinaryTreeNode as Node

class BinarySortTree(object):
    def __init__(self, data=list()):
        self.root = Node()
        for val in data:
            self.insert(val)

    def insert(self, val):
        if self.root.value is None:
            self.root.value = val
            return
        leaf, root = (self.root,)*2
        while root is not None:
            leaf = root
            if root.value > val:
                root = root.left
            else:
                root = root.right
        if leaf.value > val:
            leaf.left = Node(val)
        else:
            leaf.right = Node(val)

    def traverse(self, root=False):
        if root is False:
            root = self.root
        if root is None:
            return list()
        return self.traverse(root.left) + [root.value] + self.traverse(root.right)

    def __repr__(self):
        return repr(self.root)

data = [random.randint(0, 100) for i in range(10)]
print(data)
tree = BinarySortTree(data)
print(tree)
print(tree.traverse())

Result

[36, 43, 26, 9, 73, 76, 73, 72, 31, 19]
                    (36)
                   /    \
          (26)            (43)
         /    \               \
 (9)           (31)                 (73)
    \                              /    \
     (19)                      (72)           (76)
                                             /
                                         (73)
[9, 19, 26, 31, 36, 43, 72, 73, 73, 76]

sum

Precise sum

BINARY FLOATING POINT SUMMATION ACCURATE TO FULL PRECISION (PYTHON RECIPE)

swap

def swap_plus_minus(a, b):
    a -= b
    b -= a
    a += b
    b -= a
    b = a - b
    return a, b

def swap_xor(a, b):
    a ^= b
    b ^= a
    a ^= b
    return a, b

a, b = 17, 19
print('a, b', (a, b))
print(swap_plus_minus(a, b))
print(swap_xor(a, b))

#include <iostream>

template <class T>
void swap_plus_minus(T& a, T& b) {
    a -= b;
    b -= a;
    a += b;
    b -= a;
    b = a - b;
}

template <class T>
void swap_xor(T& a, T& b) {
    a ^= b;
    b ^= a;
    a ^= b;
}

int main() {
    int a = 17.2, b = 19.4;
    swap_plus_minus(a, b);
    std::cout << a << ' ' << b << std::endl;
    swap_xor(a, b);
    std::cout << a << ' ' << b << std::endl;
    return 0;
}

OSI

OSI 七层模型介绍

物理层，电平/电磁波频率/编码（调制解调），一定是广播，也没有地址概念，直接传输比特流

数据链路层，以太网，arp，用于分配总线上的控制权（介质访问控制，MAC），有MAC地址，单播/广播/组播其实都是广播，只不过看到和自己有关才处理数据，只能在局域网间通信。从比特流中切分出帧

网络层，IP协议，联通各个局域网，通过路由器作为中继，实现真正的远距离通信。由于数据链路层总是独占介质，省内通信不必占用国际通道，把网络划分开后可以提升拓扑关系较近的通信效率，同时也节约了拓扑关系远的通信资源。使用IP地址+子网掩码，把所有的网络的拓扑关系用二叉树表示，可以实现分布式递归查找去往目标节点的路径。

如上图所示，地址为0.0的节点想与1.0通信，通过地值计算就知道自己的子网里没它，只好发到上级0号地址，0号地址发现自己的子网也没它，继续发往自己的父节点，直到root节点看到自己的子网里有1.0,发给1号节点，1号节点发给1.0号节点，便形成了树内寻路算法。

传输层，TCP/UDP/Socket，联通各个应用，使用端口号。每个程序都想使用网络通信，用端口号区分不同程序，免得不相关的程序监听重要信息，并且正经程序也没必要监听别的程序的流量。另一方面数据跨过很远才传送过来，信息/顺序都乱套了，还常常会丢数据，也要保障可靠。

会话层/表示层/应用层，很多时候没有也不必区分清晰的界限，都是软件控制的，往往不再涉及到最关键的基础设施（比如路由器必须支持IP协议）。只要使用本层协议的两端互相商量好即可，所以这个层面的协议理论上切分为多少层就看软件工程的设计了。对于程序员来说最熟悉的也是这一层，从会话层的socks协议代理上网，到TLS多一层加密，再到HTTP约定信息传输的方式，可以有五花八门的变化。

split_panes_html

<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>div+css实现frameset效果</title>
<style type="text/css">
.header{border-bottom:1px solid #ccc;margin-bottom:5px;}
.MainContainer{min-width:960px;max-width:1600px;}
.sidebar{width:180px;float:left;margin-right:-180px;border-right:1px solid #ccc;min-height:500px;padding:5px;}
.main{float:left;margin-left:200px;padding:5px;}
.content{padding:0 10px;}
</style>
</head>
<body>

    <div class="page">
        <div class="header">
            <div id="title">
                <h1>顶部</h1>
            </div>
        </div>
        <div class="MainContainer">
            <div class="sidebar">
            边栏
            </div>
            <div  id="main" class="main">
                内容
            </div>
        </div>
    </div>
</body>
</html>

FrontEnd_html

<!DOCTYPE HTML>
<html>
<head>
  <title>加载海量点</title>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no">
  <style type="text/css">
    html,body{
        margin:0;
        width:100%;
        height:100%;
        background:#ffffff;
    }
    #map{
        width:100%;
        height:100%;
    }

  </style>
<script type="text/javascript" src="http://api.map.baidu.com/api?v=2.0&ak=your_api_key"></script>

<script type="text/javascript" src="data.json"></script>
//data.json内容
//var data = {"data":[[74.438,39.006,1],...]}

</head>
<body>
    <div id="map"></div>
    <script type="text/javascript">
    var map = new BMap.Map("map", {});                        // 创建Map实例
    map.centerAndZoom(new BMap.Point(105.000, 38.000), 5);     // 初始化地图,设置中心点坐标和地图级别
    map.enableScrollWheelZoom();                        //启用滚轮放大缩小
    if (document.createElement('canvas').getContext) {  // 判断当前浏览器是否支持绘制海量点
        var options = {
            size: BMAP_POINT_SIZE_SMALLER,
            shape: BMAP_POINT_SHAPE_CIRCLE,
            color: 'red'
        }
        var pointCollection = new BMap.PointCollection(points, options);  // 初始化PointCollection
        pointCollection.addEventListener('click', function (e) {
          alert('单击点的坐标为：' + e.point.lng + ',' + e.point.lat);  // 监听点击事件
        });
        map.addOverlay(pointCollection);  // 添加Overlay
    } else {
        alert('请在chrome、safari、IE8+以上浏览器查看本示例');
    }
  </script>
</body>
</html>

百度api获取城市坐标.py

import json
from urllib.request import urlopen, quote
import requests,csv
import pandas as pd #导入这些库后边都要用到

def getlnglat(address):
  url = 'http://api.map.baidu.com/geocoder/v2/'
  output = 'json'
  ak = '你申请的密钥***'
  add = quote(address) #由于本文城市变量为中文，为防止乱码，先用quote进行编码
  uri = url + '?' + 'address=' + add + '&output=' + output + '&ak=' + ak
  req = urlopen(uri)
  res = req.read().decode() #将其他编码的字符串解码成unicode
  temp = json.loads(res) #对json数据进行解析
  return temp

file = open(r'E:\\爬虫数据分析\调用百度地图api\point.json','w') #建立json数据文件
with open(r'E:\\爬虫数据分析\调用百度地图api\各区域房价.csv', 'r') as csvfile: #打开csv
  reader = csv.reader(csvfile)
  for line in reader: #读取csv里的数据
    # 忽略第一行
    if reader.line_num == 1: #由于第一行为变量名称，故忽略掉
      continue
    # line是个list，取得所有需要的值
    b = line[0].strip() #将第一列city读取出来并清除不需要字符
    c= line[1].strip()#将第二列price读取出来并清除不需要字符
    lng = getlnglat(b)['result']['location']['lng'] #采用构造的函数来获取经度
    lat = getlnglat(b)['result']['location']['lat'] #获取纬度
    str_temp = '{"lat":' + str(lat) + ',"lng":' + str(lng) + ',"count":' + str(c) +'},'
    #print(str_temp) #也可以通过打印出来，把数据copy到百度热力地图api的相应位置上
file.write(str_temp) #写入文档
file.close() #保存

C_or_C++

typeID

#include <typeinfo>
typeid(var).name()

argv.cc

#include <iostream>

using namespace std;

int main(int argc, const char* argv[]){
    cout << "testing argc, argv" << endl;
    cout << "argc = " << argc << endl;
    for (int i = 0; i < argc; i++) {
        cout << "i = " << i << " argv = " << argv[i] << endl;
    }
    return 0;
}

inherit.cc

#include <cstdio>
#include <iostream>

using namespace std;

class A {
  public:
    A(int i) {
        cout << "construct A int" << endl;
        this->i = i;
    }
    A(void) {
        cout << "construct A void" << endl;
        this->i = 0;
    }
    ~A(void) {
        cout << "destruct A" << endl;
    }
    void f(void) {
        cout << "A.f called" << this->i << endl;
    }

  private:
    int i;
};
class B : public A {
  public:
    B(int i) : A(33) {
        cout << "construct B int" << endl;
        this->i = i;
        // A::f();
    }
    ~B(void) {
        cout << "destruct B" << endl;
    }
    void f(void) {
        cout << "B.f called" << this->i << endl;
    }

  private:
    int i;
};
int main() {
    A *pa = new B(1);
    ((B *)pa)->f();
    pa->f();
    // delete pa;
    B *pb = (B *)pa;
    pb->f();
    delete pb;
    pa = NULL;
    pb = NULL;

    cout << endl;
    B b(2);
    b.f();

    return 0;
}

Results

construct A int
construct B int
B.f called1
A.f called33
B.f called1
destruct B
destruct A

construct A int
construct B int
B.f called2
destruct B
destruct A

STL

Vector

使用给定数值初始化

#include <vector>
vector <T> vec_a {x, y, z};  // c++11
vector <T> vec_a(vec_b);
vector <T> vec_a(vec_b.begin(), vec_b.begin() + length_to_copy);
vector <T> vec_a(array_b, array_b + length_to_copy);
vector <T> vec_a(address_begin, address_end);

遍历

for(auto v: vec) cout << v;  // c++11
/* 迭代器 */
vector <T> :: iterator t;  // t 是指针
for(t = vec.begin(); t != vec.end(); t++) cout << *t;

/* 二维向量 */
vector <vector <int>> vec {{1, 2, 3}, {4, 5}};

for(auto row: vec){  // c++11
    for(auto v: row){
        cout << v << '\t';
    }
    cout << endl;
}

for(int row=0; row < vec.size(); row++){
    for(int col=0; col < vec[row].size(); col++){
        cout << vec[row][col] << '\t';
    }
    cout << endl;
}

heap.cc

#include <algorithm>
#include <functional>
#include <iostream>
#include <vector>
using namespace std;

namespace show {

template <typename T>
void show(vector<T> data, bool ln = true) {
    cout << '[';
    for (auto item : data) cout << item << ", ";
    cout << ']';
    if (ln) cout << endl;
}

}  // namespace show

namespace algorithm {

template <typename T>
class Heap {
  protected:
    function<bool(T, T)> compare;

  public:
    vector<T> data;
    Heap(function<bool(T, T)> compare = less<T>()) : compare(compare) {}
    Heap(initializer_list<T> data, function<bool(T, T)> compare = less<T>()) : data(data), compare(compare) { this->heapify(); }
    Heap(vector<T> data, function<bool(T, T)> compare = less<T>()) : data(data), compare(compare) { this->heapify(); }

    void heapify(void) { make_heap(this->data.begin(), this->data.end(), this->compare); }
    void push(const T& value) {
        this->data.push_back(value);
        push_heap(this->data.begin(), this->data.end(), this->compare);
    }
    const T pop() {
        T top = this->top();
        pop_heap(this->data.begin(), this->data.end(), this->compare);
        this->data.pop_back();
        return top;
    }
    const T top() {
        if (this->size() > 0) {
            return this->data[0];
        } else {
            throw out_of_range("Pop from an empty heap");
        }
    }
    const int size() { return this->data.size(); }
};
}  // namespace algorithm

int main() {
    algorithm::Heap<int> heap{6, 1, 2, 5, 3, 4};

    cout << "heaped: ";
    show::show(heap.data);

    heap.push(100);
    heap.push(43);
    heap.push(10);
    heap.push(20);

    cout << "pushed: ";
    show::show(heap.data);

    for (int i = 0; i < 8; i++) {
        cout << heap.pop();
        show::show(heap.data);
    }
    return 0;
}

debug

Debug

写 C/C++ 和写其他语言相比，最重要的能力之一就是 debug 能力，因为 C/C++ 的 debug 并不容易。

print/log

简单又通用的方法就是 print 或者写入日志到文件后慢慢分析，进阶一点的方法是使用结构化日志：

按照进程号 - 线程号写入到不同文件（这样写入就不用考虑加锁了）

写入时间、日志记录等级

每个条件判断和函数调用都记录，便于后面 stack trace

使用 lnav 阅读日志

gdb

只会print/log是很低效的，gdb是个很好用的工具。推荐一个博客：StormQ’s Blog: 代码调试。

gdb

GDB

GDB 工具族

gdb 族有很多工具，经过我的测试，比较好用的有如下几个：

gdb，这个基本很多环境都自带，肯定要学一下

cgdb，和 gdb 基本差不多，但显示效果更好，有语法高亮

gdbgui，显示效果非常好，使用 python flask 做个一个网页前端

vscode 自带插件可以连接到远程 gdb 或使用 gdb attach 到指定进程

其他还有一些：

ddd，这个我感觉不是很好用，但是有 gui，可以试试

lldb，没有实际使用过，和 cgdb 类似，好像对 llvm 适配较高

本文主要使用 cgdb 和 gdb 进行讲解。

启动

启动前先说一下，需要使用 Debug 模式编译。

从可执行文件启动：

gdb --args <executable-file> <arg1> <arg2> <argn>

gdb <executable-file>

启动后，连接到正在运行的进程

基于进程号：sudo gdb --pid <pid>

基于命令名称：sudo gdb --pid ￥(pgrep -n <command name>)

远程调试

启动远程调试服务：sudo gdbserver --multi :9999 --attach <pid>

连接到远程调试服务：既然都用远程调试了，当然是为了使用 IDE 自带的功能连上

重要的 gdb 启动参数：

--tui：和 cgdb 类似的代码展示窗口

-x <file>/-ex <command>: 启动时执行命令

Best Practice

首先把常用命令先写到文件中

# debug_file.gdb

b some_namespace::SomeClass::some_func
c
n
p <var>

sudo cgdb -- --pid $(pgrep -n <command name>) -x debug_file.gdb

cgdb 使用 Esc 进入源码窗口，使用空格给当前行加断点，使用 i 回到 gdb 窗口。

常用命令

启动配置

set pagination off：关闭启动提示

set print elements 0：显示 ... 的完整内容

set listsize 30：l 命令最大显示行数

进程 / 线程

i proc：看进程、可知性文件情况

i threads：看线程，注意这里面的线程编号并不是操作系统的线程号

p ￥_thread：看断点所在线程

thread <thread-id-by-gdb> 切换到某线程

thread apply all <command>：每个线程都运行某指令

函数运行状况

bt/where/bt full：看堆栈调用层次

bt 1/bt -1 可以指定打印堆栈 traceback 的层次

p (rint)：打印，不光能打印还能赋值；能直接打印整个对象的内容

断点

增

b <file-name>:<line-number>

b <func>

b *<address>

b <func> thread <thread-id-by-gdb>：可以使用 thread 命令指定特定线程

b <func> if <expression>：可以使用 if 命令加入条件断点

查

i breakpoints <breakpoint-number>-<breakpoint-number> 查看断点

删

d breakpoints <breakpoint-number>-<breakpoint-number> 删除断点，不加参数默认删掉所有

运行

l 显示当前位置，这个命令有 cgdb 后就不需要了

n (ext)：下一行

ni/si：跳入函数

finish：跳出函数

c (ontinue)

打印变量

p (rint) <expression>：不光能直接打印变量 / 对象，还能计算表达式求值，可以调用函数 / 赋值，但调用函数一般用 call

display/undisplay/i display ：自动打印变量，好像类似于 watch

i args：打印函数参数

i locals：打印局部变量

函数

call <function-name>

return <return-value>

pthread

#include <pthread.h>
#include <stdio.h>
#include <unistd.h>

void *thread_func(void *p) {
    for (int j = 0; j < 5; j++) {
        printf("%s\n", (char *)p);
        sleep(1);
    }

    return NULL;
}

int main() {
    pthread_t t1, t2;
    pthread_create(&t1, 0, thread_func, "1st thread");
    pthread_create(&t2, 0, thread_func, "2nd thread");
    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
}

crash_on_ipy.py

import sys

class ExceptionHook:
    instance = None

    def __call__(self, *args, **kwargs):
        if self.instance is None:
            from IPython.core import ultratb
            self.instance = ultratb.FormattedTB(mode='Plain',
                 color_scheme='Linux', call_pdb=1)
        return self.instance(*args, **kwargs)

sys.excepthook = ExceptionHook()

作者：Rui L 链接：https://www.zhihu.com/question/21572891/answer/26046582 来源：知乎著作权归作者所有，转载请联系作者获得授权。

jupyter_hide_code.py

from IPython.display import HTML

HTML('''<script>
     code_show=true;
     function code_toggle() {
      if (code_show){
       {% math_inline %}('div.input').hide();
       } else {
        {% endmath_inline %}('div.input').show();
        }
      code_show = !code_show
     }
     $( document ).ready(code_toggle);
     </script>
     <form action="javascript:code_toggle()"><input type="submit" value="Click here to toggle on/off the raw code."></form>''')

sendmail

Python 使用 semdmail 发送邮件例子

代码来源：菜鸟教程

sendmail.py

我做的发送邮件的小工具，编写或构造一下三个字典变量中的必要信息，即可发送邮件。支持发送文本/HTML/图片/附件(详细文档见我的博客)

qq_smtp.py

使用 QQ 邮箱发邮件

pic.py

发送图片

attachment.py

发送附件

attachment.py

#!/usr/bin/python
# -*- coding: UTF-8 -*-

import smtplib
from __future__ import print_function
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.header import Header

sender = 'from@runoob.com'
receivers = ['429240967@qq.com']  # 接收邮件，可设置为你的QQ邮箱或者其他邮箱

# 创建一个带附件的实例
message = MIMEMultipart()
message['From'] = Header("菜鸟教程", 'utf-8')
message['To'] = Header("测试", 'utf-8')
subject = 'Python SMTP 邮件测试'
message['Subject'] = Header(subject, 'utf-8')

# 邮件正文内容
message.attach(MIMEText('这是菜鸟教程Python 邮件发送测试……', 'plain', 'utf-8'))

# 构造附件1，传送当前目录下的 test.txt 文件
att1 = MIMEText(open('test.txt', 'rb').read(), 'base64', 'utf-8')
att1["Content-Type"] = 'application/octet-stream'
# 这里的filename可以任意写，写什么名字，邮件中显示什么名字
att1["Content-Disposition"] = 'attachment; filename="test.txt"'
message.attach(att1)

# 构造附件2，传送当前目录下的 runoob.txt 文件
att2 = MIMEText(open('runoob.txt', 'rb').read(), 'base64', 'utf-8')
att2["Content-Type"] = 'application/octet-stream'
att2["Content-Disposition"] = 'attachment; filename="runoob.txt"'
message.attach(att2)

try:
    smtpObj = smtplib.SMTP('localhost')
    smtpObj.sendmail(sender, receivers, message.as_string())
    print("邮件发送成功")
except smtplib.SMTPException:
    print("Error: 无法发送邮件")

pic.py

#!/usr/bin/python
# -*- coding: UTF-8 -*-

import smtplib
from __future__ import print_function
from email.mime.image import MIMEImage
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.header import Header

sender = 'from@runoob.com'
receivers = ['429240967@qq.com']  # 接收邮件，可设置为你的QQ邮箱或者其他邮箱

msgRoot = MIMEMultipart('related')
msgRoot['From'] = Header("菜鸟教程", 'utf-8')
msgRoot['To'] = Header("测试", 'utf-8')
subject = 'Python SMTP 邮件测试'
msgRoot['Subject'] = Header(subject, 'utf-8')

msgAlternative = MIMEMultipart('alternative')
msgRoot.attach(msgAlternative)

mail_msg = """
Python 邮件发送测试...
图片演示：

<img src="cid:image1">
"""
msgAlternative.attach(MIMEText(mail_msg, 'html', 'utf-8'))

# 指定图片为当前目录
fp = open('test.png', 'rb')
msgImage = MIMEImage(fp.read())
fp.close()

# 定义图片 ID，在 HTML 文本中引用
msgImage.add_header('Content-ID', '<image1>')
msgRoot.attach(msgImage)

try:
    smtpObj = smtplib.SMTP('localhost')
    smtpObj.sendmail(sender, receivers, msgRoot.as_string())
    print("邮件发送成功")
except smtplib.SMTPException:
    print("Error: 无法发送邮件")

qq_smtp.py

#!/usr/bin/python
# -*- coding: UTF-8 -*-

import smtplib
from __future__ import print_function
from email.mime.text import MIMEText
from email.utils import formataddr

my_sender = '429240967@qq.com'    # 发件人邮箱账号
my_pass = 'xxxxxxxxxx'              # 发件人邮箱密码
my_user = '429240967@qq.com'      # 收件人邮箱账号，我这边发送给自己

def mail():
    ret = True
    try:
        msg = MIMEText('填写邮件内容', 'plain', 'utf-8')
        msg['From'] = formataddr(["FromRunoob", my_sender])  # 括号里的对应发件人邮箱昵称、发件人邮箱账号
        msg['To'] = formataddr(["FK", my_user])              # 括号里的对应收件人邮箱昵称、收件人邮箱账号
        msg['Subject'] = "菜鸟教程发送邮件测试"                # 邮件的主题，也可以说是标题

        server = smtplib.SMTP_SSL("smtp.qq.com", 465)  # 发件人邮箱中的SMTP服务器，端口是25
        server.login(my_sender, my_pass)  # 括号中对应的是发件人邮箱账号、邮箱密码
        server.sendmail(my_sender, [my_user, ], msg.as_string())  # 括号中对应的是发件人邮箱账号、收件人邮箱账号、发送邮件
        server.quit()  # 关闭连接
    except Exception:  # 如果 try 中的语句没有执行，则会执行下面的 ret=False
        ret = False
    return ret

ret = mail()
if ret:
    print("邮件发送成功")
else:
    print("邮件发送失败")

sendmail.py

#!/usr/bin/env python
# -*- coding: UTF-8 -*-

import smtplib
from email.mime.text import MIMEText
from email.mime.image import MIMEImage
from email.mime.multipart import MIMEMultipart
from email.header import Header
from email.utils import formataddr

sender = {
    'address': 'sender@domain.tld',
    'nickname': 'My Name',
    'password': 'password',
    'smtp_server': 'smtp.domain.tld',
    'smtp_port': 465, }

receivers = [('Your Name', 'receiver@domain.tld')]

mail = {
    'content': 'Email Content',
    'content_html': '<html><body><h1>HTML Email Content</h1><img src="cid:image1"></body></html>',
    'subject': 'Email subject',
    'attachments': [('filename', 'string_or_bytes_like_data')],
    'images': [{
        'Content-ID': 'image1',
        'data': '''bytes which is read from file, i.e. open("pic.png", "rb").read(),
        and use html type mail with <img src="cid:image1">'''}],
}

def sendmail(sender, receivers, mail):
    message = MIMEMultipart()
    message['From'] = formataddr((Header(sender['nickname'], 'utf-8').encode(), sender['address']))
    message['To'] = ','.join(map(lambda x: formataddr((Header(x[0], 'utf-8').encode(), x[1])), receivers))
    message['Subject'] = Header(mail['subject'], 'utf-8').encode()

    if 'content' in mail:
        message.attach(MIMEText(mail['content'], 'plain', 'utf-8'))

    if 'content_html' in mail:
        message.attach(MIMEText(mail['content_html'], 'html', 'utf-8'))

    if 'attachments' in mail:
        for attachment in mail['attachments']:
            att = MIMEText(attachment[1], 'base64', 'utf-8')
            att["Content-Type"] = "application/octet-stream"
            att["Content-Disposition"] = 'attachment; filename="{}"'.format(attachment[0])
            message.attach(att)

    if 'images' in mail:
        for image in mail['images']:
            img = MIMEImage(image['data'])
            img.add_header('Content-ID', '<{}>'.format(image['Content-ID']))
            message.attach(img)

    server = smtplib.SMTP_SSL(sender['smtp_server'], sender['smtp_port'])
    server.login(sender['address'], sender['password'])
    server.sendmail(sender['address'], list(zip(*receivers))[1], message.as_string())
    server.quit()

if __name__ == '__main__':
    sendmail(sender, receivers, mail)

argparse_example.py

import argparse

def p(s):
    for i in s:
        print(i)
    print(len(s))

c = 0
parser = argparse.ArgumentParser(description='description')

parser.add_argument('-c',
                    action='store',
                    dest='d')
parser.add_argument('integers', metavar='N', type=int, nargs='+',
                    help='an integer for the accumulator')
parser.add_argument('--sum', dest='accumulate', action='store_const',
                    const=sum, default=max,
                    help='sum the integers (default: find the max)')

args = parser.parse_args()
print(args)

create_epubook.py

from typing import List, Tuple, Union

from ebooklib import epub

def create_epubook(
    title,
    author,
    chapters: list,
    save_path,
    *,
    cover_img: Tuple[str, str, Union[bytes, str]] = None,
    introduction: Tuple[str, str] = None,
    images: List[Tuple[str, str, Union[bytes, str]]] = None,
    identifier=None,
    lang='zh',
    css=None,
):
    '''
    chapters = [
        (chapter, section, html_content),
    ]
    cover_img = (filename, media_type, content)
    images = [(filename, media_type, content)]
    introduction = (title, html_content)
    '''

    if not chapters:
        raise ValueError('chapters must not be empty')

    book = epub.EpubBook()

    # add metadata
    if identifier is None:
        identifier = title
    book.set_identifier(identifier)
    book.set_title(title)
    book.set_language(lang)

    book.add_author(author)

    if css is None:
        css = '''
body {
    text-align: justify;
}
img  {
    max-width: 100%;
    max-height: 100%;
    align: center;
}
'''
    style = epub.EpubItem(file_name='style/default.css', media_type='text/css', content=css)
    book.add_item(style)

    if cover_img is not None:
        filename, media_type, content = cover_img
        book.set_cover(**{'file_name': filename, 'content': content}, create_page=False)

    # images
    if images:
        for filename, media_type, content in images:
            payload = {'file_name': filename, 'media_type': media_type, 'content': content}
            item = epub.EpubImage(**{k: v for k, v in payload.items() if v is not None})
            book.add_item(item)

    spine = []

    # introduction
    intro_toc = []
    if introduction:
        if isinstance(introduction, str):
            introduction = ('Introduction', introduction)
        intro_title, intro_content = introduction
        filename = 'Introduction.xhtml'
        item = epub.EpubHtml(title=intro_title, file_name=filename, content=intro_content)
        spine.append(item)
        book.add_item(item)
        intro_toc.append(epub.Link(filename, intro_title, 'intro'))

    # sections
    sec_toc: list = [(epub.Section('placeholder'), list())]
    for i, (chapter, section, content) in enumerate(chapters):
        filename = f"chapter_{i}.xhtml"

        if chapter != sec_toc[-1][0].title:
            sec_toc.append((epub.Section(chapter, filename), list()))
        item = epub.EpubHtml(title=section, file_name=filename, content=content)

        sec_toc[-1][-1].append(item)

        spine.append(item)
        book.add_item(item)

    book.toc = intro_toc + sec_toc[1:]

    book.add_item(epub.EpubNcx())
    book.add_item(epub.EpubNav())

    book.spine = spine

    epub.write_epub(save_path, book, {})

if __name__ == '__main__':
    import tempfile
    _, path = tempfile.mkstemp(suffix='.epub')

    img = b'<?xml version="1.0" encoding="utf-8" standalone="no"?> <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> <svg xmlns:xlink="http://www.w3.org/1999/xlink" width="460.8pt" height="345.6pt" viewBox="0 0 460.8 345.6" xmlns="http://www.w3.org/2000/svg" version="1.1"> <g> <path d="M 73.832727 295.488 L 398.487273 53.568 "  style="stroke: #ff7f0e; stroke-width: 1.5;"/> </g> </svg>'
    create_epubook(
        'title',
        'author',
        [
            ('c1', 'title for s1', '<p>s1 content</p><img src="1.svg"/>'),
            ('c1', 'title for s1', '<p>s2 content</p><img src="static/2.svg"/>'),
            ('c1', 'title for s1', '<p>s3 content</p>'),
            ('c2', 'title for s1', '<p>s1 content</p>'),
            ('c2', 'title for s1', '<p>s2 content</p>'),
            ('c2', 'title for s1', '<p>s3 content</p>'),
        ],
        path,
        introduction=('介绍', '<p>introduction content</p>'),
        cover_img=('cover.svg', None, img),
        images={
            ('1.svg', None, img),
            ('static/2.svg', None, img),
        }
    )
    print('saved at', path)

ctypes

Source Code

args.c

#include <stdio.h>

int main(int argc, char** argv){
  for(int i=0; i<argc; i++)
  printf("num: %5d, %s\n", i, * (argv + i));
  return argc;
}

gcc -olibargs.so -shared -fPIC args.c

args.py

from ctypes import *

handler = CDLL('./libargs.so')
handler.main.argtypes = (c_int, POINTER(POINTER(c_char)))
handler.main.restype = (c_int)

args = b'aoue .uao ainst nhn'.split()

argc = len(args)

argv = (POINTER(c_char) * argc)(* map(create_string_buffer, args))
h = handler.main(argc, argv)

print(h)

Result

python args.py

num:     0, aoue
num:     1, .uao
num:     2, ainst
num:     3, nhn
4

cython

将 py 编译为可执行文件

cython --embed -3 main.py -o main.c
gcc {% math_inline %}(python-config --cflags) {% endmath_inline %}(python-config --ldflags) ./main.c -o main

将 py 编译为可被 python import 的共享库

# setup.py
from distutils.core import setup
from Cython.Build import cythonize

setup(ext_modules=cythonize('/path/to/file.py'))

python setup.py build

然后可以直接把编译成的二进制 so 文件当作 py 脚本导入运行

keyboard-mouse

Python监听终端

import sys
import tty
import termios

def detectFocusedTerminal():
    fd = sys.stdin.fileno()
    old_settings = termios.tcgetattr(fd)
    try:
        tty.setraw(fd)
        char = sys.stdin.read(1)
    finally:
        termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
    return char

Python 全局捕获键盘

from evdev import InputDevice, list_devices
from select import select

def detectInputKey():
    dev = InputDevice('/dev/input/event4')
    print(dev)
    while True:
        select([dev], [], [])
        for event in dev.read():
            print("code:%s value:%s" % (event.code, event.value))

Python 全局捕获鼠标

import struct
mou = open("/dev/input/mice", "rb")

def m_event():
    m = mou.read(3)
    b = ord(m[0])
    bl = b & 0x1
    bm = (b & 0x4) > 0
    br = (b & 0x2) > 0
    x, y = struct.unpack("bb", m[1:])
    print("Left:%d, Middle: %d, Right: %d, x: %d, y: %d\n" % (bl, bm, br, x, y)          )

while(1):
m_event()
mou.close()

logging

Logging

Configuration of logrotate with g-zip compression. These log files could be viewed by lnav command.

import gzip
import logging
import logging.config
import os
import threading
from logging.handlers import BaseRotatingHandler

def init_log_writer_orig(debug_level=None, log_path=None, show_info=False):
    logging_cfg: dict = {
        'version': 1,
        'disable_existing_loggers': True,
        'formatters': {
            'standard': {
                'format': '%(asctime)s:%(levelname)s:%(name)s:%(message)s'
            },
        },
        'handlers': {
            'errhandler': {
                'level': 'WARNING',
                'formatter': 'standard',
                'class': 'logging.StreamHandler',
                'stream': 'ext://sys.stderr',
            },
        },
        'loggers': {
            '': {
                'handlers': ['errhandler'],
                'level': 'DEBUG',
                'propagate': True,
                'formatter': 'standard',
            },
        }
    }
    if log_path:
        logging_cfg['handlers']['filehandler'] = {
            'level': 'DEBUG',
            'formatter': 'standard',
            'class': 'logging.FileHandler',
            'filename': log_path,
        }
        logging_cfg['loggers']['']['handlers'].append('filehandler')
    if show_info:
        logging_cfg['handlers']['infohandler'] = {
            'level': 'INFO',
            'formatter': 'standard',
            'class': 'logging.StreamHandler',
            'stream': 'ext://sys.stdout',
        }
        logging_cfg['loggers']['']['handlers'].append('infohandler')
    if debug_level in ['CRITICAL', 'ERROR', 'WARNING', 'INFO', 'DEBUG', 'NOTSET', ]:
        logging_cfg['loggers']['']['level'] = debug_level

    logging.config.dictConfig(logging_cfg)
    logging.info(f'LOGGING_CFG: {logging_cfg}')

class CompressedRotationFileHandler(BaseRotatingHandler):
    def __init__(self, filename, mode='a', maxBytes=0, backupCount=0, encoding='utf-8'):
        if maxBytes > 0:
            mode = 'a'
        if not mode.endswith('t'):
            mode += 't'
        BaseRotatingHandler.__init__(self, filename, mode, encoding, delay=True)
        self.maxBytes = maxBytes
        self.backupCount = backupCount
        self.namer = lambda x: f"{x}.gz"
        self.basename = self.baseFilename

    def shouldRollover(self, record):
        self.baseFilename = '.'.join([self.basename, str(os.getpid()), str(threading.current_thread().native_id)])
        if self.stream is None:
            self.stream = self._open()
        if self.maxBytes > 0:
            msg = "%s\n" % self.format(record)
            if not os.path.exists(self.rotation_filename(self.baseFilename)):
                self.close()
                self._open()
                return 0
            if os.path.getsize(self.rotation_filename(self.baseFilename)) + len(msg) >= self.maxBytes:
                return 1
        return 0

    def doRollover(self):
        if self.stream:
            self.stream.close()
            self.stream = None
        if self.backupCount > 0:
            for i in range(self.backupCount - 1, 0, -1):
                sfn = self.rotation_filename("%s.%d" % (self.baseFilename, i))
                dfn = self.rotation_filename("%s.%d" % (self.baseFilename, i + 1))
                if os.path.exists(sfn):
                    if os.path.exists(dfn):
                        os.remove(dfn)
                    os.rename(sfn, dfn)
            dfn = self.rotation_filename(self.baseFilename + ".1")
            if os.path.exists(dfn):
                os.remove(dfn)
            self.rotate(self.rotation_filename(self.baseFilename), dfn)
        if not self.delay:
            self.stream = self._open()

    def _open(self):
        baseDir = os.path.dirname(self.rotation_filename(self.baseFilename))
        os.makedirs(baseDir, exist_ok=True)
        return gzip.open(self.rotation_filename(self.baseFilename), self.mode, encoding=self.encoding)

def init_log_writer(debug_level=None, log_path=None, log_size=15000):
    logging_cfg: dict = {
        'version': 1,
        'disable_existing_loggers': False,
        'formatters': {
            'standard': {
                'format': '%(asctime)s:%(levelname)s:%(name)s:%(message)s'
            },
        },
        'handlers': {
            'errhandler': {
                'level': 'WARNING',
                'formatter': 'standard',
                'class': 'logging.StreamHandler',
                'stream': 'ext://sys.stderr',
            },
        },
        'loggers': {
            '': {
                'handlers': ['errhandler'],
                'level': 'DEBUG',
                'propagate': True
            },
        },
        'filters': {
            'main': {
                '()': logging.Filter,
                'name': 'main',
            }
        }
    }
    if log_path:
        logging_cfg['handlers']['filehandler'] = {
            'level': 'DEBUG',
            'formatter': 'standard',
            'class': 'main.utils.log_writer.CompressedRotationFileHandler',
            'maxBytes': int(log_size * 1024 * 1024),
            'backupCount': 2,
            'filename': log_path,
            'mode': 'a',
            'filters': ['main'],
        }
        logging_cfg['loggers']['']['handlers'].append('filehandler')
        os.makedirs(os.path.abspath(os.path.dirname(log_path)), exist_ok=True)
    if debug_level in ['CRITICAL', 'ERROR', 'WARNING', 'INFO', 'DEBUG', 'NOTSET', ]:
        logging_cfg['loggers']['']['level'] = debug_level
    logging.config.dictConfig(logging_cfg)
    logging.getLogger(__name__).info('LOGGING_CONFIG: {}'.format(logging_cfg))

ncurses_example.py

import curses
import locale
from curses import textpad
from math import ceil

locale.setlocale(locale.LC_ALL, '')
code = locale.getpreferredencoding()

msgs = list()
stdscr = curses.initscr()
# curses.noecho()
curses.cbreak()

len_contact = 20
contacts = curses.newwin(curses.LINES, len_contact, 0, 0)
messages = curses.newwin(curses.LINES, curses.COLS - len_contact, 0, len_contact)

attrs = {'curses.A_BLINK': curses.A_BLINK,
         'curses.A_BOLD': curses.A_BOLD,
         'curses.A_DIM': curses.A_DIM,
         'curses.A_REVERSE': curses.A_REVERSE,
         'curses.A_STANDOUT': curses.A_STANDOUT,
         'curses.A_UNDERLINE': curses.A_UNDERLINE, }
for i, attr in enumerate(attrs):
    contacts.addstr(i+2, 1, attr, attrs[attr])
contacts.addstr(1, 1, '测试')
contacts.border()
messages.border()
textpad.rectangle(messages, curses.LINES-4, 1, curses.LINES-2, curses.COLS-len_contact-2)
stdscr.refresh()
contacts.refresh()
messages.refresh()
try:
    for index in range(100):
        msglen = curses.COLS-len_contact-4
        msgheight = curses.LINES - 5
        data = messages.getstr(curses.LINES - 3, 2, msglen).decode('utf-8')
        data = [['> ', '  '][i != 0] + data[msglen * i:msglen*(i+1)] for i in range(ceil(len(data)/msglen))]
        msgs.extend(data)

        for j, info in zip(range(msgheight), msgs[-(msgheight):]):
            messages.addstr(j+1, 1, ' ' * (curses.COLS - len_contact - 2))
            messages.addstr(j+1, 1, info)

        messages.addstr(curses.LINES-3, 2, ' ' * msglen)
        messages.border()
        contacts.border()
        textpad.rectangle(messages, curses.LINES-4, 1, curses.LINES-2, curses.COLS-len_contact-2)
        messages.refresh()
        contacts.refresh()
        messages.getch()
        curses.getmouse()
except KeyboardInterrupt:
    curses.endwin()

pandas

Append 到 Excel

writer = pd.ExcelWriter(path)
yc.to_excel(writer, sheetname)
writer.save()

qrcode_example.py

#!/usr/bin/env python
import qrcode

data = input('请输入需要转换成二维码的文字：')

# 生成二维码信息
qr = qrcode.QRCode()
qr.add_data(data)

# 选择显示二维码的方式
show = {
    '1': qr.make_image().show,
    '2': qr.print_ascii,
    '3': qr.print_tty,
}
while True:
    try:
        way = input('''\n二维码输出格式有：
        1: 图片
        2: ascii
        3: tty\n请选择二维码输出格式(按Ctrl+D退出)：''')
        if way in show:
            show[way]()
    except (EOFError, KeyboardInterrupt):
        print('作者的对象：小胖儿')
        exit(0)

socket

TCP

Server

bind -> listen -> accept -> conn.recv -> conn.send

import socket

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

sock.bind(("localhost", 8080))
sock.listen(5)

conn, addr = sock.accept()
data = conn.recv(1000)
print(data.decode('utf-8'))
conn.send(b'''HTTP/2 200 OK\nContent-Type: text/plain; charset=utf-8\n\n'''+data)
conn.close()

sock.close()

Client

connect -> send -> recv

import socket

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(('localhost', 8080))

sock.send(bytes(input(), 'utf-8'))
data = sock.recv(1000)
print(data.decode('utf-8'))

sock.close()

UDP

Server

bind -> recvfrom

import socket

address = ('127.0.0.1', 31500)
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind(address)
data, addr = sock.recvfrom(2048)
print(data.decode('utf-8'))
sock.close()

Client

sendto

import socket

address = ('127.0.0.1', 31500)
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.sendto(bytes(input(), 'utf-8'), address)
sock.close()

动态修改幻灯片内容

简介

PPTX 格式的文件本质上是一个 XML 格式的树，所以有个 python-pptx 的包可以解析和读写这个树。只要遍历这棵树就可以将里面相应的内容进行修改，并动态调整里面的内容。如果是图片或者图表，可以在模板里直接将内容关联到外部文件中，直接修改文件，幻灯片中的内容也将实时改变。https://blog.csdn.net/u011932355/article/details/73287248

import pptx

def xml2dict(layout):
    root = layout.element

    def traverse(root):
        children = root.getchildren()
        return {
            root.tag: {
                'children': [traverse(child) for child in children],
                'attrib': root.attrib,
                'text': root.text
            }
        }
    return traverse(root)

def xml2slimdict(layout, data_head='{http://schemas.openxmlformats.org/drawingml/2006/main}'):
    root = layout.element

    def traverse(root):
        children = root.getchildren()
        rst = dict()
        if children:
            rst['children'] = [traverse(child) for child in children]
        if root.text:
            rst['text'] = root.text
        if rst:
            return {root.tag.replace(data_head, ''): rst}
        else:
            return root.tag.replace(data_head, '')
    return traverse(root)

def tree_find(slide, to_find):
    route = list()
    routes = list()
    root = slide.element

    def traverse(root):
        for i, child in enumerate(root.getchildren()):
            route.append(i)
            if child.text == to_find:
                routes.append(route.copy())
            traverse(child)
            route.pop()
    traverse(root)
    routes = [''.join(['[{}]'.format(i) for i in j]) for j in routes]
    return routes

def fill_cm(ppt, page, cm, auc):
    ppt.slides[page - 1].element[0][0][8][2][2][3][1].text = auc
    ppt.slides[page - 1].element[0][0][4][2][0][0][4][2][0][2][1][1].text = cm[0][0]
    ppt.slides[page - 1].element[0][0][4][2][0][0][4][3][0][2][1][1].text = cm[0][1]
    ppt.slides[page - 1].element[0][0][4][2][0][0][5][2][0][2][1][1].text = cm[1][0]
    ppt.slides[page - 1].element[0][0][4][2][0][0][5][3][0][2][1][1].text = cm[1][1]
    return ppt

def main(page, cm, auc):
    ppt = pptx.Presentation('template.pptx')
    ppt = fill_cm(ppt, cm, auc)
    ppt.save('result.pptx')

Regular_Expression

正则表达式速查表

零宽断言

正向零宽断言

(?<=exp)exp(?=exp)

零宽正回顾后发断言 – 零宽正预测先行断言

负向零宽断言

(?<!exp)exp(?!exp)

零宽负回顾后发断言 – 零宽负预测先行断言

总结

正负 | 回顾 | 预测
正 | (?<=exp) | (?=exp)
负 | (?<!exp) | (?!exp)

在匹配表达式前面的叫回顾，后面叫预测。回顾要带小于号，预测不用。

正向为匹配到，负向为不能匹配到。正向为等号，负向为叹号。

其他

注释

(?#注释)

注：当启用“忽略模式中的空白字符”选项后，#后面的都作为注释

分组与捕获

带序号捕获 (exp) \1。

带名称捕获 (?<name>exp) \k<name>，其中尖括号可以用单引号代替。

不捕获匹配 (?:exp)，可用于匹配重复的模式，比如 (?:[\d]{6})+ 匹配 1/7 循环体的整数倍。

贪婪匹配

默认是贪婪匹配，在能匹配多个的表达式后面加上?即可变为非贪婪匹配。

Design

原则

单一原则：一个类应当仅有一个引起它变化的原因（高内聚）

里氏替换原则：子类要对父类完全向上兼容。

依赖倒置原则：抽象不应该依赖于细节，细节应该依赖于抽象。应当首先规划接口，面对接口实现功能，而不是先做好功能再设计接口。

接口隔离原则：实现类不该依赖于它用不到的接口。应当拆分庞大臃肿的接口为更小和更具体的接口，一个接口不该做很多事情处理很多情况。

迪米特原则：最少知识原则，分层解耦。每个单元只能与它的直接朋友交谈，好比路由器只能与自己1-hop的路由器直接通信。

开闭原则：软件实体应当对扩展开放，对修改封闭。

Factory_Pattern

工厂模式

由工厂模式给出的启发：

当一个类功能很多，且十分复杂，就需要对该类进行功能模块拆分，而拆分后的类需要装配，使用工厂模式就十分合适。

工厂模式可以很容易适应扩展，只要产品中的每个零件都是标准件，即同类型子类接口设计高度可替换，就可以把所有零件之间的耦合限制在工厂内部。

工厂模式的核心在于装配，工厂不需要关心标准件是怎么制造的，只需要关心标准件之间如何组合在一起。如果工厂需要做一些很复杂的操作，说明需要为工厂提供更多标准件来完成复杂功能。

Learning

快速掌握一个语言最常用的50%

摘抄自快速掌握一个语言最常用的50%

现在的开发工作要求我们能够快速掌握一门语言。一般来说应对这种挑战有两种态度：其一，粗粗看看语法，就撸起袖子开干，边查Google边学习；其二是花很多时间完整地把整个语言学习一遍，做到胸有成竹，然后再开始做实际工作。然而这两种方法都有弊病。第二种方法的问题当然很明显，不仅浪费了时间，偏离了目标，而且学习效率不高。因为没有实际问题驱动的语言学习通常是不牢固不深入的。有的人学着学着成了语言专家，反而忘了自己原本是要解决问题来的。第一种路子也有问题，在对于这种语言的脾气秉性还没有了解的情况下大刀阔斧地拼凑代码，写出来的东西肯定不入流。说穿新鞋走老路，新瓶装旧酒，那都是小问题，真正严重的是这样的程序员可以在短时间内堆积大量充满缺陷的垃圾代码。由于通常开发阶段的测试完备程度有限，这些垃圾代码往往能通过这个阶段，从而潜伏下来，在后期成为整个项目的毒瘤，反反复复让后来的维护者陷入西西弗斯困境。

实际上语言学习有一定规律可循，对于已经掌握一门语言的开发者来说，对于一般的语言，完全可以以最快的速度，在几天至一周之内掌握其最常用的50%，而且保证路子基本正宗，没有出偏的弊病。其实真正写程序不怕完全不会，最怕一知半解的去攒解决方案。因为你完全不会，就自然会去认真查书学习，如果学习能力好的话，写出来的代码质量不会差。而一知半解，自己动手土法炼钢，那搞出来的基本上都是废铜烂铁。比如错误处理和序列化，很多人不去了解“正路子”，而是凭借自己的一知半解去攒野路子，这是最危险的。因此，即使时间再紧张，这些内容也是必须首先完整了解一遍的。掌握这些内容之后进入实际开发，即使有问题，也基本不会伤及项目大体。而开发者本人则可以安步当车，慢慢在实践中提高自己。

以下列出一个学习提纲，主要针对的是有经验的人，初学者不合适。这个提纲只能用于一般的庸俗编程语言学习，目前在流行编程语言排行榜上排前20的基本上都是庸俗语言。如果你要学的是LISP之类非庸俗语言，或是某个软件中的二次开发语言，这里的建议未必合适。还是那句话，仅供参考。

首先了解该语言的基本数据类型，基本语法和主要语言构造，主要数学运算符和print函数的使用，达到能够写谭浩强程序设计书课后数学习题的程度；

其次掌握数组和其他集合类的使用，有基础的话可以理解一下泛型，如果理解不了也问题不大，后面可以补；

简单字符串处理。所谓简单，就是Regex和Parser以下的内容，什么查找替换，截断去字串之类的。不过这个阶段有一个难点，就是字符编码问题。如果理解不了，可以先跳过，否则的话最好在这时候把这个问题搞定，免留后患；

基本面向对象或者函数式编程的特征，无非是什么继承、多态、Lambda函数之类的，如果有经验的话很快就明白了；

异常、错误处理、断言、日志和调试支持，对单元测试的支持。你不一定要用TDD，但是在这个时候应该掌握在这个语言里做TDD的基本技能；

程序代码和可执行代码的组织机制，运行时模块加载、符号查找机制，这是初学时的一个难点，因为大部分书都不太注意介绍这个极为重要的内容；

基本输入输出和文件处理，输入输出流类的组织，这通常是比较繁琐的一部分，可以提纲挈领学一下，搞清楚概念，用到的时候查就是了。到这个阶段可以写大部分控制台应用了；

该语言如何进行callback方法调用，如何支持事件驱动编程模型。在现代编程环境下，这个问题是涉及开发思想的一个核心问题，几乎每种语言在这里都会用足功夫，.NET的delegate，Java的anonymous inner class，Java 7的closure，C++OX的 tr1::function/bind，五花八门。如果能彻底理解这个问题，不但程序就不至于写得太走样，而且对该语言的设计思路也能有比较好的认识；

如果有必要，可在这时研究regex和XML处理问题，如无必要可跳过；

序列化和反序列化，掌握一下缺省的机制就可以了；

如果必要，可了解一下线程、并发和异步调用机制，主要是为了读懂别人的代码，如果自己要写这类代码，必须专门花时间严肃认真系统地学习，严禁半桶水上阵；

动态编程，反射和元数据编程，数据和程序之间的相互转化机制，运行时编译和执行的机制，有抱负的开发者在这块可以多下些功夫，能够使你对语言的认识高出一个层面；

如果有必要，可研究一下该语言对于泛型的支持，不必花太多时间，只要能使用现成的泛型集合和泛型函数就可以了，可在以后闲暇时抽时间系统学习。需要注意的是，泛型技术跟多线程技术一样，用不好就成为万恶之源，必须系统学习，谨慎使用，否则不如不学不用；

如果还有时间，最好咨询一下有经验的人，看看这个语言较常用的特色features是什么，如果之前没学过，应当补一下。比如Ruby的block interator, Java的dynamic proxy，C# 3的LINQ和extension method。没时间的话，我认为也可以边做边学，没有大问题。

有必要的话，在工作的闲暇时间，可以着重考察两个问题，第一，这个语言有哪些惯用法和模式，第二，这个语言的编译/解释执行机制。

至此语言的基本部分就可以说掌握了，之后是做数据库、网络还是做图形，可以根据具体需求去搞，找相应的成熟框架或库，边做边学，加深理解。对于一个庸俗语言，我自己把上面的内容走一遍大概要花2-3周时间，不能算很快，但也耽误不了太多事情，毕竟不是每个月都学新语言。掌握了以上的内容，就给练武术打好了基本功，虽然不见得有多优秀，但是肯定是根正苗红，将来不必绕大弯子。就算是临时使用的语言，把上面这个提纲精简一下，只看蓝色重体字的部分，大致能在几天到一周内搞定，不算是太耗时，而且写出来的代码不会太不靠谱。

以上提纲未设及内存模型。对于C/C++，这个问题很重要，要放在显著位置来考虑，但对于其他语言，这个问题被透明化了，除非你要做hardcore项目，否则不必太关注。

NatrualLanguageProcessing

NLP输入样本长度不是越长约好，太长反而破坏训练过程

RNN这种自回归模型信息量随着信息流过神经元的次数每次都发生指数衰减，衰减得太快了，即便用上LSTM也只是相当于长短记忆指数衰减率不同，但指数衰减规则没变

1_Nadaraya_Watson_Kernel_Regression

Attention Pooling: Nadaraya-Watson Kernel Regression

NW Kernel Regression

现有样本(x1,y1),(x2,y2),...(x_1, y_1), (x_2, y_2), ...(x1,y1),(x2,y2),...，对于变量xxx，想估计yyy，如果直接通过数据推导yyy的值，其实就可以用变量与样本接近程度x−xix-x_ix−xi产生一个权重，对yiy_iyi加权。

这么说比较抽象，核心思想是如果假设样本是从光滑曲线上采样得到，那么就可以认为x−xix-x_ix−xi越接近0则yiy_iyi的参考意义越大。时用一个核函数K(x−xi)K(x-x_i)K(x−xi)生成权重，则可建立这个通用模型： f(x)=∑iK(x−xi)∑jK(x−xj)yi f(x)=\sum\limits_i\frac{K(x-x_i)}{\sum\limits_jK(x-x_j)}y_i f(x)=i∑j∑K(x−xj)K(x−xi)yi 当然，其实不乘以yiy_iyi得到的就是权重，显然对于所有xix_ixi算出的权重加和等于1。

正常的NW核回归计算权重时一般不用全部xix_ixi，而是用滑动窗口，毕竟全都计算成本太高。为了描述起来简单，这里不考虑滑动窗口。

我们启发式地认为x−xix-x_ix−xi越接近于0越好，正态分布的钟形曲线刚好符合随机变量越接近0值越大的特点，使用该钟形曲线生成权重估计yyy是很符合直觉的（但我认为NM核回归直接做加权对于很多分布来说都是有偏的，比如对于正弦曲线这样的波动函数来讲会导致估计的值偏向0，毕竟均值会减小方差）。因此核函数使用： K(u)=12πe−u22 K(u)=\frac{1}{\sqrt{2\pi}}e^{-\frac{u^2}{2}} K(u)=2π1e−2u2 代入到最上面的公式(1)后，约掉常数项可以得到 f(x)=∑isoftmax(−12(x−xi)2)yi f(x)=\sum\limits_isoftmax\left(-\frac{1}{2}(x-x_i)^2\right)y_i f(x)=i∑softmax(−21(x−xi)2)yi 本质上就是使用偏差平方相反数的softmax作为权重，偏差越小权重越大。

我们可以加一个缩放系数www，乘在x−xix-x_ix−xi上，得到： f(x)=∑isoftmax(−12((x−xi)w)2)yi f(x)=\sum\limits_isoftmax\left(-\frac{1}{2}\left((x-x_i)w\right)^2\right)y_i f(x)=i∑softmax(−21((x−xi)w)2)yi

这本质上相当于高斯核的uuu变成uwuwuw，可以看出www越大，钟形曲线越陡峭，约注重局部信息；相反www越小则越看重全局信息。

NW Kernel Regression to Attention

用query(xxx)，从keys(xix_ixi)查询value(yiy_iyi)，就可以得到attention并估计yyy了。

import pylab

def softmax(arr):
    exp = pylab.exp(arr)
    return exp / exp.sum()

x_i = pylab.sort(pylab.rand(50) * 6)
y_true = pylab.sin(x_i) + x_i / 3  # y=sin(x)+x/3
y_i = y_true + (pylab.rand(*y_true.shape) - pylab.rand(*y_true.shape)) * 0.5

def predict(x, w=1):
    attention = softmax(-1 / 2 * ((x - x_i) * w) ** 2)
    y = sum(attention * y_i)
    return y

x_test = pylab.linspace(0, 6, 50)
pylab.scatter(x_i, y_i, color='black', alpha=0.1, label='y_true')
pylab.plot(x_i, y_true, 'k-', lw=.3)
for w in (1, 3, 10):
    y_pred = [predict(x, w=w) for x in x_test]
    pylab.plot(x_test, y_pred, label=f"{w=}")

pylab.legend()
pylab.tight_layout()
pylab.show()

2_attention

Attention

复习一下上一篇文章，现有一组数据集(xi,yi)(x_i, y_i)(xi,yi)，其中xix_ixi作为key, yiy_iyi作为value，对于新来的xxx作为query，估计yyy，就是attention的基本模型。对于一个窗口内的全部(xy,yi)(x_y, y_i)(xy,yi)，可以通过加权的方式估计yyy： y^=∑iα(x,xi)yi \hat{y}=\sum\limits_i\alpha(x,x_i)y_i y^=i∑α(x,xi)yi 其中，α(x,xi)\alpha(x, x_i)α(x,xi)就可以看作attention。注意到上一篇的注意力权重为： f(x)=∑isoftmax(−12((x−xi)w)2)yi f(x)=\sum\limits_isoftmax\left(-\frac{1}{2}\left((x-x_i)w\right)^2\right)y_i f(x)=i∑softmax(−21((x−xi)w)2)yi 可以单独把softmax的输入抽象出来，作为注意力评分函数，有： f(x)=∑isoftmax(a(x,xi))yia(x,xi)=−12((x−xi)w)2 \begin{array}{rcl} f(x)&=&\sum\limits_isoftmax\left(a(x,x_i)\right)y_i\\ a(x,x_i)&=&-\frac{1}{2}\left((x-x_i)w\right)^2 \end{array} f(x)a(x,xi)==i∑softmax(a(x,xi))yi−21((x−xi)w)2 后面各种attention只需要在注意力评分函数a(x,xi)a(x,x_i)a(x,xi)上面做文章即可。注意力评分函数a(q,k)∈Ra(\mathbf{q},\mathbf{k})\in\mathbb{R}a(q,k)∈R输入向量q\mathbf{q}q和k\mathbf{k}k，输出一个实数，一个q\mathbf{q}q与多个k\mathbf{k}k求得的注意力评分通过softmax后得到注意力权重，与yiy_iyi加权求和便可预测y^\hat{y}y^。

可以看出，注意力评分其实本质上可以看作一种相似度的度量，q\mathbf{q}q与k\mathbf{k}k越接近则评分越大。

Additive attention

对于query(xxx)和key(xix_ixi)是不同维度矢量时，可以使用additive attention，通过参数化矩阵分别与q\mathbf{q}q和k\mathbf{k}k相乘变换到相同维度hhh，然后便可求和（理解成做差也可以），最后通过与向量wv\mathbf{w}_vwv求积得到一个标量作为权重。给定q∈Rq\mathbf{q}\in\mathbb{R}^qq∈Rq和k∈Rk\mathbf{k}\in\mathbb{R}^kk∈Rk，注意力评分函数为： a(q,k)=wvTtanh(Wqq+Wkk)∈R a(\mathbf{q}, \mathbf{k}) = \mathbf{w}_v^Ttanh(\mathbf{W}_q\mathbf{q}+\mathbf{W}_k\mathbf{k})\in\mathbb{R} a(q,k)=wvTtanh(Wqq+Wkk)∈R 其中，参数尺寸为：wv∈Rh,Wq∈Rh×q,Wk∈Rh×k\mathbf{w}_v\in\mathbb{R}^{h}, \mathbf{W}_q\in\mathbb{R}^{h\times q}, \mathbf{W}_k\in\mathbb{R}^{h\times k}wv∈Rh,Wq∈Rh×q,Wk∈Rh×k，乘起来就是[1×n]=[1×h]⋅tanh([h×q]⋅[q×n]+[h×k]⋅[k×n])[1\times n] = [1\times h]\cdot tanh\left([h\times q] \cdot [q\times n] + [h\times k]\cdot[k\times n]\right)[1×n]=[1×h]⋅tanh([h×q]⋅[q×n]+[h×k]⋅[k×n])

参数量为h×(1+k+q)h\times(1+k+q)h×(1+k+q)。矩阵相乘可以等价于偏置为0的单个Linear层，这也是常见的工程实现方式。

import torch
from torch import nn

class AdditiveAttention(nn.Module):
    def __init__(self, q_size, k_size, h, dropout=0.5, **kwargs):
        super().__init__(**kwargs)
        self.W_q = nn.Linear(q_size, h, bias=False)
        self.W_k = nn.Linear(k_size, h, bias=False)
        self.w_v = nn.Linear(h, 1, bias=False)
        self.dropout = nn.Dropout(dropout)

    def forward(self, query, key, value):
        # query: [batch_size, n_q, q_size]
        # key: [batch_size, n_kv, k_size]
        # value: [batch_size, n_kv, v_size]

        query, key = self.W_q(query), self.W_k(key)
        # query: [batch_size, n_q, h]
        # key: [batch_size, n_kv, h]

        features = torch.tanh(query.unsqueeze(2) + key.unsqueeze(1))
        # features: [batch_size, n_q, n_kv, h] = [batch_size, n_q, 1, h] + [batch_size, 1, n_kv, h]

        scores = self.w_v(features).squeeze(-1)
        # scores: [b, n_q, n_kv]

        self.attention = nn.functional.softmax(scores, dim=-1)  # softmax on each key-value pair
        # attention: [b, n_q, n_kv]

        result = torch.bmm(self.dropout(self.attention), value)
        # result: [batch_size, n_q, v_size]
        return result

Scaled dot-product attention

如果q\mathbf{q}q和k\mathbf{k}k维度同时为ddd，直接使用向量内积即可作为注意力评分函数，我们知道内积本质上就是余弦相似度。为了规范化方差，将内积除以d\sqrt{d}d即可，有： a(q,k)=qTkd a(\mathbf{q},\mathbf{k})=\frac{\mathbf{q}^T\mathbf{k}}{\sqrt{d}} a(q,k)=dqTk 使用矩阵批量计算，则对于 query: Q∈Rn×d\mathbf{Q}\in\mathbb{R}^{n\times d}Q∈Rn×d，key: K∈Rm×d\mathbf{K}\in\mathbb{R}^{m\times d}K∈Rm×d和 value: V∈Rm×v\mathbf{V}\in\mathbb{R}^{m\times v}V∈Rm×v，注意力为： softmax(QKTd)V∈Rn×v softmax\left(\frac{\mathbf{Q}\mathbf{K}^T}{\sqrt{d}}\right)\mathbf{V}\in\mathbb{R}^{n\times v} softmax(dQKT)V∈Rn×v 这种注意力机制没有参数，运算过程也很简单，比较快，实现容易。

import torch
from torch import nn

class DotProductAttetnion(nn.Module):
    def __init__(self, dropout=0.5, **kwargs):
        super().__init__(**kwargs)
        self.dropout = nn.Dropout(dropout)

    def forward(self, query, key, value):
        # query: [batch_size, n_q, d]
        # key: [batch_size, n_kv, d]
        # value: [batch_size, n_kv, v_size]
        d = torch.tensor(query.shape[-1])

        scores = torch.bmm(query, key.transpose(-1,-2)) / torch.sqrt(d)
        # scores: [b, n_q, n_kv] = [b, n_q, d] @ [b, d, n_kv]

        self.attention = nn.functional.softmax(scores, dim=-1)  # softmax on each key-value pair
        # attention: [b, n_q, n_kv]

        result = torch.bmm(self.dropout(self.attention), value)
        # result: [batch_size, n_q, v_size]
        return result

推广

只要能构造出相同形状的注意力评分，要么参数化要么使用相似性度量，就可以构造出一种注意力机制。比方说使用参数化矩阵Wqk∈Rq×k\mathbf{W}_{qk}\in\mathbb{R}^{q\times k}Wqk∈Rq×k，设计如下注意力评分函数： a(q,k)=qTWqkkq×k4 a(\mathbf{q}, \mathbf{k})=\frac{\mathbf{q}^TW_{qk}\mathbf{k}}{\sqrt[4]{q\times k}} a(q,k)=4q×kqTWqkk 就可以用与内积注意力相似的计算方式，在q\mathbf{q}q和k\mathbf{k}k维度不同的情况下计算注意力了。

transformer

d2l.ai-transformer

变压器的结构本身比较简单，分为如下结构：

input encoding

embedding

positional encoding

encoder blocks

residual -> multihead attention -> layer normalization

residual -> feed forward -> layer normalization

decoder blocks

residual -> masked multihead attention -> layer normalization

residual -> multihead attention -> layer normalization

residual -> feed forward -> layer normalization

output:

feed forward

所以核心部件如下：

embedding

positional encoding

residual mechanism

(masked) multihead attention

layer normalization

feed forward net

word2vec

Word2Vec

原因：

one-hot的余弦相似度没有意义

降维

原理：

挖掘预料中词汇出现顺序之间的关联，需要注意的是word2vec只关注上下文的关联性，对上下文中的顺序一视同仁。

类别：

Continuous Bag of Words: 上下文 -> 自己

Skip-Gram: 自己 -> 上下文（其实也等价于自己->整个句子，毕竟P(x∣x)=1P(x|x)=1P(x∣x)=1）

Skip-Gram推导比较简单，据说性能也比CBOW好，此处就略过CBOW。

算法细节

首先定义词向量相似度为余弦相似度，假设词向量的模相同（起码随机初始化的情况下，模的范围差别不会太大），所以可以等价与算向量内积uoTvc\mathbf{u}_o^T\mathbf{v}_cuoTvc

所以，就可以定义矩阵WWW，其中one-hot编码的词向量wTW=vw^T W= \mathbf{v}wTW=v

然后定义词汇之间的条件概率，给定wcw_cwc的条件下wow_owo发生的概率（其实就是先算余弦相似度然后连上softmax）：

P(wo∣wc)=euoTvc∑ieuiTvc P(w_o|w_c) = \frac{e^{\mathbf{u}_o^T\mathbf{v}_c}}{\sum\limits_ie^{\mathbf{u}_i^T\mathbf{v}_c}} P(wo∣wc)=i∑euiTvceuoTvc

接下来，假设上下文的词汇相对于当前词汇条件独立：

P(wo1,wo2,…∣wc)=P(wo1∣wc)P(wo2∣wc)… P(w_{o1},w_{o2}, \ldots |w_c) = P(w_{o1}|w_c)P(w_{o2}|w_c) \ldots P(wo1,wo2,…∣wc)=P(wo1∣wc)P(wo2∣wc)…

那么对语料库滑窗获取数据集，并最大化Skip-Gram概率：

∏t∏−m≤j≤mP(wt+j∣wt) \prod \limits_{t} \prod \limits _{-m\leq j \leq m}P(w^{t+j}|w^t) t∏−m≤j≤m∏P(wt+j∣wt)

这里我没说j≠0j\neq0j=0，因为当j=0j=0j=0时P=1P=1P=1，写不写都讲等价。

用极大似然法最大化这个概率，取对数后加负号，得到：

J=−∑t∑−m≤j≤mlogP(wt+j∣wt) J=-\sum\limits_t\sum\limits_{-m\leq j \leq m} logP(w^{t+j}|w^t) J=−t∑−m≤j≤m∑logP(wt+j∣wt)

log⁡P(wo∣wc)=uoTvc−log⁡(∑ieui⊤vc) \log P(w_o \mid w_c) =\mathbf{u}_o^T \mathbf{v}_c - \log\left(\sum_{i} e^{\mathbf{u}_i^\top \mathbf{v}_c}\right) logP(wo∣wc)=uoTvc−log(i∑eui⊤vc)

此处i是对所有词向量加和，每次算梯度要遍历所有词向量比较贵，所以用负采样（感觉也可以叫欠采样），上面的公式后面那项不是求与所有向量相似度之和，而是求随机n个向量相似度之和，渐进意义上是等价的（其实也不等价，还要乘上n_words/n）

nn_mnist.py

"""这个代码还有小问题，不过不想整了
问题1：里面不该用numpy array，而是应该用mat，否则计算结果有些地方会很令人困惑
问题2：反向传播好像有地方写的不对，如果有隐含层就会不收敛
问题3：梯度消失，一直没有解决
"""
import pdb
from pylab import *
from tensorflow.examples.tutorials import mnist

data = mnist.input_data.read_data_sets('MNIST_data', one_hot=True)

def show_pic(image_data, index):
    if len(image_data.shape) == 2:
        imshow(reshape(image_data[index, :], [28, 28]))
    elif len(image_data.shape) == 1:
        imshow(reshape(image_data, [28, 28]))
    elif len(image_data.shape) == 3:
        imshow(image_data[index, :, :])

class mnist_net(object):

    def __init__(self, shape, data):
        self.data = data
        self.shape = shape
        self.weights = [0.1 * randn(a, b) for a, b in zip(shape[:-1], shape[1:])]
        self.biases = [zeros([1, a]) for a in shape[1:]]

    def sigmoid(self, z, derivative=False):
        sig = 1 / (1 + exp(-z))
        if not derivative:
            return sig
        else:
            return sig * (1 - sig)

    def fp(self, inputs):
        ai = inputs
        self.z = []
        self.a = []
        for w, b in zip(self.weights, self.biases):
            self.a.append(ai)
            zi = ai.dot(w) + b
            self.z.append(zi)
            ai = self.sigmoid(zi)
        return ai

    def bp_step(self, batch=1, learning_rate=0.01):
        self.delta = []
        inputs, t = self.data.train.next_batch(batch)
        y = self.fp(inputs)
        d = -(y - t) * self.sigmoid(y, derivative=True)
        d = ones([1, batch]).dot(d) / batch

        for w, b, z, a in zip(self.weights[::-1], self.biases[::-1], self.z[::-1], self.a[::-1]):
            self.delta.insert(0, d)
            d = d.dot(w.T) * self.sigmoid(a, derivative=True)
            d = ones([1, batch]).dot(d) / batch

        self.gradw = []
        self.gradb = []
        for a, d in zip(self.a, self.delta):
            self.gradw.append(ones([1, batch]).dot(a).T.dot(d) / batch)
            self.gradb.append(d)

        for i, (gw, gb) in enumerate(zip(self.gradw, self.gradb)):
            self.weights[i] += learning_rate * gw
            self.biases[i] += learning_rate * gb

    def training(self, times=150, batch=1, learning_rate=0.1):
        correct_prediction = equal(argmax(self.fp(data.test.images), 1), argmax(data.test.labels, 1))
        accuracy = mean(correct_prediction, axis=0)
        print(accuracy)
        for i in range(times):
            self.bp_step(batch, learning_rate)
            if i % 50 == 0:
                correct_prediction = equal(argmax(self.fp(data.test.images), 1), argmax(data.test.labels, 1))
            accuracy_test = mean(correct_prediction, axis=0)
            correct_prediction = equal(argmax(self.fp(data.train.images), 1), argmax(data.train.labels, 1))
            accuracy_train = mean(correct_prediction, axis=0)
            print('%2.1f'%(i*100/times), accuracy_train, accuracy_test)

net = mnist_net([784, 15, 10], data)
net.training()
for i in net.gradw:
    print(i)
    print(i.shape)

def s(data):
    for i in data:
        print(i.shape)

nn_mnist_static.py

import pdb
from pylab import *
from tensorflow.examples.tutorials import mnist

data = mnist.input_data.read_data_sets('MNIST_data', one_hot=True)

def show_pic(image_data, index):
    if len(image_data.shape) == 2:
        imshow(reshape(image_data[index, :], [28, 28]))
    elif len(image_data.shape) == 1:
        imshow(reshape(image_data, [28, 28]))
    elif len(image_data.shape) == 3:
        imshow(image_data[index, :, :])

class mnist_net(object):

    def __init__(self, shape, data):
        self.data = data
        self.W0 = np.mat(np.random.randn(784, 15) * .1)
        self.W1 = np.mat(np.random.randn(15, 10) * .1)
        self.b0 = np.mat(np.zeros([1, 15]))
        self.b1 = np.mat(np.zeros([1, 10]))

    def sigmoid(self, z, derivative=False):
        sig = 1 / (1 + exp(-z))
        if not derivative:
            return sig
        return np.multiply(sig, (1 - sig))

    def fp(self, inputs):
        self.l0 = np.mat(inputs)
        self.z1 = inputs.dot(self.W0) + self.b0
        self.l1 = self.sigmoid(self.z1)
        self.z2 = self.l1.dot(self.W1) + self.b1
        self.l2 = self.sigmoid(self.z2)
        return self.l2

    def bp_step(self, batch=1, learning_rate=1, lr_ampl=1):
        inputs, t = self.data.train.next_batch(batch)
        y = self.fp(inputs)
        self.d2 = np.multiply(-(y - t), self.sigmoid(self.z2, derivative=True)).mean(axis=0)

        self.d1 = np.multiply(self.d2.dot(self.W1.T).mean(axis=0), self.sigmoid(self.z1, derivative=True)).mean(axis=0)

        self.gradw1 = self.l1.mean(axis=0).T.dot(self.d2)
        self.gradw0 = self.l0.mean(axis=0).T.dot(self.d1)
        self.gradb1 = self.d2
        self.gradb0 = self.d1

        self.W1 += self.gradw1 * learning_rate
        self.W0 += self.gradw0 * learning_rate * lr_ampl
        self.b1 += self.gradb1 * learning_rate
        self.b0 += self.gradb0 * learning_rate

    def training(self, times=35000, batch=100, learning_rate=3):
        correct_prediction = equal(argmax(self.fp(data.test.images), 1), np.mat(argmax(data.test.labels, 1)).T)
        accuracy = correct_prediction.mean()
        print(accuracy)
        for i in range(times):
            self.bp_step(batch, learning_rate)
            if i % 500 == 0:
                correct_prediction = equal(argmax(self.fp(data.test.images), 1), np.mat(argmax(data.test.labels, 1)).T)
                accuracy_test = correct_prediction.mean()
                print('%2.1f'%(i*100/times), accuracy_test)

net = mnist_net([784, 15, 10], data)
net.training()
for i in [net.gradw0, net.gradw1, net.W0, net.W1]:
    print(i)
    print(i.shape)

def s(data):
    for i in data:
        print(i.shape)

ReinforcementLearning

Introduction

强化学习与其他机器学习方法非常不同。有监督学习类似于数据库检索和近邻匹配，需要外在的帮助（打标签）；无监督学习本质上只是对数据分布做一些估计和处理；而强化学习最核心的目标是通过尝试与环境做交互来学到一个策略，不一定需要人工手把手教agent做事情（有监督学习），即便手把手教（抛砖）过也能通过探索找到更好的问题解决方案（引玉）。

Environment Model

综上所述，强化学习的模型就包括两部分，一个是环境，另一个是用于和环境交互的agent。不同的环境存在着不同特性，对于环境的不同特性，不同学习方法的学习效率也会有很大不同：

确定性，就好比牛顿力学三定律描述下的简单系统，输入和内在状态确定那么输出就确定，这就意味着一方面来说，不必在相同状况下与环境做出相同的交互，因为结果必定相同；另一方面知识永不过时，不必更新。

不平衡性，靠随机探索只会有极低概率成功，比如下棋、starcraft、解魔方、机械臂抓取物品等，这种问题如果不依赖专家经验的指导或逻辑推理，纯靠随机探索怎么做都是错，肯定还是什么都学不会

随机性，比如赌博或购买彩票，对于相同的状态采取相同动作结果会有所不同（掷骰子），需要在相同状态下多次作出相同交互，来估计不同动作的好坏。

时变性，好比股票交易市场，股票涨跌是随机的，影响股票价格的规则也是随时间在发生改变的。这个过程中有几种方案：

通过大量与环境的交互跟随上环境的变化（活到老学到老，但是很难）

在改变中发现其中的不变性（透过现象看到本质，万变不离其宗，更难）

微元法，如果在短时间内环境不容易出现剧烈变化，就可以把短时间内的环境当作时不变环境处理（当然这就需要不断更新agent了，很麻烦）

部分可观测性，造成结果的原因只能被观测到一小部分，环境的内部状态就变成了未知，对于即便是确定的环境，在agent看来也不再确定。

agent是通过与环境交互学习的，那么最常见的环境模型包含三个接口：

观测，agent从环境中观测到信息

动作，agent基于观测到的信息对环境作出反应

回报，环境对agent的动作给出评价

在控制理论中，环境模型常用这种方式描述： x(k+1)=fA(x(k))+fB(u(k))y(k)=fC(x(k))+fD(u(k)) \begin{array}{rl} x(k+1) &= f_A(x(k))+f_B(u(k))\\ y(k) &= f_C(x(k))+f_D(u(k)) \end{array} x(k+1)y(k)=fA(x(k))+fB(u(k))=fC(x(k))+fD(u(k)) 其中：

x(k)表示k时刻的环境内部状态，RL中常用符号为s(t)

y(k)表示k时刻的观测信息，RL中常用符号为o(t)；可以用同样的方法算出回报信息，RL中常用符号为r(t)

u(k)表示k时刻agent的动作，RL中常用符号为a(t)

四个f表示四种函数，它们可能是时变的，随机的，多输入多输出的

Deep_Reinforcement_Learning

强化学习

强化学习的优势

期望回报：优化序列决策过程中的一条轨迹中每个动作叠加在一起的累计回报，考虑全局最优而非贪心单次最优。

得不到动作指导信号，只有动作评价信号。只能靠试错学习，没有专家指导。或者已经有了专家指导，希望超越专家。

适合解决什么问题

固定场景：状态空间不大，整个trajectory不长

问题不复杂：没有太多层次化的任务目标，reward好设计

试错成本低：咋作都没事

数据收集容易：百万千万级别的数据量，如果不能把数据收集做到小时级别，整个任务的时间成本就不能与传统的监督学习相比

目标单纯：容易被reward function量化，比如各种super-human的游戏。对于一些复杂的目标，比如几大公司都在强调拟人化，目前没有靠谱的解决方案

强化学习的问题

灾难性遗忘：同时数据分布在训练过程中会发生显著变化

credit assign：动作空间不能太大

reward强依赖：必须容易设计reward function

对抗噪声干扰能力不足：obs有噪声，reward更有噪声，直接导致训练无法收敛

mdp假设过强，对于pomdp问题效果大打折扣

强化学习训练指标

采样动作分布

Entropy

Q-function Loss

Q-function vs Return

Q1-Q2

Reward vs Return

Q-Learning

Q-Learning

Q: 为什么Q-Learning不用重要性采样？

首先，

vπ(s)=Eπ[Gt∣St=s]v_\pi(s)=\mathbb E_\pi[G_t|S_t=s]vπ(s)=Eπ[Gt∣St=s]

qπ(s,a)=Eπ[Gt∣St=s,At=a]q_\pi(s,a)=\mathbb E_\pi[G_t|S_t=s, A_t=a]qπ(s,a)=Eπ[Gt∣St=s,At=a]

注意到Q-learning要学习的是Q(s,a)=∑s′P(s′∣s,a)(R(s′)+γmax⁡aQ(s′,a))Q(s,a)=\sum\limits_{s'}P(s'|s,a)\left(R(s')+\gamma \max\limits_{a}Q(s',a)\right)Q(s,a)=s′∑P(s′∣s,a)(R(s′)+γamaxQ(s′,a))

与qπ(s,a)q_\pi(s,a)qπ(s,a)不同，此处s′∼P(s′∣s,a)s'\sim P(s'|s,a)s′∼P(s′∣s,a)，求期望过程中动作aaa不是随机变量，而是给定的值，因此不涉及重要性采样。

Value_Based

基于值函数的强化学习

Q: Q值初始值对于强化学习的影响有哪些？

A: 见(Sutton, C2.6 乐观初始值)，对于平稳问题，让初始Q更高可以奖励探索。

Variance

强化学习过程中的方差

Q: 什么是方差

A: 训练过程中的策略梯度或td-error的方差。

训练过程中，在不同sss下有着不同的rrr的分布，有的状态本身很好导致所有动作都有高回报，同时有的状态所有动作都有低回报。高回报的状态下所有动作都会得到高奖励带来的大梯度，而低回报下所有动作都会得到小奖励带来的小梯度。

在训练过程中，相同状态下动作的相对好坏是确定的，但要反复遍历完全部动作才能成功估计某个动作相对其他动作的好坏。比如存在如下两个状态和两个动作，以及相应的回报：

rrr | s1s_1s1 | s2s_2s2
a1a_1a1 | 100 | 1
a2a_2a2 | 101 | 0

一次训练过程中经过s1,a1→s2,a1s_1, a_1\rightarrow s_2, a_1s1,a1→s2,a1得到的奖励序列为[100, 1]，梯度大小与奖励正相关，因此梯度的变化非常剧烈，方差很大，影响训练稳定性。

因为强化学习实际需要学会的是每个动作相比另一个动作的优势，来指导每个状态下的动作选择，因此只需要每个状态的rrr减去当前状态的v(s)v(s)v(s)，表示做选择之后的期望回报相对于做选择之前的变化，就可以抵消不同状态本身的差异，减小方差。

进一步思考，当前的减小方差实际上是消除了不同状态之间，不同动作所对应的回报的均值，相当于对梯度进行标准化。如果归一化不同状态之间不同动作对应回报的方差，使得所有状态均衡被考虑，对于训练是否会有帮助？

karpathy-pg-pong.py

"""This is a fork from https://gist.github.com/karpathy/a4166c7fe253700972fcbc77e4ea32c5
The reward function is hacked too.
The shape is exponantial:
/\reward
/\reward
|
|       |
|      /|
|    -  |
|_ -    |
0---------->t
       /\
       |
This is the time your agent win
Trains an agent with (stochastic) Policy Gradients on Pong. Uses OpenAI Gym.
"""
import numpy as np
import cPickle as pickle
import gym

# hyperparameters
H = 200 # number of hidden layer neurons
batch_size = 10 # every how many episodes to do a param update?
learning_rate = 1e-4
gamma = 0.99 # discount factor for reward
decay_rate = 0.99 # decay factor for RMSProp leaky sum of grad^2
resume = False # resume from previous checkpoint?
render = False

# model initialization
D = 80 * 80 # input dimensionality: 80x80 grid
if resume:
  model = pickle.load(open('save.p', 'rb'))
else:
  model = {}
  model['W1'] = np.random.randn(H,D) / np.sqrt(D) # "Xavier" initialization
  model['W2'] = np.random.randn(H) / np.sqrt(H)

grad_buffer = { k : np.zeros_like(v) for k,v in model.iteritems() } # update buffers that add up gradients over a batch
rmsprop_cache = { k : np.zeros_like(v) for k,v in model.iteritems() } # rmsprop memory

def sigmoid(x):
  return 1.0 / (1.0 + np.exp(-x)) # sigmoid "squashing" function to interval [0,1]

def prepro(I):
  """ prepro 210x160x3 uint8 frame into 6400 (80x80) 1D float vector """
  I = I[35:195] # crop
  I = I[::2,::2,0] # downsample by factor of 2
  I[I == 144] = 0 # erase background (background type 1)
  I[I == 109] = 0 # erase background (background type 2)
  I[I != 0] = 1 # everything else (paddles, ball) just set to 1
  return I.astype(np.float).ravel()

def discount_rewards(r):
  """ take 1D float array of rewards and compute discounted reward """
  discounted_r = np.zeros_like(r)
  running_add = 0
  for t in reversed(xrange(0, r.size)):
    if r[t] != 0: running_add = 0 # reset the sum, since this was a game boundary (pong specific!)
    running_add = running_add * gamma + r[t]
    discounted_r[t] = running_add
  return discounted_r

def policy_forward(x):
  h = np.dot(model['W1'], x)
  h[h<0] = 0 # ReLU nonlinearity
  logp = np.dot(model['W2'], h)
  p = sigmoid(logp)
  return p, h # return probability of taking action 2, and hidden state

def policy_backward(eph, epdlogp):
  """ backward pass. (eph is array of intermediate hidden states) """
  dW2 = np.dot(eph.T, epdlogp).ravel()
  dh = np.outer(epdlogp, model['W2'])
  dh[eph <= 0] = 0 # backpro prelu
  dW1 = np.dot(dh.T, epx)
  return {'W1':dW1, 'W2':dW2}

env = gym.make("Pong-v0")
observation = env.reset()
prev_x = None # used in computing the difference frame
xs,hs,dlogps,drs = [],[],[],[]
running_reward = None
reward_sum = 0
episode_number = 0
while True:
  if render: env.render()

  # preprocess the observation, set input to network to be difference image
  cur_x = prepro(observation)
  x = cur_x - prev_x if prev_x is not None else np.zeros(D)
  prev_x = cur_x

  # forward the policy network and sample an action from the returned probability
  aprob, h = policy_forward(x)
  action = 2 if np.random.uniform() < aprob else 3 # roll the dice!

  # record various intermediates (needed later for backprop)
  xs.append(x) # observation
  hs.append(h) # hidden state
  y = 1 if action == 2 else 0 # a "fake label"
  dlogps.append(y - aprob) # grad that encourages the action that was taken to be taken (see http://cs231n.github.io/neural-networks-2/#losses if confused)

  # step the environment and get new measurements
  observation, reward, done, info = env.step(action)
  reward_sum += reward

  drs.append(reward) # record reward (has to be done after we call step() to get reward for previous action)

  if done: # an episode finished
    episode_number += 1

    # stack together all inputs, hidden states, action gradients, and rewards for this episode
    epx = np.vstack(xs)
    eph = np.vstack(hs)
    epdlogp = np.vstack(dlogps)
    epr = np.vstack(drs)
    xs,hs,dlogps,drs = [],[],[],[] # reset array memory

    # compute the discounted reward backwards through time
    discounted_epr = discount_rewards(epr)
    # standardize the rewards to be unit normal (helps control the gradient estimator variance)
    discounted_epr -= np.mean(discounted_epr)
    discounted_epr /= np.std(discounted_epr)

    epdlogp *= discounted_epr # modulate the gradient with advantage (PG magic happens right here.)
    grad = policy_backward(eph, epdlogp)
    for k in model: grad_buffer[k] += grad[k] # accumulate grad over batch

    # perform rmsprop parameter update every batch_size episodes
    if episode_number % batch_size == 0:
      for k,v in model.iteritems():
        g = grad_buffer[k] # gradient
        rmsprop_cache[k] = decay_rate * rmsprop_cache[k] + (1 - decay_rate) * g**2
        model[k] += learning_rate * g / (np.sqrt(rmsprop_cache[k]) + 1e-5)
        grad_buffer[k] = np.zeros_like(v) # reset batch gradient buffer

    # boring book-keeping
    running_reward = reward_sum if running_reward is None else running_reward * 0.99 + reward_sum * 0.01
    print 'resetting env. episode reward total was %f. running mean: %f' % (reward_sum, running_reward)
    if episode_number % 100 == 0: pickle.dump(model, open('save.p', 'wb'))
    reward_sum = 0
    observation = env.reset() # reset env
    prev_x = None

  if reward != 0: # Pong has either +1 or -1 reward exactly when game ends.
    print ('ep %d: game finished, reward: %f' % (episode_number, reward)) + ('' if reward == -1 else ' !!!!!!!!')

kvfrans-ddpg.py

"""This is a fork from https://github.com/kvfrans/openai-cartpole/blob/master/cartpole-policygradient.py
The reward function is differernt, and it is:
/\r
|
|\
| \
|  \
|   \
0------>t
    /\
    |
This is the time "done" reached
"""
#!/usr/bin/env python3
import tensorflow as tf
import numpy as np
import random
import gym

def softmax(x):
    e_x = np.exp(x - np.max(x))
    out = e_x / e_x.sum()
    return out

def policy_gradient():
    with tf.variable_scope("policy"):
        params = tf.get_variable("policy_parameters", [4, 2])
        state = tf.placeholder("float", [None, 4])
        actions = tf.placeholder("float", [None, 2])
        advantages = tf.placeholder("float", [None, 1])
        linear = tf.matmul(state, params)
        probabilities = tf.nn.softmax(linear)
        good_probabilities = tf.reduce_sum(tf.multiply(probabilities, actions), reduction_indices=[1])
        eligibility = tf.log(good_probabilities) * advantages
        loss = -tf.reduce_sum(eligibility)
        optimizer = tf.train.AdamOptimizer(0.01).minimize(loss)
        return probabilities, state, actions, advantages, optimizer

def value_gradient():
    with tf.variable_scope("value"):
        state = tf.placeholder("float", [None, 4])
        newvals = tf.placeholder("float", [None, 1])
        w1 = tf.get_variable("w1", [4, 10])
        b1 = tf.get_variable("b1", [10])
        h1 = tf.nn.relu(tf.matmul(state, w1) + b1)
        w2 = tf.get_variable("w2", [10, 1])
        b2 = tf.get_variable("b2", [1])
        calculated = tf.matmul(h1, w2) + b2
        diffs = calculated - newvals
        loss = tf.nn.l2_loss(diffs)
        optimizer = tf.train.AdamOptimizer(0.1).minimize(loss)
        return calculated, state, newvals, optimizer, loss

def run_episode(env, policy_grad, value_grad, sess, render=False):
    pl_calculated, pl_state, pl_actions, pl_advantages, pl_optimizer = policy_grad
    vl_calculated, vl_state, vl_newvals, vl_optimizer, vl_loss = value_grad
    observation = env.reset()
    totalreward = 0
    states = []
    actions = []
    advantages = []
    transitions = []
    update_vals = []

    for _ in range(20000):
        env.render(close=not render)
        # calculate policy
        obs_vector = np.expand_dims(observation, axis=0)
        probs = sess.run(pl_calculated, feed_dict={pl_state: obs_vector})
        action = 0 if random.uniform(0, 1) < probs[0][0] else 1
        # record the transition
        states.append(observation)
        actionblank = np.zeros(2)
        actionblank[action] = 1
        actions.append(actionblank)
        # take the action in the environment
        old_observation = observation
        observation, reward, done, info = env.step(action)
        transitions.append((old_observation, action, reward))
        totalreward += reward

        if done:
            break
    for index, trans in enumerate(transitions):
        '''
        这里回报函数和我设计的不一样
        '''
        obs, action, reward = trans

        # calculate discounted monte-carlo return
        future_reward = 0
        future_transitions = len(transitions) - index
        decrease = 1
        for index2 in range(future_transitions):
            future_reward += transitions[(index2) + index][2] * decrease
            decrease = decrease * 0.97
        obs_vector = np.expand_dims(obs, axis=0)
        currentval = sess.run(vl_calculated, feed_dict={vl_state: obs_vector})[0][0]

        # advantage: how much better was this action than normal
        advantages.append(future_reward - currentval)

        # update the value function towards new return
        update_vals.append(future_reward)

    # update value function
    update_vals_vector = np.expand_dims(update_vals, axis=1)
    sess.run(vl_optimizer, feed_dict={vl_state: states, vl_newvals: update_vals_vector})
    # real_vl_loss = sess.run(vl_loss, feed_dict={vl_state: states, vl_newvals: update_vals_vector})

    advantages_vector = np.expand_dims(advantages, axis=1)
    sess.run(pl_optimizer, feed_dict={pl_state: states, pl_advantages: advantages_vector, pl_actions: actions})

    return totalreward

env = gym.make('CartPole-v0')
# env.monitor.start('cartpole-hill/', force=True)
policy_grad = policy_gradient()
value_grad = value_gradient()
sess = tf.InteractiveSession()
sess.run(tf.global_variables_initializer())
for i in range(2000):
    reward = run_episode(env, policy_grad, value_grad, sess)
    if reward == 2000:
        print("reward 200")
        print(i)
        break
t = 0
for _ in range(1000):
    reward = run_episode(env, policy_grad, value_grad, sess, True)
    t += reward
print(t / 1000)
# env.monitor.close()

my_simple_PG.py

#!/usr/bin/env python3
"""Simpliest PG, with no hack in it
"""
import tensorflow as tf
import numpy as np
from tensorflow.contrib import slim
from replay_buffer import ReplayBuffer
import gym

env = gym.make('CartPole-v0')
env.reset()

buf = ReplayBuffer(5000)
x = tf.placeholder(tf.float32, [None, 4], name='x')
a = tf.placeholder(tf.float32, [None, 2], name='a')
r = tf.placeholder(tf.float32, [None, 1], name='r')

def build_net(x, a, r):
    h1 = slim.layers.fully_connected(x, 20)
    h2 = slim.layers.fully_connected(h1, 12)
    h3 = slim.layers.fully_connected(h2, 2, activation_fn=tf.identity)
    y = tf.nn.softmax(h3)
    logp = tf.log(y)

    # good_probabilities = tf.reduce_sum(tf.multiply(y, a), reduction_indices=[1])
    # # maximize the log probability
    # log_probabilities = tf.log(good_probabilities)
    # loss = -tf.reduce_sum(log_probabilities)

    loss = -tf.reduce_sum(
        tf.reduce_sum(
            tf.multiply(tf.multiply(r, logp), a),
            reduction_indices=[1]))
    return y, logp, h3, loss

y, logp, h, loss = build_net(x, a, r)

optimizer = tf.train.RMSPropOptimizer(0.001).minimize(loss)

sess = tf.InteractiveSession()
tf.global_variables_initializer().run()
buf.clear()

def get_act(feed_dict):
    act = sess.run(y, feed_dict=feed_dict)
    if np.random.uniform() <= act[0][0]:
        return np.array([1, 0])
    else:
        return np.array([0, 1])

render_close = True
for i in range(40000):
    # if i > 4000:
    #     render_close = False
    s = env.reset()
    total_reward = 0
    done = False

    bbuf = list()
    for i in range(200):
        # 重复试验收集数据
        act = get_act({x: np.reshape(s, [1, 4])})
        s_next, reward, done, _ = env.step(act[1])
        if done:
            break
        total_reward += reward
        bbuf.append([s, act, total_reward, s_next])
        env.render(close=render_close)
        s = s_next
        if i == 199:
            render_close = False
    else:
        for j in bbuf:
            j[2] = total_reward
        buf.extend(bbuf)
    if i % 150 == 140:
        for i in range(4000):
            # 使用收集到的数据训练网络
            st, at, rt, sn = zip(*buf.sample())
            rt = np.reshape(np.array(rt), [-1, 1])
            # rt 中心标准化
            rt_norm = 100 * (rt - min(rt)) / (np.sqrt(np.var(rt)) + 1)
            feed_dict = {
                x: np.array(st),
                a: np.array(at),
                r: rt_norm
            }
            loss_runned, y_runned, _ = sess.run([loss, y, optimizer], feed_dict=feed_dict)
            print('loss: ', loss_runned, 'y', y_runned[0])

replay_buffer.py

import random
from collections import deque

class ReplayBuffer(object):
    """ReplayBuffer for DRL
    Accept for tuple, (state, act, reward, next_state)"""

    def __init__(self, maxlen, data=list()):
        self._maxlen = maxlen
        self._buffer = deque(data, maxlen=maxlen)

    def __len__(self):
        return len(self._buffer)

    def sample(self, batch_size=32):
        if len(self._buffer) <= batch_size:
            return list(self._buffer)
        else:
            return random.sample(self._buffer, batch_size)

    def append(self, data):
        self._buffer.appendleft(data)

    def extend(self, data_list):
        self._buffer.extendleft(data_list)

    def clear(self):
        self._buffer.clear()

    @property
    def content(self):
        return list(self._buffer)

    @property
    def maxlen(self):
        return self._maxlen

    def __repr__(self):
        return self._buffer.__repr__()

    def __str__(self):
        return self._buffer.__str__()

def test_buffer():
    data = [(1, 2, 3)] * 5 + [(2, 3, 4)] * 3
    buffer = ReplayBuffer(7, data)
    print(buffer.content)
    buffer.append((3, 4, 5))
    print(buffer.content)
    buffer.extend([(6, 4, 2)] * 2)
    print(buffer.content, len(buffer))
    print(buffer.sample(3), len(buffer))
    print(buffer.sample(7))
    print(buffer.sample(8))
    buffer.clear()
    print(buffer.content, len(buffer))

Fine_Tuning

强化学习调优

经验

先把学习率调到很小试试，比如1e-4；weight decay调到学习率的1/10

训练时间量变产生质变，通常要1e6steps以上

网路复杂度从小渐渐变大；sigmoid比relu稳定得多；rrelu比relu稳定且比sigmoid快

如果经过了预训练，就减小探索率，从0.5衰减到0.05

reward正则化会表现好一些。网上说让reward10分位处于0点时收敛最稳定。有时在batch内去掉均值和/或方差能变稳定（因为没做reward clipping，这样可以减少极端情况）。网上说不能对reward归一化，但可以通过乘系数改变大小（而且很有用）。梯度的方差在1左右比较好，此时对于每轮100～1000步的环境，让target reward在100～1000分会比较好。

增加初始状态随机性能增加数据丰富度

在衰减得不太厉害（要看reward能够延迟多少步得到）的前提下，γ\gammaγ小收敛会更容易

多进程获取数据时以获取的数据数量为基准规范训练次数，获取数据的同时进行训练。

buffer length太长会加重off-policy的不收敛问题，但至少能把延迟的reward包含进来

先调critic再调actor

网上说BN会失效，因为强化学习的数据分布不断变化，BN会不稳定

embedding后加入dropout也许会变好

先跑一轮实验标定一下均值和方差，然后归一化

batch size不能太小

致命三要素(Reinforcement Learning, 2nd Edition, Sutton)：同时满足则一定有不收敛和发散的风险

函数逼近

自举法：使用当前目标估计值（TD/DP）而不是只依赖于真实收益和完整回报（MC）

离轨策略训练（off-policy）

TRICKS

GAE

Prioritized Experience Replay

Dueling Network

一致化特征的level

思考

强化学习中常常能见到不平衡样本，大部分情况都是负reward，极少数情况能得到正reward。当replay buffer 中存到的数据大部分都是差不多的sample时，如何能提升强化学习的学习效果，

强化学习常常遇到的问题：

不平衡，稀疏reward，通常大部分动作都是负reward，少部分正reward

reward和动作之间的关联性。影响reward的因素有很多，动作只是其中之一

ϵ\epsilonϵ-greedy搜索空间过大，随机到的大部分动作其实没什么意义

关键动作：只有少数关键动作对整个环境产生重要影响，改变整个人生的通常是少数选择

遗忘

迁移

基于数据的思考

样本数据不平衡

不平衡样本解决方法：4-Machine_Learning/Problems/Imbalance

基于仿生学的思考

好吃的和没吃过的优先选择没吃过的

探索方式

训练时使用softmax作为概率分布，替代ϵ\epsilonϵ-greedy或者A3C的概率采样，或者DDPG的确定性策略；评估时既可用确定性策略，也可用概率分布采样。

数据集

网上看到的别人调参结果，可以作为baseline参考

BipedalWalker-v2 PPO

param | value | Notation
learning rate | 1e-4 | 学习率
Update_timestep | 4000 | 多少次timestep后更新一次
Action_std | 0.4 | 动作概率分布的标准差，越大越鼓励explore
K_epochs | 120 | 对sample的batch进行K次梯度下降更新，多次利用数据
steps | 1.2e6 | 训练总steps
Return | 300 | 最终结果

Lyap Reward Shaping

Rt^=Rt+λ(Rt−Rt−1)\hat{R_t}=R_t+\lambda(R_t-R_{t-1})Rt^=Rt+λ(Rt−Rt−1)

λ=0.7\lambda=0.7λ=0.7

使用Lyrp，收敛速度有了约30%提升

OU-process

Ornstein–Uhlenbeck process

from random import gauss

class OrnsteinUhlenbeckProcess(object):
    # Ornstein–Uhlenbeck process
    def __init__(self, dt, theta, sigma, nums=1):
        self.x = [0] * nums
        self.dt = dt
        self.theta = theta
        self.sigma = sigma
        self.nums = nums

    def __call__(self):
        dx = [-self.theta * self.x[i] * self.dt + self.sigma * gauss(0, 1) for i in range(self.nums)]
        self.x = [self.x[i] + dx[i] for i in range(self.nums)]
        return self.x

cheatsheet.py

from typing import Any
import torch
F = torch.nn.functional
rewards = states = next_states = dones = actions = torch.tensor([])

class Self(object):
    def __getattribute__(self, __name: str) -> Any:
        pass

self = Self()

def GAE(gamma, lmbda, td_delta):
    td_delta = td_delta.detach().numpy()
    advantage_list = []
    advantage = 0.0
    for delta in td_delta[::-1]:
        advantage = gamma * lmbda * advantage + delta
        advantage_list.append(advantage)
    advantage_list.reverse()
    return torch.tensor(advantage_list, dtype=torch.float)

# ac
td_target = rewards + self.gamma * self.critic(next_states) * (1 - dones)
td_delta = td_target - self.critic(states)  # 时序差分误差
log_probs = torch.log(self.actor(states).gather(1, actions))

actor_loss = torch.mean(-log_probs * td_delta.detach())
critic_loss = torch.mean(F.mse_loss(self.critic(states), td_target.detach()))

# ppo
td_target = rewards + self.gamma * self.critic(next_states) * (1 - dones)
td_delta = td_target - self.critic(states)
advantage = GAE(self.gamma, self.lmbda, td_delta.cpu()).to(self.device)
old_log_probs = torch.log(self.actor(states).gather(1, actions)).detach()
for i in range(3):
    log_probs = torch.log(self.actor(states).gather(1, actions))
    ratio = torch.exp(log_probs - old_log_probs)
    surr1 = ratio * advantage
    surr2 = torch.clamp(ratio, 1 - self.eps, 1 + self.eps) * advantage  # 截断

    actor_loss = torch.mean(-torch.min(surr1, surr2))  # PPO损失函数
    critic_loss = torch.mean(F.mse_loss(self.critic(states), td_target.detach()))

# dqn
q_values = self.q_net(states).gather(1, actions)  # Q值
# 下个状态的最大Q值
max_next_q_values = self.target_q_net(next_states).max(1)[0].view(-1, 1)
q_targets = rewards + self.gamma * max_next_q_values * (1 - dones)  # TD误差目标

dqn_loss = torch.mean(F.mse_loss(q_values, q_targets))  # 均方误差损失函数

# ddpg
next_q_values = self.target_critic(next_states, self.target_actor(next_states))
q_targets = rewards + self.gamma * next_q_values * (1 - dones)

critic_loss = torch.mean(F.mse_loss(self.critic(states, actions), q_targets))
actor_loss = -torch.mean(self.critic(states, self.actor(states)))

# sac
next_actions, log_prob = self.actor(next_states)
entropy = -log_prob
q1_value = self.target_critic_1(next_states, next_actions)
q2_value = self.target_critic_2(next_states, next_actions)
next_value = torch.min(q1_value, q2_value) + self.log_alpha.exp() * entropy
td_target = rewards + self.gamma * next_value * (1 - dones)

critic_1_loss = torch.mean(F.mse_loss(self.critic_1(states, actions), td_target.detach()))
critic_2_loss = torch.mean(F.mse_loss(self.critic_2(states, actions), td_target.detach()))
actor_loss = torch.mean(-self.log_alpha.exp() * entropy - torch.min(q1_value, q2_value))
alpha_loss = torch.mean((entropy - self.target_entropy).detach() * self.log_alpha.exp())

# ppo:
# 1. batch size
# 2. model hidden size
# 3. max reuse

step_by_step_q_learning

painless q learning

机器人寻路，目标为走到户外（5）

原文链接：http://mnemstudio.org/path-finding-q-learning-tutorial.htm

地图：

图模型：

回报的矩阵表示：

The Q-Learning algorithm goes as follows:

Set the gamma parameter, and environment rewards in matrix R.
Initialize matrix Q to zero.
For each episode:
    Select a random initial state.
    Do While the goal state hasn't been reached.
        Select one among all possible actions for the current state.
        Using this possible action, consider going to the next state.
        Get maximum Q value for this next state based on all possible actions.
        Compute: Q(state, action) = R(state, action) + Gamma \* Max[Q(next state, all actions)]
        Set the next state as the current state.
    End Do
End For

import numpy as np
import random

q = np.zeros([6,6])
r = np.array([[-1,-1,-1,-1,0,-1],
            [-1,-1,-1,0,-1,100],
            [-1,-1,-1,0,-1,-1],
            [-1,0,0,-1,0,-1],
            [0,-1,-1,0,-1,100],
            [-1,0,-1,-1,0,100],
           ])
print('q=', q, '', 'r=', r, sep='\n')
gamma = 0.8
print('\ngamma=', gamma)

q=
[[ 0.  0.  0.  0.  0.  0.]
 [ 0.  0.  0.  0.  0.  0.]
 [ 0.  0.  0.  0.  0.  0.]
 [ 0.  0.  0.  0.  0.  0.]
 [ 0.  0.  0.  0.  0.  0.]
 [ 0.  0.  0.  0.  0.  0.]]

r=
[[ -1  -1  -1  -1   0  -1]
 [ -1  -1  -1   0  -1 100]
 [ -1  -1  -1   0  -1  -1]
 [ -1   0   0  -1   0  -1]
 [  0  -1  -1   0  -1 100]
 [ -1   0  -1  -1   0 100]]

gamma= 0.8

Look at the second row (state 1) of matrix R. There are two possible actions for the current state 1: go to state 3, or go to state 5. By random selection, we select to go to 5 as our action.

Now let's imagine what would happen if our agent were in state 5. Look at the sixth row of the reward matrix R (i.e. state 5). It has 3 possible actions: go to state 1, 4 or 5.

Q(state, action) = R(state, action) + Gamma * Max[Q(next state, all actions)]

Q(1, 5) = R(1, 5) + 0.8 * Max[Q(5, 1), Q(5, 4), Q(5, 5)] = 100 + 0.8 * 0 = 100

Since matrix Q is still initialized to zero, Q(5, 1), Q(5, 4), Q(5, 5), are all zero. The result of this computation for Q(1, 5) is 100 because of the instant reward from R(5, 1).

The next state, 5, now becomes the current state. Because 5 is the goal state, we've finished one episode. Our agent's brain now contains an updated matrix Q as:

For the next episode, we start with a randomly chosen initial state. This time, we have state 3 as our initial state.

Look at the fourth row of matrix R; it has 3 possible actions: go to state 1, 2 or 4. By random selection, we select to go to state 1 as our action.

Now we imagine that we are in state 1. Look at the second row of reward matrix R (i.e. state 1). It has 2 possible actions: go to state 3 or state 5. Then, we compute the Q value:

Q(state,action)=R(state,action)+γ×Max[Q(nextstate,allactions)]Q(state, action) = R(state, action) + \gamma \times Max[Q(next state, all actions)]Q(state,action)=R(state,action)+γ×Max[Q(nextstate,allactions)]

Q(1,5)=R(1,5)+0.8×Max[Q(1,2),Q(1,5)]=0+0.8×Max(0,100)=80Q(1, 5) = R(1, 5) + 0.8 \times Max[Q(1, 2), Q(1, 5)] = 0 + 0.8 \times Max(0, 100) = 80Q(1,5)=R(1,5)+0.8×Max[Q(1,2),Q(1,5)]=0+0.8×Max(0,100)=80

We use the updated matrix Q from the last episode. Q(1, 3) = 0 and Q(1, 5) = 100. The result of the computation is Q(3, 1) = 80 because the reward is zero. The matrix Q becomes:

The next state, 1, now becomes the current state. We repeat the inner loop of the Q learning algorithm because state 1 is not the goal state.

Q(state,action)=R(state,action)+γ×Max[Q(nextstate,allactions)]Q(state, action) = R(state, action) + \gamma \times Max[Q(next state, all actions)]Q(state,action)=R(state,action)+γ×Max[Q(nextstate,allactions)]

Algorithm to utilize the Q matrix:

Set current state = initial state.

From current state, find the action with the highest Q value.

Set current state = next state.

Repeat Steps 2 and 3 until current state = goal state.

The algorithm above will return the sequence of states from the initial state to the goal state.

多次运行下面的cell可以看每次迭代所得到的Q表更新效果

采取的更新方案为ϵ\epsilonϵ-greedy,可以通过调整ϵ\epsilonϵ实现或上面的greedy，或随机采样。

For epoch in max_epoches:

随机选取一个state

在选取的state下选定一个可行的动作（在本例中，不可行的动作回报为"-1")

按照公式 Q(state,action)=R(state,action)+γ×Max[Q(nextstate,allactions)]Q(state, action) = R(state, action) + \gamma \times Max[Q(next state, all actions)]Q(state,action)=R(state,action)+γ×Max[Q(nextstate,allactions)]计算Q值

采取2中选定的动作，将state更新到下一个state

如果运动到终点则中断

End For

选取下个动作的方法：greedy、random、ϵ\epsilonϵ-greedy

以\epsilon概率采取贪心算法

\epsilon = 1: 贪心

\epsilon = 0: 随机

def randomly_select_act(state):
    # 随机选定一个正回报的动作
    flag = True
    while flag:
        action = np.random.randint(0, 6)
        if r[state, action] >= 0:
            flag = False
    return action

def greedly_select_act(state):
    # 贪心选取下个动作
    actions = list(q[state, :])
    max_q = max(actions)
    max_actioin_index = list()
    for i, act in enumerate(actions):
        if act >= max_q:
            max_actioin_index.append(i)
    return random.sample(max_actioin_index, 1)[0]

def e_greedy(state, e):
    if random.random() < e:
        return greedly_select_act(state)
    else:
        return randomly_select_act(state)

For the next episode, we start with a randomly chosen initial state. This time, we have state 3 as our initial state.

Look at the fourth row of matrix R; it has 3 possible actions: go to state 1, 2 or 4. By random selection, we select to go to state 1 as our action.

Now we imagine that we are in state 1. Look at the second row of reward matrix R (i.e. state 1). It has 2 possible actions: go to state 3 or state 5. Then, we compute the Q value:

距离终点很近，Max[Q(next state, all actions)]就会很大

Q(state,action)=R(state,action)+γ×Max[Q(nextstate,allactions)]Q(state, action) = R(state, action) + \gamma \times Max[Q(next state, all actions)]Q(state,action)=R(state,action)+γ×Max[Q(nextstate,allactions)]

Q(1,5)=R(1,5)+0.8×Max[Q(1,2),Q(1,5)]=0+0.8×Max(0,100)=80Q(1, 5) = R(1, 5) + 0.8 \times Max[Q(1, 2), Q(1, 5)] = 0 + 0.8 \times Max(0, 100) = 80Q(1,5)=R(1,5)+0.8×Max[Q(1,2),Q(1,5)]=0+0.8×Max(0,100)=80

We use the updated matrix Q from the last episode. Q(1, 3) = 0 and Q(1, 5) = 100. The result of the computation is Q(3, 1) = 80 because the reward is zero. The matrix Q becomes:

一个episode：

epsilon = 1
print('ϵ:', epsilon)

print("本次epoch运动路径：")
state = np.random.randint(0,6)

for i in range(200):
    print(state, end=', ')
    if state == 5:
        break
    action = e_greedy(state, epsilon)
    action = randomly_select_act(state)

    q[state, action] = round(r[state, action] + gamma * max(q[action, :]))
    state = action

# 一个episode：
print('\n',q)

ϵ: 1
本次epoch运动路径：
5,
 [[   0.    0.    0.    0.   80.    0.]
 [   0.    0.    0.   64.    0.  100.]
 [   0.    0.    0.   64.    0.    0.]
 [   0.   80.   51.    0.   80.    0.]
 [  64.    0.    0.   64.    0.  100.]
 [   0.    0.    0.    0.    0.    0.]]

使用训练得到的Q表解题：

state = input('请输入整数初始状态（0-5）：')
assert int(state) >= 0 and int(state) <= 5, '请输入整数初始状态（0-5）'
state = int(state)
print('行走路径为：')
for i in range(200):
    print(state, end=', ')
    if state == 5:
        break
    action = greedly_select_act(state)
    state = action

请输入整数初始状态（0-5）：2
行走路径为：
2, 3, 1, 5,

地图：

Thinking

对于real-world rl问题，通常都区分了训练和评估两个过程，与bandit不同。bandit问题不区分训练和评估过程，要求exploration和exploitation找平衡，而rl问题没有必要寻找这种平衡，而是充分利用好这两个过程即可。

当前常见的RL采样过程都很有问题，采样策略不好，会逐渐收敛到原本搜索到的局优策略附近，导致对全局的把握越来越差（数据样本均衡度变差）。Experience Selection in Deep Reinforcement Learning for Control p15

RL的最终目的是通过与环境的交互学习，使得最终学到一个最优策略，无需在训练过程中最大化期望回报，训练过程中我们应当鼓励探索，力求对环境准确建模。最简单的方法就是对环境均匀采样，然而我们可能会存在多种采样偏好，具体哪种采样偏好更有有待讨论：

偏好采样高价值状态，因为RL的最终目标是最大化期望回报，尤其在正确动作极少的情况

偏好采样低价值状态，用于回避最坏的情况

偏好采样未采样过的状态，对于确定性时不变环境比较容易有整体把握，但对于时变环境可能会产生较大偏差（因为环境变了但没跟上环境的变化，尤其是重要的地方的变化，比如高和低价值状态的变化）

偏好采样难以预测的状态，即便都是未采样过的状态，有的函数曲线非常平滑，很容易预测，那么机器学习模型很容易就能泛化到，就不必浪费重要的采样资源，对未知的事物有好奇心（ICM），对已知的事物好奇心降低（对于随机或时变系统还时需要保持一定好奇心）。问题在于当遇到随机过程后就会被吸引到注意力，浪费资源，因此还可能需要对随机性进行评估，这个随机性可能还需要通过因果推理获取，直接ICM存在一些问题

强化学习并非万能，有的问题靠随机探索很难遇到正确答案，因此强化学习的未来需要考虑归纳和演绎推理，不能总是瞎猜，需要用推理来赋能探索和规划才能接近人类的学习能力，因果学习&RL

对于多维的动作（其实本质上就是multi agent），只有一个reward时，会遇到credit assignment的问题，在优化领域是维度灾难

SMO_Simple

SMO Simple

The "Simple" means randomly choose lagrange complixier αi\alpha_iαi and αj\alpha_jαj

import numpy as np
import pylab
import matplotlib.patches as mpatches
import random

pylab.ion()

Synthesised data -- decision boundary:

x+0.3y=0.1x + 0.3 y = 0.1x+0.3y=0.1

xa = np.random.randn(100, 2)
xb = np.random.randn(600, 2)
xc = np.random.randn(300, 2)
xa[:, 1] = xa[:, 1] * 3 + 9
xb[:, 0] = xb[:, 0] * 1 - 6
xb[:, 1] = xb[:, 1] * 2 - 6
xc = xc * 3
x = np.vstack([xa,xb,xc])
y = np.array(list(map(lambda x: 1 if x.dot(np.array([9, -4])) + 9 > 0 else -1, x)))
NUM_CLASSIFY = 2
NUM_DATA = 1000
C = 0
i, j = 0, 1

for i, j in zip(x, y):
    pylab.plot(i[0], i[1], '.', color='r' if j > 0 else 'b')
patch1 = mpatches.Patch(color='r', label='+1')
patch2 = mpatches.Patch(color='b', label='-1')
pylab.legend(handles=[patch1, patch2])

<matplotlib.legend.Legend at 0x7f995bc58320>

SMO

Optimize aja_jaj

aj:=aj−y(j)(Ei−Ej)ηa_j := aj - \frac{y^{(j)}(E_i-E_j)}{\eta}aj:=aj−ηy(j)(Ei−Ej)

where

Ek=f(x(k))−y(k)E_k = f(x^{(k)}) - y^{(k)}Ek=f(x(k))−y(k)

η=2x(i)∙x(j)−x(i)∙x(i)−x(j)∙x(j)\eta = 2 x^{(i)}\bullet x^{(j)} - x^{(i)}\bullet x^{(i)} - x^{(j)}\bullet x^{(j)} η=2x(i)∙x(j)−x(i)∙x(i)−x(j)∙x(j)

Clip aja_jaj

Ify(i)≠y(j)If y^{(i)} \neq y^{(j)}Ify(i)=y(j), L=max(0,aj−ai)L=max(0, a_j - a_i)L=max(0,aj−ai), H=min(C,C+aj−ai)H = min(C, C + a_j - a_i)H=min(C,C+aj−ai)

Ify(i)=y(j)If y^{(i)} = y^{(j)}Ify(i)=y(j), L=max(0,ai+aj−C)L=max(0, a_i + a_j - C)L=max(0,ai+aj−C), H=min(C,ai+aj)H = min(C, a_i + a_j)H=min(C,ai+aj)

aia_iai

ai:=ai+y(i)y(j)(aj(old)−aj)a_i:=a_i+y^{(i)}y^{(j)}(a_j^{(old)}-a_j)ai:=ai+y(i)y(j)(aj(old)−aj)

bbb

bbb is optimized refering to Machine Learning (Zhou Zhihua, (6.18) P125), Where SSS is subscript of support vector, and we choose the final iii and jjj.

b=1∣S∣∑s∈S(1/ys−∑k∈Sαkykxk∙xs)b = \frac{1}{|S|}\sum\limits_{s\in S}{(1/y_s-\sum\limits_{k\in S}\alpha_ky_k x_k \bullet x_s)}b=∣S∣1s∈S∑(1/ys−k∈S∑αkykxk∙xs)

or

b1=b−Ei−y(i)(αi−αi(old))x(i)∙x(i)−y(j)(αj−αj(old))x(i)∙x(j)b_1 = b - E_i - y{(i)}(\alpha_i - \alpha_i^{(old)})x^{(i)}\bullet x^{(i)} - y^{(j)}(\alpha_j-\alpha_j^{(old)}) x^{(i)}\bullet x^{(j)}b1=b−Ei−y(i)(αi−αi(old))x(i)∙x(i)−y(j)(αj−αj(old))x(i)∙x(j)

b2=b−Ej−y(i)(αi−αi(old))x(i)∙x(j)−y(j)(αj−αj(old))x(j)∙x(j)b_2 = b - E_j - y{(i)}(\alpha_i - \alpha_i^{(old)})x^{(i)}\bullet x^{(j)} - y^{(j)}(\alpha_j-\alpha_j^{(old)}) x^{(j)}\bullet x^{(j)}b2=b−Ej−y(i)(αi−αi(old))x(i)∙x(j)−y(j)(αj−αj(old))x(j)∙x(j)

b:=b:=b:=

b1,if0<ai<Cb_1, if 0 < ai < Cb1,if0<ai<C

b2,if0<aj<Cb_2, if 0 < aj < Cb2,if0<aj<C

(bi+bj)/2,otherwise(b_i + b_j) / 2, otherwise(bi+bj)/2,otherwise

# INIT params
a = np.abs(np.random.randn(NUM_DATA))/100
b = np.random.randn()
i, j = 0, 1

def w(a, x, y):
    return np.sum(x.T * a * y, axis=1)

wij = w(a, x, y)

def f(w, xi, b):
    return w.dot(xi.T) + b

fi = f(wij, x[i], b)

def E(w, xi, b, yi):
    return f(w, xi, b) - yi

Ei, Ej = E(wij, x[i], b, y[i]), E(wij, x[j], b, y[j])

def eta(xi, xj):
    return 2 * xi.dot(xj) - xi.dot(xi) - xj.dot(xj)

etaij = eta(x[i], x[j])

def aj(aj0, eta, Ei, Ej, yj):
    return aj0 - yj * (Ei - Ej)/eta

ajj = aj(a[j], etaij, Ei, Ej, y[j])

def clipj(ajj, ai, aj, yi, yj, C=0):
    if yi != yj:
        L = max(0, aj - ai)
        H = min(C, C + aj - ai)
    else:
        L = max(0, ai + aj - C)
        H = min(C, ai + aj)
    return np.clip(ajj, L, H)

ajj = clipj(ajj, a[i], a[j], y[i], y[j], C)

def ai(ai0, yi, yj, aj0, aj):
    return ai0 + yi*yj*(aj0-aj)

aii = ai(a[i], y[i], y[j], a[j], ajj)

def b12(b, Ei, Ej, xi, xj, yi, yj, aii, ajj, ai0, aj0, C=0):
    b1 = b - Ei - yi * (aii - ai0) * (xi.dot(xi)) - yi*(ajj-aj0)*(xi.dot(xj))
    b2 = b - Ej - yi * (aii - ai0) * (xi.dot(xj)) - yj*(ajj-aj0)*(xj.dot(xj))
    c1 = 0 < aii < C
    c2 = 0 < ajj < C
    if c1 and not c2:
        return b1
    elif c2 and not c1:
        return b2
    else:
        return (b1 + b2) / 2

b = b12(b, Ei, Ej, x[i], x[j], y[i], y[j], aii, ajj, a[i], a[j], C)
a[i] = aii
a[j] = ajj

def update(i, j, a, b, x, y):
    wij = w(a, x, y)
    Ei, Ej = E(wij, x[i], b, y[i]), E(wij, x[j], b, y[j])
    etaij = eta(x[i], x[j])
    ajj = aj(a[j], etaij, Ei, Ej, y[j])
    ajj = clipj(ajj, a[i], a[j], y[i], y[j], C)
    aii = ai(a[i], y[i], y[j], a[j], ajj)

    b = b12(b, Ei, Ej, x[i], x[j], y[i], y[j], aii, ajj, a[i], a[j], C)
    a[i] = aii
    a[j] = ajj
    return a, b

def predict(a, b, x, y):
    return np.array(list(map(lambda x: 1 if x >0 else -1, f(w(a, x, y), x, b))))

def train(n_iter, a, b, x, y):
    population = range(NUM_DATA)
    for _ in range(n_iter):
        i, j = random.sample(population, 2)
        a, b = update(i, j, a, b, x, y)
        #print(np.sum(np.equal(predict(a, b, x, y), y)) / NUM_DATA, w(a, x, y), b)
    return a, b

# INIT params
C=1
a = np.abs(np.random.randn(NUM_DATA))/100
b = 0#np.random.randn()
a, b = train(1000, a, b, x, y)

print(np.sum(np.equal(predict(a, b, x, y), y)) / NUM_DATA)
predict(a, b, x, y)

w1, w2 = w(a, x, y)
threshold = 20
x0 = (-b - w2 * (-threshold))/w1
x1 = (-b - w2 * threshold)/w1
y0 = (-b - w1 * -threshold)/w2
y1 = (-b - w1 * threshold)/w2

if x0 < -threshold:
    xx1 = -threshold
    yy1 = y0
else:
    xx1 = x0
    yy1 = -threshold

if x1 > threshold:
    xx2 = threshold
    yy2 = y1
else:
    xx2 = x1
    yy2 = threshold
pylab.plot([xx1, xx2], [yy1, yy2], 'k', label='decision boundary')

for i, j in zip(x, y):
    pylab.plot(i[0], i[1], '.', color='r' if j > 0 else 'b')
patch1 = mpatches.Patch(color='r', label='+1')
patch2 = mpatches.Patch(color='b', label='-1')
pylab.legend(handles=[patch1, patch2])

0.964

<matplotlib.legend.Legend at 0x7f998444c198>

print(w(a, x, y))
print(b)

[ 19.2305594   -7.21861262]
31.6032171919

LDA.py

LDA

ω=Sω−1(μ0−μ1) \omega = S^{-1}_\omega(\mu_0 - \mu_1) ω=Sω−1(μ0−μ1)

Sω−1=VΣ−1UT S^{-1}_\omega = V\Sigma^{-1}U^T Sω−1=VΣ−1UT

使用奇异值分解，实际S−1S^{-1}S−1直接用pinv就行(psedu invert)

Sω=Σ0+Σ1 S_\omega = \Sigma_0 + \Sigma_1 Sω=Σ0+Σ1

#!/usr/bin/env python3
from sklearn.datasets import load_breast_cancer
from tsne import *
import pandas as pd
from pylab import *
import seaborn as sns
from functools import reduce
from sklearn.preprocessing import MinMaxScaler
from sklearn.model_selection import train_test_split
data = load_breast_cancer()

x, y, label_names = data['data'], data['target'], data['target_names']
scaler = MinMaxScaler()
scaler.fit(x)
x = scaler.transform(x)

tsne(x, y, label_names=label_names)

trainx, testx, trainy, testy = train_test_split(x, reshape(y, [-1, 1]), test_size=0.1)

def variance(x, u):
    x = reshape(x, [-1, 1])
    return (x - u).dot(x - u).T

def lda2(x, y):
    x0, x1 = mat(x[y.T[0] == 0]), mat(x[y.T[0] == 1])
    u0, u1 = x0.mean(axis=0), x1.mean(axis=0)

    sigma0, sigma1 = reduce(lambda x, y: x + y, [variance(i, u0) for i in x0]), reduce(lambda x, y: x + y, [variance(i, u1) for i in x1])

    w = (u0 - u1).dot(pinv(sigma0 / x0.shape[0] + sigma1 / x1.shape[0]))
    return w

def transform(w, x):
    return w.dot(x.T).T

def predict(reduced, threshold, classes=None):
    if isinstance(threshold, (int, float)):
        threshold = [threshold]
    if classes is None:
        classes = range(len(threshold))
    threshold.sort()
    predicted = zeros(reduced.shape)
    for i, j, c in zip(threshold[:-1], threshold[1:], classes[1:-1]):
        predicted[reduced >= i and reduced < j] = c
    predicted[reduced < threshold[0]] = classes[0]
    predicted[reduced >= threshold[0]] = classes[-1]
    return predicted

def plot_prediction(reduced, predicted, target, threshold, label_names=None):
    colors = 'rgbcmyk'
    marks = '+xo.*'
    classes = set(list(target))
    if label_names is None:
        label_names = [None] * len(classes)
    for i in classes:
        subscription = target == i
        plot(reduced[subscription], predicted[subscription], '{}{}'.format(colors[i], marks[i]), label=label_names[i])
    plot([threshold, threshold], [-.5, 1.5], 'grey', label='Classify boundary')
    xlabel('Result after dimention reduced')
    ylabel('Predicted')
    legend()

def plot_prediction2(transformed, predicted, y, label_names, threshold):
    plot([threshold, threshold], [-50, 50], 'grey', label='Classify boundary')
    t = pd.DataFrame(transformed, columns=['Dimention Reduction Result After LDA Transform'])
    p = pd.DataFrame([label_names[int(i[0])] for i in predicted], columns=['prediction'])
    lab = pd.DataFrame([label_names[int(i[0])] for i in y], columns=['label'])
    data_t = pd.concat([t, p, lab], axis=1)
    sns.swarmplot(y='label', x='Dimention Reduction Result After LDA Transform', hue='prediction', data=data_t)

w = lda2(trainx, trainy)
threshold = -0.061

traint = transform(w, trainx)
trainp = predict(traint, threshold, [0, 1])
trainerr = mean(abs(reshape(trainp, [1, -1]) - reshape(trainy, [1, -1])))

testt = transform(w, testx)
testp = predict(testt, threshold, [0, 1])
testerr = mean(abs(reshape(testp, [1, -1]) - reshape(testy, [1, -1])))

figure()
plot_prediction2(
    np.vstack([traint, testt]),
    np.vstack([mat(trainp) + 4, mat(testp) + 6]),
    np.vstack([trainy, testy + 2]),
    list(map(lambda x: 'training set ' + x, label_names)) +
    list(map(lambda x: 'testing set ' + x, label_names)) +
    list(map(lambda x: 'train prediction ' + x, label_names)) +
    list(map(lambda x: 'test prediction ' + x, label_names)),
    threshold)
print('train err:', trainerr,
      '\ntest err:', testerr)
show()
# figure()
# plot_prediction(transformed, predicted, y, label_names)

Optimizers

Optimizers

随机梯度下降的梯度方差比较大，这会导致训练较为困难，不论是optimizer的tricks还是RL的tricks，很多都在于如何减小方差。

SGD

x←x−η∇fi(x) \mathbf{x} \leftarrow \mathbf{x} - \eta \nabla f_i(\mathbf{x}) x←x−η∇fi(x)

Momentum

使用leaky average，其实就是对梯度进行指数平滑，得到的平滑后的梯度更新迭代点：

vt←βvt−1+gt,t−1,xt←xt−1−ηtvt. \begin{split}\begin{aligned} \mathbf{v}_t &\leftarrow \beta \mathbf{v}_{t-1} + \mathbf{g}_{t, t-1}, \\ \mathbf{x}_t &\leftarrow \mathbf{x}_{t-1} - \eta_t \mathbf{v}_t. \end{aligned}\end{split} vtxt←βvt−1+gt,t−1,←xt−1−ηtvt.

AdaGrad

现在加入一个新的需求，希望学习率随着迭代步数以O(t−12)\mathcal{O}(t^{-\frac{1}{2}})O(t−21)的速度收敛。但现在存在一个问题：数据分布不均衡。因此我们希望常见的特征梯度参数收敛快，不常见的特征梯度参数收敛慢。这里我们使用变量 st\mathbf{s}_tst 记录每一维特征累计的梯度方差，如下所示（下面的操作都是按位置进行的操作pair-wise）：

gt=∂wl(yt,f(xt,w)),st=st−1+gt2,wt=wt−1−ηst+ϵ⋅gt. \begin{split}\begin{aligned} \mathbf{g}_t & = \partial_{\mathbf{w}} l(y_t, f(\mathbf{x}_t, \mathbf{w})), \\ \mathbf{s}_t & = \mathbf{s}_{t-1} + \mathbf{g}_t^2, \\ \mathbf{w}_t & = \mathbf{w}_{t-1} - \frac{\eta}{\sqrt{\mathbf{s}_t + \epsilon}} \cdot \mathbf{g}_t. \end{aligned}\end{split} gtstwt=∂wl(yt,f(xt,w)),=st−1+gt2,=wt−1−st+ϵη⋅gt.

这里就不考虑动量了，直接记录每一维度的梯度的累计平方，让这个累计平方较大的梯度参数衰减较快。

RMSProp

在实践中我比较喜欢这个算法，收敛速度常常显著优于其他算法。

这个算法是基于前面算法思想的改进，Adagrad的s通过不断累计会不断上升，导致梯度会逐渐收敛到0，而且噪声越大收敛越快。RMSProp 使用梯度平方的指数平滑，不再向正无穷累积：

st←γst−1+(1−γ)gt2,xt←xt−1−ηst+ϵ⊙gt. \begin{aligned} \mathbf{s}_t & \leftarrow \gamma \mathbf{s}_{t-1} + (1 - \gamma) \mathbf{g}_t^2, \\ \mathbf{x}_t & \leftarrow \mathbf{x}_{t-1} - \frac{\eta}{\sqrt{\mathbf{s}_t + \epsilon}} \odot \mathbf{g}_t. \end{aligned} stxt←γst−1+(1−γ)gt2,←xt−1−st+ϵη⊙gt.

Adadelta

st=ρst−1+(1−ρ)gt2. \begin{aligned} \mathbf{s}_t & = \rho \mathbf{s}_{t-1} + (1 - \rho) \mathbf{g}_t^2. \end{aligned} st=ρst−1+(1−ρ)gt2. gt′=Δxt−1+ϵst+ϵ⊙gt, \begin{aligned} \mathbf{g}_t' & = \frac{\sqrt{\Delta\mathbf{x}_{t-1} + \epsilon} }{\sqrt{ {\mathbf{s}_t + \epsilon} } } \odot \mathbf{g}_t, \\ \end{aligned} gt′=st+ϵΔxt−1+ϵ⊙gt, Δxt=ρΔxt−1+(1−ρ)gt′2, \begin{aligned} \Delta \mathbf{x}_t & = \rho \Delta\mathbf{x}_{t-1} + (1 - \rho) {\mathbf{g}_t'}^2, \end{aligned} Δxt=ρΔxt−1+(1−ρ)gt′2, xt=xt−1−gt′. \begin{aligned} \mathbf{x}_t & = \mathbf{x}_{t-1} - \mathbf{g}_t'. \\ \end{aligned} xt=xt−1−gt′. 总的来说和前面几个算法差别不大，只是分成两步，进一步进行平滑了，实践中我很少用这个算法，以前对比测试的时候也没感觉到有什么突出的地方。

Adam

目前最受欢迎的算法，结合了前面各种算法的特点，不过有时会因为方差控制不良导致发散。我在实践中并没有感受到该算法相比RMSProp的优势，反而常常比不过RMSProp。想让这个算法好用还需要进行预热，不如RMSProp方便。

该算法首先对梯度和梯度平方指数平滑，平滑系数通常取β1=0.9\beta_1 = 0.9β1=0.9与β2=0.999\beta_2 = 0.999β2=0.999: vt←β1vt−1+(1−β1)gt,st←β2st−1+(1−β2)gt2. \begin{aligned} \mathbf{v}_t & \leftarrow \beta_1 \mathbf{v}_{t-1} + (1 - \beta_1) \mathbf{g}_t, \\ \mathbf{s}_t & \leftarrow \beta_2 \mathbf{s}_{t-1} + (1 - \beta_2) \mathbf{g}_t^2. \end{aligned} vtst←β1vt−1+(1−β1)gt,←β2st−1+(1−β2)gt2.

然后还需要估计v0,s0v_0, s_0v0,s0:

v^0=v01−β10 and s^0=s01−β20. \hat{\mathbf{v}}_0 = \frac{\mathbf{v}_0}{1 - \beta_1^0} \text{ and } \hat{\mathbf{s}}_0 = \frac{\mathbf{s}_0}{1 - \beta_2^0}. v^0=1−β10v0 and s^0=1−β20s0.

然后和RMSProp类似，区别在于这里使用动量而不是梯度进行更新：

gt′=ηv^ts^t+ϵ. \mathbf{g}_t' = \frac{\eta \hat{\mathbf{v}}_t}{\sqrt{\hat{\mathbf{s}}_t} + \epsilon}. gt′=s^t+ϵηv^t. xt←xt−1−gt′. \mathbf{x}_t \leftarrow \mathbf{x}_{t-1} - \mathbf{g}_t'. xt←xt−1−gt′. 因为Adam使用了动量，我在实践中不喜欢动量，常常会导致收敛变慢，因此较少使用Adam。

思考

前面这些算法大多是在梯度上做文章，核心做法就是两个：

对不同维度进行个性化缩放，

对梯度（以及梯度平方）指数平滑，减小方差。

我在参数优化的时候还喜欢使用一个trick，就是将梯度的方向和长度分开使用，使用梯度得到更新方向，使用固定值或其他方法给出步长，这样不会因为

Imbalance

不平衡样本

采样

过采样（插值）

欠采样（平衡采样/分层采样）：集成学习或batch平衡化

权重

基于样本比例分配样本权重

OHEM(Online Hard Example Miniing)：基于loss判断hard example，按照3:1比例选择hard example，忽略其他的easy example。数据集大时提升明显

Focal Loss：按照loss平滑调整权重

异常检测

适用范围：

无标签

有标签但正样本极少

优先使用有监督方法，无监督方法可用于抽取特征后接入到有监督

统计模型（假设检验得到异常值，但需要做出假设）

线性模型（PCA投射后重建误差）

相似度（密度、距离、夹角、超平面划分次数）KNN距离，密度分析(LOF, LOCI, loOP)，超平面划分(Isolation Forest孤立一个样本的最少划分数)，样本夹角方差(ABOD)

异常检测+模型融合，使用bagging提升鲁棒性

基于业务的特定领域检测

人工

小样本的分类边界手工生成数据

衡量指标

ROC

mAP(全类平均正确率, mean Average Precision)：各类别PR曲线下面积之平均

prn(precision@Rank n)：假设有n个异常值时的精确度

PyTorch

流程

定义网络

class Net(nn.Module):
    def forward(self, x):
        y = f(x, ...)
        return y

训练

for ...:
    inputs, labels = Tensor(IN), Tensor(OUT)
    optimizer.zero_grad()
    outputs = net(inputs)
    loss = loss_function(outputs, labels)
    loss.backward()
    optimizer.step()

使用 GPU

device = torch.device('cuda:1') if torch.cuda.is_available() else 'cpu'
input_data = data.to(device)
model = MyModel(...).to(device)

所有的 Tensor 都有一个 to(device) 方法

不反向传播

with torch.no_grad():
    ...

保存/载入数据

torch.save(net, 'net.pkl')
net = torch.load('net.pkl')

torch.save(net.state_dict(), 'net_parm.pkl')
net = Net()
net.load_state_dict(torch.load('net_parm.pkl'))

异步梯度累积

梯度累积

optimizer.zero_grad()  # 先把梯度清零
loss = loss_function(...)
loss.backward()

梯度传递

with mutex_lock():
    global_net._grad = local_net.grad
    global_optimizer.step()

Example

import torch
from torch import nn
from torch.autograd import Variable
from torch.nn import functional as F
from torch import multiprocessing as mp
# Defining the Network

def no_op(x, *args, **kwargs):
    return x

class FeedForwardNet(nn.Module):

    def __init__(self, shape=[20, 100, 5], activation_fn=F.rrelu, initializer=nn.init.kaiming_normal, final_layer_afn=no_op,
                 final_layer_init=no_op, bn=False):
        super(FeedForwardNet, self).__init__()

        self.shape = shape
        self.activation_fn = activation_fn
        self.final_layer_afn = final_layer_afn
        self.bn = torch.nn.BatchNorm1d if bn else no_op
        self.initializer = initializer
        self.final_layer_init = final_layer_init

        for index, size in enumerate(zip(shape[:-1], shape[1:])):
            self.__setattr__("fc_{}".format(index),
                             nn.Linear(size[0], size[1]))
            initializer(self.__getattr__("fc_{}".format(index)).weight)
        final_layer_init(self.__getattr__("fc_{}".format(index)).weight)

    def forward(self, x):
        for i in range(len(self.shape) - 2):
            layer = self.__getattr__("fc_{}".format(i))
            # tensorflow feed_forward implemention is bn before activation
            x = self.bn(x)
            x = self.activation_fn(layer(x))
        x = self.final_layer_afn(self.__getattr__(
            "fc_{}".format(len(self.shape) - 2))(x))
        return x

def main():
    net = FeedForwardNet([20, 100, 5])
    optimizer = torch.optim.RMSprop(net.parameters(), lr=1e-4)
    loss_func = torch.nn.MSELoss()

    inputs, labels = Variable(torch.rand(
        [100, 20])), Variable(torch.rand([100, 5]))

    prediction = net(inputs)
    loss = loss_func(prediction, labels)
    optimizer.zero_grad()
    # print(next(net.parameters()).grad)
    # next(net.parameters())._grad.add_(10000)
    loss.backward()
    # print(next(net.parameters()).grad)
    # prediction = net(inputs)
    # loss = loss_func(prediction, labels)

    # loss.backward()
    # print(next(net.parameters()).grad)
    optimizer.step()  # step could run multiple

    inputs, labels = inputs.cuda(), labels.cuda()
    net1, net2 = FeedForwardNet(), FeedForwardNet()
    net1.cuda()
    net2.cuda()
    opt1, opt2 = [torch.optim.RMSprop(n.parameters()) for n in [net1, net2]]
    opt1.zero_grad(), opt2.zero_grad()
    loss = loss_func(net1(inputs), labels)
    for gp, lp in zip(net2.parameters(), net1.parameters()):
        gp._grad = lp.grad
    opt2.step()

if __name__ == '__main__':
    main()

tsne.py

from pylab import *
from sklearn.manifold import TSNE
from sklearn.decomposition import PCA

def plot_embedding(X, y, plot_title=None, unbalanced=True, target_names=None):
    x_min, x_max = X[:, 0].min(), X[:, 0].max()
    y_min, y_max = X[:, 1].min(), X[:, 1].max()
    figure()
    for i in range(X.shape[0]):
        text(
            X[i, 0],
            X[i, 1],
            str(y[i]),
            color=cm.Set1(1 - y[i] / 7),
            fontdict={
                'weight': 'bold',
                'size': 4 if y[i] == 0 and unbalanced else 9})
    if title is not None:
        title(plot_title)

    factor = 0.1
    margin_x, margin_y = factor * (x_max - x_min), factor * (y_max - y_min)
    axis([
        x_min - margin_x,
        x_max + margin_x,
        y_min - margin_y,
        y_max + margin_y])

    figure()
    name_dict = set()
    for i in range(X.shape[0]):
        name = None
        if target_names is not None:
            if y[i] not in name_dict:
                name_dict.add(y[i])
                name = target_names[y[i]]
        plot(
            X[i, 0],
            X[i, 1],
            '.',
            c=cm.Set1(1 - y[i] / 7),
            markersize=2 if y[i] == 0 and unbalanced else 4,
            label=name)
    if title is not None:
        title(plot_title)
    legend()

def tsne(data_test, data_label, title=None, unbalanced=False, method='tsne', label_names=None, one_hot=False):
    if not one_hot:
        label_n = data_label
    else:
        label_n = argmax(data_label, axis=1)

    models = {
        'tsne': TSNE(n_iter=5000),
        'pca': PCA()}

    model = models[method.lower()]
    tsne_transformed = model.fit_transform(data_test, label_n)

    plot_embedding(tsne_transformed, label_n, title if title else 't-sne projection', unbalanced, label_names)
    return tsne_transformed, label_n

TensorFlow

列举所有本地设备

from tensorflow github

>>> from tensorflow.python.client import device_lib
>>> device_lib.list_local_devices()

[name: "/cpu:0"
 device_type: "CPU"
 memory_limit: 268435456
 locality {
 }
 incarnation: 958941086745573894]

什么也不做的 op

>>> help(tf.identity)

identity(input, name=None)
    Return a tensor with the same shape and contents as the input tensor or value.

    Args:
      input: A `Tensor`.
      name: A name for the operation (optional).

    Returns:
      A `Tensor`. Has the same type as `input`.

mnist_with_summaries.py

# Copyright 2015 The TensorFlow Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the 'License');
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an 'AS IS' BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
# Forked from https://github.com/tensorflow/tensorflow/blob/r1.3/tensorflow/examples/tutorials/mnist/mnist_with_summaries.py
"""A simple MNIST classifier which displays summaries in TensorBoard.

This is an unimpressive MNIST model, but it is a good example of using
tf.name_scope to make a graph legible in the TensorBoard graph explorer, and of
naming summary tags so that they are grouped meaningfully in TensorBoard.

It demonstrates the functionality of every TensorBoard dashboard.
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

import argparse
import os
import sys

import tensorflow as tf

from tensorflow.examples.tutorials.mnist import input_data

FLAGS = None

def train():
  # Import data
  mnist = input_data.read_data_sets(FLAGS.data_dir,
                                    one_hot=True,
                                    fake_data=FLAGS.fake_data)

  sess = tf.InteractiveSession()
  # Create a multilayer model.

  # Input placeholders
  with tf.name_scope('input'):
    x = tf.placeholder(tf.float32, [None, 784], name='x-input')
    y_ = tf.placeholder(tf.float32, [None, 10], name='y-input')

  with tf.name_scope('input_reshape'):
    image_shaped_input = tf.reshape(x, [-1, 28, 28, 1])
    tf.summary.image('input', image_shaped_input, 10)

  # We can't initialize these variables to 0 - the network will get stuck.
  def weight_variable(shape):
    """Create a weight variable with appropriate initialization."""
    initial = tf.truncated_normal(shape, stddev=0.1)
    return tf.Variable(initial)

  def bias_variable(shape):
    """Create a bias variable with appropriate initialization."""
    initial = tf.constant(0.1, shape=shape)
    return tf.Variable(initial)

  def variable_summaries(var):
    """Attach a lot of summaries to a Tensor (for TensorBoard visualization)."""
    with tf.name_scope('summaries'):
      mean = tf.reduce_mean(var)
      tf.summary.scalar('mean', mean)
      with tf.name_scope('stddev'):
        stddev = tf.sqrt(tf.reduce_mean(tf.square(var - mean)))
      tf.summary.scalar('stddev', stddev)
      tf.summary.scalar('max', tf.reduce_max(var))
      tf.summary.scalar('min', tf.reduce_min(var))
      tf.summary.histogram('histogram', var)

  def nn_layer(input_tensor, input_dim, output_dim, layer_name, act=tf.nn.relu):
    """Reusable code for making a simple neural net layer.

    It does a matrix multiply, bias add, and then uses ReLU to nonlinearize.
    It also sets up name scoping so that the resultant graph is easy to read,
    and adds a number of summary ops.
    """
    # Adding a name scope ensures logical grouping of the layers in the graph.
    with tf.name_scope(layer_name):
      # This Variable will hold the state of the weights for the layer
      with tf.name_scope('weights'):
        weights = weight_variable([input_dim, output_dim])
        variable_summaries(weights)
      with tf.name_scope('biases'):
        biases = bias_variable([output_dim])
        variable_summaries(biases)
      with tf.name_scope('Wx_plus_b'):
        preactivate = tf.matmul(input_tensor, weights) + biases
        tf.summary.histogram('pre_activations', preactivate)
      activations = act(preactivate, name='activation')
      tf.summary.histogram('activations', activations)
      return activations

  hidden1 = nn_layer(x, 784, 500, 'layer1')

  with tf.name_scope('dropout'):
    keep_prob = tf.placeholder(tf.float32)
    tf.summary.scalar('dropout_keep_probability', keep_prob)
    dropped = tf.nn.dropout(hidden1, keep_prob)

  # Do not apply softmax activation yet, see below.
  y = nn_layer(dropped, 500, 10, 'layer2', act=tf.identity)

  with tf.name_scope('cross_entropy'):
    # The raw formulation of cross-entropy,
    #
    # tf.reduce_mean(-tf.reduce_sum(y_ * tf.log(tf.softmax(y)),
    #                               reduction_indices=[1]))
    #
    # can be numerically unstable.
    #
    # So here we use tf.nn.softmax_cross_entropy_with_logits on the
    # raw outputs of the nn_layer above, and then average across
    # the batch.
    diff = tf.nn.softmax_cross_entropy_with_logits(labels=y_, logits=y)
    with tf.name_scope('total'):
      cross_entropy = tf.reduce_mean(diff)
  tf.summary.scalar('cross_entropy', cross_entropy)

  with tf.name_scope('train'):
    train_step = tf.train.AdamOptimizer(FLAGS.learning_rate).minimize(
        cross_entropy)

  with tf.name_scope('accuracy'):
    with tf.name_scope('correct_prediction'):
      correct_prediction = tf.equal(tf.argmax(y, 1), tf.argmax(y_, 1))
    with tf.name_scope('accuracy'):
      accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
  tf.summary.scalar('accuracy', accuracy)

  # Merge all the summaries and write them out to
  # /tmp/tensorflow/mnist/logs/mnist_with_summaries (by default)
  merged = tf.summary.merge_all()
  train_writer = tf.summary.FileWriter(FLAGS.log_dir + '/train', sess.graph)
  test_writer = tf.summary.FileWriter(FLAGS.log_dir + '/test')
  tf.global_variables_initializer().run()

  # Train the model, and also write summaries.
  # Every 10th step, measure test-set accuracy, and write test summaries
  # All other steps, run train_step on training data, & add training summaries

  def feed_dict(train):
    """Make a TensorFlow feed_dict: maps data onto Tensor placeholders."""
    if train or FLAGS.fake_data:
      xs, ys = mnist.train.next_batch(100, fake_data=FLAGS.fake_data)
      k = FLAGS.dropout
    else:
      xs, ys = mnist.test.images, mnist.test.labels
      k = 1.0
    return {x: xs, y_: ys, keep_prob: k}

  for i in range(FLAGS.max_steps):
    if i % 10 == 0:  # Record summaries and test-set accuracy
      summary, acc = sess.run([merged, accuracy], feed_dict=feed_dict(False))
      test_writer.add_summary(summary, i)
      print('Accuracy at step %s: %s' % (i, acc))
    else:  # Record train set summaries, and train
      if i % 100 == 99:  # Record execution stats
        run_options = tf.RunOptions(trace_level=tf.RunOptions.FULL_TRACE)
        run_metadata = tf.RunMetadata()
        summary, _ = sess.run([merged, train_step],
                              feed_dict=feed_dict(True),
                              options=run_options,
                              run_metadata=run_metadata)
        train_writer.add_run_metadata(run_metadata, 'step%03d' % i)
        train_writer.add_summary(summary, i)
        print('Adding run metadata for', i)
      else:  # Record a summary
        summary, _ = sess.run([merged, train_step], feed_dict=feed_dict(True))
        train_writer.add_summary(summary, i)
  train_writer.close()
  test_writer.close()

def main(_):
  if tf.gfile.Exists(FLAGS.log_dir):
    tf.gfile.DeleteRecursively(FLAGS.log_dir)
  tf.gfile.MakeDirs(FLAGS.log_dir)
  train()

if __name__ == '__main__':
  parser = argparse.ArgumentParser()
  parser.add_argument('--fake_data', nargs='?', const=True, type=bool,
                      default=False,
                      help='If true, uses fake data for unit testing.')
  parser.add_argument('--max_steps', type=int, default=1000,
                      help='Number of steps to run trainer.')
  parser.add_argument('--learning_rate', type=float, default=0.001,
                      help='Initial learning rate')
  parser.add_argument('--dropout', type=float, default=0.9,
                      help='Keep probability for training dropout.')
  parser.add_argument(
      '--data_dir',
      type=str,
      default=os.path.join(os.getenv('TEST_TMPDIR', '/tmp'),
                           'tensorflow/mnist/input_data'),
      help='Directory for storing input data')
  parser.add_argument(
      '--log_dir',
      type=str,
      default=os.path.join(os.getenv('TEST_TMPDIR', '/tmp'),
                           'tensorflow/mnist/logs/mnist_with_summaries'),
      help='Summaries log directory')
  FLAGS, unparsed = parser.parse_known_args()
  tf.app.run(main=main, argv=[sys.argv[0]] + unparsed)

tensorboard_advanced.py

'''
Graph and Loss visualization using Tensorboard.
This example is using the MNIST database of handwritten digits
(http://yann.lecun.com/exdb/mnist/)

Author: Aymeric Damien
Project: https://github.com/aymericdamien/TensorFlow-Examples/
'''

from __future__ import print_function

import tensorflow as tf

# Import MNIST data
from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets("/tmp/data/", one_hot=True)

# Parameters
learning_rate = 0.01
training_epochs = 25
batch_size = 100
display_step = 1
logs_path = '/tmp/tensorflow_logs/example/'

# Network Parameters
n_hidden_1 = 256 # 1st layer number of features
n_hidden_2 = 256 # 2nd layer number of features
n_input = 784 # MNIST data input (img shape: 28*28)
n_classes = 10 # MNIST total classes (0-9 digits)

# tf Graph Input
# mnist data image of shape 28*28=784
x = tf.placeholder(tf.float32, [None, 784], name='InputData')
# 0-9 digits recognition => 10 classes
y = tf.placeholder(tf.float32, [None, 10], name='LabelData')

# Create model
def multilayer_perceptron(x, weights, biases):
    # Hidden layer with RELU activation
    layer_1 = tf.add(tf.matmul(x, weights['w1']), biases['b1'])
    layer_1 = tf.nn.relu(layer_1)
    # Create a summary to visualize the first layer ReLU activation
    tf.summary.histogram("relu1", layer_1)
    # Hidden layer with RELU activation
    layer_2 = tf.add(tf.matmul(layer_1, weights['w2']), biases['b2'])
    layer_2 = tf.nn.relu(layer_2)
    # Create another summary to visualize the second layer ReLU activation
    tf.summary.histogram("relu2", layer_2)
    # Output layer
    out_layer = tf.add(tf.matmul(layer_2, weights['w3']), biases['b3'])
    return out_layer

# Store layers weight & bias
weights = {
    'w1': tf.Variable(tf.random_normal([n_input, n_hidden_1]), name='W1'),
    'w2': tf.Variable(tf.random_normal([n_hidden_1, n_hidden_2]), name='W2'),
    'w3': tf.Variable(tf.random_normal([n_hidden_2, n_classes]), name='W3')
}
biases = {
    'b1': tf.Variable(tf.random_normal([n_hidden_1]), name='b1'),
    'b2': tf.Variable(tf.random_normal([n_hidden_2]), name='b2'),
    'b3': tf.Variable(tf.random_normal([n_classes]), name='b3')
}

# Encapsulating all ops into scopes, making Tensorboard's Graph
# Visualization more convenient
with tf.name_scope('Model'):
    # Build model
    pred = multilayer_perceptron(x, weights, biases)

with tf.name_scope('Loss'):
    # Softmax Cross entropy (cost function)
    loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=pred, labels=y))

with tf.name_scope('SGD'):
    # Gradient Descent
    optimizer = tf.train.GradientDescentOptimizer(learning_rate)
    # Op to calculate every variable gradient
    grads = tf.gradients(loss, tf.trainable_variables())
    grads = list(zip(grads, tf.trainable_variables()))
    # Op to update all variables according to their gradient
    apply_grads = optimizer.apply_gradients(grads_and_vars=grads)

with tf.name_scope('Accuracy'):
    # Accuracy
    acc = tf.equal(tf.argmax(pred, 1), tf.argmax(y, 1))
    acc = tf.reduce_mean(tf.cast(acc, tf.float32))

# Initialize the variables (i.e. assign their default value)
init = tf.global_variables_initializer()

# Create a summary to monitor cost tensor
tf.summary.scalar("loss", loss)
# Create a summary to monitor accuracy tensor
tf.summary.scalar("accuracy", acc)
# Create summaries to visualize weights
for var in tf.trainable_variables():
    tf.summary.histogram(var.name, var)
# Summarize all gradients
for grad, var in grads:
    tf.summary.histogram(var.name + '/gradient', grad)
# Merge all summaries into a single op
merged_summary_op = tf.summary.merge_all()

# Start training
with tf.Session() as sess:

    # Run the initializer
    sess.run(init)

    # op to write logs to Tensorboard
    summary_writer = tf.summary.FileWriter(logs_path,
                                            graph=tf.get_default_graph())

    # Training cycle
    for epoch in range(training_epochs):
        avg_cost = 0.
        total_batch = int(mnist.train.num_examples/batch_size)
        # Loop over all batches
        for i in range(total_batch):
            batch_xs, batch_ys = mnist.train.next_batch(batch_size)
            # Run optimization op (backprop), cost op (to get loss value)
            # and summary nodes
            _, c, summary = sess.run([apply_grads, loss, merged_summary_op],
                                     feed_dict={x: batch_xs, y: batch_ys})
            # Write logs at every iteration
            summary_writer.add_summary(summary, epoch * total_batch + i)
            # Compute average loss
            avg_cost += c / total_batch
        # Display logs per epoch step
        if (epoch+1) % display_step == 0:
            print("Epoch:", '%04d' % (epoch+1), "cost=", "{:.9f}".format(avg_cost))

    print("Optimization Finished!")

    # Test model
    # Calculate accuracy
    print("Accuracy:", acc.eval({x: mnist.test.images, y: mnist.test.labels}))

    print("Run the command line:\n" \
          "--> tensorboard --logdir=/tmp/tensorflow_logs " \
          "\nThen open http://0.0.0.0:6006/ into your web browser")

1-TensorFlow_Cheatsheet

常用功能

tf.train.get_or_create_global_step(graph=None)

tf.set_random_seed(seed)

异常检测

tf.check_numerics(tensor, message, name=None)

'''
Checks a tensor for NaN and Inf values.

When run, reports an `InvalidArgument` error if `tensor` has any values
that are not a number (NaN) or infinity (Inf). Otherwise, passes `tensor` as-is.

Args:
  tensor: A `Tensor`. Must be one of the following types: `half`, `bfloat16`, `float32`, `float64`.
  message: A `string`. Prefix of the error message.
  name: A name for the operation (optional).

Returns:
  A `Tensor`. Has the same type as `tensor`.
'''

tf.Assert(condition, data, summarize=None, name=None)

# Ensure maximum element of x is smaller or equal to 1
assert_op = tf.Assert(tf.less_equal(tf.reduce_max(x), 1.), [x])
with tf.control_dependencies([assert_op]):
  ... code using x ...
'''
Args:
  condition: The condition to evaluate.
  data: The tensors to print out when condition is false.
  summarize: Print this many entries of each tensor.
  name: A name for this operation (optional).

Returns:
  assert_op: An `Operation` that, when executed, raises a
  `tf.errors.InvalidArgumentError` if `condition` is not true.
  @compatibility{eager} returns None.

Raises:
  @compatibility{eager} `tf.errors.InvalidArgumentError` if `condition`
  is not true
'''

tf.Print(input_, data, message=None, first_n=None, summarize=None, name=None)

'''
Prints a list of tensors.

This is an identity op (behaves like `tf.identity`) with the side effect
of printing `data` when evaluating.

Note: This op prints to the standard error. It is not currently compatible
  with jupyter notebook (printing to the notebook *server's* output, not into
  the notebook).

Args:
  input_: A tensor passed through this op.
  data: A list of tensors to print out when op is evaluated.
  message: A string, prefix of the error message.
  first_n: Only log `first_n` number of times. Negative numbers log always;
           this is the default.
  summarize: Only print this many entries of each tensor. If None, then a
             maximum of 3 elements are printed per input tensor.
  name: A name for the operation (optional).

Returns:
  A `Tensor`. Has the same type and contents as `input_`.
'''

自动设定参数

slim.arg_scope(list_ops_or_scope, **kwargs)

'''
Args:
  list_ops_or_scope: List or tuple of operations to set argument scope for or
    a dictionary containing the current scope. When list_ops_or_scope is a
    dict, kwargs must be empty. When list_ops_or_scope is a list or tuple,
    then every op in it need to be decorated with @add_arg_scope to work.
  **kwargs: keyword=value that will define the defaults for each op in
            list_ops. All the ops need to accept the given set of arguments.

Yields:
  the current_scope, which is a dictionary of {op: {arg: value}}
Raises:
  TypeError: if list_ops is not a list or a tuple.
  ValueError: if any op in list_ops has not be decorated with @add_arg_scope.
'''

滑动平均

tf.train.ExponentialMovingAverage(decay, num_updates=None, zero_debias=False, name='ExponentialMovingAverage')
'''
Args:
  decay: Float.  The decay to use.
  num_updates: Optional count of number of updates applied to variables.
  zero_debias: If `True`, zero debias moving-averages that are initialized
    with tensors.
  name: String. Optional prefix name to use for the name of ops added in
    `apply()`.
'''

变量管理

类型转换

tf.cast(x, dtype, name=None)

x = tf.constant([1.8, 2.2], dtype=tf.float32)
tf.cast(x, tf.int32)  # [1, 2], dtype=tf.int32

tf.convert_to_tensor(value, dtype=None, name=None, preferred_dtype=None)

import numpy as np

def my_func(arg):
  arg = tf.convert_to_tensor(arg, dtype=tf.float32)
  return tf.matmul(arg, arg) + arg

# The following calls are equivalent.
value_1 = my_func(tf.constant([[1.0, 2.0], [3.0, 4.0]]))
value_2 = my_func([[1.0, 2.0], [3.0, 4.0]])
value_3 = my_func(np.array([[1.0, 2.0], [3.0, 4.0]], dtype=np.float32))

Scopes

特点：

唯一的区别在于 tf.get_variable 得到的变量名称

_get_variable 在任何 scope 下均不会 rotate 变量名称，tf.Variable 一直都会 rotate 变量名称

scope 的 reuse 参数通常用于联系训练模型与测试模型，尤其在于两个模型有差异却共享参数时

NameScope

with tf.names_cope('name'):
    a = tf.Variable(1, name='a')    # 'name_i/a'    自动 rotate 名称
    b = tf.get_variable('b', ...)    # 'b'    名字固定

VariableScope

with tf.variable_cope('name'):
    a = tf.Variable(1, name='a')    # 'name_i/a'    自动 rotate 名称
    b = tf.get_variable('b', ...)    # 'name/b'    名字固定

Distributed_tensorflow.py

'''
This is a fork from https://github.com/ischlag/distributed-tensorflow-example
Distributed Tensorflow 1.2.0 example of using data parallelism and share model parameters.
Trains a simple sigmoid neural network on mnist for 20 epochs on three machines using one parameter server.

Change the hardcoded host urls below with your own hosts.
Run like this:

pc-01{% math_inline %} python example.py --job_name="ps" --task_index=0
pc-02{% endmath_inline %} python example.py --job_name="worker" --task_index=0
pc-03{% math_inline %} python example.py --job_name="worker" --task_index=1
pc-04{% endmath_inline %} python example.py --job_name="worker" --task_index=2

More details here: ischlag.github.io
'''

from __future__ import print_function

import tensorflow as tf
import sys
import time

# cluster specification
parameter_servers = ["pc-01:2222"]
workers = [    "pc-02:2222",
            "pc-03:2222",
            "pc-04:2222"]
cluster = tf.train.ClusterSpec({"ps":parameter_servers, "worker":workers})

# input flags
tf.app.flags.DEFINE_string("job_name", "", "Either 'ps' or 'worker'")
tf.app.flags.DEFINE_integer("task_index", 0, "Index of task within the job")
FLAGS = tf.app.flags.FLAGS

# start a server for a specific task
server = tf.train.Server(
    cluster,
    job_name=FLAGS.job_name,
    task_index=FLAGS.task_index)

# config
batch_size = 100
learning_rate = 0.0005
training_epochs = 20
logs_path = "/tmp/mnist/1"

# load mnist data set
from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets('MNIST_data', one_hot=True)

if FLAGS.job_name == "ps":
    server.join()
elif FLAGS.job_name == "worker":

    # Between-graph replication
    with tf.device(tf.train.replica_device_setter(
        worker_device="/job:worker/task:%d" % FLAGS.task_index,
        cluster=cluster)):

        # count the number of updates
        global_step = tf.get_variable(
            'global_step',
            [],
            initializer = tf.constant_initializer(0),
            trainable = False)

        # input images
        with tf.name_scope('input'):
          # None -> batch size can be any size, 784 -> flattened mnist image
          x = tf.placeholder(tf.float32, shape=[None, 784], name="x-input")
          # target 10 output classes
          y_ = tf.placeholder(tf.float32, shape=[None, 10], name="y-input")

        # model parameters will change during training so we use tf.Variable
        tf.set_random_seed(1)
        with tf.name_scope("weights"):
            W1 = tf.Variable(tf.random_normal([784, 100]))
            W2 = tf.Variable(tf.random_normal([100, 10]))

        # bias
        with tf.name_scope("biases"):
            b1 = tf.Variable(tf.zeros([100]))
            b2 = tf.Variable(tf.zeros([10]))

        # implement model
        with tf.name_scope("softmax"):
            # y is our prediction
            z2 = tf.add(tf.matmul(x,W1),b1)
            a2 = tf.nn.sigmoid(z2)
            z3 = tf.add(tf.matmul(a2,W2),b2)
            y  = tf.nn.softmax(z3)

        # specify cost function
        with tf.name_scope('cross_entropy'):
            # this is our cost
            cross_entropy = tf.reduce_mean(
                -tf.reduce_sum(y_ * tf.log(y), reduction_indices=[1]))

        # specify optimizer
        with tf.name_scope('train'):
            # optimizer is an "operation" which we can execute in a session
            grad_op = tf.train.GradientDescentOptimizer(learning_rate)
            '''
            rep_op = tf.train.SyncReplicasOptimizer(
                grad_op,
                replicas_to_aggregate=len(workers),
                 replica_id=FLAGS.task_index,
                 total_num_replicas=len(workers),
                 use_locking=True)
             train_op = rep_op.minimize(cross_entropy, global_step=global_step)
             '''
            train_op = grad_op.minimize(cross_entropy, global_step=global_step)

        '''
        init_token_op = rep_op.get_init_tokens_op()
        chief_queue_runner = rep_op.get_chief_queue_runner()
        '''

        with tf.name_scope('Accuracy'):
            # accuracy
            correct_prediction = tf.equal(tf.argmax(y,1), tf.argmax(y_,1))
            accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))

        # create a summary for our cost and accuracy
        tf.summary.scalar("cost", cross_entropy)
        tf.summary.scalar("accuracy", accuracy)

        # merge all summaries into a single "operation" which we can execute in a session
        summary_op = tf.summary.merge_all()
        init_op = tf.global_variables_initializer()
        print("Variables initialized ...")

    sv = tf.train.Supervisor(is_chief=(FLAGS.task_index == 0),
                                                        global_step=global_step,
                                                        init_op=init_op)

    begin_time = time.time()
    frequency = 100
    with sv.prepare_or_wait_for_session(server.target) as sess:
        '''
        # is chief
        if FLAGS.task_index == 0:
            sv.start_queue_runners(sess, [chief_queue_runner])
            sess.run(init_token_op)
        '''
        # create log writer object (this will log on every machine)
        writer = tf.summary.FileWriter(logs_path, graph=tf.get_default_graph())

        # perform training cycles
        start_time = time.time()
        for epoch in range(training_epochs):

            # number of batches in one epoch
            batch_count = int(mnist.train.num_examples/batch_size)

            count = 0
            for i in range(batch_count):
                batch_x, batch_y = mnist.train.next_batch(batch_size)

                # perform the operations we defined earlier on batch
                _, cost, summary, step = sess.run(
                                                [train_op, cross_entropy, summary_op, global_step],
                                                feed_dict={x: batch_x, y_: batch_y})
                writer.add_summary(summary, step)

                count += 1
                if count % frequency == 0 or i+1 == batch_count:
                    elapsed_time = time.time() - start_time
                    start_time = time.time()
                    print("Step: %d," % (step+1),
                                " Epoch: %2d," % (epoch+1),
                                " Batch: %3d of %3d," % (i+1, batch_count),
                                " Cost: %.4f," % cost,
                                " AvgTime: %3.2fms" % float(elapsed_time*1000/frequency))
                    count = 0

        print("Test-Accuracy: %2.2f" % sess.run(accuracy, feed_dict={x: mnist.test.images, y_: mnist.test.labels}))
        print("Total Time: %3.2fs" % float(time.time() - begin_time))
        print("Final Cost: %.4f" % cost)

    sv.stop()
    print("done")

Duplicate_a_tensorflow_graph

Duplicate a tensorflow graph

This is a fork from https://stackoverflow.com/questions/37801137/duplicate-a-tensorflow-graph

Q by MBZ:

What is the best way of duplicating a TensorFlow graph and keep it uptodate?

Ideally I want to put the duplicated graph on another device (e.g. from GPU to CPU) and then time to time update the copy.

A by rdadolf:

Short answer: You probably want checkpoint files (permalink:https://github.com/tensorflow/tensorflow/blob/r1.1/tensorflow/docs_src/programmers_guide/variables.md#checkpoint-files).

Long answer:

Let's be clear about the setup here. I'll assume that you have two devices, A and B, and you are training on A and running inference on B. Periodically, you'd like to update the parameters on the device running inference with new parameters found during training on the other. The tutorial linked above is a good place to start. It shows you how tf.train.Saver objects work, and you shouldn't need anything more complicated here.

Here is an example:

import tensorflow as tf

def build_net(graph, device):
    with graph.as_default():
        with graph.device(device):
            # Input placeholders
            inputs = tf.placeholder(tf.float32, [None, 784])
            labels = tf.placeholder(tf.float32, [None, 10])
            # Initialization
            w0 = tf.get_variable('w0', shape=[784, 256], initializer=tf.contrib.layers.xavier_initializer())
            w1 = tf.get_variable('w1', shape=[256, 256], initializer=tf.contrib.layers.xavier_initializer())
            w2 = tf.get_variable('w2', shape=[256, 10], initializer=tf.contrib.layers.xavier_initializer())
            b0 = tf.Variable(tf.zeros([256]))
            b1 = tf.Variable(tf.zeros([256]))
            b2 = tf.Variable(tf.zeros([10]))
            # Inference network
            h1 = tf.nn.relu(tf.matmul(inputs, w0) + b0)
            h2 = tf.nn.relu(tf.matmul(h1, w1) + b1)
            output = tf.nn.softmax(tf.matmul(h2, w2) + b2)
            # Training network
            cross_entropy = tf.reduce_mean(-tf.reduce_sum(labels * tf.log(output), reduction_indices=[1]))
            optimizer = tf.train.GradientDescentOptimizer(0.5).minimize(cross_entropy)
            # Your checkpoint function
            saver = tf.train.Saver()
            return tf.global_variables_initializer(), inputs, labels, output, optimizer, saver

The code for the training program:

def programA_main():
    from tensorflow.examples.tutorials.mnist import input_data
    mnist = input_data.read_data_sets('MNIST_data', one_hot=True)
    # Build training network on device A
    graphA = tf.Graph()
    init, inputs, labels, _, training_net, saver = build_net(graphA, '/cpu:0')
    with tf.Session(graph=graphA) as sess:
        sess.run(init)
        for step in range(1, 10000):
            batch = mnist.train.next_batch(50)
            sess.run(training_net, feed_dict={inputs: batch[0], labels: batch[1]})
            if step % 100 == 0:
                saver.save(sess, '/tmp/graph.checkpoint')
                print('saved checkpoint')

''' ...and code for an inference program: '''

def programB_main():
    from tensorflow.examples.tutorials.mnist import input_data
    mnist = input_data.read_data_sets('MNIST_data', one_hot=True)
    # Build inference network on device B
    graphB = tf.Graph()
    init, inputs, _, inference_net, _, saver = build_net(graphB, '/cpu:0')
    with tf.Session(graph=graphB) as sess:
        batch = mnist.test.next_batch(50)

        saver.restore(sess, '/tmp/graph.checkpoint')
        print('loaded checkpoint')
        out = sess.run(inference_net, feed_dict={inputs: batch[0]})
        print(out[0])

        import time
        time.sleep(2)

        saver.restore(sess, '/tmp/graph.checkpoint')
        print('loaded checkpoint')
        out = sess.run(inference_net, feed_dict={inputs: batch[0]})
        print(out[1])

If you fire up the training program and then the inference program, you'll see the inference program produces two different outputs (from the same input batch). This is a result of it picking up the parameters that the training program has checkpointed.

Now, this program obviously isn't your end point. We don't do any real synchronization, and you'll have to decide what "periodic" means with respect to checkpointing. But this should give you an idea of how to sync parameters from one network to another.

One final warning: this does not mean that the two networks are necessarily deterministic. There are known non-deterministic elements in TensorFlow (e.g., this), so be wary if you need exactly the same answer. But this is the hard truth about running on multiple devices.

Good luck!

if __name__ == '__main__':
    programA_main()
    programB_main()

Bash

环境变量

readonly: 设置只读环境变量，禁止修改环境变量

unset: 清除环境变量

SSH 反向代理

ssh -NfR 8080:localhost:80 user@remote_host

autossh -M 23333 -NfR 8080:localhost:80 user@remote_host

查找替换

for i in (ag -s 'from' -l);sed 's/from/to/g' ￥i -i;end

按行遍历

如果用for ITEM in ￥(ls -l)，是按词遍历的，for事先就把item按照空格分隔了。这种时候需要使用ls -l|while read LINE，使用read读取一整行，并存到变量￥LINE中

bash的高级技巧

Bash高级技巧

该系列将重点介绍Linux Shell中的高级使用技巧，其主要面向有一定经验的Shell开发者、Linux系统管理员，以及Linux的爱好者。博客中的示例主要来源于网络和一些经典书籍，在经过本人的收集和整理之后，以系列博客的形式呈现给诸位。如果大家有更多更好的Shell脚本经典示例，且愿意在这里与我们一同分享的话，可以以邮件、博客回复等形式与我联系，我将会尽量保证该系列的持续更新。

一、将输入信息转换为大写字符后再进行条件判断：

我们在读取用户的正常输入后，很有可能会将这些输入信息用于条件判断，那么在进行比较时，我们将不得不考虑这些信息的大小写匹配问题。

  /> cat > test1.sh
  #!/bin/sh
  echo -n "Please let me know your name. "
  read name
  #将变量name的值通过管道输出到tr命令，再由tr命令进行大小写转换后重新赋值给name变量。
  name=`echo {% math_inline %}name | tr [a-z] [A-Z]`
  if [[ {% endmath_inline %}name == "STEPHEN" ]]; then
      echo "Hello, Stephen."
  else
      echo "You are not Stephen."
  fi
  CTRL+D
  /> ./test1.sh
  Please let me know your name. stephen
  Hello, Stephen.

二、为调试信息设置输出级别：

  我们经常在调试脚本时添加一些必要的调试信息，以便跟踪到程序中的错误。在完成调试后，一般都会选择删除这些额外的调试信息，在过了一段时间之后，如果脚本需要添加新的功能，那么我们将不得不重新进行调试，这样又有可能需要添加这些调试信息，在调试成功之后，这些信息可能会被再次删除。如果我们能够为我们的调试信息添加调试级别，使其只在必要的时候输出，我想这将会是一件非常惬意的事情。
  /> cat > test2.sh
  #!/bin/sh
  if [[ {% math_inline %}# == 0 ]]; then
      echo "Usage: ./test2.sh -d debug_level"
      exit 1
  fi
  #1. 读取脚本的命令行选项参数，并将选项赋值给变量argument。
  while getopts d: argument
  do
      #2. 只有到选项为d(-d)时有效，同时将-d后面的参数({% endmath_inline %}OPTARG)赋值给变量debug，表示当前脚本的调试级别。
      case {% math_inline %}argument in
      d) debug_level={% endmath_inline %}OPTARG ;;
      \?) echo "Usage: ./test2.sh -d debug_level"
          exit 1
          ;;
      esac
  done
  #3. 如果debug此时的值为空或者不是0-9之间的数字，给debug变量赋缺省值0.
  if [[ -z {% math_inline %}debug_level ||  {% endmath_inline %}debug_level != [0-9] ]]; then
      debug_level=0
  fi
  echo "The current debug_level level is {% math_inline %}debug_level."
  echo -n "Tell me your name."
  read name
  name=`echo {% endmath_inline %}name | tr [a-z] [A-Z]`
  if [ {% math_inline %}name = "STEPHEN" ];then
      #4. 根据当前脚本的调试级别判断是否输出其后的调试信息，此时当debug_level > 0时输出该调试信息。
      test {% endmath_inline %}debug_level -gt 0 && echo "This is stephen."
      #do something you want here.
  elif [ {% math_inline %}name = "ANN" ]; then
      #5. 当debug_level > 1时输出该调试信息。
      test {% endmath_inline %}debug_level -gt 1 && echo "This is ann."
      #do something you want here.
  else
      #6. 当debug_level > 2时输出该调试信息。
      test {% math_inline %}debug_level -gt 2 && echo "This is others."
      #do any other else.
  fi
  CTRL+D
  /> ./test2.sh
  Usage: ./test2.sh -d debug_level
  /> ./test2.sh -d 1
  The current debug level is 1.
  Tell me your name. ann
  /> ./test2.sh -d 2
  The current debug level is 2.
  Tell me your name. ann
  This is ann.

三、判断参数是否为数字：

  有些时候我们需要验证脚本的参数或某些变量的值是否为数字，如果不是则需要需要给出提示，并退出脚本。
  /> cat > test3.sh
  #!/bin/sh
  #1. {% endmath_inline %}1是脚本的第一个参数，这里作为awk命令的第一个参数传入给awk命令。
  #2. 由于没有输入文件作为输入流，因此这里只是在BEGIN块中完成。
  #3. 在awk中ARGV数组表示awk命令的参数数组，ARGV[0]表示命令本身，ARGV[1]表示第一个参数。
  #4. match是awk的内置函数，返回值为匹配的正则表达式在字符串中(ARGV[1])的起始位置，没有找到返回0。
  #5. 正则表达式的写法已经保证了匹配的字符串一定是十进制的正整数，如需要浮点数或负数，仅需修改正则即可。
  #6. awk执行完成后将结果返回给isdigit变量，并作为其初始化值。
  #7. isdigit=`echo {% math_inline %}1 | awk '{ if (match({% endmath_inline %}1, "^[0-9]+{% math_inline %}") != 0) print "true"; else print "false" }' `
  #8. 上面的写法也能实现该功能，但是由于有多个进程参与，因此效率低于下面的写法。
  isdigit=`awk 'BEGIN { if (match(ARGV[1],"^[0-9]+{% endmath_inline %}") != 0) print "true"; else print "false" }' {% math_inline %}1`
  if [[ {% endmath_inline %}isdigit == "true" ]]; then
      echo "This is numeric variable."
      number={% math_inline %}1
  else
      echo "This is not numeric variable."
      number=0
  fi
  CTRL+D
  /> ./test3.sh 12
  This is numeric variable.
  /> ./test3.sh 12r
  This is not numeric variable.

四、判断整数变量的奇偶性：

  为了简化问题和突出重点，这里我们假设脚本的输入参数一定为合法的整数类型，因而在脚本内部将不再进行参数的合法性判断。
  /> cat > test4.sh
  #!/bin/sh
  #1. 这里的重点主要是sed命令中正则表达式的写法，它将原有的数字拆分为两个模式(用圆括号拆分)，一个前面的所有高位数字，另一个是最后一位低位数字，之后再用替换符的方式(\2)，将原有数字替换为只有最后一位的数字，最后将结果返回为last_digit变量。
  last_digit=`echo {% endmath_inline %}1 | sed 's/\(.*\)\(.\){% math_inline %}/\2/'`
  #2. 如果last_digit的值为0,2,4,6,8，就表示其为偶数，否则为奇数。
  case {% endmath_inline %}last_digit in
  0|2|4|6|8)
      echo "This is an even number." ;;
  *)
      echo "This is not an even number." ;;
  esac
  CTRL+D
  /> ./test4.sh 34
  This is an even number.
  /> ./test4.sh 345
  This is not an even number.

五、将Shell命令赋值给指定变量，以保证脚本的移植性：

有的时候当我们在脚本中执行某个命令时，由于操作系统的不同，可能会导致命令所在路径的不同，甚至是命令名称或选项的不同，为了保证脚本具有更好的平台移植性，我们可以将该功能的命令赋值给指定的变量，之后再使用该命令时，直接使用该变量即可。这样在今后增加更多OS时，我们只需为该变量基于新系统赋予不同的值即可，否则我们将不得不修改更多的地方，这样很容易导致因误修改而引发的Bug。

  /> cat > test5.sh
  #!/bin/sh
  #1. 通过uname命令获取当前的系统名称，之后再根据OS名称的不同，给PING变量赋值不同的ping命令的全称。
  osname=`uname -s`
  #2. 可以在case的条件中添加更多的操作系统名称。
  case {% math_inline %}osname in
  "Linux")
      PING=/usr/sbin/ping ;;
  "FreeBSD")
      PING=/sbin/ping ;;
  "SunOS")
      PING=/usr/sbin/ping ;;
  *)
      ;;
  esac
  CTRL+D
  /> . ./test5.sh
  /> echo {% endmath_inline %}PING
  /usr/sbin/ping

六、获取当前时间距纪元时间(1970年1月1日)所经过的天数：

在获取两个时间之间的差值时，需要考虑很多问题，如闰年、月份中不同的天数等。然而如果我们能够确定两个时间点之间天数的差值，那么再计算时分秒的差值时就非常简单了。在系统提供的C语言函数中，获取的时间值是从1970年1月1日0点到当前时间所流经的秒数，如果我们基于此计算两个时间之间天数的差值，将会大大简化我们的计算公式。

  /> cat > test6.sh
  #!/bin/sh
  #1. 将date命令的执行结果(秒 分 小时 日 月 年)赋值给数组变量DATE。
  declare -a DATE=(`date +"%S %M %k %d %m %Y"`)
  #2. 为了提高效率，这个直接给出1970年1月1日到新纪元所流经的天数常量。
  epoch_days=719591
  #3. 从数组中提取各个时间部分值。
  year={% math_inline %}{DATE[5]}
  month={% endmath_inline %}{DATE[4]}
  day={% math_inline %}{DATE[3]}
  hour={% endmath_inline %}{DATE[2]}
  minute={% math_inline %}{DATE[1]}
  second={% endmath_inline %}{DATE[0]}
  #4. 当月份值为1或2的时候，将月份变量的值加一，否则将月份值加13，年变量的值减一，这样做主要是因为后面的公式中取月平均天数时的需要。
  if [ {% math_inline %}month -gt 2 ]; then
      month={% endmath_inline %}((month+1))
  else
      month={% math_inline %}((month+13))
      year={% endmath_inline %}((year-1))
  fi
  #5. year变量参与的运算是需要考虑闰年问题的，该问题可以自行去google。
  #6. month变量参与的运算主要是考虑月平均天数。
  #7. 计算结果为当前日期距新世纪所流经的天数。
  today_days={% math_inline %}(((year*365)+(year/4)-(year/100)+(year/400)+(month*306001/10000)+day))
  #8. 总天数减去纪元距离新世纪的天数即可得出我们需要的天数了。
  days_since_epoch={% endmath_inline %}((today_days-epoch_days))
  echo {% math_inline %}days_since_epoch
  seconds_since_epoch={% endmath_inline %}(((days_since_epoch*86400)+(hour*3600)+(minute*60)+second))
  echo {% math_inline %}seconds_since_epoch
  CTRL+D
  /> . ./test6.sh
  15310
  1322829080

  需要说明的是，推荐将该脚本的内容放到一个函数中，以便于我们今后计算类似的时间数据时使用。

七、非直接引用变量：

  在Shell中提供了三种为标准(直接)变量赋值的方式：
  1. 直接赋值。
  2. 存储一个命令的输出。
  3. 存储某类型计算的结果。
  然而这三种方式都是给已知变量名的变量赋值，如name=Stephen。但是在有些情况下，变量名本身就是动态的，需要依照运行的结果来构造变量名，之后才是为该变量赋值。这种变量被成为动态变量，或非直接变量。
  /> cat > test7.sh
  #!/bin/sh
  work_dir=`pwd`
  #1. 由于变量名中不能存在反斜杠，因此这里需要将其替换为下划线。
  #2. work_dir和file_count两个变量的变量值用于构建动态变量的变量名。
  work_dir=`echo {% endmath_inline %}work_dir | sed 's/\//_/g'`
  file_count=`ls | wc -l`
  #3. 输出work_dir和file_count两个变量的值，以便确认这里的输出结果和后面构建的命令名一致。
  echo "work_dir = " {% math_inline %}work_dir
  echo "file_count = " {% endmath_inline %}file_count
  #4. 通过eval命令进行评估，将变量名展开，如{% math_inline %}{work_dir}和{% endmath_inline %}file_count，并用其值将其替换，如果不使用eval命令，将不会完成这些展开和替换的操作。最后为动态变量赋值。
  eval BASE{% math_inline %}{work_dir}_{% endmath_inline %}file_count={% math_inline %}(ls {% endmath_inline %}(pwd) | wc -l)
  #5. 先将echo命令后面用双引号扩住的部分进行展开和替换，由于是在双引号内，仅完成展开和替换操作即可。
  #6. echo命令后面的参数部分，先进行展开和替换，使其成为{% math_inline %}BASE_root_test_1动态变量，之后在用该变量的值替换该变量本身作为结果输出。
  eval echo "BASE{% endmath_inline %}{work_dir}_{% math_inline %}file_count = " '{% endmath_inline %}BASE'{% math_inline %}{work_dir}_{% endmath_inline %}file_count
  CTRL+D
  /> . ./test7.sh
  work_dir =  _root_test
  file_count =  1
  BASE_root_test_1 = 1

八、在循环中使用管道的技巧：

  在Bash Shell中，管道的最后一个命令都是在子Shell中执行的。这意味着在子Shell中赋值的变量对父Shell是无效的。所以当我们将管道输出传送到一个循环结构，填入随后将要使用的变量，那么就会产生很多问题。一旦循环完成，其所依赖的变量就不存在了。
  /> cat > test8_1.sh
  #!/bin/sh
  #1. 先将ls -l命令的结果通过管道传给grep命令作为管道输入。
  #2. grep命令过滤掉包含total的行，之后再通过管道将数据传给while循环。
  #3. while read line命令从grep的输出中读取数据。注意，while是管道的最后一个命令，将在子Shell中运行。
  ls -l | grep -v total | while read line
  do
      #4. all变量是在while块内声明并赋值的。
      all="{% math_inline %}all {% endmath_inline %}line"
      echo {% math_inline %}line
  done
  #5. 由于上面的all变量在while内声明并初始化，而while内的命令都是在子Shell中运行，包括all变量的赋值，因此该变量的值将不会传递到while块外，因为块外地命令是它的父Shell中执行。
  echo "all = " {% endmath_inline %}all
  CTRL+D
  /> ./test8_1.sh
  -rw-r--r--.  1 root root 193 Nov 24 11:25 outfile
  -rwxr-xr-x. 1 root root 284 Nov 24 10:01 test7.sh
  -rwxr-xr-x. 1 root root 108 Nov 24 12:48 test8_1.sh
  all =

  为了解决该问题，我们可以将while之前的命令结果先输出到一个临时文件，之后再将该临时文件作为while的重定向输入，这样while内部和外部的命令都将在同一个Shell内完成。
  /> cat > test8_2.sh
  #!/bin/sh
  #1. 这里我们已经将命令的结果重定向到一个临时文件中。
  ls -l | grep -v total > outfile
  while read line
  do
      #2. all变量是在while块内声明并赋值的。
      all="{% math_inline %}all {% endmath_inline %}line"
      echo {% math_inline %}line
      #3. 通过重定向输入的方式，将临时文件中的内容传递给while循环。
  done < outfile
  #4. 删除该临时文件。
  rm -f outfile
  #5. 在while块内声明和赋值的all变量，其值在循环外部仍然有效。
  echo "all = " {% endmath_inline %}all
  CTRL+D
  /> ./test8_2.sh
  -rw-r--r--.  1 root root   0 Nov 24 12:58 outfile
  -rwxr-xr-x. 1 root root 284 Nov 24 10:01 test7.sh
  -rwxr-xr-x. 1 root root 140 Nov 24 12:58 test8_2.sh
  all =  -rwxr-xr-x. 1 root root 284 Nov 24 10:01 test7.sh -rwxr-xr-x. 1 root root 135 Nov 24 13:16 test8_2.sh

  上面的方法只是解决了该问题，然而却带来了一些新问题，比如临时文件的产生容易导致性能问题，以及在脚本异常退出时未能及时删除当前使用的临时文件，从而导致生成过多的垃圾文件等。下面将再介绍一种方法，该方法将同时解决以上两种方法同时存在的问题。该方法是通过HERE-Document的方式来替代之前的临时文件方法。
  /> cat > test8_3.sh
  #!/bin/sh
  #1. 将命令的结果传给一个变量
  OUTFILE=`ls -l | grep -v total`
  while read line
  do
      all="{% math_inline %}all {% endmath_inline %}line"
      echo {% math_inline %}line
  done <<EOF
  #2. 将该变量作为该循环的HERE文档输入。
  {% endmath_inline %}OUTFILE
  EOF
  #3. 在循环外部输出循环内声明并初始化的变量all的值。
  echo "all = " {% math_inline %}all
  CTRL+D
  /> ./test8_3.sh
  -rwxr-xr-x. 1 root root 284 Nov 24 10:01 test7.sh
  -rwxr-xr-x. 1 root root 135 Nov 24 13:16 test8_3.sh
  all =  -rwxr-xr-x. 1 root root 284 Nov 24 10:01 test7.sh -rwxr-xr-x. 1 root root 135 Nov 24 13:16 test8_3.sh

九、自链接脚本：

  通常而言，我们是通过脚本的命令行选项来确定脚本的不同行为，告诉它该如何操作。这里我们将介绍另外一种方式来完成类似的功能，即通过脚本的软连接名来帮助脚本决定其行为。
  /> cat > test9.sh
  #!/bin/sh
  #1. basename命令将剥离脚本的目录信息，只保留脚本名，从而确保在相对路径的模式下执行也没有任何差异。
  #2. 通过sed命令过滤掉脚本的扩展名。
  dowhat=`basename {% endmath_inline %}0 | sed 's/\.sh//'`
  #3. 这里的case语句只是为了演示方便，因此模拟了应用场景，在实际应用中，可以为不同的分支执行不同的操作，或将某些变量初始化为不同的值和状态。
  case {% math_inline %}dowhat in
  test9)
      echo "I am test9.sh"
      ;;
  test9_1)
      echo "I am test9_1.sh."
      ;;
  test9_2)
      echo "I am test9_2.sh."
      ;;
  *)
      echo "You are illegal link file."
      ;;
  esac
  CTRL+D
  /> chmod a+x test9.sh
  /> ln -s test9.sh test9_1.sh
  /> ln -s test9.sh test9_2.sh
  /> ls -l
  lrwxrwxrwx. 1 root root   8 Nov 24 14:32 test9_1.sh -> test9.sh
  lrwxrwxrwx. 1 root root   8 Nov 24 14:32 test9_2.sh -> test9.sh
  -rwxr-xr-x. 1 root root 235 Nov 24 14:35 test9.sh
  /> ./test9.sh
  I am test9.sh.
  /> ./test9_1.sh
  I am test9_1.sh.
  /> ./test9_2.sh
  I am test9_2.sh.

十、Here文档的使用技巧：

  在命令行交互模式下，我们通常希望能够直接输入更多的信息，以便当前的命令能够完成一定的自动化任务，特别是对于那些支持自定义脚本的命令来说，我们可以将脚本作为输入的一部分传递给该命令，以使其完成该自动化任务。
  #1. 通过sqlplus以dba的身份登录Oracle数据库服务器。
  #2. 在通过登录后，立即在sqlplus中执行oracle的脚本CreateMyTables和CreateMyViews。
  #3. 最后执行sqlplus的退出命令，退出sqlplus。自动化工作完成。
  /> sqlplus "/as sysdba" <<-SQL
  > @CreateMyTables
  > @CreateMyViews
  > exit
  > SQL

十一、获取进程的运行时长(单位: 分钟)：

  在进程监控脚本中，我们通常需要根据脚本的参数来确定有哪些性能参数将被收集，当这些性能参数大于最高阈值或小于最低阈值时，监控脚本将根据实际的情况，采取预置的措施，如邮件通知、直接杀死进程等，这里我们给出的例子是收集进程运行时长性能参数。
  ps命令的etime值将给出每个进程的运行时长，其格式主要为以下三种：
  1. minutes:seconds，如20:30
  2. hours:minutes:seconds，如1:20:30
  3. days-hours:minute:seconds，如2-18:20:30
  该脚本将会同时处理这三种格式的时间信息，并最终转换为进程所流经的分钟数。
  /> cat > test11.sh
  #!/bin/sh
  #1. 通过ps命令获取所有进程的pid、etime和comm数据。
  #2. 再通过grep命令过滤，只获取init进程的数据记录，这里我们可以根据需要替换为自己想要监控的进程名。
  #3. 输出结果通常为：1 09:42:09 init
  pid_string=`ps -eo pid,etime,comm | grep "init" | grep -v grep`
  #3. 从这一条记录信息中抽取出etime数据，即第二列的值09:42:09，并赋值给exec_time变量。
  exec_time=`echo {% endmath_inline %}pid_string | awk '{print {% math_inline %}2}'`
  #4. 获取exec_time变量的时间组成部分的数量，这里是3个部分，即时:分:秒，是上述格式中的第二种。
  time_field_count=`echo {% endmath_inline %}exec_time | awk -F: '{print NF}'`
  #5. 从exec_time变量中直接提取分钟数，即倒数第二列的数据(42)。
  count_of_minutes=`echo {% math_inline %}exec_time | awk -F: '{print {% endmath_inline %}(NF-1)}'`

  #6. 判断当前exec_time变量存储的时间数据是属于以上哪种格式。
  #7. 如果是第一种，那么天数和小时数均为0。
  #8. 如果是后两种之一，则需要继续判断到底是第一种还是第二种，如果是第二种，其小时部分将不存在横线(-)分隔符分隔天数和小时数，否则需要将这两个时间字段继续拆分，以获取具体的天数和小时数。对于第二种，天数为0.
  if [ {% math_inline %}time_field_count -lt 3 ]; then
      count_of_hours=0
      count_of_days=0
  else
      count_of_hours=`echo {% endmath_inline %}exec_time | awk -F: '{print {% math_inline %}(NF-2)}'`
      fields=`echo {% endmath_inline %}count_of_hours | awk -F- '{print NF}'`
      if [ {% math_inline %}fields -ne 1 ]; then
          count_of_days=`echo {% endmath_inline %}count_of_hours | awk -F- '{print {% math_inline %}1}'`
          count_of_hours=`echo {% endmath_inline %}count_of_hours | awk -F- '{print {% math_inline %}2}'`
      else
          count_of_days=0
      fi
  fi
  #9. 通过之前代码获取的各个字段值，计算出该进程实际所流经的分钟数。
  #10. bc命令是计算器命令，可以将echo输出的数学表达式计算为最终的数字值。
  elapsed_minutes=`echo "{% endmath_inline %}count_of_days*1440+{% math_inline %}count_of_hours*60+{% endmath_inline %}count_of_minutes" | bc`
  echo "The elapsed minutes of init process is" {% math_inline %}elapsed_minutes "minutes."
  CTRL+D
  /> ./test11.sh
  The elapsed minutes of init process is 577 minutes.

十二、模拟简单的top命令：

  这里用脚本实现了一个极为简单的top命令。为了演示方便，我们在脚本中将很多参数都写成硬代码，你可以根据需要更换这些参数，或者用更为灵活的方式替换现有的实现。
  /> cat > test12.sh
  #!/bin/sh
  #1. 将ps命令的title赋值给一个变量，这样在每次输出时，直接打印该变量即可。
  header=`ps aux | head -n 1`
  #2. 这里是一个无限循环，等价于while true
  #3. 每次循环先清屏，之后打印uptime命令的输出。
  #4. 输出ps的title。
  #5. 这里需要用sed命令删除ps的title行，以避免其参与sort命令的排序。
  #6. sort先基于CPU%倒排，再基于owner排序，最后基于pid排序，最后再将结果输出给head命令，仅显示前20行的数据。
  #7. 每次等待5秒后刷新一次。
 while :
  do
      clear
      uptime
      echo "{% endmath_inline %}header"
      ps aux | sed -e 1d | sort -k3nr -k1,1 -k2n | head -n 20
      sleep 5
  done
  CTRL+D
  /> ./test12.sh
  21:55:07 up 13:42,  2 users,  load average: 0.00, 0.00, 0.00
  USER       PID %CPU %MEM    VSZ   RSS   TTY      STAT START   TIME   COMMAND
  root      6408     2.0      0.0   4740   932   pts/2    R+    21:45     0:00   ps aux
  root      1755     0.2      2.0  96976 21260   ?        S      08:14     2:08   nautilus
  68        1195     0.0      0.4   6940   4416    ?        Ss    08:13     0:00   hald
  postfix   1399    0.0      0.2  10312  2120    ?        S      08:13     0:00   qmgr -l -t fifo -u
  postfix   6021    0.0      0.2  10244  2080    ?        S      21:33     0:00   pickup -l -t fifo -u
  root         1       0.0      0.1   2828   1364    ?        Ss     08:12    0:02   /sbin/init
  ... ...

十三、格式化输出指定用户的当前运行进程：

  在这个例子中，我们通过脚本参数的形式，将用户列表传递给该脚本，脚本在读取参数后，以树的形式将用户列表中用户的所属进程打印出来。
  /> cat > test13.sh
  #!/bin/sh
  #1. 循环读取脚本参数，构造egrep可以识别的用户列表变量(基于grep的扩展正则表达式)。
  #2. userlist变量尚未赋值，则直接使用第一个参数为它赋值。
  #3. 如果已经赋值，且脚本参数中存在多个用户，这里需要在每个用户名之间加一个竖线，在egrep中，竖线是分割的元素之间是或的关系。
  #4. shift命令向左移动一个脚本的位置参数，这样可以使循环中始终操作第一个参数。
  while [ {% math_inline %}# -gt 0 ]
  do
      if [ -z "{% endmath_inline %}userlist" ]; then
          userlist="{% math_inline %}1"
      else
          userlist="{% endmath_inline %}userlist|{% math_inline %}1"
      fi
       shift
  done
  #5. 如果没有用户列表，则搜索所有用户的进程。
  #6. "^ *({% endmath_inline %}userlist) ": 下面的调用方式，该正则的展开形式为"^ *(root|avahi|postfix|rpc|dbus) "。其含义为，以0个或多个空格开头，之后将是root、avahi、postfix、rpc或dbus之中的任何一个字符串，后面再跟随一个空格。
  if [ -z "{% math_inline %}userlist" ]; then
      userlist="."
  else
      userlist="^ *({% endmath_inline %}userlist) "
  fi
  #7. ps命令输出所有进程的user和命令信息，将结果传递给sed命令，sed将删除ps的title部分。
  #8. egrep过滤所有进程记录中，包含指定用户列表的进程记录，再将过滤后的结果传递给sort命令。
  #9. sort命令中的-b选项将忽略前置空格，并以user，再以进程名排序，将结果传递个uniq命令。
  #10.uniq命令将合并重复记录，-c选项将会使每条记录前加重复的行数。
  #11.第二个sort将再做一次排序，先以user，再以重复计数由大到小，最后以进程名排序。将结果传给awk命令。
  #12.awk命令将数据进行格式化，并删除重复的user。
  ps -eo user,comm | sed -e 1d | egrep "{% math_inline %}userlist" |
      sort -b -k1,1 -k2,2 | uniq -c | sort -b -k2,2 -k1nr,1 -k3,3 |
          awk ' { user = (lastuser == {% endmath_inline %}2) ? " " : {% math_inline %}2;
                    lastuser = {% endmath_inline %}2;
                    printf("%-15s\t%2d\t%s\n",user,{% math_inline %}1,{% endmath_inline %}3)
          }'
  CTRL+D
  /> ./test13.sh root avahi postfix rpc dbus
  avahi             2      avahi-daemon
  dbus             1      dbus-daemon
  postfix          1      pickup
                      1      qmgr
  root              5      mingetty
                      3      udevd
                      2      sort
                      2      sshd
  ... ...
  rpc               1      rpcbind

十四、用脚本完成which命令的基本功能：

  我们经常会在脚本中调用其他的应用程序，为了保证脚本具有更好的健壮性，以及错误提示的准确性，我们可能需要在执行前验证该命令是否存在，或者说是否可以被执行。这首先要确认该命令是否位于PATH变量包含的目录中，再有就是该文件是否为可执行文件。
  /> cat > test14.sh
  #!/bin/sh
  #1. 该函数用于判断参数1中的命令是否位于参数2所包含的目录列表中。需要说明的是，函数里面的{% math_inline %}1和{% endmath_inline %}2是指函数的参数，而不是脚本的参数，后面也是如此。
  #2. cmd={% math_inline %}1和path={% endmath_inline %}2，将参数赋给有意义的变量名，是一个很好的习惯。
  #3. 由于PATH环境变量中，目录之间的分隔符是冒号，因此这里需要临时将IFS设置为冒号，函数结束后再还原。
  #4. 在for循环中，逐个变量目录列表中的目录，以判断该命令是否存在，且为可执行程序。
  isInPath() {
      cmd={% math_inline %}1        path={% endmath_inline %}2      result=1
      oldIFS={% math_inline %}IFS   IFS=":"
      for dir in {% endmath_inline %}path
      do
          if [ -x {% math_inline %}dir/{% endmath_inline %}cmd ]; then
              result=0
          fi
      done
      IFS=oldifs
      return {% math_inline %}result
  }
  #5. 检查命令是否存在的主功能函数，先判断是否为绝对路径，即{% endmath_inline %}var变量的第一个字符是否为/，如果是，再判断它是否有可执行权限。
  #6. 如果不是绝对路径，通过isInPath函数判断是否该命令在PATH环境变量指定的目录中。
  checkCommand() {
      var={% math_inline %}1
      if [ ! -z "{% endmath_inline %}var" ]; then
          if [ "{% math_inline %}{var:0:1}" = "/" ]; then
              if [ ! -x {% endmath_inline %}var ]; then
                  return 1
              fi
          elif ! isInPath {% math_inline %}var {% endmath_inline %}PATH ; then
              return 2
          fi
      fi
  }
  #7. 脚本参数的合法性验证。
  if [ {% math_inline %}# -ne 1 ]; then
      echo "Usage: {% endmath_inline %}0 command" >&2;
  fi
  #8. 根据返回值打印不同的信息。我们可以在这里根据我们的需求完成不同的工作。
  checkCommand {% math_inline %}1
  case {% endmath_inline %}? in
  0) echo "{% math_inline %}1 found in PATH." ;;
  1) echo "{% endmath_inline %}1 not found or not executable." ;;
  2) echo "{% math_inline %}1 not found in PATH." ;;
  esac
  exit 0
  CTRL+D
  /> ./test14.sh echo
  echo found in PATH.
  /> ./test14.sh MyTest
  MyTest not found in PATH.
  /> ./test14.sh /bin/MyTest
  /bin/MyTest not found or not executable.

十五、验证输入信息是否合法：

  这里给出的例子是验证用户输入的信息是否都是数字和字母。需要说明的是，之所以将其收集到该系列中，主要是因为它实现的方式比较巧妙。
  /> cat > test15.sh
  #!/bin/sh
  echo -n "Enter your input: "
  read input
  #1. 事实上，这里的巧妙之处就是先用sed替换了非法部分，之后再将替换后的结果与原字符串比较。这种写法也比较容易扩展。
  parsed_input=`echo {% endmath_inline %}input | sed 's/[^[:alnum:]]//g'`
  if [ "{% math_inline %}parsed_input" != "{% endmath_inline %}input" ]; then
      echo "Your input must consist of only letters and numbers."
  else
      echo "Input is OK."
  fi
  CTRL+D
  /> ./test15.sh
  Enter your input: hello123
  Input is OK.
  /> ./test15.sh
  Enter your input: hello world
  Your input must consist of only letters and numbers.

十六、整数验证：

  整数的重要特征就是只是包含数字0到9和负号(-)。
  /> cat > test16.sh
  #!/bin/sh
  #1. 判断变量number的第一个字符是否为负号(-)，如果只是则删除该负号，并将删除后的结果赋值给left_number变量。
  #2. "{% math_inline %}{number#-}"的具体含义，可以参考该系列博客中"Linux Shell常用技巧(十一)"，搜索关键字"变量模式匹配运算符"即可。
  number={% endmath_inline %}1
  if [ "{% math_inline %}{number:0:1}" = "-" ]; then
      left_number="{% endmath_inline %}{number#-}"
  else
      left_number={% math_inline %}number
  fi
  #3. 将left_number变量中所有的数字都替换掉，因此如果返回的字符串变量为空，则表示left_number所包含的字符均为数字。
  nodigits=`echo {% endmath_inline %}left_number | sed 's/[[:digit:]]//g'`
  if [ "{% math_inline %}nodigits" != "" ]; then
      echo "Invalid number format!"
  else
      echo "You are valid number."
  fi
  CTRL+D
  /> ./test16.sh -123
  You are valid number.
  /> ./test16.sh 123e
  Invalid number format!

十七、判断指定的年份是否为闰年：

  这里我们先列出闰年的规则:
  1. 不能被4整除的年一定不是闰年；
  2. 可以同时整除4和400的年一定是闰年；
  3. 可以整除4和100，但是不能整除400的年，不是闰年；
  4. 其他可以整除的年都是闰年。
  #!/bin/sh
  year={% endmath_inline %}1
  if [ "{% math_inline %}((year % 4))" -ne 0 ]; then
      echo "This is not a leap year."
      exit 1
  elif [ "{% endmath_inline %}((year % 400))" -eq 0 ]; then
      echo "This is a leap year."
      exit 0
  elif [ "{% math_inline %}((year % 100))" -eq 0 ]; then
      echo "This is not a leap year."
      exit 1
  else
      echo "This is a leap year."
      exit 0
  fi
  CTRL+D
  /> ./test17.sh 1933
  This is not a leap year.
  /> ./test17.sh 1936
  This is a leap year.

十八、将单列显示转换为多列显示：

  我们经常会在显示时将单行的输出，格式化为多行的输出，通常情况下，为了完成该操作，我们将加入更多的代码，将输出的结果存入数组或临时文件，之后再重新遍历它们，从而实现单行转多行的目的。在这里我们介绍一个使用xargs命令的技巧，可以用更简单、更高效的方式来完成该功能。
  /> cat > test18.sh
  #!/bin/sh
  #1. passwd文件中，有可能在一行内出现一个或者多个空格字符，因此在直接使用cat命令的结果时，for循环会被空格字符切开，从而导致一行的文本被当做多次for循环的输入，这样我们不得不在sed命令中，将cat输出的每行文本进行全局替换，将空格字符替换为%20。事实上，我们当然可以将cat /etc/passwd的输出以管道的形式传递给cut命令，这里之所以这样写，主要是为了演示一旦出现类似的问题该如果巧妙的处理。
  #2. 这里将for循环的输出以管道的形式传递给sort命令，sort命令将基于user排序。
  #3. -xargs -n 2是这个技巧的重点，它将sort的输出进行合并，-n选项后面的数字参数将提示xargs命令将多少次输出合并为一次输出，并传递给其后面的命令。在本例中，xargs会将从sort得到的每两行数据合并为一行，中间用空格符分离，之后再将合并后的数据传递给后面的awk命令。事实上，对于awk而言，你也可以简单的认为xargs减少了对它(awk)的一半调用。
  #4. 如果打算在一行内显示3行或更多的行，可以将-n后面的数字修改为3或其它更高的数字。你还可以修改awk中的print命令，使用更为复杂打印输出命令，以得到更为可读的输出效果。
  for line in `cat /etc/passwd | sed 's/ /%20/g'`
  do
      user=`echo {% endmath_inline %}line | cut -d: -f1`
      echo {% math_inline %}user
  done | \
      sort -k1,1 | \
      xargs -n 2 | \
      awk '{print {% endmath_inline %}1, {% math_inline %}2}'
  CTRL+D
  /> ./test18.sh
  abrt adm
  apache avahi
  avahi-autoipd bin
  daemon daihw
  dbus ftp
  games gdm
  gopher haldaemon
  halt lp
  mail nobody
  ntp operator
  postfix pulse
  root rtkit
  saslauth shutdown
  sshd sync
  tcpdump usbmuxd
  uucp vcsa

十九、将文件的输出格式化为指定的宽度：

  在这个技巧中，不仅包含了如何获取和文件相关的详细信息，如行数，字符等，而且还可以让文件按照指定的宽度输出。这种应用在输出帮助信息、License相关信息时还是比较有用的。
  /> cat > test19.sh
  #!/bin/sh
  #1. 这里我们将缺省宽度设置为75，如果超过该宽度，将考虑折行显示，否则直接在一行中全部打印输出。这里只是为了演示方便，事实上，你完全可以将该值作为脚本或函数的参数传入，那样你将会得到更高的灵活性。
  my_width=75
  #2. for循环的读取列表来自于脚本的参数。
  #3. 在获取lines和chars变量时，sed命令用于过滤掉多余的空格字符。
  #4. 在if的条件判断中{% endmath_inline %}{#line}用于获取line变量的字符长度，这是Shell内置的规则。
  #5. fmt -w 80命令会将echo输出的整行数据根据其命令选项指定的宽度(80个字符)进行折行显示，再将折行后的数据以多行的形式传递给sed命令。
  #6. sed在收到fmt命令的格式化输出后，将会在折行后的第一行头部添加两个空格，在其余行的头部添加一个加号和一个空格以表示差别。
  for input; do
      lines=`wc -l < {% math_inline %}input | sed 's/ //g'`
      chars=`wc -c < {% endmath_inline %}input | sed 's/ //g'`
      owner=`ls -l {% math_inline %}input | awk '{print {% endmath_inline %}3}'`
      echo "-------------------------------------------------------------------------------"
      echo "File {% math_inline %}input ({% endmath_inline %}lines lines, {% math_inline %}chars characters, owned by {% endmath_inline %}owner):"
      echo "-------------------------------------------------------------------------------"
      while read line; do
          if [ {% math_inline %}{#line} -gt {% endmath_inline %}my_width ]; then
              echo "{% math_inline %}line" | fmt -w 80 | sed -e '1s/^/  /' -e '2,{% endmath_inline %}s/^/+ /'
          else
              echo "  {% math_inline %}line"
          fi
      done < {% endmath_inline %}input
      echo "-------------------------------------------------------------------------------"
  done | more
  CTRL+D
  /> ./test19.sh testfile
  -------------------------------------------------------------------------------
  File testfile.dat (3 lines, 645 characters, owned by root):
  -------------------------------------------------------------------------------
     The PostgreSQL Global Development Group today released updates for all
  + active branches of the PostgreSQL object-relational database system,
  + including versions 9.1.2, 9.0.6, 8.4.10, 8.3.17 and 8.2.23. Users of any of
  + the several affected features in this release, including binary replication,
  + should update their PostgreSQL installations as soon as possible.
     This is also the last update for PostgreSQL 8.2, which is now End-Of-Life
  + (EOL). Users of version 8.2 should plan to upgrade their PostgreSQL
  + installations to 8.3 or later within the next couple of months. For more
  + information, see our Release Support Policy.
     This is just a test file.
  -------------------------------------------------------------------------------

二十、监控指定目录下磁盘使用空间过大的用户：

  在将Linux用作文件服务器时，所有的注册用户都可以在自己的主目录下存放各种类型和大小的文件。有的时候，有些用户的占用空间可能会明显超过其他人，这时就需要管理员可以及时发现这一异常使用状况，并根据实际情况作出应对处理。
  /> cat > test20.sh
  #!/bin/sh
  #1. 该脚本仅用于演示一种处理技巧，其中很多阈值都是可以通过脚本参来初始化的，如limited_qutoa和dirs等变量。
  limited_quota=200
  dirs="/home /usr /var"
  #2. 以冒号作为分隔符，截取passwd文件的第一和第三字段，然后将输出传递给awk命令。
  #3. awk中的{% math_inline %}2表示的是uid，其中1-99是系统保留用户，>=100的uid才是我们自己创建的用户，awk通过print输出所有的用户名给for循环。
  #4. 注意echo命令的输出是由八个单词构成，同时由于-n选项，echo命令并不输出换行符。
  #5. 之所以使用find命令，也是为了考虑以点(DOT)开头的隐藏文件。这里的find将在指定目录列表内，搜索指定用户的，类型为普通文件的文件。并通过-ls选项输出找到文件的详细信息。其中输出的详细信息的第七列为文件大小列。
  #6. 通过awk命令累加find输出的第七列，最后再在自己的END块中将sum的值用MB计算并输出。该命令的输出将会与上面echo命令的输出合并作为for循环的输出传递给后面的awk命令。这里需要指出的是，该awk的输出就是后面awk命令的{% endmath_inline %}9，因为echo仅仅输出的8个单词。
  #7. 从for循环管道获取数据的awk命令，由于awk命令执行的动作是用双引号括起的，所以表示域字段的变量的前缀{% math_inline %}符号，需要用\进行转义。变量{% endmath_inline %}limited_quota变量将会自动完成命令替换，从而构成该awk命令的最终动作参数。
  for name in `cut -d: -f1,3 /etc/passwd | awk -F: '{% math_inline %}2 > 99 {print {% endmath_inline %}1}'`
  do
      echo -n "User {% math_inline %}name exceeds disk quota. Disk Usage is: "
      find {% endmath_inline %}dirs -user {% math_inline %}name -type f -ls |\
            awk '{ sum += {% endmath_inline %}7 } END { print sum / (1024*1024) " MB" }'
  done | awk "\{% math_inline %}9 > {% endmath_inline %}limited_quota { print \{% math_inline %}0 }"
  CTRL+D
  /> ./test20.sh

二十一、编写一个更具可读性的df命令输出脚本：

  这里我们将以awk脚本的方式来实现df -h的功能。
  /> cat > test21.sh
  #!/bin/sh
  #1. {% endmath_inline %} 表示当前Shell进程的pid。
  #2. trap信号捕捉是为了保证在Shell正常或异常退出时，仍然能够将该脚本创建的临时awk脚本文件删除。
  awk_script_file="/tmp/scf_tmp.{% math_inline %}"
  trap "rm -f {% endmath_inline %}awk_script_file" EXIT
  #3. 首先需要说明的是，'EOF'中的单引号非常重要，如果忽略他将无法通过编译，这是因为awk的命令动作必须要用单引号扩住。
  #4. awk脚本的show函数中，int(mb * 100) / 100这个技巧是为了保证输出时保留小数点后两位。
  cat << 'EOF' > {% math_inline %}awk_script_file
  function show(size) {
      mb = size / 1024;
      int_mb = (int(mb * 100)) / 100;
      gb = mb / 1024;
      int_gb = (int(gb * 100)) / 100;
      if (substr(size,1,1) !~ "[0-9]" || substr(size,2,1) !~ "[0-9]") {
          return size;
      } else if (mb < 1) {
          return size "K";
      } else if (gb < 1) {
          return int_mb "M";
      } else {
          return int_gb "G";
      }
  }
  #5. 在BEGIN块中打印重定义的输出头信息。
  BEGIN {
        printf "%-20s %7s %7s %7s %8s %s\n","FileSystem","Size","Used","Avail","Use%","Mounted"
  }
  #6. !/Filesystem/ 表示过滤掉包含Filesystem的行，即df输出的第一行。其余行中，有个域字段可以直接使用df的输出，有的需要通过show函数的计算，以得到更为可读的显示结果。
  !/Filesystem/ {
      size = show({% endmath_inline %}2);
      used = show({% math_inline %}3);
      avail = show({% endmath_inline %}4);
      printf "%-20s %7s %7s %7s %8s %s\n",{% math_inline %}1,size,used,avail,{% endmath_inline %}5,{% math_inline %}6
  }
  EOF
  df -k | awk -f {% endmath_inline %}awk_script_file
  CTRL+D
  /> ./test12.sh
  FileSystem              Size       Used      Avail     Use%   Mounted
  /dev/sda2              3.84G    2.28G     1.36G      63%   /
  tmpfs                 503.57M     100K 503.47M        1%   /dev/shm
  /dev/sda1             48.41M  35.27M  10.63M      77%   /boot
  /dev/sda3              14.8G 171.47M  13.88G        2%   /home

二十二、编写一个用于添加新用户的脚本：

  之所以在这里选择这个脚本，没有更多的用意，只是感觉这里的有些技巧和常识还是需要了解的，如/etc/passwd、/etc/shadow、/etc/group的文件格式等。
  /> cat > test22.sh
  #!/bin/sh
  #1. 初始化和用户添加相关的变量。
  passwd_file="/etc/passwd"
  shadow_file="/etc/shadow"
  group_file="/etc/group"
  home_root_dir="/home"
  #2. 只有root用户可以执行该脚本。
  if [ "{% math_inline %}(whoami)" != "root" ]; then
      echo "Error: You must be roor to run this command." >&2
      exit 1
  fi

  echo "Add new user account to {% endmath_inline %}(hostname)"
  echo -n "login: "
  read login
  #3. 去唯一uid，即当前最大uid值加一。
  uid=`awk -F: '{ if (big < {% math_inline %}3 && {% endmath_inline %}3 < 5000) big = {% math_inline %}3 } END {print big + 1}' {% endmath_inline %}passwd_file`
  #4. 设定新用户的主目录变量
  home_dir={% math_inline %}home_root_dir/{% endmath_inline %}login
  gid={% math_inline %}uid
  #5. 提示输入和创建新用户相关的信息，如用户全名和主Shell。
  echo -n "full name: "
  read fullname
  echo -n "shell: "
  read shell
  #6. 将输入的信息填充到passwd、group和shadow三个关键文件中。
  echo "Setting up account {% endmath_inline %}login for {% math_inline %}fullname..."
  echo {% endmath_inline %}{login}:x:{% math_inline %}{uid}:{% endmath_inline %}{gid}:{% math_inline %}{fullname}:{% endmath_inline %}{home_dir}:{% math_inline %}shell >> {% endmath_inline %}passwd_file
  echo {% math_inline %}{login}:*:11647:0:99999:7::: >> {% endmath_inline %}shadow_file
  echo "{% math_inline %}{login}:x:{% endmath_inline %}{gid}:{% math_inline %}login" >> {% endmath_inline %}group_file
  #7. 创建主目录，同时将新用户的profile模板拷贝到新用户的主目录内。
  #8. 设定该主目录的权限，再将其下所有文件的owner和group设置为新用户。
  #9. 为新用户设定密码。
  mkdir {% math_inline %}home_dir
  cp -R /etc/skel/.[a-zA-Z]* {% endmath_inline %}home_dir
  chmod 755 {% math_inline %}home_dir
  find {% endmath_inline %}home_dir -print | xargs chown {% math_inline %}{login}:{% endmath_inline %}{login}
  passwd {% math_inline %}login
  exit 0
  CTRL+D
  /> ./test22.sh
  Add new user account to bogon
  login: stephen
  full name: Stephen Liu
  shell: /bin/shell
  Setting up account stephen for Stephen Liu...
  Changing password for user stephen.
  New password:
  Retype new password:
  passwd: all authentication tokens updated successfully.

二十三、kill指定用户或指定终端的用户进程：

  这是一台运行Oracle数据库的Linux服务器，现在我们需要对Oracle做一些特殊的优化工作，由于完成此项工作需要消耗更多的系统资源，因此我们不得不杀掉一些其他用户正在运行的进程，以便节省出更多的系统资源，让本次优化工作能够尽快完成。
  /> cat > test23.sh
  #!/bin/sh
  user=""
  tty=""
  #1. 通过读取脚本的命令行选项获取要kill的用户或终端。-t后面的参数表示终端，-u后面的参数表示用户。这两个选项不能同时使用。
  #2. case中的代码对脚本选项进行验证，一旦失败则退出脚本。
  while getopts u:t: opt; do
      case {% endmath_inline %}opt in
      u) if [ "{% math_inline %}tty" != "" ]; then
             echo "-u and -t can not be set at one time."
             exit 1
          fi
          user={% endmath_inline %}OPTARG
          ;;
      t)  if [ "{% math_inline %}user" != "" ]; then
             echo "-u and -t can not be set at one time."
             exit 1
          fi
          tty={% endmath_inline %}OPTARG
          ;;
      ?) echo "Usage: {% math_inline %}0 [-u user|-t tty]" >&2
          exit 1
      esac
  done
  #3. 如果当前选择的是基于终端kill，就用{% endmath_inline %}tty来过滤ps命令的输出，否则就用{% math_inline %}user来过滤ps命令的输出。
  #4. awk命令将仅仅打印出pid字段，之后传递给sed命令，sed命令删除ps命令输出的头信息，仅保留后面的进程pids作为输出，并初始化pids数组。
  if [ ! -z "{% endmath_inline %}tty" ]; then
      pids={% math_inline %}(ps cu -t {% endmath_inline %}tty | awk "{print \{% math_inline %}2}" | sed '1d')
  else
      pids={% endmath_inline %}(ps cu -U {% math_inline %}user | awk "{print \{% endmath_inline %}2}" | sed '1d')
  fi
  #5. 判断数组是否为空，空则表示没有符合要求的进程，直接退出脚本。
  if [ -z "{% math_inline %}pids" ]; then
      echo "No processes matches."
      exit 1
  fi
  #6. 遍历pids数组，逐个kill指定的进程。
  for pid in {% endmath_inline %}pids; do
      echo "Killing process[pid = {% math_inline %}pid]... ..."
      kill -9 {% endmath_inline %}pid
  done
  exit 0
  CTRL+D
  /> ./test23.sh -t pts/1
  Killing process[pid = 11875]... ...
  Killing process[pid = 11894]... ...
  /> ./test23.sh -u stephen
  Killing process[pid = 11910]... ...
  Killing process[pid = 11923]... ...

二十四、判断用户输入(是/否)的便捷方法：

  对于有些交互式程序，经常需要等待用户的输入，以便确定下一步的执行流程。通常而言，当用户输入"Y/y/Yes/yes"时表示确认当前的行为，而当输入"N/n/No/no"时则表示否定当前的行为。基于这种规则，我们可以实现一个便捷确认方式，即只判断输入的首字母，如果为Y或y，表示确认，如为N或n，则为否定。
  /> cat > test24.sh
  #!/bin/sh
  echo -n "Please type[y/n/yes/no]: "
  read input
  #1. 先转换小写到大写，再通过cut截取第一个字符。
  ret=`echo {% math_inline %}input | tr '[a-z]' '[A-Z]' | cut -c1`

  if [ {% endmath_inline %}ret = "Y" ]; then
      echo "Your input is Y."
  elif [ {% math_inline %}ret = "N" ]; then
      echo "Your input is N."
  else
      echo "Your input is error."
  fi
  CTRL+D
  /> ./test24.sh
  Please type[y/n/yes/no]: y
  Your input is Y.
  /> ./test24.sh
  Please type[y/n/yes/no]: n
  Your input is N.

二十五、通过FTP下载指定的文件：

  相比于手工调用FTP命令下载文件，该脚本提供了更为方便的操作方式。
  /> cat > test25.sh
  #!/bin/sh
  #1. 测试脚本参数数量的有效性。
  if [ {% endmath_inline %}# -ne 2 ]; then
      echo "Usage: {% math_inline %}0 ftp://... username" >&2
      exit 1
  fi
  #2. 获取第一个参数的前六个字符，如果不是"ftp://"，则视为非法FTP URL格式。这里cut的-c选项表示按照字符的方式截取第一到第六个字符。
  header=`echo {% endmath_inline %}1 | cut -c1-6`
  if [ "{% math_inline %}header" != "ftp://" ]; then
      echo "{% endmath_inline %}0: Invalid ftp URL." >&2
      exit 1
  fi
  #3. 合法ftp URL的例子：ftp://ftp.myserver.com/download/test.tar
  #4. 针对上面的URL示例，cut命令通过/字符作为分隔符，这样第三个域字段表示server(ftp.myserver.com)。
  #5. 在截取filename时，cut命令也是通过/字符作为分隔符，但是"-f4-"将获取从第四个字段开始的后面所有字段(download/test.tar)。
  #6. 通过basename命令获取filename的文件名部分。
  server=`echo {% math_inline %}1 | cut -d/ -f3`
  filename=`echo {% endmath_inline %}1 | cut -d/ -f4-`
  basefile=`basename {% math_inline %}filename`
  ftpuser={% endmath_inline %}2
  #7. 这里需要调用stty -echo，以便后面的密码输入不会显示，在输入密码之后，需要再重新打开该选项，以保证后面的输入可以恢复显示。
  #8. echo ""，是模拟一次换换。
  echo -n "Password for {% math_inline %}ftpuser: "
  stty -echo
  read password
  stty echo
  echo ""
  #9. 通过HERE文档，批量执行ftp命令。
  echo {% endmath_inline %}{0}: Downloading {% math_inline %}baseile from server {% endmath_inline %}server.
  ftp -n << EOF
  open {% math_inline %}server
  user {% endmath_inline %}ftpuser {% math_inline %}password
  get {% endmath_inline %}filename {% math_inline %}basefile
  quit
  EOF
  #10.Shell内置变量{% endmath_inline %}?表示上一个Shell进程的退出值，0表示成功执行，其余值均表示不同原因的失败。
  if [ {% math_inline %}? -eq 0 ]; then
      ls -l {% endmath_inline %}basefile
  fi
  exit 0
  CTRL+D
  /> ./test25.sh  ftp://ftp.myserver.com/download/test.tar stephen
  Password for stephen:
  ./test25.sh: Downloading from server ftp.myserver.com.
  -rwxr-xr-x. 1 root root 678 Dec  9 11:46 test.tar

二十六、文件锁定：

  在工业应用中，有些来自于工业设备的文件将会被放到指定的目录下，由于这些文件需要再被重新格式化后才能被更高层的软件进行处理。而此时负责处理的脚本程序极有可能是多个实例同时运行，因此这些实例之间就需要一定的同步，以避免多个实例同时操作一个文件而造成的数据不匹配等问题的发生。文件锁定命令可以帮助我们实现这一同步逻辑。
  /> cat > test26.sh
  #!/bin/sh
  #1. 这里需要先确认flock命令是否存在。
  if [ -z {% math_inline %}(which flock) ]; then
      echo "flock doesn't exist."
      exit 1
  fi
  #2. flock中的-e选项表示对该文件加排它锁，-w选项表示如果此时文件正在被加锁，当前的flock命令将等待20秒，如果能锁住该文件，就继续执行，否则退出该命令。
  #3. 这里锁定的文件是/var/lock/lockfile1，-c选项表示，如果成功锁定，则指定其后用双引号括起的命令，如果是多个命令，可以用分号分隔。
  #4. 可以在两个终端同时启动该脚本，然后观察脚本的输出，以及lockfile1文件的内容。
  flock -e -w 20 /var/lock/lockfile1 -c "sleep 10;echo `date` | cat >> /var/lock/lockfile1"
  if [ {% endmath_inline %}? -ne 0 ]; then
      echo "Fail."
      exit 1
  else
      echo "Success."
      exit 0
  fi
  CTRL+D

二十七、用小文件覆盖整个磁盘：

  假设我们现在遇到这样一个问题，公司的关键资料copy到测试服务器上了，在直接将其删除后，仍然担心服务器供应商可以将其恢复，即便是通过fdisk进行重新格式化，也仍然存在被恢复的风险，鉴于此，我们需要编写一个脚本，创建很多小文件(5MB左右)，之后不停在关键资料所在的磁盘中复制该文件，以使Linux的inode无法再被重新恢复，为了达到这里效果，我们需要先构造该文件，如：
  /> find . -name "*" > testfile
  /> ls -l testfile
  -rwxr-xr-x. 1 root root 5123678 Dec  9 11:46 testfile
  /> cat > test27.sh
  #!/bin/sh
  #1. 初始化计数器变量，其中max的值是根据当前需要填充的磁盘空间和testfile的大小计算出来的。
  counter=0
  max=2000000
  remainder=0
  #2. 每次迭代counter变量都自增一，以保证每次生成不同的文件。当该值大于最大值时退出。
  #3. 对计数器变量counter按1000取模，这样可以在每生成1000个文件时打印一次输出，以便看到覆盖的进度，输出时间则便于预估还需要多少时间可以完成。
  #4. 创建单独的、用于存放这些覆盖文件的目录。
  #5. 生成临时文件，如果写入失败打印出提示信息。
  while true
  do
      ((counter=counter+1))
      if [ #counter -ge {% math_inline %}max ]; then
          break
      fi
      ((remainder=counter%1000))
      if [ {% endmath_inline %}remainder -eq 0 ]; then
          echo -e "counter = {% math_inline %}counter\t date = " {% endmath_inline %}(date)
      fi
      mkdir -p /home/temp2
      cat < testfile > "/home/temp/myfiles.{% math_inline %}counter"
      if [[ {% endmath_inline %}? -ne 0 ]]; then
          echo "Failed to wrtie file."
          exit 1
      fi
  done
  echo "Done"
  CTRL+D
  /> ./test27.sh
  counter = 1000        Fri Dec  9 17:25:04 CST 2011
  counter = 2000        Fri Dec  9 17:25:24 CST 2011
  counter = 3000        Fri Dec  9 17:25:54 CST 2011
  ... ...
  与此同时，可以通过执行下面的命令监控磁盘空间的使用率。
  /> watch -n 2 'df -h'
  Every 2.0s: df -h                                       Fri Dec  9 17:31:56 2011

  Filesystem            Size   Used Avail Use% Mounted on
  /dev/sda2             3.9G  2.3G  1.4G  63% /
  tmpfs                  504M  100K  504M   1% /dev/shm
  /dev/sda1              49M   36M   11M  77% /boot
  /dev/sda3              15G  172M   14G   2% /home
  我们也可以在执行的过程中通过pidstat命令监控脚本进程的每秒读写块数。

二十八、统计当前系统中不同运行状态的进程数量：

  在Linux系统中，进程的运行状态主要分为四种：运行时、睡眠、停止和僵尸。下面的脚本将统计当前系统中，各种运行状态的进程数量。
  /> cat > test28.sh
  #!/bin/sh
  #1. 初始化计数器变量，分别对应于运行时、睡眠、停止和僵尸。
  running=0
  sleeping=0
  stopped=0
  zombie=0
  #2. 在/proc目录下，包含很多以数字作为目录名的子目录，其含义为，每个数字对应于一个当前正在运行进程的pid，该子目录下包含一些文件用于描述与该pid进程相关的信息。如1表示init进程的pid。那么其子目录下的stat文件将包含和该进程运行状态相关的信息。
  #3. cat /proc/1/stat，通过该方式可以查看init进程的运行状态，同时也可以了解该文件的格式，其中第三个字段为进程的运行状态字段。
  #4. 通过let表达式累加各个计数器。
  for pid in /proc/[1-9]*
  do
      ((procs=procs+1))
      stat=`awk '{print {% math_inline %}3}' {% endmath_inline %}pid/stat`
      case {% math_inline %}stat in
          R) ((running=runing+1));;
          S) ((sleeping=sleeping+1));;
          T) ((stopped=stopped+1));;
          Z) ((zombie=zombie+1));
      esac
  done
  echo -n "Process Count: "
  echo -e "Running = {% endmath_inline %}running\tSleeping = {% math_inline %}sleeping\tStopped = {% endmath_inline %}stopped\tZombie = {% math_inline %}zombie."
  CTRL+D
  /> ./test28.sh
  Process Count: Running = 0      Sleeping = 136  Stopped = 0     Zombie = 0.

二十九、浮点数验证：

 浮点数数的重要特征就是只是包含数字0到9、负号(-)和点(.)，其中负号只能出现在最前面，点(.)只能出现一次。
  /> cat > test29.sh
  #!/bin/sh
  #1. 之前的一个条目已经介绍了awk中match函数的功能，如果匹配返回匹配的位置值，否则返回0。
  #2. 对于Shell中的函数而言，返回0表示成功，其他值表示失败，该语义等同于Linux中的进程退出值。调用者可以通过内置变量{% endmath_inline %}?获取返回值，或者作为条件表达式的一部分直接判断。
  validint() {
      ret=`echo {% math_inline %}1 | awk '{start = match({% endmath_inline %}1,/^-?[0-9]+{% math_inline %}/); if (start == 0) print "1"; else print "0"}'`
      return {% endmath_inline %}ret
  }

  validfloat() {
      fvalue="{% math_inline %}1"
      #3. 判断当前参数中是否包含小数点儿。如果包含则需要将其拆分为整数部分和小数部分，分别进行判断。
      if [ ! -z  {% endmath_inline %}(echo {% math_inline %}fvalue | sed 's/[^.]//g') ]; then
          decimalpart=`echo {% endmath_inline %}fvalue | cut -d. -f1`
          fractionalpart=`echo {% math_inline %}fvalue | cut -d. -f2`
          #4. 如果整数部分不为空，但是不是合法的整型，则视为非法格式。
          if [ ! -z {% endmath_inline %}decimalpart ]; then
              if ! validint "{% math_inline %}decimalpart" ; then
                  echo "decimalpart is not valid integer."
                  return 1
              fi
          fi
          #5. 判断小数部分的第一个字符是否为-，如果是则非法。
          if [ "{% endmath_inline %}{fractionalpart:0:1}" = "-" ]; then
              echo "Invalid floating-point number: '-' not allowed after decimal point." >&2
              return 1
          fi
          #6. 如果小数部分不为空，同时也不是合法的整型，则视为非法格式。
          if [ "{% math_inline %}fractionalpart" != "" ]; then
              if ! validint "{% endmath_inline %}fractionalpart" ; then
                  echo "fractionalpart is not valid integer."
                  return 1
              fi
          fi
          #7. 如果整数部分仅为-，或者为空，如果此时小数部分也是空，则为非法格式。
          if [ "{% math_inline %}decimalpart" = "-" -o -z "{% endmath_inline %}decimalpart" ]; then
              if [ -z {% math_inline %}fractionalpart ]; then
                  echo "Invalid floating-point format." >&2
                  return 1
              fi
          fi
      else
          #8. 如果当前参数仅为-，则视为非法格式。
          if [ "{% endmath_inline %}fvalue" = "-" ]; then
              echo "Invalid floating-point format." >&2
              return 1
          fi
          #9. 由于参数中没有小数点，如果该值不是合法的整数，则为非法格式。
          if ! validint "{% math_inline %}fvalue" ; then
              echo "Invalid floating-point format." >&2
              return 1
          fi
      fi
      return 0
  }
  if validfloat {% endmath_inline %}1 ; then
      echo "{% math_inline %}1 is a valid floating-point value."
  fi
  exit 0
  CTRL+D
  /> ./test29.sh 47895
  47895 is a valid floating-point value.
  /> ./test29.sh 47895.33
  47895.33 is a valid floating-point value.
  /> ./test29.sh 47895.3e
  fractionalpart is not valid integer.
  /> ./test29.sh 4789t.34
  decimalpart is not valid integer.

三十、统计英文文章中每个单词出现的频率：

  这个技巧的主要目的是显示如何更好的使用awk命令的脚本。
  /> cat > test30.sh
  #!/bin/sh
  #1. 通过当前脚本的pid，生成awk脚本的临时文件名。
  #2. 捕捉信号，在脚本退出时删除该临时文件，以免造成大量的垃圾临时文件。
  awk_script_file="/tmp/scf_tmp.{% endmath_inline %}"
  trap "rm -f {% math_inline %}awk_script_file" EXIT
  #3. while循环将以当前目录下的testfile作为输入并逐行读取，在读取到末尾时退出循环。
  #4. getline读取到每一行将作为awk的正常输入。在内层的for循环中，i要从1开始，因为{% endmath_inline %}0表示整行。NF表示域字段的数量。
  #5. 使{% math_inline %}i作为数组的键，如果{% endmath_inline %}i的值匹配正则表达式"^[a-zA-Z]+{% math_inline %}"，我们将其视为单词而不是标点。每次遇到单词时，其键值都会递增。
  #6. 最后通过awk脚本提供的特殊for循环，遍历数组的键值数据。
  cat << 'EOF' > {% endmath_inline %}awk_script_file
  BEGIN {
      while (getline < "./testfile" > 0) {
          for (i = 1; i <= NF; ++i) {
              if (match({% math_inline %}i,"^[a-zA-Z]+{% endmath_inline %}") != 0)
                  arr[{% math_inline %}i]++
          }
      }
      for (word in arr) {
          printf "word = %s\t count = %s\n",word,arr[word]
      }
  }
  EOF
  awk -f {% endmath_inline %}awk_script_file
  CTRL+D
  /> cat testfile
  hello world liu liu , ,
  stephen liu , ?
  /> ./test30.sh
  word = hello      count = 1
  word = world     count = 1
  word = stephen count = 1
  word = liu         count = 3

scripts

实验室服务器运维脚本

sendlog.py

将服务器日志发送到指定邮箱

charles-adduser.sh

添加用户时顺便记录时间和是谁

backup_server_data.py

使用scp将服务器某些地方的数据送到另一个服务器的某些位置。

mv2cron.sh

将上面的脚本放到服务器中/cron.hourly, /cron.daily, ...然后编辑相关位置，使得可以正常地备份

如果服务器出了问题：

去备份位置寻找以前的备份，并想办法恢复备份

想要使用本脚本，你需要学习：

ssh免密码登陆

scp使用方法

crontab的用法和/etc/cron.daily等相关文件夹

初步学会bash脚本的用法（和写法）

vi的用法

其他

你可能需要在命令行登陆 ip 网关，可以使用东北大学 ip 网关登陆器

backup_server_data.py

#!/usr/bin/python3
"""介绍：将backup_from列表内的文件通过scp备份到远程服务器的backup_to文件夹。
"""
import os
import time

# set the backup informations
backup_len = 1 # 备份数量
backup_from = ['/opt/discuz-data/data/']
backup_ssh_user = 'root@abc.net'
backup_ssh_port = '8022'
backup_to = '/data/backup/discuz/weekly'
identity_file = '/root/.ssh/id_rsa'

now = time.strftime('%Y-%m-%d_%X', time.localtime(time.time()))

os.system('ssh -p {backup_ssh_port} -i {identity_file} {backup_ssh_user} -T "mkdir -p {backup_to}/{now}"'.format(
    identity_file=identity_file,
    backup_ssh_port=backup_ssh_port,
    backup_ssh_user=backup_ssh_user,
    backup_to=backup_to,
    now=now))

for path_i in backup_from:
    os.system("scp -p -P {backup_ssh_port} -i {identity_file} -r {backup_from} {backup_ssh_user}:{backup_to}/{now}".format(
        identity_file=identity_file,
        backup_ssh_port=backup_ssh_port,
        backup_from=path_i,
        backup_ssh_user=backup_ssh_user,
        backup_to=backup_to,
        now=now))

pipe = os.popen('ssh -p {backup_ssh_port} -i {identity_file} {backup_ssh_user} -T "ls {backup_to}"'.format(
    identity_file=identity_file,
    backup_ssh_port=backup_ssh_port,
    backup_ssh_user=backup_ssh_user,
    backup_to=backup_to), 'r', -1)
pathes = []
for i in pipe:
    pathes.append(i.strip())

pathes.sort()
print(pathes)
if len(pathes) > backup_len:
    for i in pathes[:-backup_len]:
        os.system("ssh -p {backup_ssh_port} -i {identity_file} -T {backup_ssh_user} 'rm -rf {backup_to}/{now}'".format(
            identity_file=identity_file,
            backup_ssh_user=backup_ssh_user,
            backup_ssh_port = backup_ssh_port,
            backup_to=backup_to,
            now=i))

charles-adduser.sh

#!/bin/bash
# 添加一个用户，并且记录加入的是谁
# 用法： adduser username 张三
sudo useradd {% math_inline %}1 -s /bin/bash -m
echo {% endmath_inline %}1 {% math_inline %}2 {% endmath_inline %}(date) >> ~/.users
echo {% math_inline %}1 {% endmath_inline %}2 {% math_inline %}(date) |sudo tee -a ~/.users /root/.config
(echo 'password'
sleep 1
echo 'password')|sudo passwd {% endmath_inline %}1
echo "使用passwd改密码，初始密码为password"
sudo edquota -p {% math_inline %}USER {% endmath_inline %}1
sudo mkdir /data/
sudo echo 'export CUDA_VISIBLE_DEVICES=0'|tee -a /home/$1/.bashrc

mv2cron.bash

#!/bin/bash
# 将{% math_inline %}1所指定的脚本分别复制到crontab中不同的备份周期文件夹，并逐个编辑相关备份脚本
cp {% endmath_inline %}1 /etc/cron.hourly
cp {% math_inline %}1 /etc/cron.daily
cp {% endmath_inline %}1 /etc/cron.weekly
cp {% math_inline %}1 /etc/cron.monthly
vi /etc/cron.hourly/{% endmath_inline %}1
vi /etc/cron.daily/{% math_inline %}1
vi /etc/cron.weekly/{% endmath_inline %}1
vi /etc/cron.monthly/$1
ls /etc/cron.hourly
ls /etc/cron.daily
ls /etc/cron.weekly
ls /etc/cron.monthly

sendlog.py

/sbin/sendlog, 700

#!/usr/bin/env python
# -*- coding: UTF-8 -*-
# To add an autostart service, you may move this script to init.d first:
# `cp {% math_inline %}filename /etc/rc.d/init.d`
# And then, use `chkconfig --add {% endmath_inline %}filename`
# Or manually link it to /etc/rc.d:
# `ln -s /etc/rc.d/init.d/{% math_inline %}filename /etc/rc.d/rc2.d/S99{% endmath_inline %}filename`
# `ln -s /etc/rc.d/init.d/{% math_inline %}filename /etc/rc.d/rc3.d/S99{% endmath_inline %}filename`
# `ln -s /etc/rc.d/init.d/{% math_inline %}filename /etc/rc.d/rc5.d/S99{% endmath_inline %}filename`
# `ln -s /etc/rc.d/init.d/{% math_inline %}filename /etc/rc.d/rc0.d/K01{% endmath_inline %}filename`
# `ln -s /etc/rc.d/init.d/{% math_inline %}filename /etc/rc.d/rc6.d/K01{% endmath_inline %}filename`

from __future__ import print_function
import os
import sys
import time
import smtplib
from email.mime.text import MIMEText
from email.mime.image import MIMEImage
from email.mime.multipart import MIMEMultipart
from email.header import Header
from email.utils import formataddr

# Options
server_name = os.popen('hostname').read()
send_email = 'sender@domain.tld'
password = 'password'
smtp_server = 'smtp.domain.tld'
receivers = [('Admin', 'receiver@domain.tld')]

# Mailing messages
dmesg = os.popen('dmesg -HT').read()
last = os.popen('last').read()
lastb = os.popen('lastb').read()
bootlog = open('/var/log/boot.log').read()
apt_hist = open('/var/log/apt/history.log').read()
auth = open('/var/log/auth.log').read()
syslog = open('/var/log/syslog').read()
root_history = open('/root/.bash_history').read()

ifconfig = os.popen('ifconfig').read()

# More mailing options and messages
sender = {
    'address': send_email,
    'nickname': server_name,
    'password': password,
    'smtp_server': smtp_server,
    'smtp_port': 465, }

mail = {
    'content': ifconfig,
    'subject': 'Server {}: {}'.format(server_name, ' '.format(sys.argv[1:])),
    'attachments': [
        ('dmesg.txt', dmesg),
        ('last.txt', last),
        ('btmp.txt', lastb),
        ('boot.log.txt', bootlog),
        ('apt.history.log.txt', apt_hist),
        ('auth.log.txt', auth),
        ('syslog.txt', syslog),
        ('root_history.txt', root_history),
    ],
}

def sendmail(sender, receivers, mail):
    message = MIMEMultipart()
    message['From'] = formataddr((Header(sender['nickname'], 'utf-8').encode(), sender['address']))
    message['To'] = ','.join(map(lambda x: formataddr((Header(x[0], 'utf-8').encode(), x[1])), receivers))
    message['Subject'] = Header(mail['subject'], 'utf-8').encode()

    if 'content' in mail:
        message.attach(MIMEText(mail['content'], 'plain', 'utf-8'))

    if 'content_html' in mail:
        message.attach(MIMEText(mail['content_html'], 'html', 'utf-8'))

    if 'attachments' in mail:
        for attachment in mail['attachments']:
            att = MIMEText(attachment[1], 'base64', 'utf-8')
            att["Content-Type"] = "application/octet-stream"
            att["Content-Disposition"] = 'attachment; filename="{}"'.format(attachment[0])
            message.attach(att)

    if 'images' in mail:
        for image in mail['images']:
            img = MIMEImage(image['data'])
            img.add_header('Content-ID', '<{}>'.format(image['Content-ID']))
            message.attach(img)

    server = smtplib.SMTP_SSL(sender['smtp_server'], sender['smtp_port'])

    auth_success = False
    times = 0
    while not auth_success and times < 10:
        try:
            server.login(sender['address'], sender['password'])
            auth_success = True
        except smtplib.SMTPAuthenticationError as e:
            print(e)
            print('AUTH ERROR!')
            time.sleep(10)
            times += 1

    server.sendmail(sender['address'], list(zip(*receivers))[1], message.as_string())
    server.quit()

if __name__ == '__main__':
    sendmail(sender, receivers, mail)

/usr/lib/systemd/system/sendlog.service

[Unit]
Description=Send system logs using email, requires python

[Service]

Type=oneshot
ExecStart=/sbin/sendlog STARTED
ExecStop=/sbin/sendlog STOPPED
Restart=on-failure

AUTO_UPGRADE

Linux 自动安装安全更新

摘抄自 Linux 服务器安全简明指南

有一些用于服务器上自动更新的参数。Fedora 的 Wiki 上有一篇很棒的剖析自动更新的利弊的文章，但是如果你把它限制到安全更新上，自动更新的风险将是最小的。

自动更新的可行性必须你自己判断，因为它归结为你在你的服务器上做什么。请记住，自动更新仅适用于来自仓库的包，而不是自行编译的程序。你可能会发现一个复制了生产服务器的测试环境是很有必要的。可以在部署到生产环境之前，在测试环境里面更新来检查问题。

CentOS 使用 yum-cron 进行自动更新。

Debian 和 Ubuntu 使用 无人值守升级。

Fedora 使用 dnf-automatic。

QUOTA

磁盘配额设置

启动配额

安装 quota: apt install quota

编辑 /etc/fstab，在启动配额的磁盘挂载选项中添加usrquota,grpquota或usrjquota=aquota.user,grpjquota=aquota.group,jqfmt=vfsv0（如果内核支持和文件系统日志）

使用mount -afv测试一下 fstab 有没有错误，如果有错的话系统将无法启动。

重新挂载硬盘mount -o remount,usrquota,grpquota sdX

创建配额文件: quotacheck -amvugc

编辑一个配额配置: edquota -u ￥user or edquota -g group

为其他用户复制￥user的配额信息： for i in ￥(ls /home);do if [[ -d /home/￥i ]];then echo quota ￥i;edquota -p ￥user ￥i; fi;done，其中user就是上一步修改过配额的用户

报告配额使用状况

repquota -as

注意事项

edquota 的时候数值的单位为 kb

其他

mount -o remount,rw: 重新挂载

SECURITY

服务器安全指南

使用 Fail2Ban 保护 SSH 登录

本文摘抄自 Linux 服务器安全简明指南

Fail2Ban 是一个应用程序，它会在太多的失败登录尝试后禁止 IP 地址登录到你的服务器。由于合法登录通常不会超过三次尝试（如果使用 SSH 密钥，那不会超过一个），因此如果服务器充满了登录失败的请求那就表示有恶意访问。

Fail2Ban 可以监视各种协议，包括 SSH、HTTP 和 SMTP。默认情况下，Fail2Ban 仅监视 SSH，并且因为 SSH 守护程序通常配置为持续运行并监听来自任何远程 IP 地址的连接，所以对于任何服务器都是一种安全威慑。

有关安装和配置 Fail2Ban 的完整说明，请参阅我们的指南：使用 Fail2ban 保护服务器。

常用命令

fail2ban-client status: 查看有哪些 jail 和状态

fail2ban-client status jail: 查看 jail 状态

fail2ban-client set jail unbanip ip-address

禁止 Ping（禁止 Ping 并不见得利大于弊）

临时禁止 Ping:

echo 1 > /proc/sys/net/ipv4/icmp_echo_ignore_all

永久禁止 Ping:

在 /etc/sysctl.conf 增加 net.ipv4.icmp_echo_ignore_all=1

或者在防火墙添加一条`iptables -A INPUT -p icmp --icmp-type 8 -s 0/0 -j DROP

防火墙允许 Ping:

iptables -A INPUT -p icmp --icmp-type echo-request -j ACCEPT
iptables -A OUTPUT -p icmp --icmp-type echo-reply -j ACCEPT

ffmpeg

转换格式：ffmpeg -i input.mp4 -qscale 0 output.avi

不降低视频质量：-qscale 0

横向/纵向拼接：-filter_complex hstack/vstack

裁减视频：

ffmpeg -ss 00:30:00 -i vid.mp4 -t 60 -c copy out.mp4

ffmpeg -ss 00:30:00 -i vid.mp4 -to 00:31:00 -c copy out.mp4

Kazam 转码: ffmpeg -y -i input_file.mp4 -c:v libx264 -c:a aac -strict experimental -tune fastdecode -pix_fmt yuv420p -b:a 192k -ar 48000 output_file.mp4

图片变视频：convert -delay 10 -loop 0 $(ls *.png|sort -n) output.gif;ffmpeg -i output.gif -qscale 0 output.mp4

git

remove from history: git filter-branch --force --index-filter "git rm -rf --cached --ignore-unmatch <files>" --prune-empty --tag-name-filter cat -- --all

lnav

lnav

这个命令是我在 linux 上面发现的特别好用的日志查看器，可以自动整合多日志文件，自动合并 logrotate 文件，还能自动解压缩。

这个命令可以配合 logging 模块使用。

常用功能

命令行参数

-r 递归检索整个文件夹

-R 也看 logrotate 的旧日志

快捷键

筛选

:set-min-log-level <level>

tab 日志筛选器，使用i/o创建正向或反向筛选，D删掉筛选器

Ctrl+F toggle

m/M/C 标记，对于标记的内容可以进行文件操作，这个非常好用

:append-to, :write-to, :write-csv-to, :write-json-to, :pipe-to, :pipe-line-to

显示

P pretty print

Ctrl+L 拷贝模式

Ctrl+W 折行

i/I 统计，z/Z 切换统计尺度

浏览/跳转

e/E error

w/W warning

n/N, >/<搜索

s/S 查找日志记录减缓的时刻

时间跳转

d/D24h跳转

1-6/Shift 1-6 十分钟跳转

7-8 分钟跳转

0/Shift 0日期跳转（日界线）

:goto a minute later, :relative-goto + r/R 重复操作

其他命令

:unix-time <secs-or-date>, :current-time转换 timestamp

:comment, :clear-comment, :tag, :untag, :delete-tags

netcat

传输文件

发送：tar cvzf - <files>|nc <ip> <port>

接收：nc -l <port>|tar xvzf -

tmux

Tmux

new session: tmux new-session -d -s <session-name>

kill session: tmux kill-session -t <session-name>

new window:tmux new-window -n <window-name>

select window: tmux select-window -t <window-number>

split window: tmux split-window -h -t <session-name>:<window-number>

select pane: tmux select-pane -t <session-name>:<window-number>.<pane-number>,tmux select-pane -L/-R/-U/-D

run command: tmux send-keys -t <session-name>:<window-number>.<pane-number>

#!/bin/bash
set -e
echo '尝试结束之前的任务'
tmux kill-session -t fusion||true
echo '创建tmux session'
tmux new-session -d -s fusion

echo '创建window'
tmux split-window -h -t fusion:0
tmux split-window -v -t fusion:0
tmux select-pane -t fusion:0.0
tmux split-window -v -t fusion:0
tmux split-window -h -t fusion:0

echo '等待bash 10s'
sleep 10

echo '启动任务'
tmux send-keys -t fusion:0.0 './ws/0sensev2x/run/1.sh' C-m
tmux send-keys -t fusion:0.1 './ws/0sensev2x/run/2.sh' C-m
tmux send-keys -t fusion:0.2 './ws/0sensev2x/run/3.sh' C-m
tmux send-keys -t fusion:0.3 'echo HMI暂时不支持，跳过' C-m
#tmux send-keys -t fusion:0.3 './ws/0sensev2x/run/4.sh' C-m
tmux send-keys -t fusion:0.4 './ws/0sensev2x/run/5.sh' C-m

sleep 3
tmux attach-session -t fusion

Arch_Linux_Installation

准备

联网

timedatectl set-ntp true

loadkeys dvorak

pacman -S tmux fish btrfs-progs

分区

mkfs.fat -F32 /dev/sdb1

sd | flags | size | mount point | file system
sda1 |  | 711 Gib | /home/harddrive | NTFS
sda2 |  | 207 Gib | /home | XFS
sda3 |  | 15 Gib | /var | XFS
sdb1 | efi | 550 Mib | /boot | FAT32
sdb2 |  | 94.8 Gib | / | BTRFS
sdb3 | swap | 16.5 Gib | [SWAP] | SWAP

挂载

cd /mnt
mount /dev/sdb2 /mnt

mkdir -p /mnt/boot /mnt/home /mnt/var

mount /dev/sdb1 /mnt/boot
mount /dev/sda2 /mnt/home
mount /dev/sda3 /mnt/var

mkdir -p /mnt/home/harddrive

mount /dev/sda1 /mnt/home/harddrive

mkswap /dev/sdb3
swapon /dev/sdb3

安装配置基本系统

tmux
pacstrap /mnt base linux-lts  # arch-install-scripts
genfstab -L /mnt >> /mnt/etc/fstab

arch-chroot /mnt
pacman -S intel-ucode
ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
hwclock --systohc
# edit /etc/locale.gen
locale-gen
echo 'Laptop' > /etc/hostname
vim /etc/hosts

add below to /etc/hosts

# Static table lookup for hostnames.
# See hosts(5) for details.
127.0.0.1 localhost.localdomain localhost
#::1 localhost.localdomain localhost

useradd user
usermod user -aG adm
pacman -S sudo fish networkmanager
visudo

配置启动项

add to /etc/pacman.d/hooks/systemd-boot.hook

[Trigger]
Type=Package
Operation=Upgrade
Target=systemd

[Action]
Description=Updating sysdemd-boot
When=PostTransaction
Exec=/usr/bin/bootctl update

add to /boot/loader/entries/

linux-lts.conf

title Charles Xu's Arch Linux LTS
linux /vmlinuz-linux-lts
initrd /intel-ucode.img
initrd /initramfs-linux-lts.img
options root=/dev/sdb2
options resume=/dev/sdb3

linux-lts-fallback.conf

title Charles Xu's Arch Linux LTS Fallback
linux /vmlinuz-linux-lts
initrd /intel-ucode.img
initrd /initramfs-linux-lts-fallback.img
options root=/dev/sdb2
options resume=/dev/sdb3

add to /boot/loader/loader.conf

default linux-lts
timeout 3
editor 0

change /etc/mkinitcpio.conf

52:HOOKS=(base udev autodetect modconf block filesystems keyboard resume fsck)

安装启动项

bootctl install

安装后的配置

安装 git, fish-shell, tmux, curl, wget

为了防止Btrfs格式文件系统损坏，设置/etc/default/tlp:

SATA_LINKPWR_ON_BAT=max_performance

安装 Xfce4

安装 CharlesScripts

安装/删除软件 inst (inst), yao (yao)

timedatectl set-ntp true

开启并配置相应服务（部分配置应参考CharlesScripts中的自动配置脚本，比如tlp）

/etc/systemd/system
├── bluetooth.target.wants
│   └── bluetooth.service -> /usr/lib/systemd/system/bluetooth.service
├── dbus-org.bluez.service -> /usr/lib/systemd/system/bluetooth.service
├── dbus-org.freedesktop.nm-dispatcher.service -> /usr/lib/systemd/system/NetworkManager-dispatcher.service
├── dbus-org.freedesktop.timesync1.service -> /usr/lib/systemd/system/systemd-timesyncd.service
├── display-manager.service -> /usr/lib/systemd/system/lxdm.service
├── getty.target.wants
│   └── getty@tty1.service -> /usr/lib/systemd/system/getty@.service
├── graphical.target.wants
│   ├── bumblebeed.service -> /usr/lib/systemd/system/bumblebeed.service
│   └── udisks2.service -> /usr/lib/systemd/system/udisks2.service
├── multi-user.target.wants
│   ├── atd.service -> /usr/lib/systemd/system/atd.service
│   ├── btrfs-scrub@root.timer -> /usr/lib/systemd/system/btrfs-scrub@.timer
│   ├── cronie.service -> /usr/lib/systemd/system/cronie.service
│   ├── dnscrypt-proxy.service -> /usr/lib/systemd/system/dnscrypt-proxy.service
│   ├── fail2ban.service -> /usr/lib/systemd/system/fail2ban.service
│   ├── gpm.service -> /usr/lib/systemd/system/gpm.service
│   ├── NetworkManager.service -> /usr/lib/systemd/system/NetworkManager.service
│   ├── org.cups.cupsd.path -> /usr/lib/systemd/system/org.cups.cupsd.path
│   ├── pkgfile-update.timer -> /usr/lib/systemd/system/pkgfile-update.timer
│   ├── remote-fs.target -> /usr/lib/systemd/system/remote-fs.target
│   ├── sshd.service -> /usr/lib/systemd/system/sshd.service
│   └── v2ray.service -> /usr/lib/systemd/system/v2ray.service
├── network-online.target.wants
│   └── NetworkManager-wait-online.service -> /usr/lib/systemd/system/NetworkManager-wait-online.service
├── printer.target.wants
│   └── org.cups.cupsd.service -> /usr/lib/systemd/system/org.cups.cupsd.service
├── sockets.target.wants
│   └── org.cups.cupsd.socket -> /usr/lib/systemd/system/org.cups.cupsd.socket
├── sysinit.target.wants
│   └── systemd-timesyncd.service -> /usr/lib/systemd/system/systemd-timesyncd.service
└── timers.target.wants
    ├── e2scrub_all.timer -> /usr/lib/systemd/system/e2scrub_all.timer
    ├── fstrim.timer -> /usr/lib/systemd/system/fstrim.timer
    └── xfs_scrub_all.timer -> /usr/lib/systemd/system/xfs_scrub_all.timer

9 directories, 27 files

trizen powerline-console-fonts后，编辑/etc/vconsole.conf，加入FONT="ter-powerline-v16n"

Add group users; Add $USER to wheel, audio, users, realtime

/etc/lightdm/lightdm-gtk-greeter.conf

[greeter]
background = /home/charles/.cache/wallpaper/dialog-image.jpg
theme-name = Arc-Flatabulous-Dark
icon-theme-name = Papirus-Dark
screensaver-timeout = 10

/etc/polkit-1/rules.d/85-suspend.rules

polkit.addRule(function(action, subject) {
    if (action.id == "org.freedesktop.login1.suspend" &&
        subject.isInGroup("users")) {
        return polkit.Result.YES;
    }
});

/etc/polkit-1/rules.d/86-mount.rules

polkit.addRule(function(action, subject) {
    if (action.id == "org.freedesktop.udisks2.filesystem-mount-system" &&
        subject.isInGroup("users")) {
        return polkit.Result.YES;
    }
});

macOS

软件因为权限问题打不开sudo xattr -r -d com.apple.quarantine <path>

chattr -> chflags; lsattr -> /bin/ls -lO

lsblk -> diskutil list

OptimalControl_AA203

AA 203: Optimal and Learning-Based Control

Lecture_01

Lecture 1

状态空间描述

x˙(t)=f(x(t),u(t),t) \mathbf{\dot{x}}(t)=\mathbf{f}(\mathbf{x}(t), \mathbf{u}(t), t) x˙(t)=f(x(t),u(t),t)

其中：

a history of control inputs utu_{t}ut during interval [t0,tf][t_0, t_f][t0,tf] is called a control history

a history of state values xtx_{t}xt during interval [t0,tf][t_0, t_f][t0,tf] is called a state trajectory

最优控制例子

问题描述

双重积分器：控制一维空间质点位置，控制量为加速度。控制量和状态变量有如下二次积分的关系： s¨(t)=a(t) \ddot{s}(t)=a(t) s¨(t)=a(t)

控制目标：使其状态从 x0=[5,0]→us=[0,0]\mathbf{x}_0=[5,0]\rightarrow u_s=[0, 0]x0=[5,0]→us=[0,0] 。状态空间表达式（状态转移模型）为： [s˙v˙]=[0100][sv]+[01][a] \begin{bmatrix} \dot s \\ \dot v \end{bmatrix} = \begin{bmatrix} 0&1\\0&0 \end{bmatrix} \begin{bmatrix} s\\v \end{bmatrix} + \begin{bmatrix} 0\\1 \end{bmatrix} \begin{bmatrix} a \end{bmatrix} [s˙v˙]=[0010][sv]+[01][a]

比例控制器

线性负反馈：

a=−kp(s−sus)−kd(v−vus) a=-k_p(s-s_{u_s})-k_d(v-v_{u_s}) a=−kp(s−sus)−kd(v−vus)

解微分方程，得到时域响应，即可分析得知当 kp,kd>0k_p,k_d \gt 0 kp,kd>0 时可收敛到设定值。

最优控制器

这里的例子中的最优控制比较简单，直接建立最优化模型，开环控制，求解每个时间步的uuu。 min∫0tf∥x(t)∥22+∥u(t)∥22 min\int_0^{t_f}{\lVert\mathbf{x}(t)\rVert_2^2 +\lVert\mathbf{u}(t)\rVert_2^2} min∫0tf∥x(t)∥22+∥u(t)∥22

s.t.x˙(t)=Ax(t)+Bu(t)x˙(0)=x0x˙(tf)=xf \begin{array}{rrl} s.t.&\mathbf{\dot{x}}(t)=&A\mathbf{x}(t)+B\mathbf{u}(t)\\ &\mathbf{\dot{x}}(0)=&\mathbf{x}_0\\ &\mathbf{\dot{x}}(t_f)=&\mathbf{x}_f \end{array} s.t.x˙(t)=x˙(0)=x˙(tf)=Ax(t)+Bu(t)x0xf

优化目标：最小化残差和控制量

约束条件：

状态转移

边界条件

课程作业代码中：

cost = 0
constraints = [x[0]==x_0, x[t_f]=x_f]

for t in [t0 .. tf], do:
    cost = cost + norm(x[t] - x_f)
    x[t+1] == dynamics(x[t], u[t]) -add-to-> constraint

solver.solve()

Lecture_02

Lecture 2

系统辨识

线性回归

y^=θTz+ϵ \hat{y}=\mathbf{\theta}^T\mathbf{z}+\epsilon y^=θTz+ϵ

现在有数据集y1,…,yNy_1, \ldots, y_Ny1,…,yN, z1,…,zNz_1, \ldots, z_Nz1,…,zN，

使用平方损失函数 J=∑(yi−y^) J=\sum(y_i-\hat{y}) J=∑(yi−y^) 用矩阵表示为

J=∥y−Zθ∥2 J=\lVert \mathbf{y}-Z\mathbf{\theta}\rVert_2 J=∥y−Zθ∥2

如果Z满秩，导数等于0，解得 θ^=(ZTZ)−1ZTy \hat{\mathbf{\theta}}=\left(Z^TZ\right)^{-1}Z^T\mathbf{y} θ^=(ZTZ)−1ZTy

一阶线性模型参数辨识

状态转移模型：xt+1=axt+but+ϵx_{t+1}=ax_t+bu_t+\epsilonxt+1=axt+but+ϵ

线性回归模型：zt=[xt,ut]z_t=[x_t, u_t]zt=[xt,ut]；y=xt+1y=x_{t+1}y=xt+1；θ=[a,b]\mathbf{\theta}=[a,b]θ=[a,b]

收集数据求解即可，其实就是个回归问题，用其他模型也ok

MRAC: Model Reference Adaptive Control

ppt里面例子推导很有问题，完全看不懂，所以这里参考这篇博客或者PDF的内容进行介绍。

在控制过程中同时更新系统变量。

MIAC: Model Identification Adaptive Control

持续收集数据并更新模型参数，可以直接用反馈控制等，结构简单，适用性广，很容易和各种控制算法结合，不过比较缺乏稳定性保证，尤其在数据量少的时候会比较有问题。

Curriculum_Vitae

简历自动生成工具

本工具可以将 json 格式的简历信息转换成 markdown，并对简历做若干处理：

支持生成中文 / 英文简历，对应不同的标题和中文 / 双语姓名等

支持隐藏敏感信息，以便发布到互联网公开展示

支持通过爬虫获取最新信息，当前支持 github 的 follower、star、fork

工具链接：curriculum_viate_markdown_generator.py

使用指南

usage: curriculum_viate_markdown_generator.py [-h] [--cv-json CV_JSON]
                                              [--example] [--lang LANG]
                                              [--public]
                                              [--hide-education-misc]

optional arguments:
  -h, --help            show this help message and exit
  --cv-json CV_JSON, -c CV_JSON
                        CV json path, use --example to get CV json example
  --example
  --lang LANG
  --public              Show private information
  --hide-education-misc
                        hide education misc information

准备 json 格式的简历数据（可以用 curriculum_viate_markdown_generator.py --example 获取数据示例）。

数据格式简介：

联系信息中的 github_user 可省略不填

简历采用 STAR 法则，每个 project 需要填写 situation, task, action, result 四部分（也可省略某些部分不填）。

简历支持隐藏隐私信息（默认行为），在使用 --public 参数时才会打印敏感信息。

支持将使用 @private () 括号括起来的内容标记为敏感信息。

支持在任何 object 对象信息中加入 ignored: true 忽略当前 item

简历支持英文输出，使用 --lang en 开启，其中姓名和地区将显示双语，当然 json 中的数据还是要自己写英文版。

其他功能：

支持从 GitHub 爬取最新的信息，包括：

follower: 在简历中相应位置加入 {github_followers,username} 即可爬取 username 的 follower 数量

star: {github_star,username,project_name}

fork: {github_fork,username,project_name}

使用 pandoc 转换为 html: pandoc cv.md -t html5 -o cv.html --self-contained --css=cv.css，其中 css 文件可以在 这里 获取。可以用浏览器的打印功能把 html 打印成 pdf，当然也能用 word 打开 html 继续编辑。

2022.en

NAME_EN NAME

Contact

Email | TEL | GitHub (373 followers)
charl3s.xu@gmail.com | TEL | github.com/the0demiurge

REGION_EN REGION

Education

Time | University | Degree | Major
TIME | UNIVERSITY | DEGREE | MAJOR
TIME | UNIVERSITY | DEGREE | MAJOR

Skills

Mathematics and Algorithm

Deep Learning (DL): Proficient in Reinforcement Learning, Deeply understand DL optimization, experienced with PyTorch, well-informed with NLP (TransFormer, LSTM), Image Classification(ResNet, Inception), Recommendation System

Machine Learning: Skilled in Supervised Learning (tree models such as GBDT, Logistic Regression), Unsupervised Learning (Clustering, Association Analysis), Semi-Supervised Learning, Contrastive Learning, probabilistic graphical model; expert on data analysis tools(Scikit-Learn, Pandas, Numpy, Sympy, Matplotlib, scipy)

Data structures and algorithms, computational complexity

Developing

Programming Language: Expert in Python, familiar with C/C++, bash, MATLAB, Makefile

Linux: use ArchLinux/Ubuntu since 2016, managed 7 deep learning servers, own a VPS

Software Engineering (git, CI, Dockerfile, pretty code style), Network (HTTP, OSI)

Full-Stack Website Developing: HTML, Javascript, Flask, MongoDB, Nginx, Apache2

Web Crawler: requests, bs4, selenium, developed Sukiya questionnaire auto filler

Major and Profession

Operations Research: MILP (ortools), Heuristic Optimization (GA/SA/CEM), Convex Optimization (cvxpy)

Control Theory: Optimal Control, Linear Systems, System Identification

Traffic Control and AutoPilot: Traffic Wave Theory, Webster Model, Green Wave Model, Path Planning

Working Experience

COMPANY POSITION TIME

Intelligent Transportation System (ITS): Research on Applying Reinforcement Learning (RL) to Real World Production

S: Intelligent control of traffic light signal timing could enhance traffic efficiency greatly. Current state-of-the-art AI traffic control algorithms are mainly based on strong assumptions, which makes it hard to apply to real-world products.

T: 1. Bring RL to real-world production, and make it outperform traditional algorithms; 2. Enhance RL training with expert experience; 3. Research on RL generalization among several traffic scenarios.

A: 1. Design and develop a real-world compatible RL environment; 2. Implement traditional algorithms as baseline; 3. Fine-tune RL algorithm and environment; 4. Introduce information entropy to solve the problem of data imbalance, making RL agents learn expert experience and highly accelerating RL training. 5. Design an RL model which generalizes to any shape of traffic intersections.

R: Published: PRIVATE; another paper about RL generalization is under review.

Vehicle to Everything (V2X): Cooperative Adaptive Cruise Control (CACC)

S: CACC is a crucial application in autopilot from levels of L2 to L4. For example, there is a GLOSA defined in project C-ROADS (an EU project).

T: Research and develop a CACC system to reduce energy consumption and improve driving comfort level.

A: 1. Implemented energy consumption models (HBEFA, SUMO/Energy, VT-CPFM)；2. Recurrent GLOSA and Eco-CACC; 3. Designed and developed a V2X-CACC by using optimal control to perform vehicle longitudinal trajectory planning with consideration of vehicle to vehicle (V2V) and vehicle to infrastructure (V2I) interaction.

R: For single CAV, energy consumption is reduced by 6% on the cost of a 1% travel time increment; while on 10% CAV penetration, the energy consumption is reduced by 9%.

ITS: Urban Traffic Signal Control System

S: Urban traffic signal control system adjusts traffic light timing to ensure vehicles and pedestrians move safely and efficiently.

T: Design and develop large-scale traffic signal control algorithm modules, and utilize machine learning algorithoms to improve efficiency and reduce congestion among several scenarios.

A: 1. Scheme selection: Use clustering algorithm (DBSCAN & KMeans) to group traffic data with different characteristics, and use KNN to learn the mappings from traffic characteristics to signal plans; 2. Time-of-day division: divide the time of a day by sequence clustering algorithms (spectral clustering & Fisher); 3. Traffic Optimization: based on the traffic flow model, solve single intersection and arterial by Mixed-integer Linear Programming (MILP) modeling, solve congestion optimization by heuristic optimization modeling; 4. Reliability: embed traffic simulation tools (including SUMO) into the evaluation module with a fallback logic to shield poor signal plans.

R: Published 3 SDKs including PRIVATE, and evaluated in PRIVATE that the traffic control system outperforms human experts.

Obtained patents:

PRIVATE

PRIVATE

PRIVATE

Campus Projects

Reinforcement Learning-based Reactive Force field OptimizationTIME

S: Reactive force field (ReaxFF) is a function with a large number of parameters, which usually takes several months to optimize when using traditional genetic algorithms.

T: Explore a heuristic optimization method based on RL, to speed up the optimization process of the reaction field parameters of similar materials.

A: Based on the assumption of similar materials having similar ReaxFF, design and implement the RL environment, perform feature engineering and implement different optimization methods (including RL and traditional genetic algorithms).

R: 1. In terms of the optimization performance of similar materials, it has comparable results with the professional optimization software GARFField, greatly surpassing the conjugate gradient method and simulated annealing algorithm; 2. Obtain software copyright.

Internship

Data Mining-based Insurance Underwriting SystemCOMPANY TIME

S: PRIVATE project with IBM, Baidu, etc to develop an automatic underwriting system to reduce human labor.

T: Deeply understand customers' needs while communicating with underwriting experts, design and develop an automatic underwriting system, to win the bid.

A: Perform feature engineering, design evaluation criterion, train and fine-tune Logistic Regression, Random Forest, LightGBM classification models; analyze bad cases and improve features; Encapsule the ML model into an SDK with C++.

R: 1. Fulfilled PoC and won the bid; 2. Achieve 15% human labor reduction and 95% preception on the 530k training dataset.

PRIVATE

Amateur Projects

Web Crawler and Website: ShadowSocksShareTIME

S: Shadowsocks is a proxy software and was popular in China. Before the project was established, there were a lot of free accounts shared over the Internet but short of subscription services. It's not convenient for users without a subscription service.

A: Learn Crawler, HTML, JavaScript, filter and parse online contents by regular expression, develop the full website and deploy it on Heroku, OpenShift, and GAE.

R: This project earns 350 sums up to ¥1871.91 donations and open source on GitHub with 3.1k Star and 1.1k Fork.

Project link: ss.pythonic.life

Miscellaneous

Strong: have exercise habits, periodically go hiking or riding

Cooperative: 1. do research with interns on RL with expert experience and generalization; 2. communicate with product manager to decide product design, debug with the engineering team to make sure the urban traffic signal control system works well; 3. cooperate with MEC production line and RoboX to plan product layout together.

Self-Learning and Fast Learner: gain deep mastery in website development, materials science, traffic engineering, and autopilot.

Summarizing online notebook: notes.pythonic.life

Geek Spirit: develop a bunch of scripts: CharlesScripts which earns 981 Star and 731 Fork; generated curriculum vitae by this tool; use Dvorak keyboard layout since 2018.

Personal Blog: the0demiurge.blogspot.com

Google Scholar: PRIVATE

LinkedIn: linkedin.com/in/xuchengcheng

2022.zh

NAME

联系方式

Email | TEL | GitHub (373 followers)
charl3s.xu@gmail.com | TEL | github.com/the0demiurge

REGION

教育经历

时间 | 学校 | 学历 | 专业
TIME | UNIVERSITY | DEGREE | MAJOR
TIME | UNIVERSITY | DEGREE | MAJOR

技能

数学 / 算法

深度学习： 系统性掌握 强化学习，了解检测跟踪、NLP、推荐系统；熟练使用 PyTorch，熟悉深度学习优化算法原理

机器学习： 掌握或了解监督（树模型、Logistic 回归）、无监督（聚类、关联分析）、半监督、概率图模型、对比学习；熟练使用数据分析工具（pandas, numpy, matplotlib）

数据结构与算法，复杂性理论，A-star 等

专业技能

运筹优化： MILP (ortools)，启发式优化 (GA/SA/CEM)，凸优化 (cvxpy)

控制论：线性系统，系统辨识，最优控制

工程技能

编程语言： 精通Python，熟悉C/C++、bash、MATLAB、Makefile

Linux：从2016年至今使用ArchLinux/Ubuntu作为主力系统，管理过7台深度学习服务器，拥有一台VPS

软件工程(git, CI, Dockerfile, 代码规范性)，计算机网络(http, OSI), 数据中间件(Kafka)

工作经历

COMPANY POSITION TIME

基于深度强化学习的交通控制算法研究与落地

S: 目前学术界基于 AI 的信号灯控制算法大多基于强假设，没有考虑到感知范围、感知精度、感知延迟等等问题，难以落地。研究使用强化学习落地信号灯控制的可能性，在现实条件多种场景超越传统方法。

A: 1. 设计和开发与现实一致的 RL 环境 (state, action, reward)；2. 实现传统控制算法作为 专家经验；3. 实现并优化多种 RL 算法（Rainbow，SAC，PPO），使得 RL 的性能超越传统方法；4. 使用信息熵理论解决专家经验中样本分布不平衡问题，使 RL 成功模仿专家经验预训练，显著提升训练效率；5. 设计并实现可泛化的 RL 模型，解决 SOTA 基于 RL 的交通控制模型无法跨场景泛化的问题。

R: 已发表:ITSC 2021: Adaptive Coordinated Traffic Control for Arterial Intersections based on Reinforcement Learning; 另有提升泛化性的论文已投，在审稿中。

基于最优控制（运筹优化）的车路协同-协作式车速引导

S: 协作式车速引导是自动驾驶 L2 到 L4 车路协同中的一项重要应用。

A: 1. 实现多种车辆能耗模型作为优化目标函数和评价指标；2. 复现 SOTA 车速优化算法，进行纵向车辆轨迹规划；3. 设计基于最优控制的控制策略，使用序列二次规划(SQP)和启发式算法求解，综合考虑多方博弈从而达到整体最优。

R: 单车能耗降低 12%；渗透率 10% 时，自动驾驶车能耗降低 9%。

基于机器学习与运筹优化的大规模智慧交通控制系统

S: 在大数据时代，多交通信号灯的智能控制可以大幅提升交通运行效率，减少拥堵。

A: 1. 使用聚类算法(K-Means, DBSCAN)对交通流数据自动聚类并打标，作为数据集训练有监督模型(KNN)；2. 使用有序聚类(谱聚类，Fisher)对交通数据自动时段划分；3. 使用混合整数规划(MILP)、启发式算法对大规模交通控制问题分层次建模求解；4. 设计并实现整体算法工程化架构。

R: 集成到 PRIVATE 产品线中，在PRIVATE实地评测中达到显著优于人类专家手动调整的结果。

发表专利PRIVATE。

学校项目

基于强化学习的智能参数优化算法研究TIME

S: 材料学的反应力场求解是一个很困难的参数优化问题，传统方法常用遗传算法求解，耗时很长。

A: 提出相似材料具有相似优化目标函数假说，设计并实现 RL 环境，包括多种特征与优化模式，实现并改进多种RL算法(DQN,A3C,DDPG)和多种传统最优化算法(共轭梯度法，模拟退火等)，设计实验并证明了 RL 做参数优化的有效性。

R: 1. 在相似材料的优化性能上取得了与专业反应力场优化软件GarfField相当的水平，大幅超越共轭梯度法、模拟退火算法；2. 取得软件著作权。

实习经历

基于机器学习的自动核保系统COMPANY TIME

S: PRIVATE 金融大数据项目中与百度、IBM 等公司竞标，使用人工核保数据构建可靠的自动核保系统，替代人工工作量。

A: 深入理解客户需求，设计特征、评测指标、模型结构，使用Logistic Regression, Random Forest、LightGBM等模型训练、微调；bad case 分析并改进特征；用 C++ 将模型封装为 sdk。

R: 1. 完成 PoC，赢得竞标；2. 完成项目一期，在 53 万条数据集上以 95% 精度减少 15% 人工工作量。

PRIVATE

业余项目

量化交易回测 demoTIME

S: 存在验证自用基金交易策略在实际市场中的有效性的需求。

A: 使用 yfinance 获取NASDAQ、贵州茅台上市以来日交易数据，使用 python 编写简易交易成本计算逻辑、自用交易策略（累计上涨5%则买入，下跌5%则卖出），控仓策略（触发买入或卖出后每日交易K%仓位），并与原始股价对比。

R: 本策略与买入后持续持仓相比，收益率略低，但显著降低了方差，验证了该方法在实践中的可行性。

爬虫网站项目：ShadowSocksShareTIME

S: 项目创建时互联网上还没有免费的 ss 订阅服务，需要手动从免费分享站中查找 ss 信息并填写到软件中，复杂度较高。

A: 自学爬虫、HTML、Javascript 等，使用正则表达式等方法筛选并解析在线网站内容，并编写网站前后端，部署到 Heroku、OpenShift、GAE 等平台。

R: 本项目获得 350 次共计 ¥1871.91 捐款，并在 GitHub 开源获得 3.1k Star 与 1.1k Fork。

项目链接：ss.pythonic.life

自我评价

身体强壮，有锻炼习惯，有条件时常组织远足和骑行。

合作能力： 1. 与实习生合作完成RL利用专家经验和泛化性研究；2. 信号控制项目与产品侧沟通产品设计，与工程侧联调，确保整个项目流程走通；3. 车路协同项目协调上下游关系，打通端到端整体链路。

学习能力： 自学网站建设、材料学、交通工程理论、自动驾驶等，快速上手并逐渐深入掌握多种领域

善于总结，在线笔记: notes.pythonic.life。

Geek 精神，使用编程改善生活：CharlesScripts, 本项目获得 981 Star 与 731 Fork; 包括本简历也是使用这个工具生成。

个人博客: the0demiurge.blogspot.com

Google Scholar: PRIVATE

Interview

STAR 回答方向

Situation:情境

Task:任务

Action:行为/想法

Result:结果

优点

学习技术快：用一下午的时间学习学爬虫爬 ss 帐号，又用另一个下午学 Flask 并做了一个网站（已经上线一年，通过捐款收入八百多块）；用半天从入门到上手 PyTorch 并用三天写成 A3C（当然还是靠大量学习参考周莫烦的开源代码）；用一个星期写了一个做爬虫的微信公众号，学习了不少加密相关的知识，理解了微信公众号的运行原理

点子多：项目中想到的改进

思维能力强：交流中能体现出来思维比较清晰，以问题为导向；透过现象看本质，思考过拉格朗日乘子法的本质，Q-Learning 为什么有效，DQN靠什么泛化，以及为什么 DQN 收敛那么慢；喜欢结构化清晰的事物，比如我的笔记结构化非常强

好奇心强：喜欢通过现象看本质，如果想不出来原理就会深入研究，比如阅读 Python 源码看如何实现的无穷递归列表会显示为省略号，并把它用到了 DataStructureRepr 项目中；非常喜欢并关注新东西，热爱体验不同的生活，关注各种各样领域的话题，比如我对金属热处理、材料莫氏硬度霍氏硬度，心理学实验难以复现难题、光环效应、一致性效应、幸存者偏差，经济学原理（通货膨胀与失业率的两难选择）、科研中样本的观察者偏差等都略识一二，并且认为我人生的意义就在于发现、体验一切新的、未知的东西，也是我的乐趣所在

有热情&有想法：主动性强，比如对股票有兴趣想了一些做量化交易的想法，虽然由于时间有限没来得及实现

热爱挑战：喜欢挑战自己，改变旧习惯，面对不熟悉的东西可以快速上手，比如 Linux、双拼、Dvorak

认真：比如本面试就准备地比较认真，平时学习生活中也多加总结，在笔记中写下了这些要点

独立解决问题：我做过的项目除了在商汤实习和大数据比赛是团队合作，其他项目基本从构思到实现都是自己做的，克服了千难万险

Leadership：带新人上手实验室服务器运维、做微信公众号；硕士当班长组织班级活动，服务同学，自认为班里的同学对我印象很不错；当过两年军训教官，而且也很成功，两年我们连都是内务和队列双第一（也有很大的运气成分。。。）。

性格好：乐观，抗压，人际关系比较随意，非常不客气，和任何人都是平等的心态，批评不会影响心情而是从批评中找到对我指出的不足之处并想办法改进。坏处是可能有时不小心忽视别人的感受

身体强壮：喜欢骑行、远足、长跑，在实验室常常组织骑行，每次四十多公里

缺点

浮躁：不太容易静下心看大部头的书，看过的论文和书主要都是在火车上看的，现在正在努力改正（已改正）

英文写作非常欠缺（措辞积累不足导致写英文会花很久，这个听建飞的建议是先写再改，不要一次写完，不然永远写不完）

个别领域比较难退出舒适区：英文写作（等到需要写论文的时候就有机会改正了）

记忆力不够好：看公式如果符号太多完全记不住谁是谁，但是对于规律性极强的东西不需要调动记忆力的话却能记得很牢固，比如高中和大学物理完全记不住公式，要用到的时候基本全靠现场推导

字写得很差，写的字估计只有自己能认识

硕士生感觉数学学得都不错，可惜考试都没考好，硕士 GPA 不高

遇到的困难

Linux CSI tools，挑战+学习能力强+独立解决问题的能力：第一次面对不熟悉的 Linux 和 C++ 编译、编译内核等，独立把问题解决好

学习知识踩到的坑：不能过于依赖中文博客，比如学习强化学习看了一个星期的各种博客，最后发现一篇外文博客学 Q-Learning，一篇论文学 DQN 顶一个星期看博客

分析过最复杂的问题：LeetCode 179 Largest Number，设计了各种各样的规则，花了一整晚，最终看结果发现推荐方法非常简单

跨领域的时候知识欠缺，这个时候就需要快速学习能力来解决

时间紧任务重的时候，如何舍弃非重要功能（二八原理，百分之八十的功能用处都不大）

可问的问题

本次面试相关

本次面试我有哪些不足，有哪些可以改进的地方

面试什么时候会有结果

岗位相关

业务：如果我能加入，将会做什么样的工作

hc数量（竞争压力）

组内情况

技术：组内使用的技术深度，如果我来能学到什么样的技术

流程规范化：内部软件工程做的怎么样，code review，代码静态检查工具等

加班情况（加班时间，强制性，加班的时候做什么）

企业相关

内部培训制度

大公司相比创业公司能学到哪些学不到的东西

2018ST实习收获

实习收获

认真：

上线的东西，和给客户的东西，一点都不能错；表格等样式不能像给自己看的一样随意，要精益求精，不能那么随便。

交流的时候随时准备好纸笔，没有人会把话跟你说第二遍。

关系：从所有细节都要维护好与客户和合作方的关系：

给别人的东西一定要检查保证绝对无误，要让对方充分信任我们。

不要当面指出对方的错误，伤害别人的面子。

在客户场地驻场的时候不要来太晚，否则太难看。

编码：写代码之前先把要做的事情理清楚和讨论清楚；如果事情比较复杂很容易弄错（比如涉及到混淆矩阵或一些地方在讨论的时候不容易理解的时候）进行结对编程。

结果分析：上线测试结果不够好，分析原因。

数据分布发生了变化，使用t-test, u-test, kl-divergence；连续值算mean, var, median，离散值计数（比例）。

将数据归一化之后计算欧式距离，计算同类样本之间的距离中位数（即半径）。模型通常会把相似的样本归为一类，所以看看在半径内与分错样本（分别看FP与FN）最接近的N个样本的业务意义，是不是漏掉了什么特征。

看模型以极高置信度判断错误的样本的业务意义，判断原因。

测试：

上线之前固定随机数种子。

每个中间结果都要导出表格，所有的数据必须保留一个唯一识别号，确保每个结果都能反查回原始数据，每条数据都能一一对应上。特征工程中删除的每条数据的删除原因都要能找到，每个理由删掉了多少数据都要统计出来。

预处理结果、最终输出结果要与SDK、客户输出的结果要一一对应，保证完全一致。

结果展现：弄清楚客户关心什么，设计最合理的评价指标，不能一味地使用AUC，而是深入理解业务流程，提出最适于业务流程的评价指标（Precision与STCurve）

交接：走之前要列一个接手清单，交接人和接手人一人列一个清单，最后取并集。如果列出来了交接人没给就是交接人的责任；没有列出来就是接手人的责任。

2020面试总结

总结

研究好JD，投其所好。

多参加面试，总结经验，优化话术。

自我介绍采取总分结构，先说一下有哪些项目，然后根据面试官兴趣详细介绍。

面试之前半小时排练自我介绍和项目介绍。

多总结项目中的闪光点，时常更新简历，强调自己在项目中解决的难点。

准备好自己的非技术实力相关点以及例子，使用例子证明自己有相关非技术实力。

远程面试与现场面试

远程面试的优势是，可以很方便地参考各种资料，所以可以在简历上标注好关键点以及项目介绍的大纲，以及面试容易犯的错误，面试时照着简历来进行介绍，逻辑性会更好。另外，面试官在问到项目细节但是忘记了的话，可以现场查一下。当然，如果愿意的话，提前写好演讲稿或者打小抄也都是能够做到的。

现场面试的优势是，能够充分发挥自己的人格魅力和自信心，通过行为和谈吐给人留下非常好的印象。与面试官亲切大方地沟通、开始和结束时的握手，以及礼貌和微笑，都比远程面试更加容易营造出优势。我在面试时的感受就像与朋友聊天，甚至有时正事聊完了还能聊聊生活上的话题，能营造出良好氛围的能力对团队合作非常有用，是一种通过侧面展现自己实力的方法。

简历书写

按照 STAR 法则介绍项目

着重介绍在项目中我做了什么

Skills 部分按类总结好

最后一部分可加入非技术实力以及作品链接

感悟

前一位面试官问的问题常常后一位面试官也可能问；不同企业问的问题会有不少重合。

只要项目介绍得足够好足够详细，面试官就没有时间也不太想问基础问题。在面试中掌握主动权就可以让话题一直处于自己擅长的领域。

要话唠，问我的问题我可能不会，但我可以顺便把我了解到的相关的知识讲讲；即便问的问题不会，也能告诉对方我思考应该如何如何会不错，可以体现思维能力。

提问问题的时候也能从侧面体现出自己的知识面、性格。对软件工程实施的规范化感兴趣说明自己也重视规范性；想了解组内技术深度说明自己对技术兴趣浓厚；询问的时候体现出自己调查的结果，说明做事认真，前期准备工作量充足。

应聘的本质就是销售，简历是广告，面试是话术。

面试习惯

面试结束后询问面试官有哪些不足，可以有哪些改进。多总结失败经验。

面试后做好记录：没回答好的问题，可以进行的改进。

提前想好需要询问面试官的问题，调查清楚自己将会加入的小组情况。

面试不足

百度

一面（面试本身很好，未通过）

面试官所关注的问题：

特征工程的各种方法：编码、Embedding、高基数特征处理、特征选择、特征融合。

工程经验：LightGBM有哪些参数，调参技巧。

算法理解（算法原理）。

技术（编程语言等）

失败原因：缺乏分布式计算经验，应当用用 Hadoop，Spark，积累一下相关项目经验。

一面（通过）

项目介绍说得太简略，应当自己掌握主动权向面试官来介绍，而不该只进行简略介绍后让面试官来询问。

实际操作：我先简要说一下有哪些项目，如果面试官对哪个项目感兴趣会打断简介开始详细提问，之后恢复中断继续进行简要介绍。如果简要介绍过程没有发生中断，可以询问面试官“那么我就按照刚才介绍的顺序详细介绍一下这些项目如何”。

字节跳动

相比其他企业，字节面试时间比较紧，所以一般只能详细介绍一个项目，剩下的时间用来做编程题和问基础知识。

一面（未通过）

多了解一下广告业务（不该因为朋友讲不怎么问推荐系统就不看了，应当和其他企业一样好好研究JD）。

多了解一下推荐系统相关的知识和模型，比如FM，deep FM等。面试官推荐了一本书《深度学习推荐系统》，王喆。

应当先把思路想清楚再讲，不要意识流。面试官可以等我想明白，但不愿意看到混乱的思路。

二面（另一个部门，通过）

写代码时先想好思路，并且沟通好打算如何编写之后再进行实际编写；编写过程中时刻保持沟通，向面试官说明自己打算干啥。简单来讲就是类似于结对编程。

可以为每个项目准备不同时长的介绍，面试过程中根据需求选择如何介绍。

了解清楚物理背景，ReaxFF 里面参数的物理意义忘记了。

三面（通过）

多了解一下做过的项目的整个流程，了解实习项目为什么成功中标（寻找归因）。使用 Team Leader 的角度看问题，做项目的时候应当多多了解业务相关知识。

字节我所面试的这个组非常偏向业务，所以三面面试官作为 Team Leader 非常关注业务相关的能力。考虑到未来我有可能同样作为 Team Leader 负责一项业务，建议我能多思考一下整个项目的前因后果，整个流程。

高频面试题/知识漏洞

高频问题

几乎逢考必问 Logistic 回归

损失函数

为什么效果不好

如何防止过拟合，为什么会发生过拟合

过拟合是因为模型容量远超过样本容量，因此对噪声进行了拟合。根据PRML(chapter 1.1 P8)，考虑使用多项式函数进行回归，多项式次数越高则容量越大，对噪声拟合则导致高次项参数非常大。原因是噪声通常对数据的影响是毛刺状大幅震荡，对于傅立叶变换之后高频部分影响较大，或者泰勒分解后会出现很大的高次项。从泰勒分解来考虑，过拟合使得模型非常不稳定，y相对x不再是平稳变化而是大幅震荡。

解决方案：

正则项：L1、L2等(chapter 1.1 P10)，其中L1可以产生稀疏解

数据集增强：例如将图像旋转变化等

加入噪声：可以将噪声加入数据或参数中

Early Stoping：机器学习和深度学习中都会用到

Dropout：深度学习中用的比较多

Bagging等集成

基尼不纯度：从数据集中任取两个数据，不同的概率，1−∑pk21 - \sum{p_k^2}1−∑pk2

交叉熵：用p真实分布衡量非真实q分布平均编码长度，−∫p⋅log(q)-\int{p \cdot log(q)}−∫p⋅log(q)

Kl距离：p*log(p/q)−∫p⋅log(qp)-\int{p \cdot log(\frac{q}{p})}−∫p⋅log(pq)

SoulAPP

一面（未通过）

如何解决梯度爆炸或消失

预训练加微调

梯度剪切、权重正则（针对梯度爆炸）

使用不同的激活函数

dropout

batchnorm

残差结构

LSTM网络

不均衡样本

重新采样，比如smote过采样算法（选择每个正样本的k近邻，在该样本与邻样本连接线上采样）

再缩放：调整分类边界（西瓜书chapter3.6 P66）

集成学习（其实就是欠采样）

ROC

横坐标：FPR=右上/右

纵坐标：TPR=左上/左

第四范式

一面（未通过）

不同强化学习算法结构是什么样的

A3C的价值网络记错了

On-Policy 与 Off-Policy 原理

时间差分法，比如Q Learning是 off policy 的，之前的数据也可以使用，所以可以使用 Replay Buffer；Policy Gradient 是 on policy 的，策略改变后之前的数据就没用了，所以不能使用 Replay Buffer。

SenseTime

二面（通过）

git 如果想要放弃之前的 n 个提交，只要某次提交，如何操作

git cherry-pick

强化学习有哪些改进

Advantage 改进：Generalized Advancade Estimation

Replay Buffer 改进：Prioritized Experience Replay

深度学习优化算法的公式

RMSProp 本质上是将椭圆形等高线转换成圆形等高线，避免振铃效应（怎么有点像共轭梯度法）

我认为可以直接恒定优化步长，梯度只提供方向信息

询问面试官的问题

通用问题

本次面试相关

本次面试我有哪些不足，有哪些可以改进的地方

面试什么时候会有结果

岗位相关

业务：如果我能加入，将会做什么样的工作

我希望能够做富有挑战性的工作，请问能否详细讲讲这份工作里面将会存在的挑战

hc数量（竞争压力）

组内情况

技术：组内使用的技术深度，如果我来能学到什么样的技术

流程规范化：内部软件工程做的怎么样，code review，代码静态检查工具等

加班情况（加班时间，强制性，加班的时候做什么）

企业相关

内部培训制度

大公司相比创业公司能学到哪些学不到的东西

字节跳动

三面

组相关问题

我来的话将会做什么

加班情况（加班时间，强制性，加班的时候做什么），作息要求严格吗，比如能否早上八九点来，晚上八九点走

技术成长

这个组在做项目中有做过哪些改进（能否举个例子，比如有的组会做业务的过程中额外思考word2vec有哪些改进等等）

是否总出差驻场（技术增进少，全是横向）

也想了解一下同学眼中的贵部门情况，所以想问一下如何能定位到贵部门，部门编号之类的

Inwerviewer

面试总结

编程随想：招聘系列

苏格拉底式面试法，刨根问题一直问到本源。

招聘的误区

俺的招聘经验

面试 vs 笔试

考察技术 vs 考察非技术：综合素质：智力因素（思维，沟通，创造）、非智力因素（性格、意志、兴趣）

开放性问题 vs 封闭性问题：实践型问题，理解型问题（WHY），个性化问题，主观性问题（THINK）

通过笔试答题能看出啥？：思路，规范性（排版，命名，注释），细心（审题），严密（边界条件），条理

面试前的准备工作

面谈的技巧

(未完待续)

问题总结

常规问题：

能来这干多久

每周来几天

什么时候能来

base 哪里

强化学习

算法理解

用过什么算法，为什么选择该算法，算法细节，输入输出，网络设计

On-policy, off-policy, 优劣

AC, Value-based, PG, 优劣

Model-based, model-free

PG 方差与 baseline

tricks, tricks 有效的原因

nstep dqn 为什么可以 work，理论上和公式不相符 (by gxy)

为什么强化学习中 batchnorm 和 dropout 很少见 (by gxy)

收敛性的理论分析，各种因素对收敛性的影响，reward scale / 正负对算法的理论影响 (merged from gxy)

gae 与 td lambda 的关系 为什么用 gae (by gxy)

Over-estimated Q values 问题、原因和缓解方法

POMDP 有哪些类型和常见解决方法

RL 有哪些弱点和局限

工程经验

状态 / 动作 / 回报怎么设计

如何判断训练是否正常 (by gxy)

使用什么指标评估效果，有 baseline 算法吗

样本不平衡（大部分动作都不好），reward 稀疏

过拟合到环境怎么办

调过哪些参数，哪些参数分别有什么影响

不收敛的原因有哪些，如何解决

运筹优化

凸优化判别

线性规划是否凸

非线性问题如何转换为线性

单纯型法 / 分支定界法时间复杂度

非线性规划求解方法

收敛速度衡量标准 / 收敛阶定义

代理模型原理

随机优化 / 鲁棒优化

Linux

权限，用户 (chmod, chown), 比如 644 的权限，文件夹的 x 权限

文本处理：grep, sed, awk, wc, uniq, history

文件处理：find, du, df, lsblk

进程：ps, top, fg, bg

外置存储：mount, umount

环境变量，写到各种地方的区别和原理

系统管理组件：systemctl, rc, init, crontab

启动项：grub/systemd-boot

Bash 里面 字符串单双引号区别

操作系统

进程 / 线程区别

进程 / 线程通信

python 进程 / 线程（GIL）

linux 有哪些进程启动方式

堆区和栈区

软件工程

git, git workflow

如何设计整个代码架构（比如边写边改边抽象；提前设计好接口 - 自顶向下法或自底向上法等）

设计模式

代码量，文件量，分模块，各个模块功能

代码规范化

单元测试

开源协议知道哪些

python/shell

python 优缺点

GIL

mutable

==, is 区别；运算符重载

上下文管理器：with 的实现；装饰器的功能和实现，用装饰器实现上下文管理器的库 contextlib

functools, logging, collections

yield, nonlocal, break

re

sort 函数用的什么算法

Asyncio

一切都是对象的理解，a+b 运算在 cpython 的实现中做了哪些操作

inspect, dir

垃圾回收

C++ 开发

开发规范：头文件写接口，cc 文件写实现

用什么编译器编译命令

STL，sort 函数使用的什么算法

Autopointer

segfault 原因，如何 debug

内存 / 资源泄漏的原因，实现一个内存泄漏代码

安全（内存 / 线程，以及不安全的函数 strcpy，C++ 容易写出哪些安全问题）

makefile

数据结构与算法

打印螺旋下三角

多路归并

股票交易问题（每日股价已知，规定买入卖出次数，最大化收益）

递归（树，斐波那契数列）

排序，稳定性，时空复杂度，复杂度推导

链表（反转，找环，Y 形链表）

动态规划（求一下最长公共字符串），回溯（八皇后）

qsort/mergesort/bubblesort/二分查找/ 找中位数 /topK/ 第 K 大数

质数筛法

DFS, BFS

把字典中内容序列化为 list

矩阵运算（行列式，正定，逆，转置，特征值，解方程等等）

数学运算（求反函数，CDF 和 PDF 转换）

数学

硬币问题 - 几何分布（投掷不均匀硬币，设计一种公平试验让两个人胜率相等。平均多少次能结束该试验）

1/k 概率的试验运行 k 次，求中奖率。如果 k 趋于无穷，中奖率多少

机器人在数轴上，每次左或游运动 1，向左概率 p，问平均多少步到位置 k

假设检验：独立性检验、如何检验机器学习算法相比另一个更优、未知分布的随机变量分布辨识

非技术类：

（解决问题能力）遇到过过哪些很难的问题，如何解决

（学习能力）如何学习一个从没接触过的学科或者编程语言，有过哪些快速学习的经历

（学习能力）读过哪些强化学习方面资料 / 书籍 / 论文，资料中有哪些令人印象深刻的内容

（兴趣）生活中用编程做了哪些有趣的事情

（团队协作）在团队中怎么分工和合作

有哪些想问我的问题

ManagementByObject

目标管理

目标管理的8个问题和7个步骤[4]

一个优秀的目标管理体系要解决好以下8个问题：

目标是什么？实现目标的中心问题、项目名称。

达到什么程度？达到的质、量、状态。

谁来完成目标？负责人与参与人。

何时完成目标？期限、预定计划表、日程表。

怎么办？应采取的措施、手段、方法。

如何保证？应给予的资源配备和授权。

是否达成了既定目标？对成果的检查、评价。

如何对待完成情况？与奖惩安排的挂钩、进入下一轮目标管理循环。

制定目标的七个步骤：

理解公司的整体目标是什么。

制定符合SMART原则的目标。

检验目标是否与上司目标一致。前三步，大部分中层管理者都知道，但往往是到这一步就算完事了，岂不知，问题才刚刚开始。

确认可能碰到的问题，以及完成目标所需的资源。

列出实现目标所需的技能和授权。

制定目标的时候，一定要和相关部门提前沟通。

防止目标滞留在中层不往下分解。

有很多企业找我导入这个目标管理，但我发现之前搞过类似的培训、导入，但是效果不很理想——为什么呢？我在前期调研时发现——企业没有培养一种上下关注目标的氛围，使公司的各个层面都关心如何设定目标、达成目标！如果企业仅仅是在口头上、形式上重视，做起来流于形式，长期下来不但会对企业文化造成侵蚀，而且也有悖目标管理的初衷，其结果只能是事倍功半。请以此为警！

OKR

OKR

OKR 是目标管理工具，不是绩效评估工具。目标应当定得高，指明一个方向，而绩效评估应当符合实际，指明朝着该方向应该走多快。

OKR的10个要点

要点1：绝对诚实

健康的OKR文化的本质是绝对诚实、摒弃个人利益和忠于团队。

要点2：可以衡量

关键结果必须是可以衡量的，最终是可以看到的，而且令人毫无疑问：我做到了，还是没做到？是还是否？必须简单，容易判断。

要点3：人文驱动

彼得·德鲁克构想出一种新的管理理念：具有人文主义的结果驱动型管理。公司应该“建立在对员工信任和尊重的基础上——而不仅仅是作为获得利润的机器”。利用数据和员工间的经常性沟通，来实现公司长期计划与短期计划之间的平衡。

要点4：少即是多

“这些精心选定的目标传递出一个明确的信息，它们告诉我们要做什么和不做什么。”每个周期最多只需制定3到5个OKR，就能够帮助公司、团队和个人明确什么是最重要的。一般而言，每个目标都应该与5个或更少的关键结果相对应。

要点5：自下而上

为了促进员工参与，应该鼓励团队和个人与管理人员进行协商。通过这种方式制定的OKR，应该占到各自OKR的一半左右。如果所有目标都是自上而下制定的，那么员工的工作动机就会受挫。

要点6：共同参与

OKR旨在通过协作确定优先事项，并规定如何衡量进展情况。即使在公司目标已经确定的情况下，关键结果仍然是可以商讨和调整的。集体达成一致，对最大限度实现目标来讲是至关重要的。

要点7：保持灵活

如果大环境发生了变化，既定目标看起来不切实际或难以实现，则可以在执行期间修改甚至放弃某些关键结果。

要点8：敢于失败

格鲁夫写道：“如果每个人都把目标定得比自己轻而易举就能完成的目标高一些，那么结果往往会更好。如果你想要自己和下属都有最佳表现，那么这样的目标制定方式是非常重要的。”某些操作性目标必须要全部实现，但激励性OKR会让人备感压力，甚至让人觉得可能无法实现。格鲁夫将这种目标称为“挑战性目标”，它能将组织推向新的高度。

要点9：合理使用

OKR系统“就好比给你一块秒表，让你随时可以诊断自己的表现。它不是一份基于绩效评估的法律文本”。为了鼓励员工承担风险，防止消极参与，最好将OKR和奖金激励分离开来。

要点10：耐心、坚定

每个过程都需要反复试验。一个组织可能需要4到5个季度才能完全适应这个系统，而构建成熟的目标则往往需要更长的时间。

SMART

SMART 原则

目标必须是具体的（Specific）

目标必须是可以衡量的（Measurable）

目标必须是可以达到的（Attainable）

目标必须和其他目标具有相关性（Relevant）

目标必须具有明确的截止期限（Time-based）

无论是制定团队的工作目标还是员工的绩效目标都必须符合上述原则，五个原则缺一不可。

制定的过程也是自身能力不断增长的过程，经理必须和员工一起在不断制定高绩效目标的过程中共同提高绩效能力。

特别注明：有的又如此解释此原则

S代表具体(Specific)，指绩效考核要切中特定的工作指标，不能笼统；

M代表可度量(Measurable)，指绩效指标是数量化或者行为化的，验证这些绩效指标的数据或者信息是可以获得的；

A代表可实现(Attainable)，指绩效指标在付出努力的情况下可以实现，避免设立过高或过低的目标；

R代表现实性(Realistic)，指绩效指标是实实在在的，可以证明和观察；

T代表有时限(Time bound)，注重完成绩效指标的特定期限[2]

SMART 原则详解

SMART原则一 S（Specific）——明确性

所谓明确就是要用具体的语言清楚地说明要达成的行为标准。明确的目标几乎是所有成功团队的一致特点。很多团队不成功的重要原因之一就因为目标定的模棱两可，或没有将目标有效的传达给相关成员。

示例：目标——“增强客户意识”。这种对目标的描述就很不明确，因为增强客户意识有许多具体做法，如：减少客户投诉，过去客户投诉率是３％，现在把它减低到１．５％或者１％。提升服务的速度，使用规范礼貌的用语，采用规范的服务流程，也是增强客户意识的一个方面。

有这么多增强客户意识的做法，我们所说的“增强客户意识”到底指哪一块？不明确就没有办法评判、衡量。所以建议这样修改，比方说，我们将在月底前把前台收银的速度提升至正常的标准，这个正常的标准可能是两分钟，也可能是一分钟，或分时段来确定标准。

实施要求：目标设置要有项目、衡量标准、达成措施、完成期限以及资源要求，使考核人能够很清晰的看到部门或科室月计划要做哪些那些事情，计划完成到什么样的程度。

SMART原则二 M（Measurable）——衡量性

衡量性就是指目标应该是明确的，而不是模糊的。应该有一组明确的数据，作为衡量是否达成目标的依据。

如果制定的目标没有办法衡量，就无法判断这个目标是否实现。比如领导有一天问“这个目标离实现大概有多远？”团队成员的回答是“我们早实现了”。这就是领导和下属对团队目标所产生的一种分歧。原因就在于没有给他一个定量的可以衡量的分析数据。但并不是所有的目标可以衡量，有时也会有例外，比如说大方向性质的目标就难以衡量。

比方说，“为所有的老员工安排进一步的管理培训”。进一步是一个既不明确也不容易衡量的概念，到底指什么？是不是只要安排了这个培训，不管谁讲，也不管效果好坏都叫“进一步”？

改进一下：准确地说，在什么时间完成对所有老员工关于某个主题的培训，并且在这个课程结束后，学员的评分在８５分以上，低于８５分就认为效果不理想，高于８５分就是所期待的结果。这样目标变得可以衡量。

实施要求：目标的衡量标准遵循“能量化的量化，不能量化的质化”。使制定人与考核人有一个统一的、标准的、清晰的可度量的标尺，杜绝在目标设置中使用形容词等概念模糊、无法衡量的描述。对于目标的可衡量性应该首先从数量、质量、成本、时间、上级或客户的满意程度五个方面来进行，如果仍不能进行衡量，其次可考虑将目标细化，细化成分目标后再从以上五个方面衡量，如果仍不能衡量，还可以将完成目标的工作进行流程化，通过流程化使目标可衡量。

SMART原则三 A（Attainable）——可实现性

目标是要可以让执行人实现、达到的，如果上司利用一些行政手段，利用权利性的影响力一厢情愿地把自己所制定的目标强压给下属，下属典型的反映是一种心理和行为上的抗拒：我可以接受，但是否完成这个目标，有没有最终的把握，这个可不好说。一旦有一天这个目标真完成不了的时候，下属有一百个理由可以推卸责任：你看我早就说了，这个目标肯定完成不了，但你坚持要压给我。

“控制式”的领导喜欢自己定目标，然后交给下属去完成，他们不在乎下属的意见和反映，这种做法越来越没有市场。今天员工的知识层次、学历、自己本身的素质，以及他们主张的个性张扬的程度都远远超出从前。因此，领导者应该更多的吸纳下属来参与目标制定的过程，即便是团队整体的目标。

定目标成长，就先不要想达成的困难，不然热情还没点燃就先被畏惧给打消念头了。

实施要求：目标设置要坚持员工参与、上下左右沟通，使拟定的工作目标在组织及个人之间达成一致。既要使工作内容饱满，也要具有可达性。可以制定出跳起来“摘桃”的目标，不能制定出跳起来“摘星星”的目标。

SMART原则四 R（Relevant）——相关性

目标的相关性是指实现此目标与其他目标的关联情况。如果实现了这个目标，但对其他的目标完全不相关，或者相关度很低，那这个目标即使被达到了，意义也不是很大。

因为毕竟工作目标的设定，是要和岗位职责相关联的，不能跑题。比如一个前台，你让她学点英语以便接电话的时候用得上，这时候提升英语水平和前台接电话的服务质量有关联，即学英语这一目标与提高前台工作水准这一目标直接相关。若你让她去学习6sigma，就比较跑题了，因为前台学习6sigma这一目标与提高前台工作水准这一目标相关度很低。

SMART原则五 T（Time-based）——时限性

目标特性的时限性就是指目标是有时间限制的。例如，我将在2005年5月31日之前完成某事。5月31日就是一个确定的时间限制。没有时间限制的目标没有办法考核，或带来考核的不公。上下级之间对目标轻重缓急的认识程度不同，上司着急，但下面不知道。到头来上司可以暴跳如雷，而下属觉得委屈。这种没有明确的时间限定的方式也会带来考核的不公正，伤害工作关系，伤害下属的工作热情。

实施要求：目标设置要具有时间限制，根据工作任务的权重、事情的轻重缓急，拟定出完成目标项目的时间要求，定期检查项目的完成进度，及时掌握项目进展的变化情况，以方便对下属进行及时的工作指导，以及根据工作计划的异常情况变化及时地调整工作计划。

总之，无论是制定团队的工作目标，还是员工的绩效目标，都必须符合上述原则，五个原则缺一不可。 制定的过程也是对部门或科室先期的工作掌控能力提升的过程，完成计划的过程也就是对自己现代化管理能力历练和实践的过程。

Self-Control

自我控制

自我控制简而言之分为两部分：

对自我状态的观测，形成闭环系统

对其他个体的观测以及信息传递，协调控制

自我控制的内容

1、自我检查和分析

只有 在目标实施中经常进行自我检查和分析，才能及时掌握目标的状态，发现实施中的问题，并分析产生问题的原因，便于采取措施解决。 自我检查和分析的内容，包括目标实施的进度、质量和实施中的协作情况。

首先是进度，看是否按原定进度进行。控制好目标实施的进度，使各分目标在进度上大体相适应，才能保证目标均衡的发展；

控制实施质量也是非常重要的，质量不符合要求的目标，是没有实际意义的，也必然影响总目标；

总目标层层分解，分解的越细，越需要协作。

2、 主动纠正偏差

通过自我检查和分析发现了问题，找到了偏差，就要积极的采取措施去纠正偏差，保证自己承担的目标正常的实施，按计划、按要求完成。目标责任者对自己承担的目标要有高度的责任感，对目标实施中产生的偏差和出现的问题，要主动的解决，不能有依靠思想和等待情绪 。凡属于自己职权范围内的，就要敢于负责，大胆的、果断的采取措施；属于需要协作部门配合的，要主动与协作部门联系，协商解决的办法，取得协作部门的支援；属于本级解决不了或超出职权范围的，要主动向上级请示报告，以求得上级的支援和帮助。

3、及时反馈信息

自我控制是目标管理中重要的控制形式，但不是唯一的，上级的指导和控制是必不可少的。为了使上级领导及时了解和掌握目标实施情况，必须建立信息反馈制度，具体规定反馈信息的内容、时间和反馈形式。这里最主要的是建立报告制度，报告可以在目标管理活动各阶段中，提供必要的信息，成为计划、控制的依据，推动目标管理活动的深入展开。

报告的内容要适应控制的需要，通常包括目标实施的进度和质量，遇到的困难和问题，自己采取的措施，需要得到的配合和上级提供的帮助等。报告的形式可以是书面、口头、统计表、图表等，具体形式要与 报告内容相适应。报告制度，作为上级是掌握下级情况的手段，作为下级是对上级的汇报和取得上级支持的途径。所以，上级要统一规定报告制度，下级要主动向上级汇报，用信息反馈沟通上下级的联系。

Principle

经济学原理

经济学十大原理

个人如何制定决策

People face trade-offs：选择是可微分的

The cost of something is what you give up to get it：成本计算应当遍历所有选项

Rational people think at the margin：平衡各种选项时计算的是决策边界上的偏微分

People respond to incentives：不仅要注意直接反应，还要注意间接反应

人们如何相互作用

Trade can make everyone better off：贸易是正和博弈

Markets are usually a good way to organize economic activity：市场具有自组织性，自私的个体能够自发达成整体的正和博弈（看不见的手）

Governments can sometimes improve market outcomes：作为强制力，在特殊情况下作为看门狗处理异常状况，或者处理某些零和甚至负和博弈

经济整体如何运行

A country’s standard of livinig depends on its ability to produce goods and services：生活水平变化归因，整体优化目标

Prices rise when the government prints too much money：货币金融学

Society faces a short-run trade-off between inflation and unemployment

PB_PE

PB/PE

P:Price 总市值

E:Earnings 盈利（净利润）

B:Book Value 净资产

市盈率PE=每股市价每股收益PE=\frac{\text{每股市价}}{\text{每股收益}}PE=每股收益每股市价, PE=总市值/权益净利润

平均市净率PB=股价每股净资产PB=\frac{\text{股价}}{\text{每股净资产}}PB=每股净资产股价, PB=总市值/权益净资产

净资产收益率ROE=报告净利润报告期末净资产ROE=\frac{\text{报告净利润}}{\text{报告期末净资产}}ROE=报告期末净资产报告净利润, ROE=权益净利润/权益净资产

PB=PE*ROE

绝对估值法DCFDCFDCF

高PE、高PB组合只有当一个公司预期将快速增长，并且在增长期间以及在增长发生以后享有异常高的ROE时，公司的PE和PB都是高的；

高PB、低PE组合当一个公司也许在现有的投资上仍享有高的ROE，但不再快速增长时，它将具有高PB、低PE；

高PE、低PB组合当一个复苏的公司预期将从暂时的低盈利水平上增长，但是竞争又阻碍它回复到异常高的ROE时，它将具有高PE和低PB；

低PE、低PB组合当一个公司的盈利预期既没有增长也没有高ROE，它将具有低PE、低PB。

对于第1类组合，对这类公司的投资是个难题，严格的说，这已经超越了经典价值投资的范畴，更多的属于成长股投资的范围。成长股的鼻祖费老最钟情于此类股票。很多人喜欢谈“长期持有”，在哪些情况下应该长期持有呢？严格的说，应该在公司生命周期的成长期、公司业绩的高速成长期、公司竞争优势的确立和巩固期，才可以不考虑暂时高估（当然，过度高估也不行）的影响而长期持有。这类公司如前几年的贵州茅台、苏宁电器是典型的例子。这类公司并非不能做，但前提是你得判断准确。所以这类投资的难度是很大的，最难的就是对公司长期发展前景和竞争优势的判断，一旦判断失误，就会遭到惨重的损失。应该说相当多的人在这类公司的投资上会留下败笔，毕竟有卓越眼光的投资人是少数。

对于第2类组合，目前A股市场这类公司非常多，很多公司PE已经很低了，但PB仍然很高。很多研究员喜欢推介这类公司，因为他们的盈利预测上显示公司的PE已经很低了，但对这类公司的投资常常失败。出现这种“高PB、低PE”的情况，恰恰是公司缺乏竞争优势的表现，也许公司在过去业绩一直不佳，突然来了一个华丽转身；也许公司业绩具有周期性的特点，突遇到行业景气。我个人不赞成以PE估值，理由是EPS往往是一个局部的、零碎的、暂时的、片面的信息，EPS背后的竞争优势和产业结构的变化才是最根本的。很多公司的高EPS、高ROE是暂时的，而低EPS、低ROE是长期性的。这类公司业绩的确定性很低，稍一不慎，低估马上变高估，原来的“安全边际”瞬间荡然无存。

对于第3类组合，投资的难度也是很大的。这类公司属于“困境反转型”，如果公司能真的脱胎换骨当然好，但是得出这类判断需要深厚的积累和对产业结构、产业环境的深刻洞察。大部分投资者是不具备这种能力的。

对于第4类组合，应该是这类组合通常是比较好把握的。按格老经典的价值投资范式，就应该选择“低PE、低PB”的股票，但同时也要注意，EPS可能只是个暂时的、零碎的、片面的信息，它迟早影响到PB，如果公司未来的前景很黯淡，“低PE、低PB”会慢慢变成“高PE、高PB”。当然，有时候出现这种“低PE、低PB”的情况，只是因为公司处于成熟期，成长性有限，这类公司如果分红多，可能也是不错的投资对象。西格尔在《投资者的未来》中总结了几条很有意义的结论：“股价是预期的函数，高增长的公司从长期来看，往往很难达到预期；而低增长的公司往往超预期”、“高成长往往是陷阱，绝不是馅饼”、“股利收益率对于长期投资收益率很关键”、“最有价值投资的公司出在传统的能源、制造等行业，市场对它们预期低，它们往往能超预期”。总之，对这类公司一定要分析属于哪种情形。

基本概念

β\betaβ系数

作用：衡量股票相对于大盘的敏感性。

公式：βa=Cov(ra,rm)σm2\beta_a = \frac{Cov(r_a, r_m)}{\sigma_m^2}βa=σm2Cov(ra,rm)，其中Cov(ra,rm)Cov(r_a,r_m)Cov(ra,rm)是证券 a 的收益与市场收益的协方差；σm2\sigma_m^2σm2是市场收益的方差。

另一个公式：βa=ρam⋅σaσm\beta_a = \rho_{am} \cdot \frac{\sigma_a}{\sigma_m}βa=ρam⋅σmσa，其中ρam\rho_{am}ρam为证券aaa与市场的相关系数；σa\sigma_aσa为证券aaa的标准差；σm\sigma_mσm为市场的标准差。

VaR (Value at Risk, 风险价值模型)

作用：处于正常市场波动中时，在给定置信水平和持有有效期内预期最大损失量。

公式：P(ΔPΔt≤VaR)=aP(\Delta P\Delta t \leq VaR) = aP(ΔPΔt≤VaR)=a

PPP：资产价值损失小于可能损失上限的概率

ΔP\Delta PΔP：某一金融资产在一定持有期Δt的价值损失额

VaRVaRVaR：给定置信水平aaa下的在险价值，即可能的损失上限

aaa：给定的置信水平

1_Traffic_Control

原则

安全保障

权力均衡

效率优先

弱势保护

Objects

饱和度 | 目标
低 | 最小停车次数
中 | 最小车辆延误
高 | 最大通行能力
过 | 防止排队溢出

Terms

流量=一段时间内通过定点的车/人数

流率(q)=单位时间内通过定点的车/人数

需求流率

实际流率

车流平均速率v=车辆样本数×行车距离∑车辆i运行时间v=\frac{\text{车辆样本数}\times \text{行车距离}}{\sum \text{车辆i运行时间}}v=∑车辆i运行时间车辆样本数×行车距离

密度=单位长度车数/单位面积内人数：不常用，常将占有率取代密度

占有率=一定时间内一个小路段被车辆占据的时间百分比

密度=占有率平均车长+侦测区长度\text{密度}=\frac{\text{占有率}}{\text{平均车长}+\text{侦测区长度}}密度=平均车长+侦测区长度占有率

容量=单位时间内经常可通过定点最大流率：常用尖峰15分钟流率

非阻断车流：流率=密度×平均速率\text{流率}=\text{密度}\times \text{平均速率}流率=密度×平均速率

绿信比(split, λ\lambdaλ)=绿灯时长周期\frac{\text{绿灯时长}}{\text{周期}}周期绿灯时长

路口相位差(offset)=某交叉口起始/终止时间 - 参考交叉口起始/终止时间

损失时间(lost time)=不被所有车流利用的时间

绿灯间隔时间(inter-green time, clearance interval)：一个相位绿灯结束到下个冲突相位绿灯开始

饱和流率(saturation flow rate, S)：某一车道组绿灯期间能通过的最大流率

饱和度(x)=实际交通需求通行能力\frac{\text{实际交通需求}}{\text{通行能力}}通行能力实际交通需求

流量比(y)=车流量(q)饱和流量(S)\frac{\text{车流量}(q)}{\text{饱和流量}(S)}饱和流量(S)车流量(q)

信号周期(C)

2_Autopilot

自动驾驶

技术

感知

规划

预测

控制

落地场景

城市道路：复杂度高，但有机会建设V2X示范区

高快速路：复杂度较低，已经有大量落地应用

泊车：目前（2022）重点之一

矿区：需求很大，危险度高，低速封闭非公路

港口，与矿区类似但危险度低

2_0_Planning

路径规划：

搜索(最短路)

采样

直接优化

路径-速度解耦：先规划路径，基于路径规划速度（不完备，可能无解或局优）

样条函数

人工势场（容易局优）

参考资料：

自动中的驾驶决策规划算法综述 - 机器之心

2_1_Search

Dikjstra: 下一个迭代点为最小移动代价点，本质上还是B(readth)FS

Best-First-Search: 下一个迭代点为距离终点最近的点，本质上接近D(epth)FS

A*: 下一个迭代点是移动代价和距离终点距离之和最小的点，相当于同时深度和广度搜索，某种程度上类似于Beam-Search，比较适用于存在空间连续性场景

Hybrid A*: 下个迭代点不能随意选，而是要加上车辆动力学约束

D*: 环境变化情况下，减少重复的搜索过程

2_2_Sampling

概率路线图(PRM)，用于替代栅格化的方法建图

随机采样，采样点与近邻形成连接

启发式剪枝：丢弃碰撞点

用搜索算法搜索轨迹

快速搜索随机树(RRT)，相比前面网状拓展，这个是树状拓展

1-纽约摄影学院三原则

照片的主题是什么？他表达的普遍性意义是什么？

拍摄者如何把注意力集中到被摄主体上？

拍摄者在简化画面方面做了哪些？没做哪些？

Y-Academic

论文的基本要求

内容：一套方法在一个特定场合中的应用。

方法必须有创新或突破，并因而对学术界有所贡献。比如可以是新方法，或者既有方法的新应用，或者是用一个新的方法开拓一个新领域。

必须提出足够的证据证明：针对这个场合，所提出的方法确实比文献中的一切既有方法存在更优越之处。

能清楚地指出该方法的应用限制，并用充足的理由说服读者：任何应用场合，只要能满足所提出的假设（前提）条件，该方法就一定适用，所描述的优点就一定存在

必须在论文中清楚地指出这个方法的限制和可能的缺点（相对于其他文献的既有方法，或其他应用场合里）

行文严谨，逻辑清晰，实验步骤完善描述使得可复现。

每一个步骤都要有充足的理由说明“为什么非如此不可”

清楚注明所有与该题目相关文献。所有相关文献必须找出来并仔细读过。没有参考过则是重大过失。

第（2）所谓“对学术界的贡献”=本人所有成果-学术界已发表所有成果。

研究论文能获得的能力

从无组织的知识中总结知识

精确批判

创造新知识

总结：

论文要素

创新或突破（新方法或新应用或新领域）

足够的证据

（特定领域）比文献更优

假设/前提

应用限制和可能的缺点

可复现

选用每个步骤的理由

参考文献

参考文献

《硕士班研究所新生手册》，PPSC Lab，彭明辉，国立清华大学

论文写作

论文阅读

论文结构

Abstract：主要贡献、方法特色、主要内容。要只看Abstract和Introduction就能分析出该论文重点是否与研究相关，而决定是否将它读完。

Introduction：介绍背景起源，交代前人贡献，讲明白未解决的问题，和在该背景下这篇论文想解决的问题和它的重要性。

Main body: 包含 simulation and/or experimental examples。

阅读方法

看论文只需把会用到的完全搞懂，其他之要了解 idea，不该仔细看推导过程。

硕士生必须学会选择性的阅读，而且必须锻炼出他选择时的准确度以及选择的速度，不要浪费时间在学用不着的细节知识！多吸收「点子」比较重要，而不是细部的知识。

1. 掌握该领域主要方法、文献之间的关系

注意：该步骤基本只需要读 Abstract 和 Introduction

对题目不熟时，先收集30～40篇，每篇只读 Abstract 和 Introduction ，不要读Main Body，只在最后参考 Illustrative examples 和 Conclusions，直到能回答出（2A）最常被引述的方法与（2B）可以分成的主要派别（2C）每个派别的主要特色（含优点和缺点）。

[找论文的方法：寻找“review paper”]

为了能回答（2A）（2B），先阅读Introduction比较有观念的论文。

如果无法回答（2C），则先根据（2A）的答案找齐该领域最常被引述的论文，再根据（2B）分派别，按时间顺序排好。之后每次只需重读一派的Abstract和与这派有关的Introduction，便能总结出这一派的主要诉求、方法特色和优点。

之后，再使用这些论文阅读Introduction，回答“每篇论文对其他派别有什么批评”。

至此，应当掌握了该领域主要论文，并能勇者写论文测试搜索关键词的恰当程度，并用修正后的关键词重新生成论文库。如果有充足的理由和把握，可以只保留2、3个派别。

利用（2C）中的答案，进一步回答（2D）：这个领域大家关注的1. 关键问题，2. 优缺点，3. 优缺点的重视场合，4. 优缺点的不重视场合。整理出该领域不同派别应用场合以及注意事项。

根据（2A）和（2C），按派别分类，派别内按时间排序，按派别与研究方向关系由近及远的顺序，按派别依次读完每派的 main bodies。

2. 整理每一派最适合什么时候使用，最不适合什么场合使用

只需要读懂：（3A）该论文主要假设，现实条件下假设多难成立，以评估参考价值。（3B）在这些假设下，此论文的好处（3C）好处表现在哪些公式的哪些项目简化上。

公式只看最后用到的公式以便评估方便程度/计算效率，以及非理想情况下的可靠度；推导中只看加入的假设条件，而不看恒等变换。

阅读时，只需粗略浏览，挑出易懂论文后按时间顺序阅读，只需回答（3A）、（3B）、（3C）。

回答完三个问题后，整理出这一派主要发展过程、假设、理论依据、成果。根据（2D）与这一派主要假设，回答（3D）这一派主要缺点。根据（3A）、（3B）、（3C）、（3D）整理出：这一派最适合什么时候使用，最不适合什么场合使用。

3. 补充说明

一整批一起读懂到某个层次，而不是逐篇读懂。大部分论文只了解观念，不看推导。

读不懂的地方大胆猜测，然后从论文里找根据。

论文报告要求及技巧

投影片第一页必须列出论文的题目、作者、论文出处与年份。

以下每一页投影片只能讲一个观念，不可以在一张投影片里讲两个观念。

说明这篇论文所研究的问题的重点，以及这个问题可能和工业界的哪些应用相关。

清楚交代这篇论文的主要假设，主要公式，与主要应用方式（以及应用上可能的解题流程）。

说明这篇论文的范例（simulation examples and/or experiments），预测这个方法在不同场合时可能会有的准确度或好用的程度

你个人的分析、评价与批评，包括：

（6A）这篇论文最主要的创意是什么？

（6B）这些创意在应用上有什么好处？

（6C）这些创意和应用上的好处是在哪些条件下才能成立？

（6D）这篇论文最主要的缺点或局限是什么？

（6E）这些缺点或局限在应用上有什么坏处？

（6F）这些缺点和应用上的坏处是因为哪些因素而引入的？

（6G）你建议学长学弟什么时候参考这篇论文的哪些部分（点子）？

附录

带着哪些问题阅读论文

（2A）在这领域内最常被引述的方法有哪些？

（2B）这些方法可以分成哪些主要派别？

（2C）每个派别的主要特色（含优点和缺点）是什么？

（2D）：「这个领域内大家认为重要的关键问题有哪些？有哪些特性是大家重视的优点？有哪些特性是大家在意的缺点？这些优点与缺点通常在哪些应用场合时会比较被重视？在哪些应用场合时比较不会被重视？」

（3A）这篇论文的主要假设是什么（在什么条件下它是有效的），并且评估一下这些假设在现实条件下有多容易（或多难）成立。愈难成立的假设，愈不好用，参考价值也愈低。

（3B）在这些假设下，这篇论文主要有什么好处。

（3C）这些好处主要表现在哪些公式的哪些项目的简化上。

（3D）这一派主要的缺点有哪些。

方法与应用场合特性表

论文阅读流程图

DPT-RP1_Config

Calibre Configs for DPT-RP1

界面外观

文本

文本对齐方式：两端对齐

布局

删除段落间空行：Y

缩进宽度：无变更

页面设置

输出配置文件：Tablet

输入配置文件：Default Input Profile

边距：未更改

PDF 输出

自定义页面尺寸：1150x1534 point/1650x2200 point, 202.8x270.4 mm

保持封面纵宽比：Y

默认字体大小：28 px/14 px

等宽字体大小：26 px/12 px

页边距：从转换的文档使用页边距

TXT 输入

章节检测：//*[re:test(., "^\s*[第卷]\s*[0123456789一二三四五六七八九十零〇百千两]*\s*[章回部节集卷].*", "i")]

打印页面设置

Width: 207 mm, 8.15 in/ 202.8 mm, 7.98 in

Height: 276.12 mm, 10.87 in/270.4 mm, 10.65 in

屏幕比例

Width: 5x5x23, Height: 13x59

Width: 2x3x5x5x11, Height: 2x2x2x5x5x11