0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

はじめに

近年、画像認識、自然言語処理、音声認識、生成AIなど、さまざまな分野でニューラルネットワークが利用されています。Pythonのライブラリを使えば、数行のコードでモデルを構築し、学習させることもできます。

一方で、実際にニューラルネットワークが「どのような計算をしているのか」「なぜ学習によって予測精度が上がるのか」「誤差逆伝播法では何を計算しているのか」は、ライブラリを使っているだけでは見えにくい部分です。

本記事では、ニューラルネットワークの基本的なモデルである多層パーセプトロン (MLP: Multi Layer Perceptron) を題材に、ニューラルネットワークの順伝播、損失関数、勾配降下法、誤差逆伝播法の数学的な仕組みを整理します。

本記事の目的

本記事の目的は、MLPを例にして、ニューラルネットワークの学習過程を数学的に理解することです。

想定読者

本記事は、次のような読者を想定しています。

  • ニューラルネットワークの基本的な仕組みを数式で理解したい人
  • 機械学習ライブラリを使ったことはあるが、内部で行われている計算を理解したい人
  • 大学初年級程度の線形代数や微分の知識がある人
  • 連鎖律を使った偏微分の計算にある程度なじみがある人

ニューラルネットワークとは

ニューラルネットワークとは、一言でいえば関数です。数値、テキスト、音声、画像などを入力として、数値、クラス確率、テキスト、画像などを出力することができます。つまり、回帰、分類、文章生成、画像生成などが可能です。ではそれらの目的に応じた関数は、どうやって得ることができるのでしょうか?

そもそも機械学習ってなに?

まず機械学習について簡単に説明します。簡単な数値予測を例にみてみましょう。

例えば、画像のような$(x,y)$ のデータセット(青い点)が大量にあるとします。そして、最小二乗法によって、これらの点の並びを表現する直線(赤い直線)を求めることができます。そしてこの直線を用いることで、例えば$x=60$で$y$の値が分からないデータがあったときに、その$y$の値を予測することができます。
機械学習的な表現をすれば、学習データ(大量の$(x,y)$のデータ)から予測モデル$(y=ax+b)$のパラメータ$a, b$を訓練し(これを学習フェーズといいます)、得られたモデルを使って、ある$x$に対して$y$を予測することができる(これを推論フェーズといいます)、ということになります。

ちなみに、上の例のように1変数$x$から値$y$を予測することを単回帰分析といいます。2変数以上のデータ$(x_1, x_2, x_3, ...)$を入力とし、値$y$を予測する手法を重回帰分析といいます。

ニューラルネットワークの仕組み

では例えば、犬か猫のどちらかが写っている画像が与えられたときに、それが犬なのか、猫なのかを分類するにはどうしたらいいでしょうか?

この場合、入力データは画像です。(色付きの)画像は、より小さな分割単位である画素と、各画素が持つチャンネル値(例えばRGBなら0~255の整数値×3)で表現されます。これを3次元テンソル$\mathcal{X}$の形式で表現し、入力データとします。そしてニューラルネットワークのモデル$f$によって、出力が得られます。犬猫の2クラス分類であれば、出力は2つの成分を持つ1次元ベクトルであり、各成分は画像が犬あるいは猫である確率を表します。

一般的に、画像分類では畳み込みニューラルネットワーク(CNN : Convolutional Neural Network) というモデルが用いられることが多いです。このモデルは画像を3次元テンソルのまま扱います。しかし今回は、話を簡単にするために、ニューラルネットワークの代表的かつ基本的なモデルである多層パーセプトロン(MLP : Multi Layer Perceptron) というモデルの原理について説明します。MLPでは、入力データや中間出力をすべて1次元ベクトルとして扱います。したがって、入力となる画像のようなデータも、画素及びチャンネルを1次元ベクトル$\boldsymbol{x}$として扱います。MLPでも、(CNNと比較すると精度は落ちますが)画像分類はできますし、ニューラルネットワークの本質的な原理である勾配降下法誤差逆伝播は共通しています。なので今回は、これらの原理の理解に焦点を当てるために、MLPを例にして説明していきます。

以降では、MLPの仕組みについて式と図を用いてより詳しく説明します。式は一般化したものを書いていますが、図は分かりやすさのために具体化しています。

MLPによる多クラス分類問題では、次のようなモデル$f$を用いて、入力データに対して推論 (学習が完了したモデルを用いて、新しいデータに対して予測を行うこと) を行います。

$$
f: \mathbb{R}^{n_0} \to \mathbb{R}^{n_{H+1}}
$$

ここで、

  • $H$ : 隠れ層の層数
  • $n_0$: 入力層のノード数
  • $n_{H+1}$:出力層のノード数

です。入力層 (図中の「input layer」) とは入力データ(1次元ベクトル$\boldsymbol{x}$)のことであり、出力層 (図中の「output layer」) とは出力$\boldsymbol{y}$のことです。ノード (図中の丸) は、ベクトルの各成分のことです。そして、隠れ層 (図中の「hidden layer1」や「hidden layer2」) とは、前の層の出力に対して、重み行列$\mathrm{W}$を作用させ、バイアスベクトル$\boldsymbol{b}$を加え、さらに非線形関数を適用して中間出力(別の1次元ベクトル)を得る1連の処理のことです。

一般化したMLPのモデル$f$は、例えば以下のように表現されます。

$$
\boldsymbol{x} \in \mathbb{R}^{n_0}
$$

$$
\boldsymbol{u}^1 = \mathrm{W}^1 \boldsymbol{x} + \boldsymbol{b}^1 \tag{1}
$$

$$
\boldsymbol{h}^1 = \sigma(\boldsymbol{u^1}) \tag{2}
$$

$$
:
$$

$$
\boldsymbol{u}^k = \mathrm{W}^k \boldsymbol{h}^{k-1} + \boldsymbol{b}^k \tag{3}
$$

$$
\boldsymbol{h}^k = \sigma(\boldsymbol{u^k}) \tag{4}
$$

$$
:
$$

$$
\boldsymbol{y} = \mathrm{W}^{H+1} \boldsymbol{h}^H + \boldsymbol{b}^{H+1} \tag{5}
$$

ここで、上付きの$k(=1,2,...,H)$は何番目の隠れ層における値なのかを表します。
重み行列 $\mathrm{W}^1$ のサイズは、隠れ層1 のノードの数を $n_1$ とすると $(n_1 \times n_0)$ です。 また、$\boldsymbol{b}^1 \in \mathbb{R}^{n_1}$ はバイアスベクトルです。同様に、隠れ層$k$のノード数を $n_k$とすると、 $\mathrm{W}^k$は $(n_k \times n_{k-1})$行列、 $\boldsymbol{b}^k \in \mathbb{R}^{n_k}$ 、... 、 $\mathrm{W}^{H+1}$は $(n_{H+1} \times n_H)$行列、 $\boldsymbol{b}^{H+1} \in \mathbb{R}^{n_{H+1}}$ となります。
また、$\sigma$ はニューラルネットワークに非線形性を入れるための活性化関数です。

図を例にして考えてみます。
入力データは6個の成分を持つ1次元ベクトル$\boldsymbol{x}$です。そして、4個のノードを持つ隠れ層1と、2個のノードを持つ隠れ層2があり、出力は2個の成分を持つ1次元ベクトル$\boldsymbol{y}$です。隠れ層1では、入力データ$\boldsymbol{x}$に重み行列$\mathrm{W}_1$を作用させ、バイアスベクトル$\boldsymbol{b}_1$を加えて$\boldsymbol{u}_1$を得たのち、$\boldsymbol{u}_1$の各成分に非線形関数$\sigma$を適用して$\boldsymbol{h}_1$を得ます。$\mathrm{W}_1$は$(4×6)$行列、$\boldsymbol{b}_1, \boldsymbol{u}_1, \boldsymbol{h}_1$はいずれも4つの成分を持つ1次元ベクトルです。隠れ層2では、隠れ層1で得られた$\boldsymbol{h}_1$に重み行列$\mathrm{W}_2$を作用させ、バイアスベクトル$\boldsymbol{b}_2$を加えて$\boldsymbol{u}_2$を得たのち、$\boldsymbol{u}_2$の各成分に非線形関数$\sigma$を適用して$\boldsymbol{h}_2$を得ます。$\mathrm{W}_2$は$(2×4)$行列、$\boldsymbol{b}_2, \boldsymbol{u}_2, \boldsymbol{h}_2$はいずれも2つの成分を持つ1次元ベクトルです。省略しますが、隠れ層2→出力層の変換も同様です。各層で適用する非線形関数$\sigma$は、すべての層で共通の関数を用いるのが一般的です。隠れ層を何層にするか、また各隠れ層のノード数をそれぞれいくつにするかは自由に設定することができ、出力層のノード数は問題に応じて決定されます。犬猫分類では出力層のノード数は2になります。
 この図のMLPモデルでは、$\mathrm{W}_1, \mathrm{W}_2, \mathrm{W}_3, \boldsymbol{b}_1, \boldsymbol{b}_2, \boldsymbol{b}_3$の各成分が学習対象のパラメータです。これらについて、( 上の例での a, b )のように、何らかの方法で最適な値を求めることで、予測モデルを得ることができます。このときに使われるのが学習データです。犬猫分類の例なら、学習データは入力としての犬あるいは猫の画像と 出力としての正解ラベル(犬画像なら$(1,0)$, 猫画像なら$(0,1)$) の組み合わせになります。

ニューラルネットワークの訓練

設定したモデルのパラメータ$\mathrm{W}^1, ..., \mathrm{W}^k, ... , \mathrm{W}^{H+1}, \boldsymbol{b}^1, ..., \boldsymbol{b}^k, ..., \boldsymbol{b}^{H+1}$を最適化して、未知の入力データに対して精度の良い予測ができるようにします。

以降の記述では、 $t_i y_i$ のように同じ下付き添え字が1つの項に複数含まれる場合がありますが、総和規約ではありません。和を取るときは明示的に $\sum$で書いています。

前準備

非線形関数としてのシグモイド関数

今回は非線形関数として、次式で表されるシグモイド関数を用いることにします。

$$
\sigma (x) = \dfrac{1}{1+e^{-x}} \tag{6}
$$

出力値を確率に変換するためのsoftmax関数

$(5)$によって得られる出力値$\boldsymbol{y}$を確率$\hat{\boldsymbol{y}}$に変換するための正規化関数として、次式で表される$\mathrm{softmax}$ 関数を用いることにします。

$$
\hat{y_i} = \left( \mathrm{softmax} \left( \boldsymbol{y} \right) \right)_i = \frac{e^{y_i}}{\sum_{j=1}^{n_{H+1}} e^{y_j}} \tag{7}
$$

$\hat{y}_i$ はクラス $i$ に対する予測確率を表します。

重み行列とバイアスベクトルの初期値

最初に、重み行列とバイアスベクトルの各成分に対して適当な初期値を設定します。例えば、重み行列の各成分は一様乱数を入力サイズでスケーリングした値で初期化し、バイアスベクトルの各成分はすべて0で初期化します。

$$
\mathrm{W}^k の各成分 \sim \mathrm{Uniform} (-1, 1) × \sqrt{1 / n_{k-1}} \tag{8}
$$

$$
\boldsymbol{b}^i = \boldsymbol{0} \tag{9}
$$

損失関数(目的関数)

分類問題なので、目的関数(objective function)として次の交差エントロピー(cross entropy) を用います。

$$
L = - \sum_{i=1}^{n_{H+1}} t_i \log \hat{y}_i \tag{10}
$$

ここで、 $\boldsymbol{t}$ は正解ラベルであり、 $t_i (i= 1,2, ..., n_{H+1})$ のいずれか1つだけが1で、それ以外が0であるようなベクトル(ワンホットベクトル)です。$L$の値が小さいほど、正解との誤差が小さいことを意味します。
したがって$L$を最小にする重み行列 $\boldsymbol{W}^i$ およびバイアスベクトル $\boldsymbol{b}^i$ ($i=1,2, ..., H+1$) を求めればいい、ということになります。

重み行列とバイアスベクトルの最適化

ここからがこの記事の本題です。
$L(\mathrm{W}^1, ..., \mathrm{W}^i, ... , \mathrm{W}^{H+1}, \boldsymbol{b}^1, ..., \boldsymbol{b}^i, ..., \boldsymbol{b}^{H+1})$ を最小にする $\mathrm{W}^i$ および $\boldsymbol{b}^i$ ($i=1,2, ..., H+1$)を決定します。 $\mathrm{W}^i$ および $\boldsymbol{b}^i$はそれぞれ独立変数なので、それぞれの変数で $L$を偏微分し、微小量$\eta$(学習率と呼ばれる)との積をとって、$L$が小さくなる方向へ変数を更新すればよいです。

$$
\mathrm{W}^k \leftarrow \mathrm{W}^k - \eta \frac{\partial L}{\partial \mathrm{W}^k},\qquad \boldsymbol{b}^k \leftarrow \boldsymbol{b}^k - \eta \frac{\partial L}{\partial \boldsymbol{b}^k}
$$

(したがって、各変数の最終的な収束値に対して $L$がとるのは局所最小であり、必ずしも大域最小ではありません。重み行列やバイアスベクトルの初期値次第で、最終的に収束する(訓練後の)重み行列やバイアスベクトルの値は異なる可能性があります。)

出力層

まず、 $\mathrm{W}^{H+1}$を更新するために $\partial L / \partial \mathrm{W}^{H+1}$ を求めます。何が$L$を決定するかを考えて$(y→\hat{y}→L$の順なので$)$ 素直に連鎖則を用いれば、その$(i,j)$成分について、

$$
\dfrac{\partial L}{\partial \mathrm{W}^{H+1} _{i, j} } = \sum_{k} \dfrac{\partial L}{\partial \hat{y}_k} \dfrac{\partial \hat{y}_k}{\partial \mathrm{W}^{H+1} _{i,j} } = \sum_k \sum_l \dfrac{\partial L}{\partial \hat{y}_k} \dfrac{\partial \hat{y}_k}{\partial y_l} \dfrac{\partial y_l}{\partial \mathrm{W}^{H+1} _{i,j} } \tag{11}
$$

と書けます。しかしここであえて、次のように書きます。

$$
\dfrac{\partial L}{\partial \mathrm{W}^{H+1} _{i, j} } = \sum_{l} \dfrac{\partial L}{\partial y_l} \dfrac{\partial y_l}{\partial \mathrm{W}^{H+1} _{i,j} } \tag{12}
$$

そして、

$$
\dfrac{\partial L}{\partial y_l}=\sum_k \dfrac{\partial L}{\partial \hat{y}_k} \dfrac{\partial \hat{y}_k}{\partial y_l} \tag{13}
$$

について、

$$
\dfrac{\partial L}{\partial \hat{y}_k} = \dfrac{\partial}{\partial \hat{y}_k} \left(- \sum_{i=1}^{n_{H+1}} t_i \log \hat{y}_i \right) = - \dfrac{t_k}{\hat{y}_k} \tag{14}
$$

です。また、

$$
\dfrac{\partial \hat{y}_k}{\partial y_l} = \dfrac{\partial}{\partial y_l} \frac{e^{y_k}}{\sum_{i=1}^{n_{H+1}} e^{y_i}} \tag{15}
$$

であり、$k=l$ なら

$$
\dfrac{\partial}{\partial y_l} \frac{e^{y_k}}{\sum_{i=1}^{n_{H+1}} e^{y_i}} = \dfrac{\partial}{\partial y_k} \frac{e^{y_k}}{\sum_{i=1}^{n_{H+1}} e^{y_i}} = \dfrac{e^{y_k} \sum_{i=1}^{n_{H+1}} e^{y_i} - \left( e^{y_k}\right)^2}{\left( \sum_{i=1}^{n_{H+1}} e^{y_i}\right)^2} = \hat{y}_k \left( 1-\hat{y}_k\right)
$$

$k \neq l$ なら

$$
\dfrac{\partial}{\partial y_l} \frac{e^{y_k}}{\sum_{i=1}^{n_{H+1}} e^{y_i}} = \dfrac{-e^{y_k} e^{y_l}}{\left( \sum_{i=1}^{n_{H+1}} e^{y_i}\right)^2} = - \hat{y}_k \hat{y}_l
$$

であるので、これらをクロネッカーのデルタを用いてまとめて表せば $(15)$ は

$$
\dfrac{\partial \hat{y}_k}{\partial y_l} = \hat{y}_k \left( \delta_{kl} - \hat{y}_l\right) \tag{16}
$$

となります。

$(14)$, $(16)$を$(13)$に代入して

$$
\dfrac{\partial L}{\partial y_l}=\sum_k \left( t_k \left( \hat{y}_l - \delta_{kl} \right)\right) = \hat{y}_l - t_l \tag{17}
$$

と表せます。ベクトルを用いれば$(17)$は次のように表せます。

$$
\dfrac{\partial L}{\partial \boldsymbol{y}}= \hat{\boldsymbol{y}}-\boldsymbol{t} \tag{18}
$$

ところで、出力層なので特別に$\boldsymbol{y}$という文字を使っていますが、これは$\boldsymbol{u}^{H+1}$と同じ意味です。そして(天下り的ではありますが)、

$$
\dfrac{\partial L}{\partial \boldsymbol{u}^i}
$$

を $i$番目の隠れ層の "誤差" と呼び、$\boldsymbol{\delta}^i$と表すことにします。今回は、$H+1$番目の隠れ層、すなわち出力層の誤差として、

$$
\boldsymbol{\delta}^{H+1} = \dfrac{\partial L}{\partial \boldsymbol{u}^{H+1}} = \hat{\boldsymbol{y}}-\boldsymbol{t} \tag{19}
$$

と求まったことになります。

話を$(11)$、$(12)$あたりまで戻して、 $\dfrac{\partial y_l}{\partial W^{H+1}_{i,j}}$ について、

$$
\dfrac{\partial y_l}{\partial W^{H+1}_{i,j}} = \dfrac{\partial }{\partial W^{H+1} _{i,j}} \left( \sum_{m} W^{H+1} _{l,m} h^H _m + b^{H+1} _{l} \right) = h^H _j \delta _{li} \tag{20}
$$

と表せます。

$(17)$および$(20)$を$(12)$に代入すれば、

$$
\dfrac{\partial L}{\partial \mathrm{W}^{H+1} _{i, j} } = \sum_{l} \left( \hat{y}_l - \delta_{rl} \right)h^H_j \delta_{li} = \left( \hat{y}_i - \delta_{ri}\right) h^H_j \tag{21}
$$

であり、これをまとめて表記すれば

$$
\dfrac{\partial L}{\partial \mathrm{W}^{H+1} } = \boldsymbol{\delta}^{H+1}\left( \boldsymbol{h}^H\right)^T \tag{22}
$$

と書けます。これにより、$\partial L / \partial \mathrm{W} ^{H+1}$ を計算することができます。

次に、 $\dfrac{\partial L}{\partial \boldsymbol{b}^{H+1}}$ について、 $i$ 成分について考えると

$$
\dfrac{\partial L}{\partial b^{H+1}_{i} } = \sum_j \dfrac{\partial L}{\partial \hat{y}_j} \dfrac{\partial \hat{y}_j}{\partial b^{H+1}_i} = \sum_j \sum_k \dfrac{\partial L}{\partial \hat{y}_j} \dfrac{\partial \hat{y}_j}{\partial y_k} \dfrac{\partial y_k}{\partial b^{H+1} _i} \tag{23}
$$

ここでも、あえて

$$
\dfrac{\partial L}{\partial b^{H+1}_{i} } = \sum_k \dfrac{\partial L}{\partial y_k} \dfrac{\partial y_k}{\partial b^{H+1}_i} \tag{24}
$$

とします。

$$
\dfrac{\partial y_k}{\partial b^{H+1}_i}=\delta_{ki} \tag{25}
$$

であるから、$(17)$とあわせて$(24)$に代入すれば

$$
\dfrac{\partial L}{\partial b^{H+1} _i} = \sum_k \left( \delta_{ki} \left( \hat{y}_k - \delta_{rk} \right)\right) = \hat{y}_i - \delta_{ri} \tag{26}
$$

となります。ベクトルを用いて表せば

$$
\dfrac{\partial L}{\partial \boldsymbol{b}^{H+1}} = \hat{\boldsymbol{y}} - \boldsymbol{t} = \boldsymbol{\delta}^{H+1} \tag{27}
$$

となります。これにより、$\partial L / \partial \boldsymbol{b}^{H+1}$が計算できます。

隠れ層

(入力層を含めず)入力層側から数えて$k$番目の隠れ層について、$\mathrm{W}^k$を更新するために$\partial L / \partial \mathrm{W}^k$ を求めます。その$(i,j)$成分について、

$$
\dfrac{\partial L}{\partial \mathrm{W}^k_{i,j}} = \sum_l \dfrac{\partial L}{\partial u_l^{k+1}} \dfrac{\partial u_l^{k+1}}{\partial \mathrm{W}^k_{i,j}} = \sum_l \sum_m \dfrac{\partial L}{\partial u^{k+1}_l} \dfrac{\partial u_l^{k+1}}{\partial h_m^k} \dfrac{\partial h_m^k}{\partial \mathrm{W}^k_{i,j}}
$$

$$
= \sum_l \sum_m \dfrac{\partial L}{\partial u^{k+1}_l} \dfrac{\partial u_l^{k+1}}{\partial h_m^k} \dfrac{\partial h_m^k}{\partial u^k_m} \dfrac{\partial u^k_m}{\partial \mathrm{W}^k_{i,j}} \tag{28}
$$

ここで、$\partial L /\partial u^{k+1}_l=\delta^{k+1}_l$ は$(k+1)$番目の隠れ層における誤差であり、すでに求められているものとします。また、$(3)$より $\partial u^{k+1}_l / \partial h^k_m = \mathrm{W}^{k+1}_{l,m}$ であり、$(4)$より、$\partial h^k_m / \partial u^k_m = \sigma ' ( u_m^k )$ です。また、$(3)$より $\partial u_m^k / \partial \mathrm{W}^k_{i,j} = h^{k-1}_j \delta_{mi}$ であるので、これらを$(28)$に代入して

$$
\dfrac{\partial L}{\partial \mathrm{W}^k_{i,j}} = \sum_l \delta^{k+1}_l \mathrm{W}^{k+1}_{l,i} \sigma'(u^k_i)h^{k-1}_j \tag{29}
$$

と書けます。これは計算で求めることができます。また、$\partial L / \partial \mathrm{W}^k_{i,j}$は別の書き方をすれば

$$
\dfrac{\partial L}{\partial \mathrm{W}^k_{i,j}} = \sum_l \dfrac{\partial L}{\partial u^k_l} \dfrac{\partial u^k_l}{\partial \mathrm{W}^k_{i,j}} = \sum_l \dfrac{\partial L}{\partial u^k_l} h_j^{k-1}\delta_{li} = \dfrac{\partial L}{\partial u^k_i} h_j^{k-1} \tag{30}
$$

であり、$(29)$と$(30)$を比較して、$k$番目の隠れ層における誤差 $\partial L / \partial u^k_i$ は、

$$
\dfrac{\partial L}{\partial u^k_i} = \sum_l \delta^{k+1}_l \mathrm{W}^{k+1}_{l,i} \sigma'(u^k_i) \tag{31}
$$

と求められることが分かります。$(31)$をベクトルで表記すれば、$k$番目の隠れ層における誤差 $\boldsymbol{\delta}^k$は、

$$
\boldsymbol{\delta}^k = \left( \mathrm{W}^{k+1}\right)^T \boldsymbol{\delta}^{k+1} \odot \sigma '(\boldsymbol{u}^k) \tag{32}
$$

と表すことができます。ここで、$\odot$ はアダマール積 (同じサイズの行列同士で要素ごとに掛け算) を表します。そして、$(32)$から分かるように、$k+1$番目の隠れ層における誤差が分かれば、$k$番目の隠れ層における誤差を求めることができます。このように、出力層側の隠れ層から入力層側の隠れ層に向かって順番に、誤差$\boldsymbol{\delta}^k$および$\partial L / \partial \mathrm{W}^k$ (および$\partial L / \partial \boldsymbol{b}^k$) を求めていくことができます。これを誤差逆伝播といいます。

また、$(32)$を用いれば、$\partial L / \partial \mathrm{W}^k$ も以下のようによりシンプルに表現することができます。

$$
\dfrac{\partial L}{\partial \mathrm{W}^k} =
\boldsymbol{\delta}^k \left( \boldsymbol{h}^{k-1}\right) ^T \tag{33}
$$

これは$(22)$と同じ形です。

参考文献

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?