Commit 6f47bff6 by 曹润柘

合并分支 'master' 到 'caorunzhe'

Master

查看合并请求 !191
parents d93b88a7 bc6ad7b2
...@@ -742,6 +742,20 @@ c_{\textrm{KN}}(\cdot) = \left\{\begin{array}{ll} ...@@ -742,6 +742,20 @@ c_{\textrm{KN}}(\cdot) = \left\{\begin{array}{ll}
\parinterval Kneser-Ney平滑是很多语言模型工具的基础\upcite{heafield2011kenlm,stolcke2002srilm}。还有很多以此为基础衍生出来的算法,感兴趣的读者可以通过参考文献自行了解\upcite{parsing2009speech,ney1994structuring,chen1999empirical} \parinterval Kneser-Ney平滑是很多语言模型工具的基础\upcite{heafield2011kenlm,stolcke2002srilm}。还有很多以此为基础衍生出来的算法,感兴趣的读者可以通过参考文献自行了解\upcite{parsing2009speech,ney1994structuring,chen1999empirical}
%---------------------------------------------------------------------------------------- %----------------------------------------------------------------------------------------
% NEW SSUB-SECTION
%----------------------------------------------------------------------------------------
\subsection{语言模型的评价}
\parinterval 在使用语言模型时,往往需要知道模型的质量。{\small\sffamily\bfseries{困惑度}}\index{困惑度}(Perplexity\index{Perplexity},PPL)是一种衡量语言模型的好坏的指标。对于一个真实的词序列$ w_1\dots w_m $,困惑度被定义为
\begin{eqnarray}
{\rm{PPL}}&=&{\rm P}{(w_1\dots w_m)}^{- \frac{1}{m}}
\label{eq:5-65}
\end{eqnarray}
\parinterval 本质上,PPL反映了语言模型对序列可能性预测能力的一种评估。如果$ w_1\dots w_m $\\是真实的自然语言,``完美''的模型会得到$ {\rm P} (w_1\dots w_m)=1 $,它对应了最低的困惑度$ {\rm{PPL}}=1$,这说明模型可以完美地对词序列出现的可能性进行预测。当然,真实的语言模型是无法达到$ {\rm{PPL}}=1$的,比如,在著名的Penn Treebank(PTB)数据上最好的语言模型的PPL值也只能到达35左右。可见自然语言处理任务的困难程度。
%----------------------------------------------------------------------------------------
% NEW SECTION % NEW SECTION
%---------------------------------------------------------------------------------------- %----------------------------------------------------------------------------------------
......
...@@ -10,11 +10,11 @@ ...@@ -10,11 +10,11 @@
\node [anchor=south west,inner sep=2pt] (step100) at ([xshift=0.5em,yshift=-0.8em]h.north east) {\scriptsize{$\textbf{s}^K = \textbf{h}^{K-1} \textbf{w}^K$}}; \node [anchor=south west,inner sep=2pt] (step100) at ([xshift=0.5em,yshift=-0.8em]h.north east) {\scriptsize{$\textbf{s}^K = \textbf{h}^{K-1} \textbf{w}^K$}};
\node [anchor=south west] (slabel) at ([yshift=1em,xshift=0.3em]s.north) {\scriptsize{\red{\textbf{{已经得到:$\pi^K = \frac{\partial L}{\partial \textbf{s}^K}$}}}}}; \node [anchor=south west] (slabel) at ([yshift=1em,xshift=0.3em]s.north) {\scriptsize{\textbf{{已经得到:$\pi^K = \frac{\partial L}{\partial \textbf{s}^K}$}}}};
\draw [->,red] ([yshift=0.3em]slabel.south) .. controls +(south:0.5) and +(north:0.5) .. ([xshift=0.5em]s.north); \draw [->] ([yshift=0.3em]slabel.south) .. controls +(south:0.5) and +(north:0.5) .. ([xshift=0.5em,yshift=0.1em]s.north);
{ {
\draw [->,very thick,red] ([yshift=1em,xshift=-0.1em]s.north) -- ([yshift=1em,xshift=0.1em]h.north) node [pos=0.5,above] {\scriptsize{{$\frac{\partial L}{\partial \textbf{w}^K} = ?$, $\frac{\partial L}{\partial \textbf{h}^{K-1}} = ?$}}}; \draw [->,very thick,red] ([yshift=1em,xshift=-0.1em]s.north) -- ([yshift=1.0em,xshift=0.1em]h.north) node [pos=0.5,above] {\scriptsize{{$\frac{\partial L}{\partial \textbf{w}^K} = ?$, $\frac{\partial L}{\partial \textbf{h}^{K-1}} = ?$}}};
\draw [-,very thick,red] ([yshift=0.5em]h.north) -- ([yshift=1.5em]h.north); \draw [-,very thick,red] ([yshift=0.5em]h.north) -- ([yshift=1.5em]h.north);
\draw [-,very thick,red] ([yshift=0.5em]s.north) -- ([yshift=1.5em]s.north); \draw [-,very thick,red] ([yshift=0.5em]s.north) -- ([yshift=1.5em]s.north);
} }
......
...@@ -51,15 +51,15 @@ ...@@ -51,15 +51,15 @@
\node [anchor=south,draw,rounded corners,inner sep=2pt,minimum width=8em,minimum height=1.2em,fill=green!30!white,blur shadow={shadow xshift=1pt,shadow yshift=-1pt}] (h3) at ([yshift=1.5em]h2.north) {\scriptsize{h2 = Relu(h1 * w2)}}; \node [anchor=south,draw,rounded corners,inner sep=2pt,minimum width=8em,minimum height=1.2em,fill=green!30!white,blur shadow={shadow xshift=1pt,shadow yshift=-1pt}] (h3) at ([yshift=1.5em]h2.north) {\scriptsize{h2 = Relu(h1 * w2)}};
\node [anchor=south,draw,rounded corners,inner sep=2pt,minimum width=8em,minimum height=1.2em,fill=green!30!white,blur shadow={shadow xshift=1pt,shadow yshift=-1pt}] (h4) at ([yshift=1.5em]h3.north) {\scriptsize{h3 = h2 + h1}}; \node [anchor=south,draw,rounded corners,inner sep=2pt,minimum width=8em,minimum height=1.2em,fill=green!30!white,blur shadow={shadow xshift=1pt,shadow yshift=-1pt}] (h4) at ([yshift=1.5em]h3.north) {\scriptsize{h3 = h2 + h1}};
{\draw [<-,very thick,red] (h1.north) -- (h2.south);} {\draw [->,very thick] (h1.north) -- (h2.south);}
{\draw [<-,very thick,red] (h2.north) -- (h3.south);} {\draw [->,very thick] (h2.north) -- (h3.south);}
{\draw [<-,very thick,red] (h3.north) -- (h4.south);} {\draw [->,very thick] (h3.north) -- (h4.south);}
{\draw [<-,very thick,red,rounded corners] (h2.east) -- ([xshift=0.5em]h2.east) -- ([xshift=0.5em,yshift=0.5em]h3.north east) -- ([xshift=-2em,yshift=0.5em]h3.north east) -- ([xshift=-2em,yshift=1.5em]h3.north east);} {\draw [->,very thick,rounded corners] (h2.east) -- ([xshift=0.5em]h2.east) -- ([xshift=0.5em,yshift=0.5em]h3.north east) -- ([xshift=-2em,yshift=0.5em]h3.north east) -- ([xshift=-2em,yshift=1.5em]h3.north east);}
\node [anchor=south,draw,rounded corners,inner sep=2pt,minimum width=8.0em,minimum height=1.2em,fill=red!30!white,blur shadow={shadow xshift=1pt,shadow yshift=-1pt}] (slayer) at ([yshift=1.5em]h4.north) {\tiny{h4 = Softmax(h3 * w4) (output)}}; \node [anchor=south,draw,rounded corners,inner sep=2pt,minimum width=8.0em,minimum height=1.2em,fill=red!30!white,blur shadow={shadow xshift=1pt,shadow yshift=-1pt}] (slayer) at ([yshift=1.5em]h4.north) {\tiny{h4 = Softmax(h3 * w4) (output)}};
\node [anchor=south] (losslabel) at (slayer.north) {\scriptsize{\textbf{Cross Entropy Loss}}}; \node [anchor=south] (losslabel) at (slayer.north) {\scriptsize{\textbf{Cross Entropy Loss}}};
{\draw [<-,very thick,red] (h4.north) -- (slayer.south);} {\draw [->,very thick] (h4.north) -- (slayer.south);}
\end{tikzpicture} \end{tikzpicture}
\end{center} \end{center}
......
...@@ -42,10 +42,10 @@ ...@@ -42,10 +42,10 @@
%% sigmoid box %% sigmoid box
\begin{scope} \begin{scope}
{ {
\node [anchor=west] (flabel) at ([xshift=0.5in]y.east) {\scriptsize{sigmoid:}}; \node [anchor=west] (flabel) at ([xshift=0.5in]y.east) {\scriptsize{Sigmoid:}};
\node [anchor=north east] (slabel) at ([xshift=0]flabel.south east) {\scriptsize{sum:}}; \node [anchor=north east] (slabel) at ([xshift=0]flabel.south east) {\scriptsize{Sum:}};
\node [anchor=west,inner sep=2pt] (flabel2) at (flabel.east) {\scriptsize{$f(s)=1/(1+e^{-s})$}}; \node [anchor=west,inner sep=2pt] (flabel2) at (flabel.east) {\scriptsize{$f(s_2)=1/(1+e^{-s_2})$}};
\node [anchor=west,inner sep=2pt] (flabel3) at (slabel.east) {\scriptsize{$s=x_1 \cdot w + b$}}; \node [anchor=west,inner sep=2pt] (flabel3) at (slabel.east) {\scriptsize{$s_2=x_1 \cdot w_2 + b$}};
\draw [->,thick,dotted] ([yshift=-0.3em,xshift=-0.1em]n11.60) .. controls +(east:1) and +(west:2) .. ([xshift=-0.2em]flabel.west) ; \draw [->,thick,dotted] ([yshift=-0.3em,xshift=-0.1em]n11.60) .. controls +(east:1) and +(west:2) .. ([xshift=-0.2em]flabel.west) ;
\begin{pgfonlayer}{background} \begin{pgfonlayer}{background}
...@@ -136,10 +136,10 @@ ...@@ -136,10 +136,10 @@
%% sigmoid box %% sigmoid box
\begin{scope} \begin{scope}
{ {
\node [anchor=west] (flabel) at ([xshift=0.8in]y.east) {\scriptsize{sigmoid:}}; \node [anchor=west] (flabel) at ([xshift=0.8in]y.east) {\scriptsize{Sigmoid:}};
\node [anchor=north east] (slabel) at ([xshift=0]flabel.south east) {\scriptsize{sum:}}; \node [anchor=north east] (slabel) at ([xshift=0]flabel.south east) {\scriptsize{Sum:}};
\node [anchor=west,inner sep=2pt] (flabel2) at (flabel.east) {\scriptsize{$f(s)=1/(1+e^{-s})$}}; \node [anchor=west,inner sep=2pt] (flabel2) at (flabel.east) {\scriptsize{$f(s_2)=1/(1+e^{-s_2})$}};
\node [anchor=west,inner sep=2pt] (flabel3) at (slabel.east) {\scriptsize{$s=x_1 \cdot w + b$}}; \node [anchor=west,inner sep=2pt] (flabel3) at (slabel.east) {\scriptsize{$s_2=x_1 \cdot w_2 + b$}};
\draw [->,thick,dotted] ([yshift=-0.3em,xshift=-0.1em]n11.60) .. controls +(east:1) and +(west:2) .. ([xshift=-0.2em]flabel.west) ; \draw [->,thick,dotted] ([yshift=-0.3em,xshift=-0.1em]n11.60) .. controls +(east:1) and +(west:2) .. ([xshift=-0.2em]flabel.west) ;
\begin{pgfonlayer}{background} \begin{pgfonlayer}{background}
{ {
......
...@@ -26,12 +26,12 @@ ...@@ -26,12 +26,12 @@
\end{pgfonlayer} \end{pgfonlayer}
\node [anchor=west] (layer00label) at ([xshift=1.3em]x5.east) {\footnotesize{第0层}}; \node [anchor=west] (layer00label) at ([xshift=1.3em]x5.east) {\footnotesize{第0层}};
\node [anchor=west] (layer00label2) at (layer00label.east) {\footnotesize{\red{(输入层)}}}; \node [anchor=west] (layer00label2) at (layer00label.east) {\footnotesize{(输入层)}};
{ {
\node [anchor=west] (layer01label) at ([xshift=1em]layer01.east) {\footnotesize{第1层}}; \node [anchor=west] (layer01label) at ([xshift=1em]layer01.east) {\footnotesize{第1层}};
} }
{ {
\node [anchor=west] (layer01label2) at (layer01label.east) {\footnotesize{\red{({隐层})}}}; \node [anchor=west] (layer01label2) at (layer01label.east) {\footnotesize{({隐层})}};
} }
%%% layer 2 %%% layer 2
...@@ -57,7 +57,7 @@ ...@@ -57,7 +57,7 @@
\node [anchor=west] (layer02label) at ([xshift=4.4em]layer02.east) {\footnotesize{第2层}}; \node [anchor=west] (layer02label) at ([xshift=4.4em]layer02.east) {\footnotesize{第2层}};
{ {
\node [anchor=west] (layer02label2) at (layer02label.east) {\footnotesize{\red{({隐层})}}}; \node [anchor=west] (layer02label2) at (layer02label.east) {\footnotesize{({隐层})}};
} }
} }
...@@ -87,7 +87,7 @@ ...@@ -87,7 +87,7 @@
\node [anchor=west] (layer03label) at ([xshift=1em]layer03.east) {\footnotesize{第3层}}; \node [anchor=west] (layer03label) at ([xshift=1em]layer03.east) {\footnotesize{第3层}};
{ {
\node [anchor=west] (layer03label2) at (layer03label.east) {\footnotesize{\red{({输出层})}}}; \node [anchor=west] (layer03label2) at (layer03label.east) {\footnotesize{({输出层})}};
} }
} }
......
...@@ -4,21 +4,21 @@ $$ ...@@ -4,21 +4,21 @@ $$
\begin{smallmatrix} \underbrace{ \begin{smallmatrix} \underbrace{
\left\{ \left\{
\begin{smallmatrix} \begin{smallmatrix}
\left[ \left(
\begin{array}{cccc} \begin{array}{cccc}
1& 0 &0 \\ 1& 0 &0 \\
0& 1 &0 \\ 0& 1 &0 \\
0& 0 &1 0& 0 &1
\end{array} \end{array}
\right ] \right )
\cdots \cdots
\left[ \left(
\begin{array}{cccc} \begin{array}{cccc}
1& 0 &0 \\ 1& 0 &0 \\
0& 1 &0 \\ 0& 1 &0 \\
0& 0 &1 0& 0 &1
\end{array} \end{array}
\right] \right)
\end{smallmatrix} \end{smallmatrix}
\right\} \right\}
}\\5 }\\5
...@@ -37,21 +37,21 @@ $$ ...@@ -37,21 +37,21 @@ $$
\begin{smallmatrix} \underbrace{ \begin{smallmatrix} \underbrace{
\left\{ \left\{
\begin{smallmatrix} \begin{smallmatrix}
\left[ \left(
\begin{array}{cccc} \begin{array}{cccc}
1 \\ 1 \\
1 \\ 1 \\
1 1
\end{array} \end{array}
\right ] \right)
\cdots \cdots
\left[ \left(
\begin{array}{cccc} \begin{array}{cccc}
1 \\ 1 \\
1 \\ 1 \\
1 1
\end{array} \end{array}
\right] \right)
\end{smallmatrix} \end{smallmatrix}
\right\} \right\}
}\\5 }\\5
......
...@@ -12,7 +12,7 @@ ...@@ -12,7 +12,7 @@
\node [anchor=north] (data) at ([yshift=-1em]system.south) {\scriptsize{\textbf{目标任务有标注数据}}}; \node [anchor=north] (data) at ([yshift=-1em]system.south) {\scriptsize{\textbf{目标任务有标注数据}}};
\draw [->,thick] (data.north) -- ([yshift=-0.1em]system.south); \draw [->,thick] (data.north) -- ([yshift=-0.1em]system.south);
\node [anchor=north] (label) at ([yshift=-0em]data.south) {\scriptsize{(a) standard method}}; \node [anchor=north] (label) at ([yshift=-0em]data.south) {\scriptsize{(a) 标准方法}};
\end{scope} \end{scope}
...@@ -31,7 +31,7 @@ ...@@ -31,7 +31,7 @@
\draw [->,thick] (data.north) -- ([yshift=-0.1em]system.south); \draw [->,thick] (data.north) -- ([yshift=-0.1em]system.south);
\node [anchor=north] (data2) at ([yshift=-1em,xshift=-7em]system.south) {\scriptsize{\textbf{大规模无标注数据}}}; \node [anchor=north] (data2) at ([yshift=-1em,xshift=-7em]system.south) {\scriptsize{\textbf{大规模无标注数据}}};
\draw [->,thick] (data2.north) -- ([yshift=-0.1em]encoderpre.south); \draw [->,thick] (data2.north) -- ([yshift=-0.1em]encoderpre.south);
\node [anchor=north] (label) at ([yshift=-0em,xshift=-4em]data.south) {\scriptsize{(b) pre-training + fine-tuning}}; \node [anchor=north] (label) at ([yshift=-0em,xshift=-4em]data.south) {\scriptsize{(b) 预训练 + 微调}};
\end{scope} \end{scope}
......
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
\node[parametershard,anchor=west,fill=yellow!10] (param1) at (0,0) {$W_o$}; \node[parametershard,anchor=west,fill=yellow!10] (param1) at (0,0) {$W_o$};
\node (param2) at ([xshift=1em]param1.east) {}; \node (param2) at ([xshift=1em]param1.east) {};
\node[parametershard,anchor=west,fill=red!10] (param3) at ([xshift=1em]param2.east) {$W_h$}; \node[parametershard,anchor=west,fill=red!10] (param3) at ([xshift=1em]param2.east) {$W_h$};
\node[anchor=south,inner sep=1pt] (serverlabel) at ([yshift=0.2em]param2.north) {\footnotesize{\textbf{parameter server}: $\mathbf w_{new} = \mathbf w - \alpha\cdot \frac{\partial L}{\partial \mathbf w}$}}; \node[anchor=south,inner sep=1pt] (serverlabel) at ([yshift=0.2em]param2.north) {\footnotesize{\textbf{parameter server}: $\mathbf w_{\textrm{new}} = \mathbf w - \alpha\cdot \frac{\partial L}{\partial \mathbf w}$}};
} }
\begin{pgfonlayer}{background} \begin{pgfonlayer}{background}
...@@ -33,7 +33,7 @@ ...@@ -33,7 +33,7 @@
{ {
\draw[->,very thick,red] ([xshift=-0.5em,yshift=2pt]processor2.north) -- ([xshift=-0.5em,yshift=-2pt]serverbox.south) node [pos=0.5,align=right,xshift=-2em] (pushlabel) {\scriptsize{$\frac{\partial L}{\partial \mathbf w}$}};; \draw[->,very thick,red] ([xshift=-0.5em,yshift=2pt]processor2.north) -- ([xshift=-0.5em,yshift=-2pt]serverbox.south) node [pos=0.5,align=right,xshift=-2em] (pushlabel) {\scriptsize{$\frac{\partial L}{\partial \mathbf w}$}};;
\draw[<-,very thick,blue] ([xshift=0.5em,yshift=2pt]processor2.north) -- ([xshift=0.5em,yshift=-2pt]serverbox.south) node [pos=0.5,align=left,xshift=2.2em] (fetchlabel) {\scriptsize{$\mathbf w_{new}$}};;; \draw[<-,very thick,blue] ([xshift=0.5em,yshift=2pt]processor2.north) -- ([xshift=0.5em,yshift=-2pt]serverbox.south) node [pos=0.5,align=left,xshift=2.2em] (fetchlabel) {\scriptsize{$\mathbf w_{\textrm{new}}$}};;;
\draw[->,very thick,red] ([xshift=-0.5em,yshift=2pt]processor3.north) -- \draw[->,very thick,red] ([xshift=-0.5em,yshift=2pt]processor3.north) --
([xshift=3em,yshift=-2pt]serverbox.south); ([xshift=3em,yshift=-2pt]serverbox.south);
\draw[<-,very thick,blue] ([xshift=0.5em,yshift=2pt]processor3.north) -- ([xshift=4em,yshift=-2pt]serverbox.south) node [pos=0.5,align=left,xshift=2.2em] (fetchlabel) {\scriptsize{fetch (F)}}; \draw[<-,very thick,blue] ([xshift=0.5em,yshift=2pt]processor3.north) -- ([xshift=4em,yshift=-2pt]serverbox.south) node [pos=0.5,align=left,xshift=2.2em] (fetchlabel) {\scriptsize{fetch (F)}};
......
\definecolor{ublue}{rgb}{0.152,0.250,0.545}
\begin{tikzpicture}
\begin{axis}[
width=8cm, height=5cm,
xtick={-6,-4,...,6},
ytick={0,0.5,1},
xlabel={\small{$x$}},
ylabel={\small{Softmax($x$)}},
xlabel style={xshift=3.0cm,yshift=1cm},
axis y line=middle,
ylabel style={xshift=-2.4cm,yshift=-0.2cm},
x axis line style={->},
axis line style={very thick},
% ymajorgrids,
%xmajorgrids,
axis x line*=bottom,
xmin=-6,
xmax=6,
ymin=0,
ymax=1]
\addplot[draw=ublue,very thick]{(tanh(x/2) + 1)/2};
\end{axis}
\end{tikzpicture}
%---------------------------------------------------------------------
\ No newline at end of file
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
\begin{tikzpicture} \begin{tikzpicture}
\begin{scope}[yshift=6.5em,xshift=1em] \begin{scope}[yshift=6.5em,xshift=1em]
\setcounter{mycount1}{1} \setcounter{mycount1}{1}
\draw[step=0.5cm,color=orange,line width=0.2mm] (-2,-2) grid (1,1); \draw[step=0.5cm,color=orange,line width=0.4mm] (-2,-2) grid (1,1);
\foreach \y in {+0.5,-0.5,-1.5} \foreach \y in {+0.5,-0.5,-1.5}
\foreach \x in {-1.5,-0.5,0.5}{ \foreach \x in {-1.5,-0.5,0.5}{
\node [fill=orange!20,inner sep=0pt,minimum height=0.98cm,minimum width=0.98cm] at (\x,\y) {\number\value{mycount1}}; \node [fill=orange!20,inner sep=0pt,minimum height=0.98cm,minimum width=0.98cm] at (\x,\y) {\number\value{mycount1}};
...@@ -17,7 +17,7 @@ ...@@ -17,7 +17,7 @@
\begin{scope}[yshift=5.5em,xshift=0em] \begin{scope}[yshift=5.5em,xshift=0em]
\setcounter{mycount2}{2} \setcounter{mycount2}{2}
\draw[step=0.5cm,color=blue,line width=0.2mm] (-2,-2) grid (1,1); \draw[step=0.5cm,color=blue,line width=0.4mm] (-2,-2) grid (1,1);
\foreach \y in {+0.5,-0.5,-1.5} \foreach \y in {+0.5,-0.5,-1.5}
\foreach \x in {-1.5,-0.5,0.5}{ \foreach \x in {-1.5,-0.5,0.5}{
\node [fill=blue!20,inner sep=0pt,minimum height=0.98cm,minimum width=0.98cm] at (\x,\y) {\number\value{mycount2}}; \node [fill=blue!20,inner sep=0pt,minimum height=0.98cm,minimum width=0.98cm] at (\x,\y) {\number\value{mycount2}};
...@@ -27,7 +27,7 @@ ...@@ -27,7 +27,7 @@
\begin{scope}[yshift=4.5em,xshift=-1em] \begin{scope}[yshift=4.5em,xshift=-1em]
\setcounter{mycount3}{3} \setcounter{mycount3}{3}
\draw[step=0.5cm,color=ugreen,line width=0.2mm] (-2,-2) grid (1,1); \draw[step=0.5cm,color=ugreen,line width=0.4mm] (-2,-2) grid (1,1);
\foreach \y in {+0.5,-0.5,-1.5} \foreach \y in {+0.5,-0.5,-1.5}
\foreach \x in {-1.5,-0.5,0.5}{ \foreach \x in {-1.5,-0.5,0.5}{
\node [fill=green!20,inner sep=0pt,minimum height=0.98cm,minimum width=0.98cm] at (\x,\y) {\number\value{mycount3}}; \node [fill=green!20,inner sep=0pt,minimum height=0.98cm,minimum width=0.98cm] at (\x,\y) {\number\value{mycount3}};
...@@ -37,7 +37,7 @@ ...@@ -37,7 +37,7 @@
\begin{scope}[yshift=3.5em,xshift=-2em] \begin{scope}[yshift=3.5em,xshift=-2em]
\setcounter{mycount4}{4} \setcounter{mycount4}{4}
\draw[step=0.5cm,color=red,line width=0.2mm] (-2,-2) grid (1,1); \draw[step=0.5cm,color=red,line width=0.4mm] (-2,-2) grid (1,1);
\foreach \y in {+0.5,-0.5,-1.5} \foreach \y in {+0.5,-0.5,-1.5}
\foreach \x in {-1.5,-0.5,0.5}{ \foreach \x in {-1.5,-0.5,0.5}{
\node [fill=red!20,inner sep=0pt,minimum height=0.98cm,minimum width=0.98cm] at (\x,\y) {\number\value{mycount4}}; \node [fill=red!20,inner sep=0pt,minimum height=0.98cm,minimum width=0.98cm] at (\x,\y) {\number\value{mycount4}};
......
...@@ -44,10 +44,10 @@ ...@@ -44,10 +44,10 @@
%% sigmoid box %% sigmoid box
\begin{scope} \begin{scope}
{ {
\node [anchor=west] (flabel) at ([xshift=1in]y.east) {\footnotesize{sigmoid:}}; \node [anchor=west] (flabel) at ([xshift=1in]y.east) {\footnotesize{Sigmoid:}};
\node [anchor=north east] (slabel) at ([xshift=0]flabel.south east) {\footnotesize{sum:}}; \node [anchor=north east] (slabel) at ([xshift=0]flabel.south east) {\footnotesize{Sum:}};
\node [anchor=west,inner sep=2pt] (flabel2) at (flabel.east) {\footnotesize{$f(s)=1/(1+e^{-s})$}}; \node [anchor=west,inner sep=2pt] (flabel2) at (flabel.east) {\footnotesize{$f(s_2)=1/(1+e^{-s_2})$}};
\node [anchor=west,inner sep=2pt] (flabel3) at (slabel.east) {\footnotesize{$s=x_1 \cdot w + b$}}; \node [anchor=west,inner sep=2pt] (flabel3) at (slabel.east) {\footnotesize{$s_2=x_1 \cdot w_2 + b$}};
\draw [->,thick,dotted] ([yshift=-0.3em,xshift=-0.1em]n11.60) .. controls +(east:1) and +(west:2) .. ([xshift=-0.2em]flabel.west) ; \draw [->,thick,dotted] ([yshift=-0.3em,xshift=-0.1em]n11.60) .. controls +(east:1) and +(west:2) .. ([xshift=-0.2em]flabel.west) ;
\begin{pgfonlayer}{background} \begin{pgfonlayer}{background}
......
...@@ -757,7 +757,7 @@ x_0\cdot w_0+x_1\cdot w_1+x_2\cdot w_2 & = & 0\cdot 1+0\cdot 1+1\cdot 1 \nonumbe ...@@ -757,7 +757,7 @@ x_0\cdot w_0+x_1\cdot w_1+x_2\cdot w_2 & = & 0\cdot 1+0\cdot 1+1\cdot 1 \nonumbe
%------------------------------------------- %-------------------------------------------
\vspace{-0.5em} \vspace{-0.5em}
\parinterval 那激活函数又是什么?神经元在接收到经过线性变换的结果后,通过激活函数的处理,得到最终的输出$ \mathbf y $。激活函数的目的是解决实际问题中的非线性变换,线性变换只能拟合直线,而激活函数的加入,使神经网络具有了拟合曲线的能力。 特别是在实际问题中,很多现象都无法用简单的线性关系描述,这时激活函数的非线性就为描述更加复杂的问题提供了工具。常见的非线性函数有Sigmoid、ReLU、Tanh等。如图\ref{fig:5-15}列举了几种激活函数的形式。\\ \parinterval 那激活函数又是什么?神经元在接收到经过线性变换的结果后,通过激活函数的处理,得到最终的输出$ \mathbf y $。激活函数的目的是解决实际问题中的非线性变换,线性变换只能拟合直线,而激活函数的加入,使神经网络具有了拟合曲线的能力。 特别是在实际问题中,很多现象都无法用简单的线性关系描述,这时可以使用非线性激活函数来描述更加复杂的问题。常见的非线性函数有Sigmoid、ReLU、Tanh等。如图\ref{fig:5-15}列举了几种激活函数的形式。\\
%---------------------------------------------- %----------------------------------------------
\begin{figure}\centering \begin{figure}\centering
...@@ -819,7 +819,7 @@ x_0\cdot w_0+x_1\cdot w_1+x_2\cdot w_2 & = & 0\cdot 1+0\cdot 1+1\cdot 1 \nonumbe ...@@ -819,7 +819,7 @@ x_0\cdot w_0+x_1\cdot w_1+x_2\cdot w_2 & = & 0\cdot 1+0\cdot 1+1\cdot 1 \nonumbe
\begin{figure}[htp] \begin{figure}[htp]
\centering \centering
\input{./Chapter9/Figures/fig-four-layers-of-neural-network} \input{./Chapter9/Figures/fig-four-layers-of-neural-network}
\caption{具有四层神经元的三层神经网络} \caption{具有四层神经元的(三层)神经网络}
\label{fig:5-17} \label{fig:5-17}
\end{figure} \end{figure}
%------------------------------------------- %-------------------------------------------
...@@ -834,7 +834,11 @@ x_0\cdot w_0+x_1\cdot w_1+x_2\cdot w_2 & = & 0\cdot 1+0\cdot 1+1\cdot 1 \nonumbe ...@@ -834,7 +834,11 @@ x_0\cdot w_0+x_1\cdot w_1+x_2\cdot w_2 & = & 0\cdot 1+0\cdot 1+1\cdot 1 \nonumbe
\parinterval 神经网络方法之所以受到青睐一方面是由于它提供了端到端学习的模式,另一方面是由于它强大的函数拟合能力。理论上说,神经网络可以拟合任何形状的函数。下面就来看一下为什么神经网络会有这样的能力。 \parinterval 神经网络方法之所以受到青睐一方面是由于它提供了端到端学习的模式,另一方面是由于它强大的函数拟合能力。理论上说,神经网络可以拟合任何形状的函数。下面就来看一下为什么神经网络会有这样的能力。
\parinterval 众所周知,单层神经网络无法解决线性不可分问题,比如经典的异或问题。但是具有一个隐藏层的两层神经网络在理论上就可以拟合所有的函数了。接下来我们分析一下为什么仅仅是多了一层,神经网络就能变得如此强大。在此之前,需要明确的一点是,``拟合''是把平面上一系列的点,用一条光滑的曲线连接起来,并用函数来表示这条拟合的曲线。在用神经网络解决问题时,可以通过拟合训练数据中的``数据点''来获得输入与输出之间的函数关系,并利用其对未知数据做出判断。可以假设输入与输出之间存在一种函数关系,而神经网络的``拟合''能力是要尽可能地逼近原函数输出值,与原函数输出值越逼近,则意味着拟合得越优秀。 \parinterval 众所周知,单层神经网络无法解决线性不可分问题,比如经典的异或问题。但是具有一个隐藏层的两层神经网络在理论上就可以拟合所有的函数了。接下来我们分析一下为什么仅仅是多了一层,神经网络就能变得如此强大。对于二维空间(平面),``拟合''是指:把平面上一系列的点,用一条光滑的曲线连接起来,并用函数来表示这条拟合的曲线。这个概念可以推广到更高维空间上。在用神经网络解决问题时,可以通过拟合训练数据中的`` 数据点''来获得输入与输出之间的函数关系,并利用其对未知数据做出判断。可以假设输入与输出之间存在一种函数关系,而神经网络的``拟合''能力是要尽可能地逼近原函数输出值,与原函数输出值越逼近,则意味着拟合得越好。
\parinterval 如图\ref{fig:5-18}是一个以Sigmoid作为隐藏层激活函数的两层神经网络。通过调整参数$ \mathbf w=(w_1,w_2) $$ \mathbf b=(b_1,b_2) $$ \mathbf w^{'}=(w'_{1},w'_{2}) $ 的值,可以不断地改变目标函数的形状。
%---------------------------------------------- %----------------------------------------------
\begin{figure}[htp] \begin{figure}[htp]
...@@ -845,18 +849,17 @@ x_0\cdot w_0+x_1\cdot w_1+x_2\cdot w_2 & = & 0\cdot 1+0\cdot 1+1\cdot 1 \nonumbe ...@@ -845,18 +849,17 @@ x_0\cdot w_0+x_1\cdot w_1+x_2\cdot w_2 & = & 0\cdot 1+0\cdot 1+1\cdot 1 \nonumbe
\end{figure} \end{figure}
%------------------------------------------- %-------------------------------------------
\parinterval 如图\ref{fig:5-18}是一个以Sigmoid作为隐藏层激活函数的两层神经网络。通过调整参数$ \mathbf w=(w_1,w_2) $$ \mathbf b=(b_1,b_2) $$ \mathbf w^{'}=(w'_{1},w'_{2}) $ 的值,可以不断地改变目标函数的形状 \parinterval 设置$ w'_1=1 $$ w_1=1 $$ b_1=0 $,其他参数设置为0。可以得到如图\ref{fig:5-19}(a)所示的目标函数,此时目标函数还是比较平缓的。通过调大$ w_1 $,可以将图\ref{fig:5-19}(a) 中函数的坡度调得更陡:当$ w_1=10 $时,如图\ref{fig:5-19}(b)所示,目标函数的坡度与图\ref{fig:5-19}(a)相比变得更陡了;当$ w_1=100 $时,如图\ref{fig:5-19}(c)所示,目标函数的坡度变得更陡、更尖锐,已经逼近一个阶梯函数
%---------------------------------------------- %----------------------------------------------
\begin{figure}[htp] \begin{figure}[htp]
\centering \centering
\input{./Chapter9/Figures/fig-weight} \input{./Chapter9/Figures/fig-weight}
\caption{通过改变权重$ w_1 $改变目标函数平滑程度} \caption{通过调整权重$ w_1 $改变目标函数平滑程度}
\label{fig:5-19} \label{fig:5-19}
\end {figure} \end {figure}
%------------------------------------------- %-------------------------------------------
\parinterval 设置$ w'_1=1 $$ w_1=1 $$ b_1=0 $,其他参数设置为0。可以得到如图\ref{fig:5-19}(a)所示的目标函数,此时目标函数还是比较平缓的。通过调大$ w_1 $,可以将图\ref{fig:5-19}(a) 中函数的坡度调得更陡:当$ w_1=10 $时,如图\ref{fig:5-19}(b)所示,目标函数的坡度与图\ref{fig:5-19}(a)相比变得更陡了;当$ w_1=100 $时,如图\ref{fig:5-19}(c)所示,目标函数的坡度变得更陡、更尖锐,已经逼近一个阶梯函数。
\parinterval 设置$ w'_1=1 $$ w_1=100 $$ b_1=0 $,其他参数设置为0。可以得到如图\ref{fig:5-20}(a)所示的目标函数,此时目标函数是一个阶梯函数,其``阶梯''恰好与y轴重合。通过改变$ b_1 $,可以将整个函数沿x轴向左右平移:当$ b_1=-2 $时,如图\ref{fig:5-20}(b)所示,与图\ref{fig:5-20}(a)相比目标函数的形状没有发生改变,但其位置沿x轴向右平移;当$ b_1=-4 $时,如图\ref{fig:5-20}(c)所示,目标函数的位置继续沿x轴向右平移。 \parinterval 设置$ w'_1=1 $$ w_1=100 $$ b_1=0 $,其他参数设置为0。可以得到如图\ref{fig:5-20}(a)所示的目标函数,此时目标函数是一个阶梯函数,其``阶梯''恰好与y轴重合。通过改变$ b_1 $,可以将整个函数沿x轴向左右平移:当$ b_1=-2 $时,如图\ref{fig:5-20}(b)所示,与图\ref{fig:5-20}(a)相比目标函数的形状没有发生改变,但其位置沿x轴向右平移;当$ b_1=-4 $时,如图\ref{fig:5-20}(c)所示,目标函数的位置继续沿x轴向右平移。
...@@ -864,7 +867,7 @@ x_0\cdot w_0+x_1\cdot w_1+x_2\cdot w_2 & = & 0\cdot 1+0\cdot 1+1\cdot 1 \nonumbe ...@@ -864,7 +867,7 @@ x_0\cdot w_0+x_1\cdot w_1+x_2\cdot w_2 & = & 0\cdot 1+0\cdot 1+1\cdot 1 \nonumbe
\begin{figure}[htp] \begin{figure}[htp]
\centering \centering
\input{./Chapter9/Figures/fig-bias} \input{./Chapter9/Figures/fig-bias}
\caption{通过改变偏置量$ b_1 $改变目标函数位置} \caption{通过调整偏置量$ b_1 $改变目标函数位置}
\label{fig:5-20} \label{fig:5-20}
\end {figure} \end {figure}
%------------------------------------------- %-------------------------------------------
...@@ -886,7 +889,7 @@ x_0\cdot w_0+x_1\cdot w_1+x_2\cdot w_2 & = & 0\cdot 1+0\cdot 1+1\cdot 1 \nonumbe ...@@ -886,7 +889,7 @@ x_0\cdot w_0+x_1\cdot w_1+x_2\cdot w_2 & = & 0\cdot 1+0\cdot 1+1\cdot 1 \nonumbe
\begin{figure}[htp] \begin{figure}[htp]
\centering \centering
\input{./Chapter9/Figures/fig-w2} \input{./Chapter9/Figures/fig-w2}
\caption{通过设置第二组参数将目标函数分段数增加} \caption{通过设置第二组参数$b_2$$w'_2$将目标函数分段数增加}
\label{fig:5-22} \label{fig:5-22}
\end {figure} \end {figure}
%------------------------------------------- %-------------------------------------------
...@@ -915,7 +918,7 @@ x_0\cdot w_0+x_1\cdot w_1+x_2\cdot w_2 & = & 0\cdot 1+0\cdot 1+1\cdot 1 \nonumbe ...@@ -915,7 +918,7 @@ x_0\cdot w_0+x_1\cdot w_1+x_2\cdot w_2 & = & 0\cdot 1+0\cdot 1+1\cdot 1 \nonumbe
\end {figure} \end {figure}
%------------------------------------------- %-------------------------------------------
\parinterval 两层神经元的神经网络在理论上可以拟合所有函数了,但是在实际问题中所使用的神经网络都远远超过了两层,这也是对深度学习这个概念中``深度''的一种体现。主要是有以下几方面的原因: \parinterval 两层神经元的神经网络在理论上可以拟合所有函数了,但是在实际问题中所使用的神经网络都远远超过了两层,这也是对深度学习这个概念中``深度''的一种体现。使用深层神经网络主要有以下几方面的原因:
\begin{itemize} \begin{itemize}
\vspace{0.5em} \vspace{0.5em}
...@@ -972,7 +975,7 @@ x_0\cdot w_0+x_1\cdot w_1+x_2\cdot w_2 & = & 0\cdot 1+0\cdot 1+1\cdot 1 \nonumbe ...@@ -972,7 +975,7 @@ x_0\cdot w_0+x_1\cdot w_1+x_2\cdot w_2 & = & 0\cdot 1+0\cdot 1+1\cdot 1 \nonumbe
\end{figure} \end{figure}
%------------------------------------------- %-------------------------------------------
\parinterval 虽然这里所使用的张量是出于编程实现的视角,但是数学中张量有严格的定义。从数学上看``张量并不是向量和矩阵的简单扩展,多维数组也并不是张量所必须的表达形式''。从某种意义上说,矩阵才是张量的扩展。当然,这个逻辑可能和大家在深度学习中的认知是不一致的。但是,本书仍然遵循深度学习中常用的概念,把张量理解为多维数组。在保证数学表达的简洁性的同时,使程序实现接口更加统一。 \parinterval 虽然这里所使用的张量是出于编程实现的视角,但是数学中张量有严格的定义。从数学上看``张量并不是向量和矩阵的简单扩展,多维数组也并不是张量所必须的表达形式''。从某种意义上说,矩阵才是张量的扩展。当然,这个逻辑可能和人们在深度学习中的认知是不一致的。但是,本书仍然遵循深度学习中常用的概念,把张量理解为多维数组。在保证数学表达的简洁性的同时,使程序实现接口更加统一。
%---------------------------------------------------------------------------------------- %----------------------------------------------------------------------------------------
% NEW SUBSUB-SECTION % NEW SUBSUB-SECTION
...@@ -1050,7 +1053,7 @@ f(x)=\begin{cases} 0 & x\le 0 \\x & x>0\end{cases} ...@@ -1050,7 +1053,7 @@ f(x)=\begin{cases} 0 & x\le 0 \\x & x>0\end{cases}
\subsection{张量的物理存储形式} \subsection{张量的物理存储形式}
\parinterval 在深度学习世界中,张量就是多维数组,无论是向量还是矩阵都可以看作是数学上``张量''的扩展。因此,张量的物理存储方式也与多维数组相同 \parinterval 在深度学习世界中,张量就是多维数组。因此,张量的物理存储方式也与多维数组相同。如下就是一些实例
\begin{itemize} \begin{itemize}
\vspace{0.5em} \vspace{0.5em}
...@@ -1077,17 +1080,11 @@ f(x)=\begin{cases} 0 & x\le 0 \\x & x>0\end{cases} ...@@ -1077,17 +1080,11 @@ f(x)=\begin{cases} 0 & x\le 0 \\x & x>0\end{cases}
% NEW SUB-SECTION % NEW SUB-SECTION
%---------------------------------------------------------------------------------------- %----------------------------------------------------------------------------------------
\subsection{实现张量计算的实现示例} \subsection{张量的实现手段}
\parinterval {\red{前两段可能需要删掉}}
\parinterval 实现神经网络的开源系统有很多,比如,一个简单好用的Python工具包\ \dash \ Numpy(\url{https://numpy.org/})。Numpy提供了张量表示和使用的范式,可以很方便地定义、使用多维数组。
\parinterval 此外,如今深度学习框架已经非常成熟。比如, Tensorflow和Pytorch就是非常受欢迎的深度学习工具包,除此之外还有很多其他优秀的框架:CNTK、MXNet、\\PaddlePaddle、Keras、Chainer、dl4j、NiuTensor等。开发者可以根据自身的喜好和开发项目的要求选择所采用的框架。
\parinterval 本节将使用NiuTensor来描述张量计算。NiuTensor是一个面向自然语言处理任务的张量库,它支持丰富的张量计算接口。此外,该NiuTensor内核基于C++语言编写,代码高度优化。该工具包获取网址为\url{https://developer.niutrans.com/ArticleContent/technicaldoc/doc1/1.NiuTensor} \parinterval 实现神经网络的开源系统有很多,比如,使用经典的Python工具包Numpy。也可以使用成熟的深度学习框架,比如,Tensorflow和Pytorch就是非常受欢迎的深度学习工具包,除此之外还有很多其他优秀的框架:CNTK、MXNet、PaddlePaddle、\\Keras、Chainer、dl4j、NiuTensor等。开发者可以根据自身的喜好和开发项目的要求选择所采用的框架
\parinterval NiuTensor的使用非常简单,如图\ref{fig:5-30}是一个使用NiuTensor声明、定义张量的C++代码: \parinterval 本节将使用NiuTensor来描述张量计算。NiuTensor是一个面向自然语言处理任务的张量库,它支持丰富的张量计算接口。如图\ref{fig:5-30}是一个使用NiuTensor声明、定义张量的C++代码:
%---------------------------------------------- %----------------------------------------------
\begin{figure}[htp] \begin{figure}[htp]
...@@ -1145,7 +1142,8 @@ f(x)=\begin{cases} 0 & x\le 0 \\x & x>0\end{cases} ...@@ -1145,7 +1142,8 @@ f(x)=\begin{cases} 0 & x\le 0 \\x & x>0\end{cases}
\end{figure} \end{figure}
%------------------------------------------- %-------------------------------------------
\parinterval 除了上述单元算子外,NiuTensor还支持张量之间的高阶运算,其中最常用的是矩阵乘法,图\ref{fig:5-35}(b)是张量之间进行矩阵乘法的程序示例。表\ref{tab:5-2}展示了一些NiuTensor支持的其他函数操作,除此还有很多其他操作无法在此一一列举,有兴趣可以参考网站上的详细说明。 \parinterval 除了上述单元算子外,NiuTensor还支持张量之间的高阶运算,其中最常用的是矩阵乘法,图\ref{fig:5-35}(b)是张量之间进行矩阵乘法的程序示例。表\ref{tab:5-2}展示了一些NiuTensor支持的其他函数操作。
%-------------------------------------------------------------------- %--------------------------------------------------------------------
\begin{table}[htp] \begin{table}[htp]
\centering \centering
...@@ -1182,7 +1180,7 @@ f(x)=\begin{cases} 0 & x\le 0 \\x & x>0\end{cases} ...@@ -1182,7 +1180,7 @@ f(x)=\begin{cases} 0 & x\le 0 \\x & x>0\end{cases}
\subsection{前向传播与计算图} \subsection{前向传播与计算图}
\parinterval 有了张量这个工具,可以很容易地实现任意的神经网络。反过来,神经网络都可以被看作是张量的函数。一种经典的神经网络计算模型是:给定输入张量,通过各个神经网络层所对应的张量计算之后,最后得到输出张量。这个过程也被称作{\small\sffamily\bfseries{前向传播}}\index{前向传播},它常常被应用在使用神经网络对新的样本进行推断中。 \parinterval 有了张量这个工具,可以很容易地实现任意的神经网络。反过来,神经网络都可以被看作是张量的函数。一种经典的神经网络计算模型是:给定输入张量,通过各个神经网络层所对应的张量计算之后,最后得到输出张量。这个过程也被称作{\small\sffamily\bfseries{前向传播}}\index{前向传播}(Forward Prorogation\index{Forward Prorogation},它常常被应用在使用神经网络对新的样本进行推断中。
\parinterval 来看一个具体的例子,如图\ref{fig:5-37}(a)是一个根据天气情况判断穿衣指数(穿衣指数是人们穿衣薄厚的依据)的过程,将当天的天空状况、低空气温、水平气压作为输入,通过一层神经元在输入数据中提取温度、风速两方面的特征,并根据这两方面的特征判断穿衣指数。需要注意的是,在实际的神经网络中,并不能准确地知道神经元究竟可以提取到哪方面的特征,以上表述是为了让读者更好地理解神经网络的建模过程和前向传播过程。这里将上述过程建模为如图\ref{fig:5-37}(b)所示的两层神经网络。 \parinterval 来看一个具体的例子,如图\ref{fig:5-37}(a)是一个根据天气情况判断穿衣指数(穿衣指数是人们穿衣薄厚的依据)的过程,将当天的天空状况、低空气温、水平气压作为输入,通过一层神经元在输入数据中提取温度、风速两方面的特征,并根据这两方面的特征判断穿衣指数。需要注意的是,在实际的神经网络中,并不能准确地知道神经元究竟可以提取到哪方面的特征,以上表述是为了让读者更好地理解神经网络的建模过程和前向传播过程。这里将上述过程建模为如图\ref{fig:5-37}(b)所示的两层神经网络。
...@@ -1211,7 +1209,9 @@ y&=&{\rm{Sigmoid}}({\rm{Tanh}}(\mathbf x\cdot \mathbf w^{[1]}+\mathbf b^{[1]})\c ...@@ -1211,7 +1209,9 @@ y&=&{\rm{Sigmoid}}({\rm{Tanh}}(\mathbf x\cdot \mathbf w^{[1]}+\mathbf b^{[1]})\c
\end{figure} \end{figure}
%------------------------------------------- %-------------------------------------------
\parinterval\ref{fig:5-38}实际上是神经网络的一种{\small\bfnew{计算图}}\index{计算图}(Computation Graph)\index{Computation Graph}表示。现在很多深度学习框架都是把神经网络转化为计算图,这样可以把复杂的运算分解为简单的运算。通过对计算图中节点的遍历,可以方便地完成神经网络的计算。比如,可以对图中节点进行拓扑排序(由输入到输出),之后依次访问每个节点,同时完成相应的计算,这也就实现了一个前向计算的过程。构建计算图的方式有很多,比如,动态图、静态图等。在\ref{sec5:para-training}节会进一步对计算图在模型参数训练中的应用进行介绍。 \parinterval\ref{fig:5-38}实际上是神经网络的一种{\small\bfnew{计算图}}\index{计算图}(Computation Graph)\index{Computation Graph}表示。现在很多深度学习框架都是把神经网络转化为计算图,这样可以把复杂的运算分解为简单的运算,称为{\small\bfnew{算子}}\index{算子}(Calculus\index{Calculus})。通过对计算图中节点的遍历,可以方便地完成神经网络的计算。比如,可以对图中节点进行拓扑排序(由输入到输出),之后依次访问每个节点,同时完成相应的计算,这也就实现了一个前向计算的过程。
\parinterval 使用计算图的另一个优点在于,这种方式易于参数梯度的计算。在后面的内容中会看到,计算神经网络中参数的梯度是模型训练的重要步骤。在计算图中,可以使用{\small\bfnew{反向传播}}\index{反向传播} (Backward Prorogation\index{Backward Prorogation})的方式逐层计算不同节点上的梯度信息。在\ref{sec5:para-training} 节会看到使用计算图这种结构可以非常方便、高效地计算反向传播中所需的梯度信息。
%---------------------------------------------------------------------------------------- %----------------------------------------------------------------------------------------
% NEW SECTION % NEW SECTION
...@@ -1220,7 +1220,7 @@ y&=&{\rm{Sigmoid}}({\rm{Tanh}}(\mathbf x\cdot \mathbf w^{[1]}+\mathbf b^{[1]})\c ...@@ -1220,7 +1220,7 @@ y&=&{\rm{Sigmoid}}({\rm{Tanh}}(\mathbf x\cdot \mathbf w^{[1]}+\mathbf b^{[1]})\c
\sectionnewpage \sectionnewpage
\section{神经网络的参数训练} \section{神经网络的参数训练}
\parinterval 简单来说,神经网络可以被看作是由变量和函数组成的表达式,例如:$ \mathbf y=\mathbf x+\mathbf b $$ \mathbf y={\rm{ReLU}}(\mathbf x\cdot \mathbf w+\mathbf b) $$ \mathbf y={\rm{Sigmoid}}({\rm{ReLU}}(\mathbf x\cdot \mathbf w^{[1]}+\mathbf b^{[1]})\cdot \mathbf w^{[2]}+\mathbf b^{[2]}) $等等,其中的$ \mathbf x $$ \mathbf y $作为输入和输出变量, $ \mathbf w $$ \mathbf b $等其他变量作为{\small\sffamily\bfseries{模型参数}}\index{模型参数}(Model Parameters)\index{Model Parameters}。确定了函数表达式和模型参数,也就确定了神经网络模型。通常,表达式的形式需要系统开发者设计,而模型参数的数量有时会非常巨大,因此需要自动学习,这个过程也被称为模型学习或{\small\bfnew{训练}}\index{训练}(Training)\index{Training}。为了实现这个目标,通常会准备一定量的带有标准答案的数据,称之为{\small\sffamily\bfseries{有标注数据}}\index{有标注数据}(Annotated Data/Labeled Data)\index{Annotated Data/Labeled Data}。这些数据会用于对模型参数的学习,这也对应了统计模型中的参数估计过程。在机器学习中,一般把这种使用有标注数据进行统计模型参数训练的过程称为{\small\sffamily\bfseries{有指导的训练}}\index{有指导的训练}{\small\sffamily\bfseries{有监督的训练}}\index{有监督的训练}(Supervised Training)\index{Supervised Training}。在本章中,如果没有特殊说明,模型训练都是指有监督的训练。那么神经网络内部是怎样利用有标注数据对参数进行训练的呢?\\ \\ \parinterval 简单来说,神经网络可以被看作是由变量和函数组成的表达式,例如:$ \mathbf y=\mathbf x+\mathbf b $$ \mathbf y={\rm{ReLU}}(\mathbf x\cdot \mathbf w+\mathbf b) $$ \mathbf y={\rm{Sigmoid}}({\rm{ReLU}}(\mathbf x\cdot \mathbf w^{[1]}+\mathbf b^{[1]})\cdot \mathbf w^{[2]}+\mathbf b^{[2]}) $等等,其中的$ \mathbf x $$ \mathbf y $作为输入和输出变量, $ \mathbf w $$ \mathbf b $等其他变量作为{\small\sffamily\bfseries{模型参数}}\index{模型参数}(Model Parameters)\index{Model Parameters}。确定了函数表达式和模型参数,也就确定了神经网络模型。通常,表达式的形式需要系统开发者设计,而模型参数的数量有时会非常巨大,因此需要自动学习,这个过程也被称为模型学习或{\small\bfnew{训练}}\index{训练}(Training)\index{Training}。为了实现这个目标,通常会准备一定量的带有标准答案的数据,称之为{\small\sffamily\bfseries{有标注数据}}\index{有标注数据}(Annotated Data\index{Annotated Data}/Labeled Data\index{Labeled Data}。这些数据会用于对模型参数的学习,这也对应了统计模型中的参数估计过程。在机器学习中,一般把这种使用有标注数据进行统计模型参数训练的过程称为{\small\sffamily\bfseries{有指导的训练}}\index{有指导的训练}{\small\sffamily\bfseries{有监督的训练}}\index{有监督的训练}(Supervised Training)\index{Supervised Training}。在本章中,如果没有特殊说明,模型训练都是指有监督的训练。那么神经网络内部是怎样利用有标注数据对参数进行训练的呢?\\ \\
\vspace{-2.5em} \vspace{-2.5em}
\parinterval 为了回答这个问题,可以把模型参数的学习过程看作是一个优化问题,即找到一组参数,使得模型达到某种最优的状态。这个问题又可以被转化为两个新的问题: \parinterval 为了回答这个问题,可以把模型参数的学习过程看作是一个优化问题,即找到一组参数,使得模型达到某种最优的状态。这个问题又可以被转化为两个新的问题:
...@@ -1271,12 +1271,13 @@ y&=&{\rm{Sigmoid}}({\rm{Tanh}}(\mathbf x\cdot \mathbf w^{[1]}+\mathbf b^{[1]})\c ...@@ -1271,12 +1271,13 @@ y&=&{\rm{Sigmoid}}({\rm{Tanh}}(\mathbf x\cdot \mathbf w^{[1]}+\mathbf b^{[1]})\c
\rule{0pt}{15pt} Logistic损失 & $ L={\rm{log}}(1+\mathbf {\widetilde y}_i\cdot \mathbf y_i) $ & 回归 \\ \rule{0pt}{15pt} Logistic损失 & $ L={\rm{log}}(1+\mathbf {\widetilde y}_i\cdot \mathbf y_i) $ & 回归 \\
\rule{0pt}{15pt} 平方损失 & $ L={(\mathbf {\widetilde y}_i-\mathbf y_i)}^2 $ & 回归 \\ \rule{0pt}{15pt} 平方损失 & $ L={(\mathbf {\widetilde y}_i-\mathbf y_i)}^2 $ & 回归 \\
\rule{0pt}{15pt} 指数损失 & $ L={\rm{exp}}(-\mathbf {\widetilde y}_i\cdot \mathbf y_i) $ & AdaBoost \\ \rule{0pt}{15pt} 指数损失 & $ L={\rm{exp}}(-\mathbf {\widetilde y}_i\cdot \mathbf y_i) $ & AdaBoost \\
\rule{0pt}{15pt} 交叉熵损失 & $ L=-\sum_{k}{\mathbf y_i^{[k]}{\rm {log}}\mathbf {\widetilde y}_i^{[k]}} $ & 多分类 \\ \rule{0pt}{15pt} 交叉熵损失 & $ L=-\sum_{k}{\mathbf y_i(k)}{\rm {log}}\mathbf {\widetilde y}_i(k) $ & 多分类 \\
\rule{0pt}{15pt} & 其中,$\mathbf y_i(k)$ 表示 $\mathbf y_i$的第$k$
\end{tabular} \end{tabular}
\end{table} \end{table}
%-------------------------------------------------------------------- %--------------------------------------------------------------------
\parinterval 在实际系统开发中,损失函数中除了损失项(即用来度量正确答案$ \mathbf {\widetilde y}_i $和神经网络输出$ \mathbf y_i $之间的偏差的部分)之外,还可以包括正则项,比如L1正则和L2正则。设置正则项本质上是要加入一些偏置,使模型在优化的过程中偏向某个方向多一些。关于正则项的内容将在\ref{sec:5.4.5}详细阐述。此外,在第七章的内容中还会看到,使用恰当的正则项可以大大提升基于神经网络的机器翻译系统性能 \parinterval 在实际系统开发中,损失函数中除了损失项(即用来度量正确答案$ \mathbf {\widetilde y}_i $和神经网络输出$ \mathbf y_i $之间的偏差的部分)之外,还可以包括正则项,比如L1正则和L2正则。设置正则项本质上是要加入一些偏置,使模型在优化的过程中偏向某个方向多一些。关于正则项的内容将在\ref{sec:5.4.5}介绍
%---------------------------------------------------------------------------------------- %----------------------------------------------------------------------------------------
% NEW SUB-SECTION % NEW SUB-SECTION
...@@ -1292,7 +1293,7 @@ y&=&{\rm{Sigmoid}}({\rm{Tanh}}(\mathbf x\cdot \mathbf w^{[1]}+\mathbf b^{[1]})\c ...@@ -1292,7 +1293,7 @@ y&=&{\rm{Sigmoid}}({\rm{Tanh}}(\mathbf x\cdot \mathbf w^{[1]}+\mathbf b^{[1]})\c
\noindent 其中,$ \widehat{\mathbf w} $表示在训练数据上使损失的平均值达到最小的参数。$ \frac{1}{n}\sum_{i=1}^{n}{L(\mathbf x_i,\mathbf {\widetilde y}_i;\mathbf w)} $也被称作{\small\sffamily\bfseries{代价函数}}\index{代价函数}(Cost Function)\index{Cost Function},它是损失函数均值期望的估计,记为$ J(\mathbf w) $ \noindent 其中,$ \widehat{\mathbf w} $表示在训练数据上使损失的平均值达到最小的参数。$ \frac{1}{n}\sum_{i=1}^{n}{L(\mathbf x_i,\mathbf {\widetilde y}_i;\mathbf w)} $也被称作{\small\sffamily\bfseries{代价函数}}\index{代价函数}(Cost Function)\index{Cost Function},它是损失函数均值期望的估计,记为$ J(\mathbf w) $
\parinterval 参数优化的核心问题是:找到使代价函数$ J(\mathbf w) $达到最小的$ \mathbf w $。然而$ J(\mathbf w) $可能会包含大量的参数,比如,基于神经网络的机器翻译模型的参数量可能会超过一亿个。这时不可能用手动方法进行调参。为了实现高效的参数优化,比较常用的手段是使用{\small\bfnew{梯度下降方法}}\index{梯度下降方法}Gradient Descent Method)\index{Gradient Descent Method} \parinterval 参数优化的核心问题是:找到使代价函数$ J(\mathbf w) $达到最小的$ \mathbf w $。然而$ J(\mathbf w) $可能会包含大量的参数,比如,基于神经网络的机器翻译模型的参数量可能会超过一亿个。这时不可能用手动方法进行调参。为了实现高效的参数优化,比较常用的手段是使用{\small\bfnew{梯度下降方法}}\index{梯度下降方法}The Gradient Descent Method)\index{The Gradient Descent Method}
%---------------------------------------------------------------------------------------- %----------------------------------------------------------------------------------------
% NEW SUBSUB-SECTION % NEW SUBSUB-SECTION
...@@ -1319,7 +1320,7 @@ y&=&{\rm{Sigmoid}}({\rm{Tanh}}(\mathbf x\cdot \mathbf w^{[1]}+\mathbf b^{[1]})\c ...@@ -1319,7 +1320,7 @@ y&=&{\rm{Sigmoid}}({\rm{Tanh}}(\mathbf x\cdot \mathbf w^{[1]}+\mathbf b^{[1]})\c
\noindent 其中$t $表示更新的步数,$ \alpha $是一个超参数,被称作{\small\sffamily\bfseries{学习率}}\index{学习率}(Learning Rate)\index{Learning Rate},表示更新步幅的大小。$ \alpha $的设置需要根据任务进行调整。 \noindent 其中$t $表示更新的步数,$ \alpha $是一个超参数,被称作{\small\sffamily\bfseries{学习率}}\index{学习率}(Learning Rate)\index{Learning Rate},表示更新步幅的大小。$ \alpha $的设置需要根据任务进行调整。
\parinterval 从优化的角度看,梯度下降是一种典型的 {\small\bfnew{基于梯度的方法}}\index{基于梯度的方法}Gradient-based Method)\index{Gradient-based Method},属于基于一阶导数的方法。其他类似的方法还有牛顿法、共轭方向法、拟牛顿法等。在具体实现时,公式\ref{eq:5-29}可以有以下不同的形式。\\ \parinterval 从优化的角度看,梯度下降是一种典型的 {\small\bfnew{基于梯度的方法}}\index{基于梯度的方法}The Gradient-based Method)\index{The Gradient-based Method},属于基于一阶导数的方法。其他类似的方法还有牛顿法、共轭方向法、拟牛顿法等。在具体实现时,公式\ref{eq:5-29}可以有以下不同的形式。\\
%---------------------------------------------------------------------------------------- %----------------------------------------------------------------------------------------
% %
...@@ -1371,7 +1372,7 @@ J(\mathbf w)&=&\frac{1}{m}\sum_{i=j}^{j+m-1}{L(\mathbf x_i,\mathbf {\widetilde y ...@@ -1371,7 +1372,7 @@ J(\mathbf w)&=&\frac{1}{m}\sum_{i=j}^{j+m-1}{L(\mathbf x_i,\mathbf {\widetilde y
\label{eq:5-32} \label{eq:5-32}
\end{eqnarray} \end{eqnarray}
\noindent 其中$ m $表示一个批次中的样本的数量,$ j $表示这个批次在全体训练数据的起始位置。这种方法可以更充分的利用GPU设备,因为批次中的样本可以一起计算。而且每次使用多个样本可以大大减小使模型收敛所需要的参数更新次数。但是需要注意的是批次大小的选择对模型的最终性能是存在一定影响的。 \noindent 其中$ m $表示一个批次中的样本的数量,$ j $表示这个批次在全体训练数据的起始位置。这种方法可以更充分的利用GPU设备,因为批次中的样本可以一起计算。而且每次使用多个样本可以大大减小使模型收敛所需要的参数更新次数。但是需要注意的是批次大小的选择对模型的最终性能是存在一定影响的。
%---------------------------------------------------------------------------------------- %----------------------------------------------------------------------------------------
% NEW SUBSUB-SECTION % NEW SUBSUB-SECTION
...@@ -1452,7 +1453,15 @@ $+2x^2+x+1)$ & \ \ $(x^4+2x^3+2x^2+x+1)$ & $+6x+1$ \\ ...@@ -1452,7 +1453,15 @@ $+2x^2+x+1)$ & \ \ $(x^4+2x^3+2x^2+x+1)$ & $+6x+1$ \\
\parinterval 由于它只对基本函数或常数运用符号微分法则,所以它非常适合嵌入编程语言的循环条件等结构中,形成一种程序化的微分过程。在具体实现时,自动微分往往被当做是一种基于图的计算,相关的理论和技术方法相对成熟,因此是深度学习中使用最广泛的一种方法。不同于一般的编程模式,图计算先生成计算图,然后按照计算图执行计算过程。 \parinterval 由于它只对基本函数或常数运用符号微分法则,所以它非常适合嵌入编程语言的循环条件等结构中,形成一种程序化的微分过程。在具体实现时,自动微分往往被当做是一种基于图的计算,相关的理论和技术方法相对成熟,因此是深度学习中使用最广泛的一种方法。不同于一般的编程模式,图计算先生成计算图,然后按照计算图执行计算过程。
\parinterval 自动微分可以用一种{\small\sffamily\bfseries{反向模式}}\index{反向模式}(Backward Mode)\index{Backward Mode}即反向传播思想进行描述。首先,从神经网络的输入,逐层计算每层网络的输出值。如图\ref{fig:5-44},第$ i $层的输出$ \mathbf h_i $作为第$ i+1 $层的输入,数据流在神经网络内部逐层传递。 \parinterval 自动微分可以用一种{\small\sffamily\bfseries{反向模式}}\index{反向模式}(Reverse Mode\index{Reverse Mode}/Backward Mode\index{Backward Mode})即反向传播思想进行描述\cite{baydin2017automatic}。令$h_i$是神经网络的计算图中第$i$个节点的输出。反向模式的自动微分是要计算:
\begin{eqnarray}
\bar{h_i} = \frac{\partial L}{\partial h_i} \label{eq:reverse-mode-v}
\end{eqnarray}
\noindent 这里,$\bar{h_i}$表示损失函数$L$相对于$h_i$的梯度信息,它会被保存在节点$i$处。为了计算$\bar{h_i}$,需要从网络的输出反向计算每一个节点处的梯度。具体实现时,这个过程由一个包括前向计算和反向计算的两阶段方法实现。
\parinterval 首先,从神经网络的输入,逐层计算每层网络的输出值。如图\ref{fig:5-44},第$ i $ 层的输出$ \mathbf h_i $ 作为第$ i+1 $ 层的输入,数据流在神经网络内部逐层传递。
%---------------------------------------------- %----------------------------------------------
\begin{figure}[htp] \begin{figure}[htp]
...@@ -1467,9 +1476,9 @@ $+2x^2+x+1)$ & \ \ $(x^4+2x^3+2x^2+x+1)$ & $+6x+1$ \\ ...@@ -1467,9 +1476,9 @@ $+2x^2+x+1)$ & \ \ $(x^4+2x^3+2x^2+x+1)$ & $+6x+1$ \\
\begin{itemize} \begin{itemize}
\vspace{0.5em} \vspace{0.5em}
\item 动态图(如Pytorch、NiuTensor等):前向计算与计算图的搭建同时进行,函数表达式写完即能得到前向计算的结果,有着灵活、易于调试的优点。 \item 动态图:前向计算与计算图的搭建同时进行,函数表达式写完即能得到前向计算的结果,有着灵活、易于调试的优点。
\vspace{0.5em} \vspace{0.5em}
\item 静态图(如Tensorflow):先搭建计算图,后执行运算,函数表达式完成后,并不能得到前向计算结果,需要显性调用一个Forward函数。但是计算图可以进行深度优化,执行效率较高。 \item 静态图:先搭建计算图,后执行运算,函数表达式完成后,并不能得到前向计算结果,需要显性调用一个Forward函数。但是计算图可以进行深度优化,执行效率较高。
\vspace{0.5em} \vspace{0.5em}
\end{itemize} \end{itemize}
...@@ -1498,19 +1507,10 @@ $+2x^2+x+1)$ & \ \ $(x^4+2x^3+2x^2+x+1)$ & $+6x+1$ \\ ...@@ -1498,19 +1507,10 @@ $+2x^2+x+1)$ & \ \ $(x^4+2x^3+2x^2+x+1)$ & $+6x+1$ \\
\label{} \label{}
\end{eqnarray} \end{eqnarray}
\noindent 其中$ \alpha $是一个超参数,表示更新步幅的大小,称作{\small\bfnew{学习率}}\index{学习率}(Learning Rate)\index{Learning Rate}。当然,这是一种最基本的梯度下降方法。如果函数的形状非均向,比如呈延伸状,搜索最优点的路径就会非常低效,因为这时梯度的方向并没有指向最小值的方向,并且随着参数的更新,梯度方向往往呈锯齿状,这将是一条相当低效的路径;此外这种梯度下降算法并不是总能到达最优点,而是在其附近徘徊;还有一个最令人苦恼的问题\ \dash \ 设置学习率,如果学习率设置的比较小,会导致训练收敛速度慢,如果学习率设置的比较大,会导致训练过程中因为优化幅度过大而频频跳过最优点。我们希望网络在优化的时候损失函数有一个很好的收敛速度同时又不至于摆动幅度太大。 \noindent 其中$ \alpha $是一个超参数,表示更新步幅的大小,称作{\small\bfnew{学习率}}\index{学习率}(Learning Rate)\index{Learning Rate}。当然,这是一种最基本的梯度下降方法。如果函数的形状非均向,比如呈延伸状,搜索最优点的路径就会非常低效,因为这时梯度的方向并没有指向最小值的方向,并且随着参数的更新,梯度方向往往呈锯齿状,这将是一条相当低效的路径;此外这种梯度下降算法并不是总能到达最优点,而是在其附近徘徊;还有一个最令人苦恼的问题\ \dash \ 设置学习率,如果学习率设置的比较小,会导致训练收敛速度慢,如果学习率设置的比较大,会导致训练过程中因为优化幅度过大而频频跳过最优点。我们希望网络在优化的时候损失函数有一个很好的收敛速度同时又不至于摆动幅度太大。
\parinterval 针对以上问题,很多学者尝试对梯度下降方法做出改进,如Momentum, Adagrad, Adadelta, RMSprop, Adam, AdaMax, Nadam, AMSGrad等等,在这里将介绍Momentum、AdaGrad、RMSprop、Adam这4 种方法。 \parinterval 针对以上问题,很多学者尝试对梯度下降方法做出改进,如Momentum, Adagrad, Adadelta, RMSprop, Adam, AdaMax, Nadam, AMSGrad等等,在这里将介绍Momentum、AdaGrad、RMSprop、Adam这4 种方法。
%----------------------------------------------
\begin{figure}[htp]
\centering
\input{./Chapter9/Figures/fig-sawtooth}
\caption{Momentum梯度下降 vs 普通梯度下降}
\label{fig:5-46}
\end{figure}
%-------------------------------------------
%---------------------------------------------------------------------------------------- %----------------------------------------------------------------------------------------
% %
%---------------------------------------------------------------------------------------- %----------------------------------------------------------------------------------------
...@@ -1530,6 +1530,16 @@ w_{t+1}&=&w_t-\alpha v_t ...@@ -1530,6 +1530,16 @@ w_{t+1}&=&w_t-\alpha v_t
\parinterval 这里的``梯度''不再只是现在的损失函数的梯度,而是之前的梯度的加权和。在原始的梯度下降算法中,如果在某个参数状态下,梯度方向变化特别大,甚至与上一次参数更新中梯度方向成90度夹角,下一次参数更新中梯度方向可能又是一次90度的改变,这时参数优化路径将会成``锯齿''状(如图\ref{fig:5-46}所示),优化效率极慢。而Momentum梯度下降算法不会让梯度发生90度的变化,而是让梯度慢慢发生改变:如果当前的梯度方向与之前的梯度方向相同,在原梯度方向上加速更新参数;如果当前的梯度方向与之前的梯度方向相反,并不会产生一个急转弯,而是尽量把优化路径平滑地进行改变。这样做的优点也非常明显,一方面杜绝了``锯齿''状优化路径的出现,另一方面将优化幅度变得更加平滑,不会导致频频跳过最优点。 \parinterval 这里的``梯度''不再只是现在的损失函数的梯度,而是之前的梯度的加权和。在原始的梯度下降算法中,如果在某个参数状态下,梯度方向变化特别大,甚至与上一次参数更新中梯度方向成90度夹角,下一次参数更新中梯度方向可能又是一次90度的改变,这时参数优化路径将会成``锯齿''状(如图\ref{fig:5-46}所示),优化效率极慢。而Momentum梯度下降算法不会让梯度发生90度的变化,而是让梯度慢慢发生改变:如果当前的梯度方向与之前的梯度方向相同,在原梯度方向上加速更新参数;如果当前的梯度方向与之前的梯度方向相反,并不会产生一个急转弯,而是尽量把优化路径平滑地进行改变。这样做的优点也非常明显,一方面杜绝了``锯齿''状优化路径的出现,另一方面将优化幅度变得更加平滑,不会导致频频跳过最优点。
%----------------------------------------------
\begin{figure}[htp]
\centering
\input{./Chapter9/Figures/fig-sawtooth}
\caption{Momentum梯度下降 vs 普通梯度下降}
\label{fig:5-46}
\end{figure}
%-------------------------------------------
%---------------------------------------------------------------------------------------- %----------------------------------------------------------------------------------------
% %
%---------------------------------------------------------------------------------------- %----------------------------------------------------------------------------------------
...@@ -1588,7 +1598,7 @@ w_{t+1}&=&w_t-\frac{\eta}{\sqrt{z_t+\epsilon}} v_t ...@@ -1588,7 +1598,7 @@ w_{t+1}&=&w_t-\frac{\eta}{\sqrt{z_t+\epsilon}} v_t
\noindent 可以看到Adam 算法相当于在RMSProp算法中引入了Momentum算法中的动量项,这样做使得Adam算法兼具了Momentum算法和RMSProp算法的优点:既能使梯度更为``平滑''地更新,同时可以为神经网络中的每个参数设置不同的学习率。 \noindent 可以看到Adam 算法相当于在RMSProp算法中引入了Momentum算法中的动量项,这样做使得Adam算法兼具了Momentum算法和RMSProp算法的优点:既能使梯度更为``平滑''地更新,同时可以为神经网络中的每个参数设置不同的学习率。
\parinterval 需要注意的是包括Adam在内的很多参数更新算法中的学习率都需要人为设置。而且模型学习的效果与学习率的设置关系极大,甚至在研发实际系统时工程师需要进行大量的实验,才能得到最佳的模型。第六章还会具体介绍在机器翻译中参数更新学习率设置的策略。 \parinterval 需要注意的是包括Adam在内的很多参数更新算法中的学习率都需要人为设置。而且模型学习的效果与学习率的设置关系极大,甚至在研发实际系统时工程师需要进行大量的实验,才能得到最佳的模型。
%---------------------------------------------------------------------------------------- %----------------------------------------------------------------------------------------
% NEW SUB-SECTION % NEW SUB-SECTION
...@@ -1596,7 +1606,7 @@ w_{t+1}&=&w_t-\frac{\eta}{\sqrt{z_t+\epsilon}} v_t ...@@ -1596,7 +1606,7 @@ w_{t+1}&=&w_t-\frac{\eta}{\sqrt{z_t+\epsilon}} v_t
\subsection{参数更新的并行化策略} \subsection{参数更新的并行化策略}
\parinterval 虽然通过GPU可以加速神经网络的运算,但当神经网络较为复杂时,模型训练还是需要几天甚至几周的时间。如果希望尽可能缩短一次学习所需的时间,最直接的想法就是把不同的训练样本分配给多个GPU 或CPU,然后在这些设备上同时进行训练,即实现并行化训练。这种方法也被称作{\small\sffamily\bfseries{数据并行}}\index{数据并行}。具体实现时,有两种常用的并行化策略:(参数)同步更新和(参数)异步更新。 \parinterval 当神经网络较为复杂时,模型训练还是需要几天甚至几周的时间。如果希望尽可能缩短一次学习所需的时间,最直接的想法就是把不同的训练样本分配给多个GPU 或CPU,然后在这些设备上同时进行训练,即实现并行化训练。这种方法也被称作{\small\sffamily\bfseries{数据并行}}\index{数据并行}。具体实现时,有两种常用的并行化策略:(参数)同步更新和(参数)异步更新。
\begin{itemize} \begin{itemize}
\vspace{0.5em} \vspace{0.5em}
...@@ -1606,6 +1616,10 @@ w_{t+1}&=&w_t-\frac{\eta}{\sqrt{z_t+\epsilon}} v_t ...@@ -1606,6 +1616,10 @@ w_{t+1}&=&w_t-\frac{\eta}{\sqrt{z_t+\epsilon}} v_t
\vspace{0.5em} \vspace{0.5em}
\end{itemize} \end{itemize}
\parinterval\ref{fig:5-47}对比了同步更新和异步更新的区别,在这个例子中,使用4台设备对一个两层神经网络中的参数进行更新,其中使用了一个{\small\bfnew{参数服务器}}\index{参数服务器}(Parameter Server\index{Parameter Server})来保存最新的参数,不同设备(Worker,图中的G1、G2、G3)可以通过同步或者异步的方式访问参数服务器。图中的$ \mathbf w_o $$ \mathbf w_h $分别代表输出层和隐藏层的全部参数,操作push(P) 表示设备向参数服务器传送梯度,操作fetch(F)表示参数服务器向设备传送更新后的参数。
\parinterval 此外,在使用多个设备进行并行训练的时候,由于设备间带宽的限制,大量的数据传输会有较高的延时。对于复杂神经网络来说,设备间参数和梯度传递的时间消耗也会成为一个不得不考虑的因素。有时候,设备间数据传输的时间甚至比模型计算的时间都长,大大降低了并行度\cite{xiao2017fast}。对于这种问题,可以考虑对数据进行压缩或者减少传输的次数来缓解问题。
%---------------------------------------------- %----------------------------------------------
\begin{figure}[htp] \begin{figure}[htp]
\centering \centering
...@@ -1615,9 +1629,6 @@ w_{t+1}&=&w_t-\frac{\eta}{\sqrt{z_t+\epsilon}} v_t ...@@ -1615,9 +1629,6 @@ w_{t+1}&=&w_t-\frac{\eta}{\sqrt{z_t+\epsilon}} v_t
\end {figure} \end {figure}
%------------------------------------------- %-------------------------------------------
\parinterval\ref{fig:5-47}对比了同步更新和异步更新的区别,在这个例子中,使用4台设备对一个两层神经网络中的参数进行更新,其中使用了一个{\small\bfnew{参数服务器}}\index{参数服务器}(Parameter Server\index{Parameter Server})来保存最新的参数,不同设备(Worker,图中的G1、G2、G3)可以通过同步或者异步的方式访问参数服务器。图中的$ \mathbf w_o $$ \mathbf w_h $分别代表输出层和隐藏层的全部参数,操作push(P) 表示设备向参数服务器传送梯度,操作fetch(F)表示参数服务器向设备传送更新后的参数。
\parinterval 此外,在使用多个设备进行并行训练的时候,由于设备间带宽的限制,大量的数据传输会有较高的延时。对于复杂神经网络来说,设备间参数和梯度传递的时间消耗也会成为一个不得不考虑的因素。有时候,设备间数据传输的时间甚至比模型计算的时间都长,大大降低了并行度\cite{xiao2017fast}。对于这种问题,可以考虑对数据进行压缩或者减少传输的次数来缓解问题。
%---------------------------------------------------------------------------------------- %----------------------------------------------------------------------------------------
% NEW SUB-SECTION % NEW SUB-SECTION
...@@ -1635,41 +1646,6 @@ w_{t+1}&=&w_t-\frac{\eta}{\sqrt{z_t+\epsilon}} v_t ...@@ -1635,41 +1646,6 @@ w_{t+1}&=&w_t-\frac{\eta}{\sqrt{z_t+\epsilon}} v_t
\parinterval 网络训练过程中,如果每层网络的梯度都小于1,各层梯度的偏导数会与后面层传递而来的梯度相乘得到本层的梯度,并向前一层传递。该过程循环进行,最后导致梯度指数级地减小,这就产生了梯度消失现象。这种情况会导致神经网络层数较浅的部分梯度接近0。一般来说,产生很小梯度的原因是使用了类似于Sigmoid这样的激活函数,当输入的值过大或者过小的时候这类函数曲线会趋于直线,梯度近似为零。针对这个问题,主要的解决办法是使用更加易于优化的激活函数,比如,使用ReLU代替Sigmoid和Tanh作为激活函数。 \parinterval 网络训练过程中,如果每层网络的梯度都小于1,各层梯度的偏导数会与后面层传递而来的梯度相乘得到本层的梯度,并向前一层传递。该过程循环进行,最后导致梯度指数级地减小,这就产生了梯度消失现象。这种情况会导致神经网络层数较浅的部分梯度接近0。一般来说,产生很小梯度的原因是使用了类似于Sigmoid这样的激活函数,当输入的值过大或者过小的时候这类函数曲线会趋于直线,梯度近似为零。针对这个问题,主要的解决办法是使用更加易于优化的激活函数,比如,使用ReLU代替Sigmoid和Tanh作为激活函数。
\parinterval 缓解梯度消失问题最直接的想法就是希望各层的偏导数大于或等于1。图\ref{fig:5-48}展示了Sigmoid激活函数$ y=\frac{1}{1+e^{-x}}$的函数曲线和导函数曲线,如果使用Sigmoid作为损失函数,其梯度不可能超过0.25,这样经过链式求导之后,很容易发生梯度消失。
%----------------------------------------------
\begin{figure}[htp]
\centering
\input{./Chapter9/Figures/fig-derivative1}
\caption{Sigmoid激活函数的函数曲线和导函数曲线}
\label{fig:5-48}
\end {figure}
%-------------------------------------------
\parinterval 同理,Tanh作为激活函数也容易出现梯度消失现象,图\ref{fig:5-49}展示了Tanh激活函数$ y=\frac{e^x-e^{-x}}{e^x+e^{-x}}$的函数曲线和导函数曲线,可以看出,Tanh激活函数比Sigmoid激活函数要好一些,但是Tanh激活函数的导数也小于1,因此无法避免梯度消失现象。
%----------------------------------------------
\begin{figure}[htp]
\centering
\input{./Chapter9/Figures/fig-derivative2}
\caption{Tanh激活函数的函数曲线和导函数曲线}
\label{fig:5-49}
\end {figure}
%-------------------------------------------
\parinterval ReLU激活函数的思想也很简单,如果激活函数的导数为1,那么就不存在梯度消失爆炸的问题了。图\ref{fig:5-50}展示了ReLU激活函数$ y={\rm{max}}(0,x)$的函数曲线和导函数曲线。可以很容易看出,ReLU函数的导数在正数部分是恒等于1的,因此在深层网络中使用ReLU激活函数就不会产生很小的梯度。
%----------------------------------------------
\begin{figure}[htp]
\centering
\input{./Chapter9/Figures/fig-derivative3}
\caption{ReLU激活函数的函数曲线和导函数曲线}
\label{fig:5-50}
\end {figure}
%-------------------------------------------
\parinterval 当然,梯度消失并不是仅仅可以通过改变激活函数就可以完全消除掉。随着网络层数的增加,很多因素都可能会造成梯度消失。后面也会进一步介绍其他手段,可以综合运用这些方法达到很好的缓解梯度消失问题的目的。
%---------------------------------------------------------------------------------------- %----------------------------------------------------------------------------------------
% NEW SUBSUB-SECTION % NEW SUBSUB-SECTION
%---------------------------------------------------------------------------------------- %----------------------------------------------------------------------------------------
...@@ -1694,19 +1670,18 @@ w_{t+1}&=&w_t-\frac{\eta}{\sqrt{z_t+\epsilon}} v_t ...@@ -1694,19 +1670,18 @@ w_{t+1}&=&w_t-\frac{\eta}{\sqrt{z_t+\epsilon}} v_t
\parinterval 为了使神经网络模型训练更加稳定,通常还会考虑其他策略。 \parinterval 为了使神经网络模型训练更加稳定,通常还会考虑其他策略。
\parinterval {\red{考虑是否弄成小标题}} \begin{itemize}
\item {\small\bfnew{批量归一化}}\index{批量归一化}(Batch Normalization)\index{Batch Normalization}。批量归一化,顾名思义,是以进行学习时的小批量样本为单位进行归一化\cite{ioffe2015batch}。具体而言,就是对神经网络隐层输出的每一个维度,沿着批次的方向进行均值为0、方差为1的归一化。在深层神经网络中,每一层网络都可以使用批量归一化操作。这样使神经网络任意一层的输入不至于过大或过小,从而防止隐层中异常值导致模型状态的巨大改变。
\parinterval (1){\small\bfnew{批量归一化}}\index{批量归一化}(Batch Normalization)\index{Batch Normalization}
\parinterval 批量归一化,顾名思义,是以进行学习时的小批量样本为单位进行归一化\cite{ioffe2015batch}。具体而言,就是对神经网络隐层输出的每一个维度,沿着批次的方向进行均值为0、方差为1的归一化。在深层神经网络中,每一层网络都可以使用批量归一化操作。这样使神经网络任意一层的输入不至于过大或过小,从而防止隐层中异常值导致模型状态的巨大改变。
\parinterval (2){\small\bfnew{层归一化}}\index{层归一化}(Layer Normalization)\index{Layer Normalization} \item {\small\bfnew{层归一化}}\index{层归一化}(Layer Normalization)\index{Layer Normalization}。类似的,层归一化更多是针对自然语言处理这种序列处理任务\cite{Ba2016LayerN},它和批量归一化的原理是一样的,只是归一化操作是在序列上同一层网络的输出结果上进行的,也就是归一化操作沿着序列方向进行。这种方法可以很好的避免序列上不同位置神经网络输出结果的不可比性。同时由于归一化后所有的结果都转化到一个可比的范围,使得隐层状态可以在不同层之间进行自由组合。
\parinterval 类似的,层归一化更多是针对自然语言处理这种序列处理任务\cite{Ba2016LayerN},它和批量归一化的原理是一样的,只是归一化操作是在序列上同一层网络的输出结果上进行的,也就是归一化操作沿着序列方向进行。这种方法可以很好的避免序列上不同位置神经网络输出结果的不可比性。同时由于归一化后所有的结果都转化到一个可比的范围,使得隐层状态可以在不同层之间进行自由组合。 \item {\small\bfnew{残差网络}}\index{残差网络}(Residual Networks)\index{Residual Networks}。最初,残差网络是为了解决神经网络持续加深时的模型退化问题\cite{DBLP:journals/corr/HeZRS15},但是残差结构对解决梯度消失和梯度爆炸问题也有所帮助。有了残差结构,可以很轻松的构建几十甚至上百层的神经网络,而不用担心层数过深造成的梯度消失问题。残差网络的结构如图\ref{fig:5-51}所示。图\ref{fig:5-51}中右侧的曲线叫做{\small\bfnew{跳接}}\index{跳接}(Shortcut Connection)\index{Shortcut Connection},通过跳接在激活函数前,将上一层(或几层)之前的输出与本层计算的输出相加,将求和的结果输入到激活函数中作为本层的输出。假设残差结构的输入为$ \mathbf x_l $,输出为$ \mathbf x_{l+1} $,则有
\parinterval (3){\small\bfnew{残差网络}}\index{残差网络}(Residual Networks)\index{Residual Networks} \begin{eqnarray}
\mathbf x_{l+1}&=&F(\mathbf x_l)+\mathbf x_l
\label{eq:5-44}
\end{eqnarray}
\parinterval 最初,残差网络是为了解决神经网络持续加深时的模型退化问题\cite{DBLP:journals/corr/HeZRS15},但是残差结构对解决梯度消失和梯度爆炸问题也有所帮助。有了残差结构,可以很轻松的构建几十甚至上百层的神经网络,而不用担心层数过深造成的梯度消失问题。残差网络的结构如图\ref{fig:5-51}所示:
%---------------------------------------------- %----------------------------------------------
\begin{figure}[htp] \begin{figure}[htp]
...@@ -1717,13 +1692,7 @@ w_{t+1}&=&w_t-\frac{\eta}{\sqrt{z_t+\epsilon}} v_t ...@@ -1717,13 +1692,7 @@ w_{t+1}&=&w_t-\frac{\eta}{\sqrt{z_t+\epsilon}} v_t
\end{figure} \end{figure}
%------------------------------------------- %-------------------------------------------
\parinterval\ref{fig:5-51}中右侧的曲线叫做{\small\bfnew{跳接}}\index{跳接}(Shortcut Connection)\index{Shortcut Connection},通过跳接在激活函数前,将上一层(或几层)之前的输出与本层计算的输出相加,将求和的结果输入到激活函数中作为本层的输出。假设残差结构的输入为$ \mathbf x_l $,输出为$ \mathbf x_{l+1} $,则有 相比较于简单的多层堆叠的结构,残差网络提供了跨层连接结构。这种结构在反向传播中有很大的好处,比如,对于$ \mathbf x_l $处的梯度可以进行如下计算:
\begin{eqnarray}
\mathbf x_{l+1}&=&F(\mathbf x_l)+\mathbf x_l
\label{eq:5-44}
\end{eqnarray}
\parinterval 相比较于简单的多层堆叠的结构,残差网络提供了跨层连接结构。这种结构在反向传播中有很大的好处,比如,对于$ \mathbf x_l $处的梯度可以进行如下计算:
\begin{eqnarray} \begin{eqnarray}
\frac{\partial L}{\partial \mathbf x_l}&=&\frac{\partial L}{\partial \mathbf x_{l+1}} \cdot \frac{\partial \mathbf x_{l+1}}{\partial \mathbf x_l}\nonumber\\ \frac{\partial L}{\partial \mathbf x_l}&=&\frac{\partial L}{\partial \mathbf x_{l+1}} \cdot \frac{\partial \mathbf x_{l+1}}{\partial \mathbf x_l}\nonumber\\
&=&\frac{\partial L}{\partial \mathbf x_{l+1}} \cdot \left(1+\frac{\partial F(\mathbf x_l)}{\partial \mathbf x_l}\right)\nonumber\\ &=&\frac{\partial L}{\partial \mathbf x_{l+1}} \cdot \left(1+\frac{\partial F(\mathbf x_l)}{\partial \mathbf x_l}\right)\nonumber\\
...@@ -1731,7 +1700,9 @@ w_{t+1}&=&w_t-\frac{\eta}{\sqrt{z_t+\epsilon}} v_t ...@@ -1731,7 +1700,9 @@ w_{t+1}&=&w_t-\frac{\eta}{\sqrt{z_t+\epsilon}} v_t
\label{eq:5-45} \label{eq:5-45}
\end{eqnarray} \end{eqnarray}
\parinterval 由上式可知,残差网络可以将后一层的梯度$ \frac{\partial L}{\partial \mathbf x_{l+1}} $不经过任何乘法项直接传递到$ \frac{\partial L}{\partial \mathbf x_l} $,从而缓解了梯度经过每一层后多次累乘造成的梯度消失问题。在第六章中还会看到,在机器翻译中残差结构可以和层归一化一起使用,而且这种组合可以取得很好的效果。 由上式可知,残差网络可以将后一层的梯度$ \frac{\partial L}{\partial \mathbf x_{l+1}} $不经过任何乘法项直接传递到$ \frac{\partial L}{\partial \mathbf x_l} $,从而缓解了梯度经过每一层后多次累乘造成的梯度消失问题。在{\chaptertwelve}中还会看到,在机器翻译中残差结构可以和层归一化一起使用,而且这种组合可以取得很好的效果。
\end{itemize}
%---------------------------------------------------------------------------------------- %----------------------------------------------------------------------------------------
% NEW SUB-SECTION % NEW SUB-SECTION
...@@ -1745,7 +1716,7 @@ w_{t+1}&=&w_t-\frac{\eta}{\sqrt{z_t+\epsilon}} v_t ...@@ -1745,7 +1716,7 @@ w_{t+1}&=&w_t-\frac{\eta}{\sqrt{z_t+\epsilon}} v_t
\parinterval 过拟合的模型通常会表现为部分非零参数过多或者参数的值过大。这种参数产生的原因在于模型需要复杂的参数才能匹配样本中的个别现象甚至噪声。基于此,常见的正则化方法有L1正则化和L2正则化,其命名方式是由$ R(\mathbf w) $的计算形式来决定的。在L1正则化中,$ R(\mathbf w) $即为参数$ w $$ l_1 $范数,即$ R(\mathbf w) ={\Vert \mathbf w\Vert}_1=\sum_{i=1}^{n}{\vert w_i\vert} $;在L2正则化中,$ R(\mathbf w) $即为参数$ w $$ l_2 $范数的平方,即$ R(\mathbf w) =({\Vert \mathbf w\Vert}_2)^2=\sum_{i=1}^{n}{w_i^2} $。L1正则化中的正则项衡量了模型权数中的绝对值大小,倾向于生成值为0的参数,从而让参数变得更加稀疏;而L2正则化由于平方的加入,当参数中的某一项小到一定程度,比如0.001的时候,参数的平方结果已经可以忽略不计了,因此L2正则化会倾向生成很小的参数,在这种情况下,即便训练数据中含有少量随机噪音,模型也不太容易通过增加个别参数的值来对噪声进行过度拟合,即提高了模型的抗扰动能力。 \parinterval 过拟合的模型通常会表现为部分非零参数过多或者参数的值过大。这种参数产生的原因在于模型需要复杂的参数才能匹配样本中的个别现象甚至噪声。基于此,常见的正则化方法有L1正则化和L2正则化,其命名方式是由$ R(\mathbf w) $的计算形式来决定的。在L1正则化中,$ R(\mathbf w) $即为参数$ w $$ l_1 $范数,即$ R(\mathbf w) ={\Vert \mathbf w\Vert}_1=\sum_{i=1}^{n}{\vert w_i\vert} $;在L2正则化中,$ R(\mathbf w) $即为参数$ w $$ l_2 $范数的平方,即$ R(\mathbf w) =({\Vert \mathbf w\Vert}_2)^2=\sum_{i=1}^{n}{w_i^2} $。L1正则化中的正则项衡量了模型权数中的绝对值大小,倾向于生成值为0的参数,从而让参数变得更加稀疏;而L2正则化由于平方的加入,当参数中的某一项小到一定程度,比如0.001的时候,参数的平方结果已经可以忽略不计了,因此L2正则化会倾向生成很小的参数,在这种情况下,即便训练数据中含有少量随机噪音,模型也不太容易通过增加个别参数的值来对噪声进行过度拟合,即提高了模型的抗扰动能力。
\parinterval 此外,在第六章即将介绍的Dropout和Label Smoothing方法也可以被看作是一种正则化操作。它们都可以提高模型在未见数据上的泛化能力。 \parinterval 此外,在{\chaptertwelve}即将介绍的Dropout和Label Smoothing方法也可以被看作是一种正则化操作。它们都可以提高模型在未见数据上的泛化能力。
%---------------------------------------------------------------------------------------- %----------------------------------------------------------------------------------------
% NEW SUB-SECTION % NEW SUB-SECTION
...@@ -1753,7 +1724,7 @@ w_{t+1}&=&w_t-\frac{\eta}{\sqrt{z_t+\epsilon}} v_t ...@@ -1753,7 +1724,7 @@ w_{t+1}&=&w_t-\frac{\eta}{\sqrt{z_t+\epsilon}} v_t
\subsection{反向传播}\label{sec:5.4.6} \subsection{反向传播}\label{sec:5.4.6}
\parinterval 为了获取梯度,最常用的做法是使用自动微分技术,通常通过{\small\sffamily\bfseries{反向传播}}\index{反向传播}back propagation)\index{back propagation}来实现。该方法分为两个计算过程:前向计算和反向计算。前向计算的目的是从输入开始,逐层计算,得到网络的输出,并记录计算图中每个节点的局部输出。反向计算过程从输出端反向计算梯度,这个过程可以被看作是一种梯度的``传播'',最终计算图中所有节点都会得到相应的梯度结果。 \parinterval 为了获取梯度,最常用的做法是使用自动微分技术,通常通过{\small\sffamily\bfseries{反向传播}}\index{反向传播}Back Propagation)\index{Back Propagation}来实现。该方法分为两个计算过程:前向计算和反向计算。前向计算的目的是从输入开始,逐层计算,得到网络的输出,并记录计算图中每个节点的局部输出。反向计算过程从输出端反向计算梯度,这个过程可以被看作是一种梯度的``传播'',最终计算图中所有节点都会得到相应的梯度结果。
\parinterval 这里,首先对反向传播算法中涉及到的符号进行统一说明。图\ref{fig:5-52}是一个多层神经网络,其中层$ k-1 $、层$ k $、层$ k+1 $均为神经网络中的隐藏层,层$ K $为神经网络中的输出层。为了化简问题,这里每层网络没有使用偏置项。 \parinterval 这里,首先对反向传播算法中涉及到的符号进行统一说明。图\ref{fig:5-52}是一个多层神经网络,其中层$ k-1 $、层$ k $、层$ k+1 $均为神经网络中的隐藏层,层$ K $为神经网络中的输出层。为了化简问题,这里每层网络没有使用偏置项。
...@@ -1971,9 +1942,9 @@ w_{t+1}&=&w_t-\frac{\eta}{\sqrt{z_t+\epsilon}} v_t ...@@ -1971,9 +1942,9 @@ w_{t+1}&=&w_t-\frac{\eta}{\sqrt{z_t+\epsilon}} v_t
% NEW SUBSUB-SECTION % NEW SUBSUB-SECTION
%---------------------------------------------------------------------------------------- %----------------------------------------------------------------------------------------
\subsubsection{3. 程序实现} \subsubsection{3. 实例}
\parinterval 在了解了反向传播的原理之后,实现反向传播就变得非常容易了。实际上,现在主流的深度学习框架都支持自动微分。为了进一步说明反向传播的过程,这里使用NiuTensor工具构建两个简单的实例,并分别尝试手动编写反向传播代码和使用NiuTensor自带的自动微分模块 \parinterval 为了进一步理解反向传播的过程,图\ref{fig:5-58}展示了一个简单的神经网络的反向传播程序示例
%---------------------------------------------- %----------------------------------------------
\begin{figure}[htp] \begin{figure}[htp]
...@@ -1984,7 +1955,9 @@ w_{t+1}&=&w_t-\frac{\eta}{\sqrt{z_t+\epsilon}} v_t ...@@ -1984,7 +1955,9 @@ w_{t+1}&=&w_t-\frac{\eta}{\sqrt{z_t+\epsilon}} v_t
\end{figure} \end{figure}
%------------------------------------------- %-------------------------------------------
\parinterval\ref{fig:5-58}展示了一个简单的神经网络的反向传播程序示例。这种反向传播的实现方式正是上一节内容的代码实现:按层实现自动微分并将梯度向前一层传播。
\parinterval 此外,很多张量计算工具都提供了封装好的反向传播函数。如图\ref{fig:5-59}所示,在完成神经网络的搭建后,无论前向计算过程是怎样的,直接利用Backward 函数就可以实现整个神经网络的反向传播,系统开发人员可以完全不用关心其求解过程。
%---------------------------------------------- %----------------------------------------------
\begin{figure}[htp] \begin{figure}[htp]
\centering \centering
...@@ -1994,7 +1967,6 @@ w_{t+1}&=&w_t-\frac{\eta}{\sqrt{z_t+\epsilon}} v_t ...@@ -1994,7 +1967,6 @@ w_{t+1}&=&w_t-\frac{\eta}{\sqrt{z_t+\epsilon}} v_t
\end{figure} \end{figure}
%------------------------------------------- %-------------------------------------------
\parinterval 此外,NiuTensor还提供了一种更简单的一步完成神经网络反向传播的实现方式。如图\ref{fig:5-59}所示,在完成神经网络的搭建后,无论前向计算过程是怎样的,直接利用Backward 函数就可以实现整个神经网络的反向传播,系统开发人员可以完全不用关心其求解过程。
%---------------------------------------------------------------------------------------- %----------------------------------------------------------------------------------------
% NEW SECTION % NEW SECTION
...@@ -2003,7 +1975,7 @@ w_{t+1}&=&w_t-\frac{\eta}{\sqrt{z_t+\epsilon}} v_t ...@@ -2003,7 +1975,7 @@ w_{t+1}&=&w_t-\frac{\eta}{\sqrt{z_t+\epsilon}} v_t
\sectionnewpage \sectionnewpage
\section{神经语言模型}\label{sec5:nlm} \section{神经语言模型}\label{sec5:nlm}
\parinterval 神经网络给我们提供了一种工具,只要将问题的输入和输出定义好,就可以学习输入和输出之间的对应关系。显然,很多自然语言处理任务都可以用神经网络进行实现。比如,在机器翻译中,可以把输入的源语言句子和输出的目标语言句子用神经网络建模;在文本分类中,可以把输入的文本内容和输出的类别标签进行神经网络建模,等等。 \parinterval 神经网络提供了一种工具,只要将问题的输入和输出定义好,就可以学习输入和输出之间的对应关系。显然,很多自然语言处理任务都可以用神经网络进行实现。比如,在机器翻译中,可以把输入的源语言句子和输出的目标语言句子用神经网络建模;在文本分类中,可以把输入的文本内容和输出的类别标签进行神经网络建模,等等。
\parinterval 为了更好地理解神经网络和深度学习在自然语言处理中的应用。这里介绍一种基于神经网络的语言建模方法\ \dash \ {\small\sffamily\bfseries{神经语言模型}}\index{神经语言模型}(Neural Language Model)\index{Neural Language Model}。可以说,神经语言模型是深度学习时代下自然语言处理的标志性成果,它所涉及的许多概念至今仍是研究的热点,比如:词嵌入、表示学习、预训练等。此外,神经语言模型也为机器翻译的建模提供了很好的思路。从某种意义上说,机器翻译的深度学习建模的很多灵感均来自神经语言模型,二者在一定程度上是统一的。 \parinterval 为了更好地理解神经网络和深度学习在自然语言处理中的应用。这里介绍一种基于神经网络的语言建模方法\ \dash \ {\small\sffamily\bfseries{神经语言模型}}\index{神经语言模型}(Neural Language Model)\index{Neural Language Model}。可以说,神经语言模型是深度学习时代下自然语言处理的标志性成果,它所涉及的许多概念至今仍是研究的热点,比如:词嵌入、表示学习、预训练等。此外,神经语言模型也为机器翻译的建模提供了很好的思路。从某种意义上说,机器翻译的深度学习建模的很多灵感均来自神经语言模型,二者在一定程度上是统一的。
...@@ -2011,9 +1983,9 @@ w_{t+1}&=&w_t-\frac{\eta}{\sqrt{z_t+\epsilon}} v_t ...@@ -2011,9 +1983,9 @@ w_{t+1}&=&w_t-\frac{\eta}{\sqrt{z_t+\epsilon}} v_t
% NEW SUB-SECTION % NEW SUB-SECTION
%---------------------------------------------------------------------------------------- %----------------------------------------------------------------------------------------
\subsection{基于神经网络的语言建模} \subsection{基于前馈神经网络的语言模型}
\parinterval 回顾一下第二章的内容,语言建模的问题被定义为:对于一个词序列$ w_1w_2\dots w_m$,如何计算该词序列的可能性?词序列出现的概率可以通过链式法则得到: \parinterval 回顾一下{\chaptertwo}的内容,语言建模的问题被定义为:对于一个词序列$ w_1w_2\dots w_m$,如何计算该词序列的可能性?词序列出现的概率可以通过链式法则得到:
\begin{eqnarray} \begin{eqnarray}
{\rm P}(w_1w_2\dots w_m)&=&{\rm P}(w_1){\rm P}(w_2|w_1){\rm P}(w_3|w_1w_2)\dots {\rm P}(w_m|w_1\dots w_{m-1}) {\rm P}(w_1w_2\dots w_m)&=&{\rm P}(w_1){\rm P}(w_2|w_1){\rm P}(w_3|w_1w_2)\dots {\rm P}(w_m|w_1\dots w_{m-1})
\label{eq:5-57} \label{eq:5-57}
...@@ -2025,7 +1997,7 @@ w_{t+1}&=&w_t-\frac{\eta}{\sqrt{z_t+\epsilon}} v_t ...@@ -2025,7 +1997,7 @@ w_{t+1}&=&w_t-\frac{\eta}{\sqrt{z_t+\epsilon}} v_t
\label{eq:5-58} \label{eq:5-58}
\end{eqnarray} \end{eqnarray}
\noindent 其中,$ {\rm P}(w_m|w_{m-n+1}\dots w_{m-1}) $可以通过相对频次估计进行计算$ {\rm{count}}(\cdot) $表示在训练数据上的频次 \noindent 其中,$ {\rm P}(w_m|w_{m-n+1}\dots w_{m-1}) $可以通过相对频次估计进行计算。令$ {\rm{count}}(\cdot) $表示在训练数据上的频次,于是有
\begin{eqnarray} \begin{eqnarray}
{\rm P}(w_m|w_{m-n+1}\dots w_{m-1})&=&\frac{{\rm{count}}(w_{m-n+1}\dots w_m)}{{\rm{count}}(w_{m-n+1}\dots w_{m-1})} {\rm P}(w_m|w_{m-n+1}\dots w_{m-1})&=&\frac{{\rm{count}}(w_{m-n+1}\dots w_m)}{{\rm{count}}(w_{m-n+1}\dots w_{m-1})}
\label{} \label{}
...@@ -2046,7 +2018,7 @@ w_{t+1}&=&w_t-\frac{\eta}{\sqrt{z_t+\epsilon}} v_t ...@@ -2046,7 +2018,7 @@ w_{t+1}&=&w_t-\frac{\eta}{\sqrt{z_t+\epsilon}} v_t
% NEW SUBSUB-SECTION % NEW SUBSUB-SECTION
%---------------------------------------------------------------------------------------- %----------------------------------------------------------------------------------------
\subsubsection{1. 基于前馈神经网络的语言模型} \subsubsection{1. 模型结构}
\parinterval 最具代表性的神经语言模型是Bengio等人提出的{\small\sffamily\bfseries{前馈神经网络语言模型}}\index{前馈神经网络语言模型}(Feed-forward Neural Network Language Model\index{Feed-forward Neural Network Language Model},简称FNNLM)。这种语言模型的目标是用神经网络计算$ {\rm P}(w_m|w_{m-n+1}\dots w_{m-1}) $,之后将多个$n$-gram的概率相乘得到整个序列的概率\cite{bengio2003a} \parinterval 最具代表性的神经语言模型是Bengio等人提出的{\small\sffamily\bfseries{前馈神经网络语言模型}}\index{前馈神经网络语言模型}(Feed-forward Neural Network Language Model\index{Feed-forward Neural Network Language Model},简称FNNLM)。这种语言模型的目标是用神经网络计算$ {\rm P}(w_m|w_{m-n+1}\dots w_{m-1}) $,之后将多个$n$-gram的概率相乘得到整个序列的概率\cite{bengio2003a}
...@@ -2059,23 +2031,80 @@ w_{t+1}&=&w_t-\frac{\eta}{\sqrt{z_t+\epsilon}} v_t ...@@ -2059,23 +2031,80 @@ w_{t+1}&=&w_t-\frac{\eta}{\sqrt{z_t+\epsilon}} v_t
\end{figure} \end{figure}
%------------------------------------------- %-------------------------------------------
\parinterval 为了有一个直观的认识,这里以4-gram的FNNLM语言模型为例,即根据前三个单词$ w_{i-3} $$ w_{i-2} $$ w_{i-1} $预测当前单词$ w_i $的概率。如图\ref{fig:5-60}所示,$ w_{i-3} $$ w_{i-2} $$ w_{i-1} $为该语言模型的输入(绿色方框),输入为每个词的One-hot向量表示(维度大小与词表大小一致),每个One-hot向量仅一维为1,其余为0,比如:$ (0,0,1,\dots,0) $表示词表中第三个单词。之后把One-hot向量乘以一个矩阵$ \mathbf C $得到单词的分布式表示(紫色方框)。令$ w_i $为第$ i $个词的One-hot表示,$ \mathbf e_i $为第$ i $个词的分布式表示,有: \parinterval 为了有一个直观的认识,这里以4-gram的FNNLM语言模型为例,即根据前三个单词$ w_{i-3} $$ w_{i-2} $$ w_{i-1} $预测当前单词$ w_i $的概率。模型结构如图\ref{fig:5-60}所示。从结构上看,FNNLM是一个典型的多层神经网络结构。主要有三层:
\begin{itemize}
\vspace{0.3em}
\item 输入层(词的分布式表示层),即把输入的离散的单词变为分布式表示对应的实数向量;
\vspace{0.3em}
\item 隐藏层,即将得到的词的分布式表示进行线性和非线性变换;
\vspace{0.3em}
\item 输出层(Softmax层),根据隐藏层的输出预测单词的概率分布。
\vspace{0.3em}
\end{itemize}
\parinterval 这三层堆叠在一起构成了整个网络,而且也可以加入从词的分布式表示直接到输出层的连接(红色虚线箭头)。
%----------------------------------------------------------------------------------------
% NEW SUBSUB-SECTION
%----------------------------------------------------------------------------------------
\subsubsection{2. 输入层}
\parinterval $ w_{i-3} $$ w_{i-2} $$ w_{i-1} $为该语言模型的输入(绿色方框),输入为每个词的One-hot向量表示(维度大小与词表大小一致),每个One-hot向量仅一维为1,其余为0,比如:$ (0,0,1,\dots,0) $ 表示词表中第三个单词。之后把One-hot向量乘以一个矩阵$ \mathbf C $得到单词的分布式表示(紫色方框)。令$ w_i $为第$ i $个词的One-hot表示,$ \mathbf e_i $为第$ i $个词的分布式表示,有:
\begin{eqnarray} \begin{eqnarray}
\mathbf e_i&=&w_i\mathbf C \mathbf e_i&=&w_i\mathbf C
\label{eq:5-60} \label{eq:5-60}
\end{eqnarray} \end{eqnarray}
\noindent 这里的$ \mathbf C $可以被理解为一个查询表,根据$ w_i $中为1的那一维,在$ \mathbf C $中索引到相应的行进行输出(结果是一个行向量)。随后,把得到的$ \mathbf e_0 $$ \mathbf e_1 $$ \mathbf e_2 $三个向量级联在一起,经过两层网络,最后通过Softmax函数(橙色方框)得到输出: \noindent 这里的$ \mathbf C $可以被理解为一个查询表,根据$ w_i $中为1的那一维,在$ \mathbf C $中索引到相应的行进行输出(结果是一个行向量)。通常,把$\mathbf e_i$这种单词的实数向量表示称为{\small\bfnew{词嵌入}}\index{词嵌入}(Word Embedding\index{Word Embedding}),把$ \mathbf C $称为词嵌入矩阵。
%----------------------------------------------------------------------------------------
% NEW SUBSUB-SECTION
%----------------------------------------------------------------------------------------
\subsubsection{3. 隐藏层和输出层}
\parinterval 把得到的$ \mathbf e_0 $$ \mathbf e_1 $$ \mathbf e_2 $三个向量级联在一起,经过两层网络,最后通过Softmax函数(橙色方框)得到输出:
\begin{eqnarray} \begin{eqnarray}
\mathbf y&=&{\rm{Softmax}}(\mathbf h_0\mathbf U)\label{eq:5-61}\\ \mathbf y&=&{\rm{Softmax}}(\mathbf h_0\mathbf U)\label{eq:5-61}\\
\mathbf h_0&=&{\rm{Tanh}}([\mathbf e_{i-3},\mathbf e_{i-2},\mathbf e_{i-1}]\mathbf H+\mathbf d) \mathbf h_0&=&{\rm{Tanh}}([\mathbf e_{i-3},\mathbf e_{i-2},\mathbf e_{i-1}]\mathbf H+\mathbf d)
\label{eq:5-62} \label{eq:5-62}
\end{eqnarray} \end{eqnarray}
\parinterval 输出$ \mathbf y $是词表上的一个分布,通过单词$ w_i $可以索引到对应概率$ {\rm P}(w_i|w_{i-1},w_{i-2},\\w_{i-3}) $$ \mathbf U $$ \mathbf H $$ \mathbf d $是模型的参数。从结构上看,FNNLM主要有三层:1)词的分布式表示层,即把输入的离散的单词变为分布式表示对应的实数向量;2)隐藏层,即将得到的词的分布式表示进行线性和非线性变换;3)输出层,根据隐藏层的输出预测单词的概率分布。这三层堆叠在一起构成了整个网络,而且也可以加入从词的分布式表示直接到输出层的连接(红色虚线箭头)。 \noindent 这里,输出$ \mathbf y $是词表$V$上的一个分布,来表示$ {\rm P}(w_i|w_{i-1},w_{i-2},w_{i-3}) $$ \mathbf U $$ \mathbf H $$ \mathbf d $是模型的参数。这样,对于给定的$w_i$可以用$\mathbf y(i)$得到其概率,其中$\mathbf y(i)$表示向量$\mathbf y$的第$i$维。
\parinterval Softmax($\cdot$)的作用是根据输入的$|V|$维向量(即$\mathbf h_0\mathbf U$),得到一个$|V|$维的分布。令$\mathbf{\tau}$表示Softmax($\cdot$)的输入向量,Softmax函数可以被定义为
\begin{eqnarray}
\textrm{Softmax}(\tau(i))=\frac{\textrm{exp}(\tau(i))} {\sum_{i'=1}^{|V|} \textrm{exp}(\tau(i'))}
\label{eq:6-9}
\end{eqnarray}
\noindent 这里,exp($\cdot$)表示指数函数。Softmax函数是一个典型的归一化函数,它可以将输入的向量的每一维都转化为0-1之间的数,同时保证所有维的和等于1。Softmax的另一个优点是,它本身(对于输出的每一维)都是可微的(如图\ref{fig:softmax}所示),因此可以直接使用基于梯度的方法进行优化。实际上,Softmax经常被用于分类任务。也可以把机器翻译中目标语单词的生成看作一个分类问题,它的类别数是|$V$|。
%----------------------------------------------
\begin{figure}[htp]
\centering
\input{./Chapter9/Figures/fig-softmax}
\caption{ Softmax函数(一维)所对应的曲线}
\label{fig:softmax}
\end{figure}
%----------------------------------------------
%----------------------------------------------------------------------------------------
% NEW SUBSUB-SECTION
%----------------------------------------------------------------------------------------
\subsubsection{4. 连续空间表示能力}
\parinterval 值得注意的是,在FNNLM中,单词已经不再是一个孤立的符号串,而是被表示为一个实数向量。这样,两个单词之间可以通过向量计算某种相似度或距离。这导致相似的单词会具有相似的分布,进而缓解$n$-gram语言模型的问题\ \dash \ 明明意思很相近的两个词但是概率估计的结果差异性却很大。 \parinterval 值得注意的是,在FNNLM中,单词已经不再是一个孤立的符号串,而是被表示为一个实数向量。这样,两个单词之间可以通过向量计算某种相似度或距离。这导致相似的单词会具有相似的分布,进而缓解$n$-gram语言模型的问题\ \dash \ 明明意思很相近的两个词但是概率估计的结果差异性却很大。
\parinterval 在FNNLM中,所有的参数、输入、输出都是连续变量,因此FNNLM也是典型的一个连续空间模型。通过使用交叉熵等损失函数,FNNLM很容易进行优化。比如,可以使用梯度下降方法对FNNLM的模型参数进行训练。
\parinterval FNNLM的实现也非常简单,图\ref{fig:5-61}展示了基于FNNLM一个简单实现。虽然FNNLM模型形式简单,却为处理自然语言提供了一个全新的视角。首先,该模型重新定义了``词是什么''\ \dash \ 它并非词典的一项,而是可以用一个连续实数向量进行表示的可计算的``量''。此外,由于$n$-gram不再是离散的符号序列,模型不需要记录$n$-gram,所以很好的缓解了上面所提到的数据稀疏问题,模型体积也大大减小。
%---------------------------------------------- %----------------------------------------------
\begin{figure}[htp] \begin{figure}[htp]
\centering \centering
...@@ -2085,33 +2114,23 @@ w_{t+1}&=&w_t-\frac{\eta}{\sqrt{z_t+\epsilon}} v_t ...@@ -2085,33 +2114,23 @@ w_{t+1}&=&w_t-\frac{\eta}{\sqrt{z_t+\epsilon}} v_t
\end{figure} \end{figure}
%------------------------------------------- %-------------------------------------------
\parinterval 在FNNLM中,所有的参数、输入、输出都是连续变量,因此FNNLM也是典型的一个连续空间模型。通过使用交叉熵等损失函数,FNNLM很容易进行优化。比如,可以使用梯度下降方法对FNNLM的模型参数进行训练。 \parinterval 当然,FNNLM模型也引发后人的许多思考,比如:神经网络每一层都学到了什么?是词法、句法,还是一些其他知识?如何理解词的分布式表示?等等。在随后的内容中也会看到,随着近几年深度学习和自然语言处理的发展,部分问题已经得到了很好的解答,但是仍有许多问题需要进一步探索。
\parinterval FNNLM的实现也非常简单,图\ref{fig:5-61}展示了基于NiuTensor的FNNLM的部分代码。需要注意的是,在程序实现时, Tanh函数一般会用HardTanh函数代替。因为 Tanh函数中的指数运算容易导致溢出:
\begin{eqnarray}
{\rm{Tanh}}(x)&=&\frac{{\rm{exp}}(x)-{\rm{exp}}(-x)}{{\rm{exp}}(x)+{\rm{exp}}(-x)}
\label{}
\end{eqnarray}
\noindent 而HardTanh函数不存在这个问题,因此具有数值计算的稳定性。HardTanh函数表达式如下: %----------------------------------------------------------------------------------------
\begin{eqnarray} % NEW SUB-SECTION
{\rm{HardTanh}}(x)&=&\begin{cases} -1 & x<-1\\x & -1\leqslant x\leqslant 1\\1 & x>1\end{cases} %----------------------------------------------------------------------------------------
\label{}
\end{eqnarray}
\parinterval 虽然FNNLM模型形式简单,却为处理自然语言提供了一个全新的视角。首先,该模型重新定义了``词是什么''\ \dash \ 它并非词典的一项,而是可以用一个连续实数向量进行表示的可计算的``量''。此外,由于$n$-gram不再是离散的符号序列,模型不需要记录$n$-gram,所以很好的缓解了上面所提到的数据稀疏问题,模型体积也大大减小。 \subsection{对于长序列的建模}
\parinterval 当然,FNNLM模型也引发后人的许多思考,比如:神经网络每一层都学到了什么?是词法、句法,还是一些其他知识?如何理解词的分布式表示?等等。在随后的内容中也会看到,随着近几年深度学习和自然语言处理的发展,部分问题已经得到了很好的解答,但是仍有许多问题需要进一步探索 \parinterval FNNLM模型固然有效,但是和传统的$n$-gram语言模型一样需要依赖有限上下文假设,也就是$ w_i $的生成概率只依赖于之前的$ n-1 $个单词。很自然的一个想法是引入更大范围的历史信息,这样可以捕捉单词间的长距离依赖
%---------------------------------------------------------------------------------------- %----------------------------------------------------------------------------------------
% NEW SUBSUB-SECTION % NEW SUBSUB-SECTION
%---------------------------------------------------------------------------------------- %----------------------------------------------------------------------------------------
\subsubsection{2. 基于循环神经网络的语言模型} \subsubsection{1. 基于循环神经网络的语言模型}
\parinterval FNNLM模型固然有效,但是和传统的$n$-gram语言模型一样需要依赖有限上下文假设,也就是$ w_i $的生成概率只依赖于之前的$ n-1 $个单词。很自然的一个想法是引入更大范围的历史信息,这样可以捕捉单词间的长距离依赖。
\parinterval 对于这个问题,可以通过{\small\sffamily\bfseries{循环神经网络}}\index{循环神经网络}(Recurrent Neural Network\index{Recurrent Neural Network},或RNN)进行求解。通过引入循环单元这种特殊的结构,循环神经网络可以对任意长度的历史进行建模,因此在一定程度上解决了传统$n$-gram语言模型有限历史的问题。正是基于这个优点,{\small\sffamily\bfseries{循环神经网络语言模型}}\index{循环神经网络语言模型}(RNNLM)\index{RNNLM}应运而生\cite{mikolov2010recurrent} \parinterval 对于长距离依赖问题,可以通过{\small\sffamily\bfseries{循环神经网络}}\index{循环神经网络}(Recurrent Neural Network\index{Recurrent Neural Network},或RNN)进行求解。通过引入循环单元这种特殊的结构,循环神经网络可以对任意长度的历史进行建模,因此在一定程度上解决了传统$n$-gram语言模型有限历史的问题。正是基于这个优点,{\small\sffamily\bfseries{循环神经网络语言模型}}\index{循环神经网络语言模型}(RNNLM)\index{RNNLM}应运而生\cite{mikolov2010recurrent}
\parinterval 在循环神经网络中,输入和输出都是一个序列,分别记为$ (\mathbf x_1,\dots,\mathbf x_m) $$ (\mathbf y_1,\dots,\\ \mathbf y_m) $。它们都可以被看作是时序序列,其中每个时刻$ t $都对应一个输入$ \mathbf x_t $和输出$ \mathbf y_t $。循环神经网络的核心是{\small\sffamily\bfseries{循环单元}}\index{循环单元}(RNN Cell)\index{RNN Cell},它读入前一个时刻循环单元的输出和当前时刻的输入,生成当前时刻循环单元的输出。图\ref{fig:5-62}展示了一个简单的循环单元结构,对于时刻$ t $,循环单元的输出被定义为: \parinterval 在循环神经网络中,输入和输出都是一个序列,分别记为$ (\mathbf x_1,\dots,\mathbf x_m) $$ (\mathbf y_1,\dots,\\ \mathbf y_m) $。它们都可以被看作是时序序列,其中每个时刻$ t $都对应一个输入$ \mathbf x_t $和输出$ \mathbf y_t $。循环神经网络的核心是{\small\sffamily\bfseries{循环单元}}\index{循环单元}(RNN Cell)\index{RNN Cell},它读入前一个时刻循环单元的输出和当前时刻的输入,生成当前时刻循环单元的输出。图\ref{fig:5-62}展示了一个简单的循环单元结构,对于时刻$ t $,循环单元的输出被定义为:
\begin{eqnarray} \begin{eqnarray}
...@@ -2138,35 +2157,19 @@ w_{t+1}&=&w_t-\frac{\eta}{\sqrt{z_t+\epsilon}} v_t ...@@ -2138,35 +2157,19 @@ w_{t+1}&=&w_t-\frac{\eta}{\sqrt{z_t+\epsilon}} v_t
\end{figure} \end{figure}
%------------------------------------------- %-------------------------------------------
\parinterval RNNLM体现了一种``记忆''的能力。对于每一个时刻,循环单元都会保留一部分``以前''的信息,并加入``现在''的信息。从这个角度说,RNNLM本质上是一种记忆模型。在简单的循环单元结构的基础上,也有很多改进工作,如LSTM、GRU等模型,这部分内容将会在第六章进行介绍。 \parinterval RNNLM体现了一种``记忆''的能力。对于每一个时刻,循环单元都会保留一部分``以前''的信息,并加入``现在''的信息。从这个角度说,RNNLM本质上是一种记忆模型。在简单的循环单元结构的基础上,也有很多改进工作,如LSTM、GRU等模型,这部分内容将会在{\chapterten}进行介绍。
%---------------------------------------------------------------------------------------- %----------------------------------------------------------------------------------------
% NEW SUBSUB-SECTION % NEW SUBSUB-SECTION
%---------------------------------------------------------------------------------------- %----------------------------------------------------------------------------------------
\subsubsection{3. 其他类型的语言模型} \subsubsection{2. 其他类型的语言模型}
\parinterval 通过引入记忆历史的能力,RNNLM缓解了$n$-gram模型中有限上下文的局限性,但依旧存在一些问题。随着序列变长,不同单词之间信息传递路径变长,信息传递的效率变低。对于长序列,很难通过很多次的循环单元操作保留很长的历史信息。过长的序列还容易引起梯度消失和梯度爆炸问题(详见\ref{sec:5.4.4}节),增加模型训练的难度。 \parinterval 通过引入记忆历史的能力,RNNLM缓解了$n$-gram模型中有限上下文的局限性,但依旧存在一些问题。随着序列变长,不同单词之间信息传递路径变长,信息传递的效率变低。对于长序列,很难通过很多次的循环单元操作保留很长的历史信息。过长的序列还容易引起梯度消失和梯度爆炸问题(详见\ref{sec:5.4.4}节),增加模型训练的难度。
\parinterval 对于这个问题,研究者又提出了一种新的结构$\ \dash \ ${\small\bfnew{自注意力机制}}\index{自注意力机制}(Self-Attention Mechanism)\index{Self-Attention Mechanism}。自注意力是一种特殊的神经网络结构,它可以对序列上任意两个词的相互作用直接进行建模,这样也就避免了循环神经网络中随着距离变长信息传递步骤增多的缺陷。在自然语言处理领域,自注意力机制被成功地应用在机器翻译任务上,形成了著名的Transformer模型\cite{vaswani2017attention}。第六章会系统地介绍自注意力机制和Transformer模型 \parinterval 针对这个问题,一种解决方法是使用卷积神经网络({\color{red} 引用基于卷积的语言模型论文})。卷积神经网络的特点是可以对一定窗口大小内的连续单词进行统一建模,这样非常易于捕捉窗口内单词之间的依赖,同时对它们进行整体的表示。进一步,卷积操作可以被多次叠加使用,通过更多层的卷积神经网络可以捕捉更大范围的依赖关系。关于卷积神经网络及其在机器翻译中的应用,{\chaptereleven} 会有详细论述
\parinterval 在传统的语言模型中,给定一个单词$ w_i $,其他单词对它的影响并没有被显性地建模。而在基于注意力机制的语言模型中,当前需要预测的单词会更加关注与该位置联系较大的单词。具体来说,注意力机制会计算位置$ i $与其他任意位置之间的相关度,称为{\small\sffamily\bfseries{注意力权重}}\index{注意力权重}(Attention Weight)\index{Attention Weight},通过这个权重可以更多地使用与$ w_i $关联紧密的位置的信息。举个简单的例子,在``我\ 喜欢\ 学习\ 数学''这个句子中,需要预测``数学''这个词,通过注意力机制很可能知道``数学''与``学习''的联系更紧密,所以在预测过程中``学习''所占的权重会更大,预测结果会更加精确。\\ \parinterval 此外,研究者也提出了另一种新的结构$\ \dash \ ${\small\bfnew{自注意力机制}}\index{自注意力机制}(Self-Attention Mechanism)\index{Self-Attention Mechanism}。自注意力是一种特殊的神经网络结构,它可以对序列上任意两个词的相互作用直接进行建模,这样也就避免了循环神经网络中随着距离变长信息传递步骤增多的缺陷。在自然语言处理领域,自注意力机制被成功地应用在机器翻译任务上,形成了著名的Transformer模型\cite{vaswani2017attention}{\chaptertwelve}会系统地介绍自注意力机制和Transformer模型。
\parinterval {\red{待补充CNN}}
%----------------------------------------------------------------------------------------
% NEW SUBSUB-SECTION
%----------------------------------------------------------------------------------------
\subsubsection{4. 语言模型的评价}
\parinterval 在使用语言模型时,往往需要知道模型的质量。{\small\sffamily\bfseries{困惑度}}\index{困惑度}(Perplexity\index{Perplexity},PPL)是一种衡量语言模型的好坏的指标。对于一个真实的词序列$ w_1\dots w_m $,困惑度被定义为
\begin{eqnarray}
{\rm{PPL}}&=&{\rm P}{(w_1\dots w_m)}^{- \frac{1}{m}}
\label{eq:5-65}
\end{eqnarray}
\parinterval 本质上,PPL反映了语言模型对序列可能性预测能力的一种评估。如果$ w_1\dots w_m $\\是真实的自然语言,``完美''的模型会得到$ {\rm P} (w_1\dots w_m)=1 $,它对应了最低的困惑度$ {\rm{PPL}}=1$,这说明模型可以完美地对词序列出现的可能性进行预测。当然,真实的语言模型是无法达到$ {\rm{PPL}}=1$的,比如,在著名的Penn Treebank(PTB)数据上最好的语言模型的PPL值也只能到达35左右。可见自然语言处理任务的困难程度。
%---------------------------------------------------------------------------------------- %----------------------------------------------------------------------------------------
% NEW SUB-SECTION % NEW SUB-SECTION
...@@ -2220,9 +2223,9 @@ w_{t+1}&=&w_t-\frac{\eta}{\sqrt{z_t+\epsilon}} v_t ...@@ -2220,9 +2223,9 @@ w_{t+1}&=&w_t-\frac{\eta}{\sqrt{z_t+\epsilon}} v_t
\begin{example} \begin{example}
屋里 要 摆放 一个 \_\_\_\_\_ \hspace{0.5em} \quad \quad 预测下个词 屋里 要 摆放 一个 \_\_\_\_\_ \hspace{0.5em} \quad \quad 预测下个词
\qquad \qquad \quad 屋里 要 摆放 一个{ \red{桌子} }\hspace{0.5em}\quad\quad 见过 \qquad 屋里 要 摆放 一个{ \red{桌子} }\hspace{0.5em}\quad\quad 见过
\qquad \qquad \quad 屋里 要 摆放 一个{ \blue{椅子}} \hspace{0.5em}\quad\quad 没见过,但是仍然是合理预测 \qquad 屋里 要 摆放 一个{ \blue{椅子}} \hspace{0.5em}\quad\quad 没见过,但是仍然是合理预测
\end{example} \end{example}
\parinterval 关于单词的分布式表示还有一个经典的例子:通过词嵌入可以得到如下关系:$\textrm{``国王''}=\textrm{``女王''}-\textrm{``女人''} +\textrm{``男人''}$。从这个例子可以看出,词嵌入也具有一些代数性质,比如,词的分布式表示可以通过加、减等代数运算相互转换。图\ref{fig:5-66}展示了词嵌入在一个二维平面上的投影,不难发现,含义相近的单词分布比较临近。 \parinterval 关于单词的分布式表示还有一个经典的例子:通过词嵌入可以得到如下关系:$\textrm{``国王''}=\textrm{``女王''}-\textrm{``女人''} +\textrm{``男人''}$。从这个例子可以看出,词嵌入也具有一些代数性质,比如,词的分布式表示可以通过加、减等代数运算相互转换。图\ref{fig:5-66}展示了词嵌入在一个二维平面上的投影,不难发现,含义相近的单词分布比较临近。
...@@ -2255,7 +2258,7 @@ w_{t+1}&=&w_t-\frac{\eta}{\sqrt{z_t+\epsilon}} v_t ...@@ -2255,7 +2258,7 @@ w_{t+1}&=&w_t-\frac{\eta}{\sqrt{z_t+\epsilon}} v_t
\subsection{句子表示模型} \subsection{句子表示模型}
\parinterval 目前,词嵌入已经成为诸多自然语言处理系统的标配,也衍生出很多有趣的研究法方向,甚至有人开玩笑的喊出``embed everything''的口号。但是,冷静地看,词嵌入依旧存在一些问题:每个词都对应唯一的向量表示,那么对于一词多义现象,词义需要通过上下文进行区分,这时使用简单的词嵌入式是无法处理的。有一个著名的例子: \parinterval 目前,词嵌入已经成为诸多自然语言处理系统的标配,也衍生出很多有趣的研究法方向。但是,冷静地看,词嵌入依旧存在一些问题:每个词都对应唯一的向量表示,那么对于一词多义现象,词义需要通过上下文进行区分,这时使用简单的词嵌入式是无法处理的。有一个著名的例子:
\begin{example} \begin{example}
Jobs was the CEO of {\red{\underline{apple}}}. Jobs was the CEO of {\red{\underline{apple}}}.
...@@ -2265,13 +2268,9 @@ Jobs was the CEO of {\red{\underline{apple}}}. ...@@ -2265,13 +2268,9 @@ Jobs was the CEO of {\red{\underline{apple}}}.
\parinterval 这两句中``apple''的语义显然是不同的,第一句中的上下文``Jobs''和``CEO''可以帮助我们判断``apple''是一个公司名字,而不是水果。但是词嵌入只有一个结果,因此无法区分这两种情况。这个例子给我们一个启发:在一个句子中,不能孤立的看待单词,应同时考虑其上下文的信息。也就是需要一个能包含句子中上下文信息的表示模型。 \parinterval 这两句中``apple''的语义显然是不同的,第一句中的上下文``Jobs''和``CEO''可以帮助我们判断``apple''是一个公司名字,而不是水果。但是词嵌入只有一个结果,因此无法区分这两种情况。这个例子给我们一个启发:在一个句子中,不能孤立的看待单词,应同时考虑其上下文的信息。也就是需要一个能包含句子中上下文信息的表示模型。
%---------------------------------------------------------------------------------------- \parinterval 回忆一下神经语言模型的结构,它需要在每个位置预测单词生成的概率。这个概率是由若干层神经网络进行计算后,通过输出层得到的。实际上,在送入输出层之前,系统已经得到了这个位置的一个向量(隐藏层的输出),因此可以把它看作是含有一部分上下文信息的表示结果。
% NEW SUBSUB-SECTION
%----------------------------------------------------------------------------------------
\subsubsection{1. 简单的上下文表示模型 \red{这就一小节,是去掉小标题还是后续补充其他内容?}}
\parinterval 回忆一下神经语言模型的结构,它需要在每个位置预测单词生成的概率。这个概率是由若干层神经网络进行计算后,通过输出层得到的。实际上,在送入输出层之前,系统已经得到了这个位置的一个向量(隐藏层的输出),因此可以把它看作是含有一部分上下文信息的表示结果。以RNN为例,图\ref{fig:5-68}展示了一个由四个词组成的句子,这里使用了一个两层循环神经网络对其进行建模。可以看到,对于第三个位置,RNN已经积累了从第1个单词到第3个单词的信息,因此可以看作是单词1-3(``乔布斯\ 就职\ 于'')的一种表示;另一方面,第4个单词的词嵌入可以看作是``苹果''自身的表示。这样,可以把第3 个位置RNN的输出和第4个位置的词嵌入进行合并,就得到了第4个位置上含有上下文信息的表示结果。从另一个角度说,这里得到了``苹果''的一种新的表示,它不仅包含苹果这个词自身的信息,也包含它前文的信息。 \parinterval 以RNN为例,图\ref{fig:5-68}展示了一个由四个词组成的句子,这里使用了一个两层循环神经网络对其进行建模。可以看到,对于第三个位置,RNN已经积累了从第1个单词到第3个单词的信息,因此可以看作是单词1-3(``乔布斯\ 就职\ 于'')的一种表示;另一方面,第4个单词的词嵌入可以看作是``苹果''自身的表示。这样,可以把第3 个位置RNN的输出和第4个位置的词嵌入进行合并,就得到了第4个位置上含有上下文信息的表示结果。从另一个角度说,这里得到了``苹果''的一种新的表示,它不仅包含苹果这个词自身的信息,也包含它前文的信息。
%---------------------------------------------- %----------------------------------------------
\begin{figure}[htp] \begin{figure}[htp]
...@@ -2284,8 +2283,6 @@ Jobs was the CEO of {\red{\underline{apple}}}. ...@@ -2284,8 +2283,6 @@ Jobs was the CEO of {\red{\underline{apple}}}.
\parinterval 在自然语言处理中,{\small\sffamily\bfseries{句子表示模型}}\index{句子表示模型}是指把输入的句子进行分布式表示。不过表示的形式不一定是一个单独的向量。现在广泛使用的句子表示模型可以被描述为:给定一个输入的句子$ \{ w_1,\dots ,w_m\} $,得到一个表示序列$ \{ \mathbf h_1,\dots ,\mathbf h_m\} $,其中$ h_i $是句子在第$ i $个位置的表示结果。$ \{ \mathbf h_1,\dots ,\mathbf h_m\} $就被看作是{\small\sffamily\bfseries{句子的表示}}\index{句子的表示},它可以被送入下游模块。比如,在机器翻译任务中,可以用这种模型表示源语言句子,然后通过这种表示结果进行目标语译文的生成;在序列标注(如词性标注)任务中,可以对输入的句子进行表示,然后在这个表示之上构建标签预测模块。很多自然语言处理任务都可以用句子表示模型进行建模,因此句子的表示模型也是应用最广泛的深度学习模型之一。而学习这种表示的过程也被称作{\small\sffamily\bfseries{表示学习}}\index{表示学习}(Representation Learning)\index{Representation Learning} \parinterval 在自然语言处理中,{\small\sffamily\bfseries{句子表示模型}}\index{句子表示模型}是指把输入的句子进行分布式表示。不过表示的形式不一定是一个单独的向量。现在广泛使用的句子表示模型可以被描述为:给定一个输入的句子$ \{ w_1,\dots ,w_m\} $,得到一个表示序列$ \{ \mathbf h_1,\dots ,\mathbf h_m\} $,其中$ h_i $是句子在第$ i $个位置的表示结果。$ \{ \mathbf h_1,\dots ,\mathbf h_m\} $就被看作是{\small\sffamily\bfseries{句子的表示}}\index{句子的表示},它可以被送入下游模块。比如,在机器翻译任务中,可以用这种模型表示源语言句子,然后通过这种表示结果进行目标语译文的生成;在序列标注(如词性标注)任务中,可以对输入的句子进行表示,然后在这个表示之上构建标签预测模块。很多自然语言处理任务都可以用句子表示模型进行建模,因此句子的表示模型也是应用最广泛的深度学习模型之一。而学习这种表示的过程也被称作{\small\sffamily\bfseries{表示学习}}\index{表示学习}(Representation Learning)\index{Representation Learning}
\parinterval{\red{(下一段应该会删掉)}}
\parinterval 句子表示模型有两种训练方法。最简单的方法是把它作为目标系统中的一个模块进行训练,比如把句子表示模型作为机器翻译系统的一部分。也就是,并不单独训练句子表示模型,而是把它作为一个内部模块放到其他系统中。另一种方法是把句子表示作为独立的模块,用外部系统进行训练,之后把训练好的表示模型放入目标系统中,再进行微调。这种方法构成了一种新的范式:预训练+微调(pre-training + fine-tuning)。图\ref{fig:5-69}对比了这两种不同的方法。 \parinterval 句子表示模型有两种训练方法。最简单的方法是把它作为目标系统中的一个模块进行训练,比如把句子表示模型作为机器翻译系统的一部分。也就是,并不单独训练句子表示模型,而是把它作为一个内部模块放到其他系统中。另一种方法是把句子表示作为独立的模块,用外部系统进行训练,之后把训练好的表示模型放入目标系统中,再进行微调。这种方法构成了一种新的范式:预训练+微调(pre-training + fine-tuning)。图\ref{fig:5-69}对比了这两种不同的方法。
%---------------------------------------------- %----------------------------------------------
...@@ -2297,7 +2294,7 @@ Jobs was the CEO of {\red{\underline{apple}}}. ...@@ -2297,7 +2294,7 @@ Jobs was the CEO of {\red{\underline{apple}}}.
\end{figure} \end{figure}
%------------------------------------------- %-------------------------------------------
\parinterval 目前,句子表示模型的预训练方法在多项自然语言处理任务上取得了很好的效果。预训练模型也成为了当今自然语言处理中的热点方向,相关系统也在很多评测任务上刷榜。 \parinterval 目前,句子表示模型的预训练方法在多项自然语言处理任务上取得了很好的效果。预训练模型也成为了当今自然语言处理中的热点方向,相关系统也在很多评测任务上刷榜。不过,上面介绍的模型是一种最简单句子表示模型,在{\chaptersixteen}会对一些前沿的预训练方法和句子表示模型进行介绍。
%---------------------------------------------------------------------------------------- %----------------------------------------------------------------------------------------
% NEW SECTION % NEW SECTION
...@@ -2306,12 +2303,14 @@ Jobs was the CEO of {\red{\underline{apple}}}. ...@@ -2306,12 +2303,14 @@ Jobs was the CEO of {\red{\underline{apple}}}.
\sectionnewpage \sectionnewpage
\section{小结及深入阅读} \section{小结及深入阅读}
\parinterval 神经网络为解决自然语言处理问题提供了全新的思路。而所谓深度学习也是建立在多层神经网络结构之上的一系列模型和方法。本章从神经网络的基本概念到其在语言建模中的应用进行了概述。由于篇幅所限,这里无法覆盖所有神经网络和深度学习的相关内容,感兴趣的读者可以进一步阅读《Neural Network Methods in Natural Language Processing》\cite{goldberg2017neural}和《Deep Learning》\cite{Goodfellow-et-al-2016}。此外,也有很多研究方向值得关注: \parinterval 神经网络为解决自然语言处理问题提供了全新的思路。而所谓深度学习也是建立在多层神经网络结构之上的一系列模型和方法。本章从神经网络的基本概念到其在语言建模中的应用进行了概述。由于篇幅所限,这里无法覆盖所有神经网络和深度学习的相关内容,感兴趣的读者可以进一步阅读\textit{Neural Network Methods in Natural Language Processing}\cite{goldberg2017neural}\textit{Deep Learning}\cite{Goodfellow-et-al-2016}。此外,也有一些研究方向值得关注:
\begin{itemize} \begin{itemize}
\vspace{0.5em} \vspace{0.5em}
\item 端到端学习是神经网络方法的特点之一。这样,系统开发者不需要设计输入和输出的隐含结构,甚至连特征工程都不再需要。但是,另一方面,由于这种端到端学习完全由神经网络自行完成,整个学习过程没有人的先验知识做指导,导致学习的结构和参数很难进行解释。针对这个问题也有很多研究者进行{\small\sffamily\bfseries{可解释机器学习}}\index{可解释机器学习}(Explainable Machine Learning)\index{Explainable Machine Learning}的研究\cite{guidotti2018survey}\cite{koh2017understanding}。对于自然语言处理,方法的可解释性是十分必要的。从另一个角度说,如何使用先验知识改善端到端学习也是很多人关注的方向\cite{arthur2016incorporating}\cite{Zhang2017PriorKI},比如,如何使用句法知识改善自然语言处理模型\cite{zollmann2006syntax}\cite{charniak2003syntax}\cite{stahlberg2016syntactically} \item 端到端学习是神经网络方法的特点之一。这样,系统开发者不需要设计输入和输出的隐含结构,甚至连特征工程都不再需要。但是,另一方面,由于这种端到端学习完全由神经网络自行完成,整个学习过程没有人的先验知识做指导,导致学习的结构和参数很难进行解释。针对这个问题也有很多研究者进行{\small\sffamily\bfseries{可解释机器学习}}\index{可解释机器学习}(Explainable Machine Learning)\index{Explainable Machine Learning}的研究\cite{guidotti2018survey}\cite{koh2017understanding}。对于自然语言处理,方法的可解释性是十分必要的。从另一个角度说,如何使用先验知识改善端到端学习也是很多人关注的方向\cite{arthur2016incorporating}\cite{Zhang2017PriorKI},比如,如何使用句法知识改善自然语言处理模型\cite{zollmann2006syntax}\cite{charniak2003syntax}\cite{stahlberg2016syntactically}
\vspace{0.5em} \vspace{0.5em}
\item {\color{red} 神经语言模型的一些典型工作,可以参考survey:https://arxiv.org/pdf/1906.03591.pdf}
\vspace{0.5em}
\item 词嵌入是自然语言处理近些年的重要进展。所谓“嵌入”是一类方法,理论上,把一个事物进行分布式表示的过程都可以被看作是广义上的“嵌入”。基于这种思想的表示学习也成为了自然语言处理中的前沿方法。比如,如何对树结构,甚至图结构进行分布式表示\cite{plank2013embedding}\cite{perozzi2014deepwalk}成为了分析自然语言的重要方法。此外,除了语言建模,还有很多方式可以进行词嵌入的学习,比如,SENNA\cite{collobert2011natural}、word2vec\cite{DBLP:journals/corr/abs-1301-3781}\cite{mikolov2013distributed}、Glove\cite{DBLP:conf/emnlp/PenningtonSM14}、CoVe\cite{mccann2017learned} 等。 \item 词嵌入是自然语言处理近些年的重要进展。所谓“嵌入”是一类方法,理论上,把一个事物进行分布式表示的过程都可以被看作是广义上的“嵌入”。基于这种思想的表示学习也成为了自然语言处理中的前沿方法。比如,如何对树结构,甚至图结构进行分布式表示\cite{plank2013embedding}\cite{perozzi2014deepwalk}成为了分析自然语言的重要方法。此外,除了语言建模,还有很多方式可以进行词嵌入的学习,比如,SENNA\cite{collobert2011natural}、word2vec\cite{DBLP:journals/corr/abs-1301-3781}\cite{mikolov2013distributed}、Glove\cite{DBLP:conf/emnlp/PenningtonSM14}、CoVe\cite{mccann2017learned} 等。
\vspace{0.5em} \vspace{0.5em}
\end{itemize} \end{itemize}
...@@ -136,10 +136,11 @@ ...@@ -136,10 +136,11 @@
%\include{Chapter3/chapter3} %\include{Chapter3/chapter3}
%\include{Chapter4/chapter4} %\include{Chapter4/chapter4}
%\include{Chapter5/chapter5} %\include{Chapter5/chapter5}
\include{Chapter6/chapter6} %\include{Chapter6/chapter6}
%\include{Chapter7/chapter7} %\include{Chapter7/chapter7}
%\include{Chapter8/chapter8} %\include{Chapter8/chapter8}
%\include{Chapter9/chapter9}
\include{Chapter9/chapter9}
%\include{Chapter10/chapter10} %\include{Chapter10/chapter10}
%\include{Chapter11/chapter11} %\include{Chapter11/chapter11}
%\include{Chapter12/chapter12} %\include{Chapter12/chapter12}
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论