Quantcast
Channel: Life is Tech ! Advent Calendarの記事 - Qiita
Viewing all articles
Browse latest Browse all 25

Unityで体感する強化学習

$
0
0

目的

  • 強化学習について概要を理解する

  • 迷路課題を用いて実際に強化学習をやってみる

  • ML-Agentsを使ってUnity上で強化学習をやってみる

人工知能・機械学習・強化学習

はじめに、強化学習に関連する用語、人工知能(Artificial Intelligence)、機械学習(Machine Learning)、強化学習(Reinforcement Learning)の意味を簡単に押さえておきます。

まず、人工知能についてですが、この言葉には明確な定義が存在しません。人工知能に関する研究領域はかなり広く、研究者についても様々な立場があるため、特定の定義を置くことが難しいという背景があるようです。ここでは「人間が知能を使って行うことをコンピュータに模倣させようとする技術」としておきましょう。

次に、機械学習とは、大量のデータからルールや知識を自ら学習する技術のことを指します。もう少し人工知能との関係を明瞭にする言い方をすれば、「データからその法則性を学習することで、解法が明確でない(人が規則を設計するのが難しい)タスクを遂行するためのモデルを構築すること」となるでしょうか。

重要なのは、機械学習の定義に学習するという文言が含まれることです。一見知的に振る舞うように見えるからといってもそれが全て機械学習であるとは限りません。専門家の知識を人手によってルール化し、そのルールに従って処理するようなシステム(エキスパートシステム)もある種知的に振る舞っているように見えることも多々あります。

機械学習の分類

機械学習の手法を「教師データ(正解ラベル)がどのように与えられるか」という視点で大まかに分類すると以下のようになります。

machine_learning.png

まず、教師あり学習教師なし学習に関してはそのままの意味で、前者は教師データが与えられる手法、後者は教師データが与えられない手法になります。これらの手法とは異なり、強化学習は間接的に教師データが与えられる手法になります。

教師あり学習

売上予測や株価予測のように、入力データと正解の数値の組み合わせを学習し、未知データから数値(連続値)を予測する回帰(Regression)と、画像分類、迷惑メールの判定といった入力データと正解のクラスの組み合わせを学習することで、未知データからクラスを予測する分類(Classification)の2種類に大別されます。

教師なし学習

代表的な手法として、データを同じような性質を持ついくつかのグループに分けるクラスタリング(Clustering)というものがあります。例えば、商品の購入状況から顧客をグループ化し、それぞれのグループに適したマーケティング手段を用いることでマーケティング効果を上げるといった使い方もできます。

他にも、

  • ソーシャルネットワーク分析(SNS上で人のつながりを分析する)
  • アソシエーション分析(顧客ごとに異なったオススメ商品を紹介する)

も教師なし学習の一種です。

強化学習

間接的に教師データが与えられるというのは、個々の決定に対して明確な正解は与えられないが、連続した決定の結果に対して(形を変えた)教師データが遅れて与えられるということです。例えば、コンピュータ将棋のソフトウェアを考えたとき、各盤面でどの駒をどのように動かせばよいかという正解は与えられませんが、最終的に試合に勝ったか負けたかなどの結果は分かります。この、「ゲームに勝つ」というような「長期的な価値」を最大化するように試行錯誤を繰り返し行動を学習するのが強化学習なのです。


深層学習

近年注目を浴びている深層学習も機械学習の一手法です。具体的には下図のように多層なニューラルネットワークを用いる手法です。様々な分野(特に画像、音声、自然言語の分野)でこれまで使われてきた古典的な手法を圧倒する性能を示しており、期待が寄せられています。
スクリーンショット 2018-12-01 03.01.32.png

強化学習

強化学習の概要とよく使われる用語を整理するために、迷路を解くための強化学習を考えます。迷路内をエージェントが移動してゴールを目指し、エージェントがゴールに辿り着くと報酬が与えられるという設定です。このとき、ある状態からどのように行動するのかを定めたルールを方策といいます。ここで、状態とはエージェントがいる位置になり、行動は上下左右のいずれかに進むことに当たります。さらに、今回でいう迷路の構造や設定全体のことを環境といいます。

「環境」についての補足

上記では、環境について少し曖昧な説明をしましたが、もう少し正確に書くと、環境はエージェントから行動の情報を受取り、その結果を報酬と次の状態という形で引き渡すものになります。

用語 説明
エージェント(agent) 行動する主体
環境(environment) エージェントが行動する対象
報酬(reward) エージェントの行動の結果として獲得できる報酬
方策(policy) 行動を決めるルール

方策勾配に基づくアルゴリズム

強化学習のアルゴリズムの一手法として方策勾配に基づくものを紹介します。

方策勾配に基づく強化学習を簡潔に述べると、「方策を確率モデルで表現し、そのパラメータを学習する」ということになりますが、文章を見ていても分かりづらいと思うので先程の迷路の設定で例を考えていきます。

まず、今回エージェントの行動を決めるルールである方策$\pi_{\theta}$を、ある状態から上下左右のいずれかに進むかの確率とします。例えば、迷路上の状態 s0(スタート)では、右(s1)、下(s4)の2方向に進むことができますが、初めのうちは、エージェントはどちらに進めばよいか分からないので、$\frac{1}{2}$の確率で右か下に進むことになります。ただし、迷路の構造を分かっている我々からすれば、下に進んでもゴールにはたどりつけないので、状態 s0 では右に進む確率の方が大きくなってくれると嬉しいです。このように、ゴールにより近づく方向に進む確率が大きくなるように何度も迷路を解くことで訓練していきます。

方策(各状態で上下左右に進む確率)を行列で表すと次のようになります。壁がある方向には進めないので確率は0とし、行は状態(s0〜s14)を、列は方向(↑、→、↓、←)を示します。例えば s1(2行目) は、↑以外の3方向(→、↓、←)に等しく0.333の確率で進むようになっています。

[[0.    0.5   0.5   0.   ]
 [0.    0.333 0.333 0.333]
 [0.    0.    0.    1.   ]
 [0.    0.    1.    0.   ]
 [0.5   0.    0.5   0.   ]
 [0.333 0.333 0.333 0.   ]
 [0.    0.5   0.    0.5  ]
 [0.5   0.    0.    0.5  ]
 [0.5   0.    0.5   0.   ]
 [0.5   0.5   0.    0.   ]
 [0.    0.5   0.    0.5  ]
 [0.    0.    0.5   0.5  ]
 [0.5   0.5   0.    0.   ]
 [0.    0.5   0.    0.5  ]
 [0.    0.    0.    1.   ]]

この方策で迷路問題を解くと、次のようになります。当然ランダムに移動していくようなものなので、最短ルート s0(Start) → s1 ↓ s5 ↓ s9 → s10 → s11 → s15(Goal) (6step)からはほど遠く、同じところを何度も行ったり来たりしています。

[[0, '→'], [1, '←'], [0, '↓'], [4, '↓'], [8, '↑'], [4, '↑'], [0, '↓'], [4, '↑'], [0, '→'], 
[1, '↓'], [5, '→'], [6, '→'], [7, '↑'], [3, '↓'], [7, '↑'], [3, '↓'], [7, '←'], [6, '→'], 
[7, '←'], [6, '→'], [7, '←'], [6, '←'], [5, '→'], [6, '→'], [7, '←'], [6, '←'], [5, '↓'], 
[9, '↑'], [5, '↓'], [9, '↑'], [5, '↓'], [9, '↑'], [5, '→'], [6, '→'], [7, '←'], [6, '→'], 
[7, '←'], [6, '←'], [5, '↓'], [9, '→'], [10, '→'], [11, '↓'], [15, 'np.nan']]
steps : 42

エージェントが最短ルートで迷路をクリアすることができるよう、方策を少しずつ更新していくのですが、実際には、その元になるパラメータ$\theta$を更新していきます。パラメータと言っても今回はかなりシンプルなもので、下のように、進める方向に対しては1、進めない方向(壁)に対してはnp.nanとした行列とします。

np.nan

NaNとはNot a Numberのことで、無限大や不定形など実数で表せないものを表すときや、欠損値などを示すのに使われます。

今回、進めない方向なので0とすればよいのでは?と、疑問かもしれませんが、この壁に進む方向の確率は方策の更新とは無関係に常に0とする必要がありますが、0としていると更新されてしまうかもしれないので、np.nanとしています。np.nanに対しては演算を施してもnp.nanになるので、更新されません。

    theta_0 = np.array([[np.nan, 1, 1, np.nan],       # s0
                        [np.nan, 1, 1, 1],            # s1
                        [np.nan, np.nan, np.nan, 1],  # s2
                        [np.nan, np.nan, 1, np.nan],  # s3
                        [1, np.nan, 1, np.nan],       # s4
                        [1, 1, 1, np.nan],            # s5
                        [np.nan, 1, np.nan, 1],       # s6
                        [1, np.nan, np.nan, 1],       # s7
                        [1, np.nan, 1, np.nan],       # s8
                        [1, 1, np.nan, np.nan],       # s9
                        [np.nan, 1, np.nan, 1],       # s10
                        [np.nan, np.nan, 1, 1],       # s11
                        [1, 1, np.nan, np.nan],       # s12
                        [np.nan, 1, np.nan, 1],       # s13
                        [np.nan, np.nan, np.nan, 1],  # s14
                        ])

このパラメータを下のようにして単純に割合を求め、先程示したような方策とします。

def policy(theta):
    [m, n] = theta.shape
    pi = np.zeros((m, n))

    for i in range(0, m):
        pi[i, :] = theta[i, :] / np.nansum(theta[i, :])

    pi = np.nan_to_num(pi)
    return pi

それでは、下のように方策を更新していきます。

    stop_epsilon = 10**-4

    theta = theta_0
    pi = pi_0

    is_continue = True
    while is_continue:
        sa_history = solve(pi) #迷路を解く
        new_theta = update_theta(theta, pi, sa_history) #パラメータの更新
        new_pi = policy(new_theta) #方策の更新(パラメータ -> 方策)

        if np.sum(np.abs(new_pi - pi)) < stop_epsil
            is_continue = False
        else:
            theta = new_theta
            pi = new_pi

迷路を解く関数

引数:方策
戻り値:エージェントがどのルートを通ったかの記録
def solve(pi):
    s = 0  # スタート
    sa_history = [[0, np.nan]]
    while (1):
        [action, next_s] = action_and_next_s(pi, s)
        sa_history[-1][1] = action
        sa_history.append([next_s, np.nan])

        if next_s == 15:  #ゴール
            break
        else:
            s = next_s
    return sa_history

パラメータの更新

引数:パラメータ、方策、エージェントがどのルートを通ったかの記録
戻り値:更新後のパラメータ
def update_theta(theta, pi, sa_history):
    eta = 0.1 # 学習率
    T = len(sa_history) - 1  # ゴールまでの総ステップ数

    [m, n] = theta.shape
    delta_theta = theta.copy()

    for i in range(0, m):
        for j in range(0, n):
            if not(np.isnan(theta[i, j])):
                SA_i = [SA for SA in sa_history if SA[0] == i]
                SA_ij = [SA for SA in sa_history if SA == [i, j]]

                N_i = len(SA_i)
                N_ij = len(SA_ij)

                delta_theta[i, j] = (N_ij - pi[i, j] * N_i) / T

    new_theta = theta + eta * delta_theta
    return new_theta

学習が進んでいくと、段々と最短ルートに近いステップ数(6step)でゴールすることができるようになっています。

Update amount0.0133  Steps 66
Update amount0.0161  Steps 62
Update amount0.0104  Steps 94
Update amount0.0105  Steps 78
Update amount0.0097  Steps 252
Update amount0.0100  Steps 74

~~~~~~~~~~~~省略~~~~~~~~~~~~~~

Update amount0.0041  Steps 6
Update amount0.0131  Steps 10
Update amount0.0036  Steps 6
Update amount0.0144  Steps 8
Update amount0.0036  Steps 6
Update amount0.0036  Steps 6

ゴールまでにかかったステップ数の遷移を図に表すと下のようになります。しっかり減っていっているのが確認できます。
Figure_1.png

最終的な方策と、その方策で迷路を解いた結果は次のようになります。例えばs11(下から4段目)は↓に進めばすぐにゴールですが、↓に進む確率が1で他が0となっており、うまく方策が学習できています。また、その方策で迷路を解くとs0(Start) → s1 ↓ s5 ↓ s9 → s10 → s11 → s15(Goal) (6step)の最短ルートでゴールできていることがわかります。

[[0.    1.    0.    0.   ]
 [0.    0.    0.991 0.008]
 [0.    0.    0.    1.   ]
 [0.    0.    1.    0.   ]
 [0.631 0.    0.369 0.   ]
 [0.    0.    1.    0.   ]
 [0.    0.299 0.    0.701]
 [0.468 0.    0.    0.532]
 [0.574 0.    0.426 0.   ]
 [0.    1.    0.    0.   ]
 [0.    1.    0.    0.   ]
 [0.    0.    1.    0.   ]
 [0.523 0.477 0.    0.   ]
 [0.    0.499 0.    0.501]
 [0.    0.    0.    1.   ]]

[[0, '→'], [1, '↓'], [5, '↓'], [9, '→'], [10, '→'], [11, '↓'], [15, 'np.nan']]
steps : 6

今回使用した、ソースコードは以下に置いてあります。
https://github.com/casp39/rl_maze

ML-Agents

Unity上で、強化学習、模倣学習、その他機械学習が行えるプラグインであるML-Agentsの導入をし、サンプルのゲームを動かしてみます。

ML-Agents 環境構築

Unityのインストール

Unityをダウンロードし、インストールします。

ML-Agents5.0 は Unity2017.4 以降のバージョンにしか対応していません。
今回は、Unity2017.4を使用していきます。

ML-Agentsリポジトリをクローン

適当なディレクトリにリポジトリをクローンします。

git clone https://github.com/Unity-Technologies/ml-agents.git

Python と mlagents Packageのインストール

ML-Agentsのインストールが終わったらPythonと、周辺ライブラリを入れます。

まず、Anacondaをダウンロードし、インストールします。

Anaconda

Anacondaは、データサイエンス、機械学習などの科学計算でよく用いられるライブラリをまとめた、ディストリビューションです。
スクリーンショット 2018-12-03 18.24.33.png

Anacondaのインストールが終わったらML-Agents用の仮想環境を用意します。

conda create --name mlagents python=3.6
source activate mlagents
cd ml-agents/ml-agents/
pip3 install .

※注 Anacondaを使用せず直接Pythonやライブラリをインストールしても大丈夫ですが、ML-Agents5.0は(2018年12月現在)Pythonの最新バージョンPython3.7には対応していないので注意してください。

UnityでML-Agentsをセットアップ

1. Unityを起動する

2. Openボタン を選択し、ml-agents/UnitySDK/ を開く

3. Edit > Project Settings > Player

 Other Settingsを変更する

Scripting Runtime Version Experimental (.NET 4.6 Equivalent or .NET 4.x Equivalent)
Scripting Defined Symbols ENABLE_TENSORFLOW

参考画像

スクリーンショット 2018-11-29 00.22.20.png

4. File > Save Project

5. TensorFlowSharpプラグインをインポートする

 TensorFlowSharpプラグインをダウンロードします。ダウンロードしたファイルをダブルクリックしてUnityにインポートします。
 https://s3.amazonaws.com/unity-ml-agents/0.5/TFSharpPlugin.unitypackage

参考画像

スクリーンショット 2018-11-29 01.16.04.png

サンプルの学習環境

Project window で Assets/ML-Agents/Examples/3DBall フォルダの中の 3DBall シーンファイル を開きます。今回扱うサンプルは板の上に落下してくるボールを落とさないように板を動かしてバランスをとるというようなものです。

スクリーンショット 2018-11-29 01.24.01.png

強化学習によるトレーニング

準備

1. Hierarchy window で Ball3DAcademy>Ball3DBrain オブジェクトを選択

2. Inspector window で Brain Type を External に

参考画像

スクリーンショット 2018-11-29 02.30.12.png

学習

1. 再度ターミナルを開き ML-Agentsリポジトリ をクローンしたディレクトリへ移動し、次を実行する

mlagents-learn config/trainer_config.yaml --run-id=firstRun --train

オプションの--train学習を実行することを表しています。(推論時は不要)

2. Start training by pressing the Play button in the Unity Editor. のメッセージが表示されるのを確認後 Unity に戻り、 再生ボタン を押す
スクリーンショット 2018-11-29 23.47.33.png

すると、学習が始まります。初めのうちは板はボールをボロボロ落としています(左図)が、学習が進んでくるとなかなかボールを落とさなくなってくる(右図)のが分かります。

1000ステップ後 10000ステップ後
before.gif after.gif

ターミナルにはもう少し詳しい情報が表示されています。少し確認してみましょう。

情報全体
INFO:mlagents.envs:Hyperparameters for the PPO Trainer of brain Ball3DBrain:
    batch_size: 64
    beta:   0.001
    buffer_size:    12000
    epsilon:    0.2
    gamma:  0.995
    hidden_units:   128
    lambd:  0.99
    learning_rate:  0.0003
    max_steps:  5.0e4
    normalize:  True
    num_epoch:  3
    num_layers: 2
    time_horizon:   1000
    sequence_length:    64
    summary_freq:   1000
    use_recurrent:  False
    graph_scope:
    summary_path:   ./summaries/firstRun-0
    memory_size:    256
    use_curiosity:  False
    curiosity_strength: 0.01
    curiosity_enc_size: 128
INFO:mlagents.trainers: firstRun-0: Ball3DBrain: Step: 1000. Mean Reward: 1.180. Std of Reward: 0.684. Training.
INFO:mlagents.trainers: firstRun-0: Ball3DBrain: Step: 2000. Mean Reward: 1.328. Std of Reward: 0.755. Training.
INFO:mlagents.trainers: firstRun-0: Ball3DBrain: Step: 3000. Mean Reward: 1.664. Std of Reward: 0.970. Training.
INFO:mlagents.trainers: firstRun-0: Ball3DBrain: Step: 4000. Mean Reward: 2.355. Std of Reward: 1.631. Training.
INFO:mlagents.trainers: firstRun-0: Ball3DBrain: Step: 5000. Mean Reward: 3.706. Std of Reward: 3.188. Training.
INFO:mlagents.trainers: firstRun-0: Ball3DBrain: Step: 6000. Mean Reward: 6.255. Std of Reward: 6.104. Training.
INFO:mlagents.trainers: firstRun-0: Ball3DBrain: Step: 7000. Mean Reward: 9.339. Std of Reward: 8.283. Training.
INFO:mlagents.trainers: firstRun-0: Ball3DBrain: Step: 8000. Mean Reward: 12.943. Std of Reward: 11.746. Training.
INFO:mlagents.trainers: firstRun-0: Ball3DBrain: Step: 9000. Mean Reward: 22.260. Std of Reward: 20.912. Training.
INFO:mlagents.trainers: firstRun-0: Ball3DBrain: Step: 10000. Mean Reward: 39.189. Std of Reward: 29.556. Training.
INFO:mlagents.trainers: firstRun-0: Ball3DBrain: Step: 11000. Mean Reward: 48.570. Std of Reward: 33.496. Training.
INFO:mlagents.trainers: firstRun-0: Ball3DBrain: Step: 12000. Mean Reward: 66.494. Std of Reward: 32.763. Training.
INFO:mlagents.trainers: firstRun-0: Ball3DBrain: Step: 13000. Mean Reward: 81.663. Std of Reward: 33.271. Training.
INFO:mlagents.trainers: firstRun-0: Ball3DBrain: Step: 14000. Mean Reward: 88.731. Std of Reward: 21.941. Training.
INFO:mlagents.trainers: firstRun-0: Ball3DBrain: Step: 15000. Mean Reward: 95.200. Std of Reward: 15.920. Training.
INFO:mlagents.trainers: firstRun-0: Ball3DBrain: Step: 16000. Mean Reward: 91.186. Std of Reward: 25.583. Training.
INFO:mlagents.trainers: firstRun-0: Ball3DBrain: Step: 17000. Mean Reward: 86.814. Std of Reward: 32.300. Training.
INFO:mlagents.trainers: firstRun-0: Ball3DBrain: Step: 18000. Mean Reward: 85.985. Std of Reward: 20.061. Training.
INFO:mlagents.trainers: firstRun-0: Ball3DBrain: Step: 19000. Mean Reward: 100.000. Std of Reward: 0.000. Training.
^C--------------------------Now saving model-------------------------
INFO:mlagents.envs:Learning was interrupted. Please wait while the graph is generated.

まず、以下の部分では強化学習のアルゴリズムとしてPPO(Proximal Policy Optimization)を用いていることを表しています。これは、先程説明した方策勾配法ベースの手法で、実装が比較的簡単なアルゴリズムとして注目されています。

INFO:mlagents.envs:Hyperparameters for the PPO Trainer of brain Ball3DBrain:

次に、ステップごとの平均報酬が表されていますが、段々と多くの報酬が貰えるようになっているので学習が上手くいっていることが分かります。

INFO: Step: 1000. Mean Reward: 1.180. 
INFO: Step: 2000. Mean Reward: 1.328. 
INFO: Step: 3000. Mean Reward: 1.664. 
INFO: Step: 4000. Mean Reward: 2.355. 
INFO: Step: 5000. Mean Reward: 3.706. 
INFO: Step: 6000. Mean Reward: 6.255. 
INFO: Step: 7000. Mean Reward: 9.339. 
INFO: Step: 8000. Mean Reward: 12.943. 
INFO: Step: 9000. Mean Reward: 22.260. 
INFO: Step: 10000. Mean Reward: 39.189. 
INFO: Step: 11000. Mean Reward: 48.570. 
INFO: Step: 12000. Mean Reward: 66.494. 
INFO: Step: 13000. Mean Reward: 81.663. 
INFO: Step: 14000. Mean Reward: 88.731. 
INFO: Step: 15000. Mean Reward: 95.200. 
INFO: Step: 16000. Mean Reward: 91.186. 
INFO: Step: 17000. Mean Reward: 86.814. 
INFO: Step: 18000. Mean Reward: 85.985. 
INFO: Step: 19000. Mean Reward: 100.000. 

ちなみに、途中で学習をやめるにはCtrl + Cを押します。

推論

さきほど学習したモデルはmodels/firstRun-0/editor_Ball3DAcademy_firstRun-0.bytesにあります。これをProject windowML-Agents/Examples/3DBall/TFModels/にドラッグします。

参考画像

スクリーンショット 2018-11-30 01.38.15.png

1. Hierarchy window で Ball3DAcademy>Ball3DBrain オブジェクトを選択

2. Inspector window で Brain Type を Internal に

3. Ball3DAcademy>Ball3DBrain の Inspector window の Graph Modelにさきほど学習したモデルをドラッグします。

4. 再生ボタンを押すと推論が始まります。
板がボールを落とさないように制御できていることが分かります。

ML-Agentsには他にも様々なサンプルがあるので、試してみると良いでしょう。

tennis soccer pyramids
tennis.gif soccer.gif pyramids.gif

補足

人工知能の定義

松尾豊「人工知能は人間を超えるか」(KADOKAWA)に国内の研究者複数人の考える人工知能の定義がまとめられています。

人工知能の分類

人工知能は「汎用人工知能」「特化型人工知能」の2つに分類することができます。前者は様々な一般的な状況に対応し、自律的な思考・検討を行なうことができる人工知能のことを指します。ドラえもんをイメージするとわかりやすいでしょう。後者は自動運転や、画像識別、囲碁・将棋など特定の領域に対して専門化した能力を持つ人工知能です。今回の定義は後者寄りですが、実際昨今の人工知能関連の研究の多くは後者にあたります。

中間的学習

機械学習を教師データの与えられ方を基準に、教師あり学習、教師なし学習、強化学習の3種類に分類しましたが、この他に少量のラベル(正解)付きデータと大量のラベル無しデータを用いて学習する半教師あり学習というものもあります。この半教師あり学習と強化学習をまとめて中間的学習とよぶ文献もあります。

参考文献

強化学習

[1] 牧野 貴樹ほか『これからの強化学習』(森北出版、2016)

[2] Richard S.Sutton and Andrew G.Barto(三上 貞芳、 皆川 雅章訳)『強化学習』(森北出版、2006)

[3] 小川雄太郎『つくりながら学ぶ! 深層強化学習』(マイナビ出版、2018)

ML-Agents

[4] 「Learn how to use Unity Machine Learning Agents - Unity」
https://unity3d.com/jp/how-to/unity-machine-learning-agents
(最終検索日 : 2018 年 12 月 9 日)

[5] 布留川 英一『Unityではじめる機械学習・強化学習 Unity ML-Agents実践ゲームプログラミング』(ボーンデジタル、2018)

Proximal Policy Optimization

[6] J Schulman, F Wolski, P Dhariwal, A Radford and O Klimov. “Proximal Policy Optimization Algorithms”. In: arXiv preprint arXiv:1707.06347 (2017).


Viewing all articles
Browse latest Browse all 25

Trending Articles