深度学习复习

# M-P 神经元

M-P神经元

函数 g 对输入进行聚合,函数 f 基于聚合值做出一个决策

g(x1,x2,...,xn)=i=1nxig(x_1,x_2,...,x_n)=\sum\limits_{i=1}^nx_i

y=f(g(x))={1ifg(x)θ0ifg(x)<θy=f(g(x))=\begin{cases}1&if\ g(x)\ge\theta\\0&if\ g(x)<\theta\end{cases}

其中θ\theta 是阈值参数

  • 总结:
    • 一个单个的 M-P 神经元能够用来实现线性可分的布尔函数
    • 对于布尔函数而言,线性可分意味着:存在一条直线或一个平面使得所有产生输出 1 的输入位于线 (或平面) 的一侧,所有产生输出 0 的输入位于线 (或平面) 的另一侧

# Perception (感知机)

感知机

y={1ifi=0nwixi00ifi=0nwixi<0y=\begin{cases}1&if\ \sum\limits_{i=0}^nw_i*x_i\ge0\\0&if\ \sum\limits_{i=0}^nw_i*x_i<0\end{cases}

  • 单独一个感知机只能用于实现线性可分的函数

  • 感知机与 M-P 神经元的差别:

    • 感知机的权重包括阈值能通过学习得到
    • 感知机的输入可以是实数
  • 感知机学习算法:

    Algorithm:\text{Algorithm}: 感知机学习算法 (Perception Learning Algorithm)

    PP\leftarrowinputs with label 1;

    NN\leftarrowinputs with label 0;

    Initialize w randomly;

    while !convergence do

    Pick randomxPN;\text{Pick random}\ x\in P\cup N;

    ifxPandi=0nwixi<0thenif\ x\in P\ and\ \sum_{i=0}^nw_i*x_i<0\ then

    w=w+xw=w+x

    endend

    ifxNandi=0nwixi0thenif\ x\in N\ and\ \sum_{i=0}^nw_i*x_i\ge0\ then

    w=wxw=w-x

    endend

定理:任何有 n 个输入的布尔函数能够由一个感知机网络实现,该网络包含一个有2n2^n 个感知机的隐含层和一个有一个感知机的输出层

  • 多层感知机
    • 由一个输入层、一个输出层 (一个感知机模型)、一个或多个隐含层 (多个感知机模型) 构成的网络称作多层感知机 (MLP)
    • 具有一层隐含层的 MLP 能够表示任意布尔函数

# Sigmoid 神经元

Sigmoid神经元

一个典型的有监督机器学习设置包括以下部分:

  • 数据 (Data):{xi,yi}i=1n{\{}x_i,y_i{\}}_{i=1}^n
  • 模型 (Model):描述 x 和 y 之间关系的函数,例如:
    • y^=11+e(wTx)\hat{y}=\frac{1}{1+e^{-(w^Tx)}}
    • or y^=wTx\hat{y}=w^Tx
    • or y^=xTwx\hat{y}=x^Twx
    • 或者是任意函数
  • 参数 (Parameters):在所有上面的例子中,w 是需要从数据中学习的参数
  • 学习算法 (Learning algorithm):学习模型参数 w 的算法,例如感知机学习算法、梯度下降算法等
  • 目标 / 损失 / 误差函数 (Objective/Loss/Error function):用于指导学习算法 —— 学习算法的目标是最小化损失函数

梯度下降规则:

  • 移动的方向 u 应该是与梯度的方向相差 180°
  • 即沿着梯度的反方向移动

参数更新公式:

wt+1=wtηwtw_{t+1}=w_t-\eta\nabla w_t

bt+1=btηbtb_{t+1}=b_t-\eta\nabla b_t

where,wt=δφ(w,b)δw,b=δφ(w,b)δbatw=wt,b=bt\text{where},\nabla w_t=\frac{\delta\varphi(w,b)}{\delta w},\nabla b=\frac{\delta\varphi(w,b)}{\delta b}_{at\ w=w_t,b=b_t}

假设有一个训练数据 (x,y)

φ(w,b)=12(f(x)y)2\varphi(w,b)=\frac{1}{2}*(f(x)-y)^2

w=δδw[12(f(x)y)2]=(f(x)y)f(x)(1f(x))x\nabla w=\frac{\delta}{\delta w}[\frac{1}{2}*(f(x)-y)^2]=(f(x)-y)*f(x)*(1-f(x))*x

δδw(11+e(wx+b))=f(x)(1f(x))x\frac{\delta}{\delta w}(\frac{1}{1+e^{-(wx+b)}})=f(x)*(1-f(x))*x

若有多个数据

w=i=1n(f(xi)yi)f(xi)(1f(xi))xi\nabla w=\sum\limits_{i=1}^n(f(x_i)-y_i)*f(x_i)*(1-f(x_i))*x_i

b=i=1n(f(xi)yi)f(xi)(1f(xi))\nabla b=\sum\limits_{i=1}^n(f(x_i)-y_i)*f(x_i)*(1-f(x_i))

梯度下降代码:

def do_gradient_descent():
    w,b,eta,max_epochs=-2,-2,1.0,1000
    for i in range(max_epochs):
        dw,db=0,0
        for x,y in zip(X,Y):
            dw+=grad_w(w,b,x,y)
            db+=grad_b(w,b,x,y)
        w-=eta*dw
        b-=eta*db

多层感知机网络的表示能力 vs 多层 Sigmoid 神经元网络的表示能力:

  • 多层感知机神经网络:有一个隐含层的多层感知机网络能精确地表示任何 boolean 函数
  • 多层 Sigmoid 神经元网络:有一个隐含层的多层 Sigmoid 神经元网络能以期望的精度近似任意连续函数

# 反向传播算法

举一个例子,一个网络中单独分割出一条路径:

瘦且长的网络

单独对W111W_{111} 求导:

δφ(θ)δW111=δφ(θ)δy^δy^δaL11δaL11δh21δh21δa21δa21δh11δh11δa11δa11δW111\dfrac{\delta\varphi(\theta)}{\delta W_{111}}=\dfrac{\delta\varphi(\theta)}{\delta\hat{y}}\dfrac{\delta\hat{y}}{\delta a_{L11}}\dfrac{\delta a_{L11}}{\delta h_{21}}\dfrac{\delta h_{21}}{\delta a_{21}}\dfrac{\delta a_{21}}{\delta h_{11}}\dfrac{\delta h_{11}}{\delta a_{11}}\dfrac{\delta a_{11}}{\delta W_{111}}

对于梯度下降法,梯度的计算:

需要计算的梯度:

  • Gradient w.r.t output units
  • Gradient w.r.t hidden units
  • Gradient w.r.t weights and biases

# 计算损失函数对输出单元的梯度

y^φ(θ)=1y^lel\nabla_{\hat{y}}\varphi(\theta)=-\frac{1}{\hat{y}_l}e_l

其中ele_l 是一个kk 维向量,它的第ll 个元素是 1,其他元素为 0

δφ(θ)δaL,i=(1l=iy^i)\dfrac{\delta\varphi(\theta)}{\delta a_{L,i}}=-(1_{l=i}-\hat{y}_i)

aLφ(θ)=(e(l)y^)\nabla_{a_L}\varphi(\theta)=-(e(l)-\hat{y})

# 计算损失函数对隐含单元的梯度

δφ(θ)δhij=(Wi+1,.,j)Tai+1φ(θ)\dfrac{\delta\varphi(\theta)}{\delta h_{ij}}=(W_{i+1,.,j})^T\nabla_{a_{i+1}}\varphi(\theta)

hiφ(θ)=(Wi+1)T(ai+1φ(θ))\nabla_{h_i}\varphi(\theta)=(W_{i+1})^T(\nabla_{a_{i+1}}\varphi(\theta))

δφ(θ)δaij=δφ(θ)δhijg(aij)[hij=g(aij)]\dfrac{\delta\varphi(\theta)}{\delta a_{ij}}=\dfrac{\delta\varphi(\theta)}{\delta h_{ij}}g^\prime(a_{ij})\ [\because\ h_{ij}=g(a_{ij})]

aiφ(θ)=[δφ(θ)δhi1g(ai1)δφ(θ)δhing(ain)]\nabla_{a_i}\varphi(\theta)=\begin{bmatrix}\dfrac{\delta\varphi(\theta)}{\delta h_{i1}}g^\prime(a_{i1})\\\vdots\\\dfrac{\delta\varphi(\theta)}{\delta h_{in}}g^\prime(a_{in})\end{bmatrix}

# 计算损失函数对参数的梯度

δφ(θ)δWkij=δφ(θ)δakiδakiδWkij=δφ(θ)δakihk1,j\dfrac{\delta\varphi(\theta)}{\delta W_{kij}}=\dfrac{\delta\varphi(\theta)}{\delta a_{ki}}\dfrac{\delta a_{ki}}{\delta W_{kij}}=\dfrac{\delta\varphi(\theta)}{\delta a_{ki}}h_{k-1,j}

Wkφ(θ)=akφ(θ)hk1T\nabla_{W_k}\varphi(\theta)=\nabla_{a_k}\varphi(\theta)\cdot \bold{h}_{k-1}^T

其中加粗的 h 代表的是一个矩阵

δφ(θ)δbki=δφ(θ)δakiδakiδbki=δφ(θ)δaki\dfrac{\delta\varphi(\theta)}{\delta b_{ki}}=\dfrac{\delta\varphi(\theta)}{\delta a_{ki}}\dfrac{\delta a_{ki}}{\delta b_{ki}}=\dfrac{\delta\varphi(\theta)}{\delta a_{ki}}

bkφ(θ)=[δφ(θ)δak1δφ(θ)δak2δφ(θ)δakn]=akφ(θ)\nabla_{b_k}\varphi(\theta)=\begin{bmatrix}\dfrac{\delta\varphi(\theta)}{\delta a_{k1}}\\\dfrac{\delta\varphi(\theta)}{\delta a_{k2}}\\\vdots\\\dfrac{\delta\varphi(\theta)}{\delta a_{kn}}\end{bmatrix}=\nabla_{a_k}\varphi(\theta)

# 算法流程:

Algorithm: gradient_descent()

t0t\leftarrow 0

max_iterations1000;max\_iterations\leftarrow 1000;

Initializeθ0=[W10,...,WL0,b10,...,bL0]\text{Initialize}\ \theta_0=[W_1^0,...,W_L^0,b_1^0,...,b_L^0]

while t++<max_iterationsdo\text{while t++<max\_iterations}\ do

h1,h2,...,hL1,a1,a2,...,aL,y^=forward_propagation(θt);h_1,h_2,...,h_{L-1},a_1,a_2,...,a_L,\hat{y}=\text{forward\_propagation}(\theta_t);

θt=backward_propagation(h1,h2,...,hL1,a1,a2,...,aL,y,y^);\nabla\theta_t=\text{backward\_propagation}(h_1,h_2,...,h_{L-1},a_1,a_2,...,a_L,y,\hat{y});

θt+1θηθt;\theta_{t+1}\leftarrow\theta-\eta\nabla\theta_t;

end

Algorithm: forward_propagation(θ\theta)

for k=1 to L-1do\text{for k=1 to L-1}\ do

​ a_k=b_k+W_kh_

hk=g(ak)h_k=g(a_k)

end

a_L=b_L+W_Lh_

y^=O(aL);\hat{y}=O(a_L);

Algorithm: back_propagation(h1,h2,...,hL1,a1,a2,...,aL,y,y^h_1,h_2,...,h_{L-1},a_1,a_2,...,a_L,y,\hat{y})

aLφ(θ)=(e(y)y^);\nabla_{a_L}\varphi(\theta)=-(e(y)-\hat{y});(计算输出层梯度)

for k=L to 1do\text{for k=L to 1}\ do

​ (计算隐藏层参数梯度)

wkφ(θ)=akφ(θ)hk1T;\nabla_{w_k}\varphi(\theta)=\nabla_{a_k}\varphi(\theta)h_{k-1}^T;

bkφ(θ)=akφ(θ);\nabla_{b_k}\varphi(\theta)=\nabla_{a_k}\varphi(\theta);

​ (计算隐藏层输出前梯度)

hk1φ(θ)=WkT(akφ(θ));\nabla_{h_{k-1}}\varphi(\theta)=W_k^T(\nabla_{a_k}\varphi(\theta));

ak1φ(θ)=hk1φ(θ)[...,g(ak1,j),...];\nabla_{a_{k-1}}\varphi(\theta)=\nabla_{h_{k-1}}\varphi(\theta)\odot[...,g^\prime(a_{k-1,j}),...];

end

# 激活函数的导数

  • Logistic function:

    • g(z)=11+ezg(z)=\dfrac{1}{1+e^{-z}}

    • g(z)=g(z)(1g(z))g^\prime(z)=g(z)(1-g(z))

  • tanh:

    • g(z)=ezezez+ezg(z)=\dfrac{e^z-e^{-z}}{e^z+e^{-z}}

    • g(z)=1(g(z))2g^\prime(z)=1-(g(z))^2

# 梯度下降及其变体

随机梯度下降算法:

def do_stochastic_gradient_descent():
	w,b,eta,max_epochs=-2,-2,1.0,1000
    for i in range(max_epochs):
        dw,db=0,0
        for x,y in zip(X,Y):
            dw=grad_w(w,b,x,y)
            db=grad_b(w,b,x,y)
            w-=eta*dw
            b-=eta*db

小批量梯度下降算法:

def do_mini_batch_gradient_descent():
    w,b,eta=-2,-2,1.0
    mini_batch_size,num_points_seen=2,0
    for i in range(max_epochs):
        dw,db,num_points=0,0,0
        for x,y in zip(X,Y):
            dw+=grad_w(w,b,x,y)
            db+=grad_b(w,b,x,y)
            num_points_seen+=1
            if num_points_seen%mini_batch_size==0:
                w-=eta*dw
                b-=eta*db
                dw,db=0,0

动量梯度下降算法:

def do_momentum_gradient_descent():
    w,b,eta=init_w,init_b,1.0
    prev_v_w,prev_v_b,gamma=0,0,0.9
    for i in range(max_epochs):
        dw,db=0,0
        for x,y in zip(X,Y):
            dw+=grad_w(w,b,x,y)
            db+=grad_b(w,b,x,y)
        v_w = gamma*prev_v_w+eta*dw
        v_b = gamma*prev_v_b+eta*db
        w-=v_w
        b-=v_b
        prev_v_w=v_w
        prev_v_b=v_b

NAGD 算法:

def do_nesterov_accelerated_gradient_descent():
    w,b,eta=init_w,init_b,1.0
    prev_v_w,prev_v_b,gamma=0,0,0.9
    for i in range(max_epochs):
        dw,db=0,0
       	v_w=gamma*prev_v_w
        v_b=gamma*prev_v_b
        for x,y in zip(X,Y):
            dw+=grad_w(w-v_w,b-v_b,x,y)
            db+=grad_b(w-v_w,b-v_b,x,y)
        v_w=gamma*prev_v_w+eta*dw
        v_b=gamma*prev_v_b+eta*db
        w-=v_w
        b-=v_b
        prev_v_w=v_w
        prev_v_b=v_b

Adagrad 算法:

def do_adagrad():
    w,b,eta=init_w,init_b,0.1
    v_w,v_b,eps=0,0,1e-8
    for i in range(max_epochs):
        dw,db=0,0
        for x,y in zip(X,Y):
            dw+=grad_w(w,b,x,y)
            db+=grad_b(w,b,x,y)
        v_w+=dw**2
        v_b+=db**2
        w-=(eta/np.sqrt(v_w+eps))*dw
        b-=(eta/np.sqrt(v_b+eps))*db

RMSProp 算法:

def do_rmsprop():
    w,b,eta=init_w,init_b,0,1
    v_w,v_b,eps,beta1=0,0,1e-8,0.9
    for i in range(max_epochs):
        dw,db=0,0
        for x,y in zip(X,Y):
            dw+=grad_w(w,b,x,y)
            db+=grad_b(w,b,x,y)
        v_w=beta1*v_w+(1-beta1)*dw**2
        v_b=beta1*v_b+(1-beta1)*db**2
        w-=(eta/np.sqrt(v_w+eps))*dw
        b-=(eta/np.sqrt(v_b+eps))*db

Adam 算法:

def do_adam():
    w,b,eta=init_w,init_b,0.1
    m_w,m_b,v_w,v_b,m_w_hat,m_b_hat,v_w_hat,v_b_hat,eps,beta1,beta2=0,0,0,0,0,0,0,0,1e-8,0.9,0.999
    for i in range(max_epochs):
        dw,db=0,0
        for x,y in zip(X,Y):
            dw+=grad_w(w,b,x,y)
            db+=grad_b(w,b,x,y)
        m_w=beta1*m_w+(1-beta1)*dw
        m_b=beta1*m_b+(1-beta1)*db
        v_w=beta2*v_w+(1-beta2)*dw**2
        v_b=beta2*v_b+(1-beta2)*db**2
        m_w_hat=m_w/(1-math.pow(beta1,i+1))
        m_b_hat=m_b/(1-math.pow(beta1,i+1))
        v_w_hat=v_w/(1-math.pow(beta2,i+1))
        v_b_hat=v_b/(1-math.pow(beta2,i+1))
        w-=(eta/np.sqrt(v_w_hat+eps))*m_w_hat
        b-=(eta/np.sqrt(v_b_hat+eps))*m_b_hat

# 自编码器及其变体

# AutoCoder

AutoCoder

  • 自编码器是一类特殊的前馈神经网络

  • Encodes 输入xix_i 得到隐含表示 h

  • Decodes 输入 h 得到\hat

  • 最小化特定的损失函数确保xi^\hat{x_i} 接近与xix_i

  • dim(h)<dim(xi)dim(h)<dim(x_i) 时,称为 under complete autocoder

    • h 提取了xix_i 所有重要的信息
  • dim(h)>dim(xi)dim(h)>dim(x_i) 时,称为 over complete autocoder

    • h 在实际中没有什么作用

线性自编码器的编码器等价于 PCA,如果满足:

  • 使用线性编码器
  • 使用线性解码器
  • 使用平方根误差损失函数
  • 对输入进行如下归一化:

xij^=1m(xij1mk=1mxkj)\hat{x_{ij}}=\dfrac{1}{\sqrt{m}}(x_{ij}-\dfrac{1}{m}\sum\limits_{k=1}^mx_{kj})

# Regularization in Autoencoder

  • 自编码器的泛化能力差,特别是对于过完备的自编码器
  • 对于过完备的自编码器,模型会简单将xix_i 复制进 h,然后再把 h 复制进\hat
  • 为了避免较差的泛化能力,可以在目标函数中引入正则项
  • 最简单的正则项是在目标函数中增加一个L2L_2-regularization 项:

θ=minW,W,b,c1mi=1mj=1n(x^ijxij)2+λθ2\theta=\min\limits_{W,W^*,b,c}\dfrac{1}{m}\sum\limits_{i=1}^m\sum\limits_{j=1}^n(\hat{x}_{ij}-x_{ij})^2+\lambda\Vert\theta\Vert^2

  • 在使用梯度下降求解最优参数时,只需要在梯度项δL(θ)δW\dfrac{\delta L(\theta)}{\delta W} 中增加一项λW\lambda W (求解其他参数时,有类似的操作)

  • 另一个方案是对编码器和解码器的权重进行捆绑W=WTW^*=W^T

  • 这种方案有效地减小了自编码器的容量,也能起到正则化的作用

# 去噪自编码器

去噪编码器

  • 一个去噪编码器在处理输入数里时,简单地使用一个概率分布(P(x~ijxij))(P(\tilde{x}_{ij}\vert x_{ij})) 对输入数据施加噪声

  • 实际中,一个简单的概率分布P(x~ijxij)P(\tilde{x}_{ij}\vert x_{ij}) 可以使用下:

    • P(x~ij=0xij)=qP(\tilde{x}_{ij}=0\vert x_{ij})=q
    • P(x~ij=xijxij)=1qP(\tilde{x}_{ij}=x_{ij}\vert x_{ij})=1-q
  • 以概率 q 将输入数据置成 0,以概率 (1-q) 保持原来的输入

  • 优化的目标仍然是重构原始的输入xix_i

argminθ1mi=1mj=1n(x^ijxij)2\arg\min\limits_{\theta}\dfrac{1}{m}\sum\limits_{i=1}^m\sum\limits_{j=1}^n(\hat{x}_{ij}-x_{ij})^2

  • 加入噪声后,单纯复制后会导致目标函数不是最优,从而避免这种简单的复制
  • 模型会正确地捕获输入数据的特性

# 稀疏自动编码器

  • 隐含层经过 Sigmoid 激活后,输出结果在 0 到 1 之间
  • 当神经元的输出接近 1,则认为神经元被激活,当输出接近 0 时,则认为该神经元没有被激活
  • 稀疏自动编码器的目的是确保神经元在大部分时间处于非激活状态

# 收缩自编码器

  • 收缩自编码器的设计是为了防止学习特定映射函数时,产生 overcomplete (隐藏层神经元个数大于输出神经元个数)
  • 为了实现该目的,可以向损失函数添加一下正则项:Ω(θ)=Jx(h)F2\Omega(\theta)=\Vert J_x(h)\Vert^2_F,其中Jx(h)J_x(h) 是编码器的 Jacobian 矩阵
  • Jacobian 矩阵的(l,j)(l,j) 项能够反应第 j 个输入的小变化量对第 l 个神经元的输出的影响

# 神经网络训练

# 预训练

  • 问题:对于很深的神经网络,训练多个 epochs 后仍然不收敛
  • 方法:
    • 考虑神经网络前两层,用一个无监督的目标来训练这两层之间的权重
    • 目标是要从隐含表示 (h1h_1) 中重构出输入 (x)
    • 经过这一步后,第一层的权重被训练,使得h1h_1 捕获输入 x 的重要信息
    • 然后将第一层固定,在第二层上重复这一过程,然后h2h_2 将捕获h1h_1 的重要信息
    • 继续这一过程,直到最后一个隐含层
    • 预训练结束后,使用训练出的权重来初始化隐含层权重。所得到的的网络能够学习到输入数据类别独立的特征表示
    • 预训练结束后,再在网络上增加输出层,使用特定的目标 (或损失函数) 来训练整个网络
  • 整个过程可以理解为:先使用无监督的预训练 (无监督的目标) 来初始化网络权重,再使用特定有监督的目标来 fine tune 整个网络
  • 正则化的作用:
    • 它将权重约束到参数空间的特定区域
    • L-1 regularization: 约束大多数权重为 0
    • L-2 regularization: 阻止大多数权重取值较大
    • 预训练将权重约束到参数空间的特定区域,将权重约束到能很好捕获数据特性的区域

# 激活函数

  • 问题:Sigmoid 神经元容易很快达到饱和,导致梯度消失 (Sigmoid 神经元不是 0 中心化的)

  • ReLU 神经元:学习率过高会导致大部分 ReLU 神经元死亡

  • ReLU 神经元变体:

    • Leaky ReLU
    • Maxout
    • ELU
  • 记忆点:

    • Sigmoid 激活函数效果不好
    • ReLU 是 CNN 的标准激活函数
    • 可以尝试 Leaky ReLU/Maxout/ELU
    • tanh 和 Sigmoids 仍然在 LSTMs/RNNs 中使用

# 块归一化

  • 如果每一层的预激活服从单位高斯分布,对输入的变化会更鲁棒
  • 可以对每一层预激活进行如下 "标准化" 操作:

sik^=sikEsikvar(sik)\hat{s_{ik}}=\dfrac{s_{ik}-E\vert s_{ik}\vert}{\sqrt{var(s_{ik})}}

  • 用小批量的数据计算E[sik]E[s_{ik}]Var[sik]Var[s_{ik}]

  • 因此,确保不同层的输入的分布不会随着 batch 的不同而改变

  • 该操作称为 Batch Normalization

# Dropout

  • 在神经网络中,移除一个结点和与之相连的边,会得到一个瘦身的网路
  • Dropout 本质上可以看作是对隐含单元施加噪声
  • 阻止隐含单元互相适,一个隐含单元不能过于依赖其他隐含单元 (因为其他隐含单元可能被移除)
  • 每个隐含单元必须学习到对其他隐含单元的移除足够鲁棒
  • Dropout 提高了网络的冗余性和鲁棒性

# 卷积神经网络

# 卷积

  • 对于 1D 的输入,使用一个 1D 的滤波器在其上面滑动
  • 对于 @D 的输入,使用一个 @D 的滤波器在其上面滑动

卷积计算:

  • Padding:P (扩展大小)

  • Stride:S (移动步长)

  • Kernel_Size:F (卷积核大小)

  • Map_Size: W (输入图片大小)

  • OutputMap_Size:WF+2PS+1\dfrac{W-F+2P}{S}+1

通道数取决于输入通道数以及卷积核的个数

例子:

卷积例子

经典卷积网络:

  • AlexNet
  • ZFNet
  • VGGNet
  • GoogleNet
  • ResNet
更新于 阅读次数

请我喝[茶]~( ̄▽ ̄)~*