かざん技術ブログ

Python, Unity, Deep Q Network, 強化学習

Vagrantを使ってMac上にHiveの実行環境構築

前回Macにたてた仮想マシン上でHadoopの擬似分散環境を構築したので、今回はさらにHiveが使えるようにしていく。

参考

[1]VagrantでCDH擬似分散環境構築
[2]Setup a Single-node Hadoop machine using CDH5 and HUE – Part 5

VM起動、接続

$ vagrant up
$ vagrant ssh

手順

ファイルディスクリプタ:数の上限を増やす
[root@cdh-train ~]# vim /etc/security/limits.conf
# /etc/security/limits.conf
* soft nofile 65536
* hard nofile 65536
Hiveとmetastore関連パッケージのインストール
[root@cdh-train ~]# yum install hive
[root@cdh-train ~]# yum install hive-metastore mysql-server mysql-connector-java
hiveユーザができていることを確認
[root@cdh-train ~]# hdfs dfs -ls /user
~ 中略 ~
drwxrwxrwx   - hive    supergroup          0 2018-09-17 04:48 /user/hive
~ 中略 ~
hive metastoreをmysqlに設定([2])
[root@cdh-train ~]# sudo -u hive hdfs dfs -mkdir /user/hive/warehouse
[root@cdh-train ~]# sudo -u hive hdfs dfs -chmod 1777 /user/hive/warehouse
[root@cdh-train ~]# cp /usr/share/java/mysql-connector-java.jar /usr/lib/hive/lib/.
[root@cdh-train ~]# service mysqld start
[root@cdh-train ~]# chkconfig mysqld on
[root@cdh-train ~]# /usr/bin/mysql_secure_installation
[root@cdh-train ~]# cd /usr/lib/hive/scripts/metastore/upgrade/mysql
[root@cdh-train mysql]# mysql -u root -p
mysql> create database metastore;
mysql> use metastore;
mysql> source hive-schema-1.1.0.mysql.sql;
mysql> create user 'hive'@'localhost' identified by 'mypassword';
mysql> REVOKE ALL PRIVILEGES, GRANT OPTION FROM 'hive'@'localhost';
mysql> GRANT SELECT,INSERT,UPDATE,DELETE,LOCK TABLES,EXECUTE ON metastore.* TO 'hive'@'localhost';
mysql> FLUSH PRIVILEGES;
mysql> quit;
/usr/lib/hive/conf/hive-site.xmlの編集
  • 変更箇所(4箇所)
<property>
<name>javax.jdo.option.ConnectionURL</name>
<!--<value>jdbc:derby:;databaseName=/var/lib/hive/metastore/metastore_db;create=true</value>-->
<value>jdbc:mysql://localhost/metastore?createDatabaseIfNotExist=true</value>
<description>JDBC connect string for a JDBC metastore</description>
</property>

<property>
<name>javax.jdo.option.ConnectionDriverName</name>
<!--<value>org.apache.derby.jdbc.EmbeddedDriver</value>-->
<!--<description>Driver class name for a JDBC metastore</description>-->
<value>com.mysql.jdbc.Driver</value>
</property>

<property>
<name>javax.jdo.option.ConnectionUserName</name>
<value>hive</value>
</property>

<property>
<name>javax.jdo.option.ConnectionPassword</name>
<value>mypassword</value>
</property>

</configuration>
hive-server2のインストール
yum install hive-server2
spark関連のインストール

[root@cdh-train ~]# yum install spark-core && \ [root@cdh-train ~]# yum install spark-master && \ [root@cdh-train ~]# yum install spark-worker && \ [root@cdh-train ~]# yum install spark-history-server && \ [root@cdh-train ~]# yum install spark-python

HBase,Mahout,Hueのインストール

[root@cdh-train ~]# yum install hbase [root@cdh-train ~]# yum install mahout [root@cdh-train ~]# yum install hue

Vagrantfileを編集し、ポートフォワード設定
  • Vagrantfile
#HDFS namenode
config.vm.network "forwarded_port", guest: 50070, host: 50070
#HDFS datanode
config.vm.network "forwarded_port", guest: 50075, host: 50075

#YARN ResourceManager
config.vm.network "forwarded_port", guest: 8088, host: 8088
#YARN NodeManager
config.vm.network "forwarded_port", guest: 8042, host: 8042
# もしかしてこっち?config.vm.network "forwarded_port", guest: 50060, host: 50060
#JobHistory
config.vm.network "forwarded_port", guest: 19888, host: 19888

## Spark Master, Worker, Driver, HistoryServer
config.vm.network "forwarded_port", guest: 8080, host: 8080
config.vm.network "forwarded_port", guest: 8081, host: 8081
config.vm.network "forwarded_port", guest: 4040, host: 4040
config.vm.network "forwarded_port", guest: 18080, host: 18080

実際にHive使ってみる

Beelineを使ってみる
[root@cdh-train ~]# beeline
Beeline version 1.1.0-cdh5.15.1 by Apache Hive
beeline> !connect jdbc:hive2://localhost:10000/
scan complete in 6ms
Connecting to jdbc:hive2://localhost:10000/
Enter username for jdbc:hive2://localhost:10000/: <- Enter
Enter password for jdbc:hive2://localhost:10000/: <- Enter
Connected to: Apache Hive (version 1.1.0-cdh5.15.1)
Driver: Hive JDBC (version 1.1.0-cdh5.15.1)
Transaction isolation: TRANSACTION_REPEATABLE_READ

0: jdbc:hive2://localhost:10000/> create database test;

0: jdbc:hive2://localhost:10000/> show databases;
INFO  : Compiling command(queryId=hive_20180925092727_ebd569f2-8196-4a3c-98b1-f4f63ae283f7): show databases
INFO  : Semantic Analysis Completed
INFO  : Returning Hive schema: Schema(fieldSchemas:[FieldSchema(name:database_name, type:string, comment:from deserializer)], properties:null)
INFO  : Completed compiling command(queryId=hive_20180925092727_ebd569f2-8196-4a3c-98b1-f4f63ae283f7); Time taken: 0.492 seconds
INFO  : Concurrency mode is disabled, not creating a lock manager
INFO  : Executing command(queryId=hive_20180925092727_ebd569f2-8196-4a3c-98b1-f4f63ae283f7): show databases
INFO  : Starting task [Stage-0:DDL] in serial mode
INFO  : Completed executing command(queryId=hive_20180925092727_ebd569f2-8196-4a3c-98b1-f4f63ae283f7); Time taken: 0.069 seconds
INFO  : OK
+----------------+--+
| database_name  |
+----------------+--+
| default        |
| test           |
+----------------+--+
2 rows selected (0.858 seconds)
データロードしてみる
  • 以下のようなタブ区切りでレコードが記述されたデータ(sample.txt)を用意
  • 左から西暦、気温、品質コードを表すこととする
1950 0   1
1950    22  1
1950    -22 2
1950    -11 1
1950    -13 2
1949    35  1
1949    111 2
1949    111 2
1949    13  1
  • テーブル作成
jdbc:hive2://localhost:10000/> create table records(year STRING,temperature INT,quality INT)ROW FORMAT DELIMITEDfields terminated by '\t';

jdbc:hive2://localhost:10000/> show tables in test;
~ 中略 ~
+-----------+--+
| tab_name  |
+-----------+--+
| records   |
+-----------+--+
1 row selected (0.155 seconds)
  • データのロード
  • sample.txtは/vagrantディrクトリ直下に置いておく
jdbc:hive2://localhost:10000/> load data local inpath '/vagrant/sample.txt' overwrite into table records;
  • このコマンドを実行するとHiveは指定されたローカルファイルをHiveの保管用ディレクトリに保存する
  • ファイルはそのままのフォーマットで保存され、Hiveが手を入れることはない
  • なお、Hiveの保管用ディレクトリはHDFSに設定している(fs.default.name)
  • 確認
[root@cdh-train ~]# sudo -u hive hdfs dfs -ls warehouse/test.db/records
Found 1 items
-rwxrwxrwt   1 anonymous supergroup         51 2018-09-25 09:36 warehouse/test.db/records/sample.txt
  • 無事できている
  • では、実際にデータを引っ張ってくるクエリを叩いてみる
jdbc:hive2://localhost:10000/> select year, MAX(temperature)
. . . . . . . . . . . . . . . . > from records
. . . . . . . . . . . . . . . . > where temperature != 9999
. . . . . . . . . . . . . . . . > and quality = 1
. . . . . . . . . . . . . . . . > group by year;
~ 中略 ~
+-------+------+--+
| year  | _c1  |
+-------+------+--+
| 1949  | 35  |
| 1950  | 22   |
+-------+------+--+
2 rows selected (80.498 seconds)

Vagrantを使ってMac上にHadoop擬似分散環境を構築

Mac上でHadoopの擬似分散モードを試す環境を構築したので、その備忘録

参考

[1]VagrantでCDH擬似分散環境構築
[2]Macのローカル環境でhadoopを1から勉強する
[3]bash 起動時の環境ファイルについて調べてみた
[4]2017.03.21 CDH5で擬似分散モードのHadoopクラスタを構築する - hellosungbum’s diary

VM起動、接続

$ vagrant up
$ vagrant ssh
  • ※以下はVM上での作業

不要サービス無効化

  • ローカルからSSHする際に邪魔になりそうなのでfirewallを切ったり
[vagrant@cdh-train ~]$ sudo service iptables stop
[vagrant@cdh-train ~]$ sudo chkconfig iscsi off
[vagrant@cdh-train ~]$ sudo chkconfig iscsid off
[vagrant@cdh-train ~]$ sudo chkconfig iptables off
[vagrant@cdh-train ~]$ sudo chkconfig ip6tables off
[vagrant@cdh-train ~]$ sudo chkconfig mdmonitor off
[vagrant@cdh-train ~]$ sudo chkconfig netfs off
[vagrant@cdh-train ~]$ sudo chkconfig nfslock off
[vagrant@cdh-train ~]$ sudo chkconfig postfix off
[vagrant@cdh-train ~]$ sudo chkconfig udev-post off

wgetをインストール

[vagrant@cdh-train ~]$ sudo yum install wget

JDKインストール

前準備(ローカル上にて)
インストール
[vagrant@cdh-train vagrant]$ sudo su -
[root@cdh-train ~]# cd /vagrant
[root@cdh-train ~]# chmod 777 jdk-8u181-linux-x64.rpm
[root@cdh-train ~]# rpm -ivh jdk-8u181-linux-x64.rpm
確認
[root@cdh-train ~]# java -version
java version "1.8.0_181"
Java(TM) SE Runtime Environment (build 1.8.0_181-b13)
Java HotSpot(TM) 64-Bit Server VM (build 25.181-b13, mixed mode)
パス通す
  • $PATH に値を追加する場合、システムグローバルに適用する場合は /etc/profile.d/ 配下にファイルを追加、個人毎の適用の場合は ~/.bashrc(bash_profile)に追記するのが良さそう
    • ログイン時、profile.shの中に入っている.shファイルが全て実行されるので、そこで環境変数が代入される
[root@cdh-train ~]# echo "export JAVA_HOME=/usr/java/default" > /etc/profile.d/jdk.sh
[root@cdh-train ~]# source /etc/profile.d/jdk.sh
[root@cdh-train ~]# exit

CDH5をインストール

[vagrant@cdh-train ~]$ wget http://archive.cloudera.com/cdh5/redhat/6/x86_64/cdh/cloudera-cdh5.repo
[vagrant@cdh-train ~]$ sudo mv cloudera-cdh5.repo /etc/yum.repos.d/
  • RPMをダウンロードするやり方(今回やってない)
    • 鍵を追加
[vagrant@cdh-train ~]$ sudo rpm --import http://archive.cloudera.com/cdh4/redhat/6/x86_64/cdh/RPM-GPG-KEY-cloudera
[vagrant@cdh-train ~]$ wget http://archive.cloudera.com/cdh5/one-click-install/redhat/6/x86_64/cloudera-cdh-5-0.x86_64.rpm
[vagrant@cdh-train ~]$ yum -y --nogpgcheck localinstall cloudera-cdh-5-0.x86_64.rpm
[vagrant@cdh-train ~]$ yum clean all

擬似分散モードの設定のHadoopをインストール

インストール
  • 擬似分散環境用の設定ファイルhadoop-conf-pseudoをインストール
  • 依存関係のあるモジュールも自動でインストールされるため、HDFSやYARNも含まれる
  • インストールすることで自動的にhdfsユーザ、mapredユーザが作られる
[vagrant@cdh-train ~]$ sudo yum install hadoop-conf-pseudo
確認
[vagrant@cdh-train ~]$ sudo yum list installed | grep hadoop
hadoop.x86_64                        2.6.0+cdh5.15.1+2822-1.cdh5.15.1.p0.4.el6
hadoop-0.20-mapreduce.x86_64         2.6.0+cdh5.15.1+2822-1.cdh5.15.1.p0.4.el6
hadoop-client.x86_64                 2.6.0+cdh5.15.1+2822-1.cdh5.15.1.p0.4.el6
hadoop-conf-pseudo.x86_64            2.6.0+cdh5.15.1+2822-1.cdh5.15.1.p0.4.el6
hadoop-hdfs.x86_64                   2.6.0+cdh5.15.1+2822-1.cdh5.15.1.p0.4.el6
hadoop-hdfs-datanode.x86_64          2.6.0+cdh5.15.1+2822-1.cdh5.15.1.p0.4.el6
hadoop-hdfs-namenode.x86_64          2.6.0+cdh5.15.1+2822-1.cdh5.15.1.p0.4.el6
hadoop-hdfs-secondarynamenode.x86_64 2.6.0+cdh5.15.1+2822-1.cdh5.15.1.p0.4.el6
hadoop-mapreduce.x86_64              2.6.0+cdh5.15.1+2822-1.cdh5.15.1.p0.4.el6
hadoop-mapreduce-historyserver.x86_64
hadoop-yarn.x86_64                   2.6.0+cdh5.15.1+2822-1.cdh5.15.1.p0.4.el6
hadoop-yarn-nodemanager.x86_64       2.6.0+cdh5.15.1+2822-1.cdh5.15.1.p0.4.el6
hadoop-yarn-resourcemanager.x86_64   2.6.0+cdh5.15.1+2822-1.cdh5.15.1.p0.4.el6

[vagrant@cdh-train ~]$ hadoop version
Hadoop 2.6.0-cdh5.15.1
Subversion http://github.com/cloudera/hadoop -r 2d822203265a2827554b84cbb46c69b86ccca149
Compiled by jenkins on 2018-08-09T16:23Z
Compiled with protoc 2.5.0
From source with checksum 96bc735f7d923171f18968309fa3c477
This command was run using /usr/lib/hadoop/hadoop-common-2.6.0-cdh5.15.1.jar

HDFSフォーマット

  • 注意点はhdfsユーザで実行するところ
[vagrant@cdh-train ~]$ sudo su -
[root@cdh-train ~]# sudo -u hdfs hdfs namenode -format

各デーモン起動

[root@cdh-train ~]# for x in `cd /etc/init.d ; ls hadoop-hdfs-*` ; do sudo service $x start ; done

HDFSに必要なディレクトリの作成

  • たくさんのファイルが出来上がっていたのでログも残していく
[root@cdh-train ~]# sudo /usr/lib/hadoop/libexec/init-hdfs.sh
+ su -s /bin/bash hdfs -c '/usr/bin/hadoop fs -mkdir -p /tmp'
+ su -s /bin/bash hdfs -c '/usr/bin/hadoop fs -chmod -R 1777 /tmp'
+ su -s /bin/bash hdfs -c '/usr/bin/hadoop fs -mkdir -p /var'
+ su -s /bin/bash hdfs -c '/usr/bin/hadoop fs -mkdir -p /var/log'
+ su -s /bin/bash hdfs -c '/usr/bin/hadoop fs -chmod -R 1775 /var/log'
+ su -s /bin/bash hdfs -c '/usr/bin/hadoop fs -chown yarn:mapred /var/log'
+ su -s /bin/bash hdfs -c '/usr/bin/hadoop fs -mkdir -p /tmp/hadoop-yarn'
+ su -s /bin/bash hdfs -c '/usr/bin/hadoop fs -chown -R mapred:mapred /tmp/hadoop-yarn'
+ su -s /bin/bash hdfs -c '/usr/bin/hadoop fs -mkdir -p /tmp/hadoop-yarn/staging/history/done_intermediate'
+ su -s /bin/bash hdfs -c '/usr/bin/hadoop fs -chown -R mapred:mapred /tmp/hadoop-yarn/staging'
+ su -s /bin/bash hdfs -c '/usr/bin/hadoop fs -chmod -R 1777 /tmp'
+ su -s /bin/bash hdfs -c '/usr/bin/hadoop fs -mkdir -p /var/log/hadoop-yarn/apps'
+ su -s /bin/bash hdfs -c '/usr/bin/hadoop fs -chmod -R 1777 /var/log/hadoop-yarn/apps'
+ su -s /bin/bash hdfs -c '/usr/bin/hadoop fs -chown yarn:mapred /var/log/hadoop-yarn/apps'
+ su -s /bin/bash hdfs -c '/usr/bin/hadoop fs -mkdir -p /hbase'
+ su -s /bin/bash hdfs -c '/usr/bin/hadoop fs -chown hbase /hbase'
+ su -s /bin/bash hdfs -c '/usr/bin/hadoop fs -mkdir -p /benchmarks'
+ su -s /bin/bash hdfs -c '/usr/bin/hadoop fs -chmod -R 777 /benchmarks'
+ su -s /bin/bash hdfs -c '/usr/bin/hadoop fs -mkdir -p /user'
+ su -s /bin/bash hdfs -c '/usr/bin/hadoop fs -mkdir -p /user/history'
+ su -s /bin/bash hdfs -c '/usr/bin/hadoop fs -chown mapred /user/history'
+ su -s /bin/bash hdfs -c '/usr/bin/hadoop fs -mkdir -p /user/jenkins'
+ su -s /bin/bash hdfs -c '/usr/bin/hadoop fs -chmod -R 777 /user/jenkins'
+ su -s /bin/bash hdfs -c '/usr/bin/hadoop fs -chown jenkins /user/jenkins'
+ su -s /bin/bash hdfs -c '/usr/bin/hadoop fs -mkdir -p /user/hive'
+ su -s /bin/bash hdfs -c '/usr/bin/hadoop fs -chmod -R 777 /user/hive'
+ su -s /bin/bash hdfs -c '/usr/bin/hadoop fs -chown hive /user/hive'
+ su -s /bin/bash hdfs -c '/usr/bin/hadoop fs -mkdir -p /user/root'
+ su -s /bin/bash hdfs -c '/usr/bin/hadoop fs -chmod -R 777 /user/root'
+ su -s /bin/bash hdfs -c '/usr/bin/hadoop fs -chown root /user/root'
+ su -s /bin/bash hdfs -c '/usr/bin/hadoop fs -mkdir -p /user/hue'
+ su -s /bin/bash hdfs -c '/usr/bin/hadoop fs -chmod -R 777 /user/hue'
+ su -s /bin/bash hdfs -c '/usr/bin/hadoop fs -chown hue /user/hue'
+ su -s /bin/bash hdfs -c '/usr/bin/hadoop fs -mkdir -p /user/oozie'
+ su -s /bin/bash hdfs -c '/usr/bin/hadoop fs -mkdir -p /user/oozie/share'
+ su -s /bin/bash hdfs -c '/usr/bin/hadoop fs -mkdir -p /user/oozie/share/lib'
+ su -s /bin/bash hdfs -c '/usr/bin/hadoop fs -mkdir -p /user/oozie/share/lib/hive'
+ su -s /bin/bash hdfs -c '/usr/bin/hadoop fs -mkdir -p /user/oozie/share/lib/mapreduce-streaming'
+ su -s /bin/bash hdfs -c '/usr/bin/hadoop fs -mkdir -p /user/oozie/share/lib/distcp'
+ su -s /bin/bash hdfs -c '/usr/bin/hadoop fs -mkdir -p /user/oozie/share/lib/pig'
+ su -s /bin/bash hdfs -c '/usr/bin/hadoop fs -mkdir -p /user/oozie/share/lib/sqoop'
+ ls '/usr/lib/hive/lib/*.jar'
+ ls /usr/lib/hadoop-mapreduce/hadoop-streaming-2.6.0-cdh5.15.1.jar /usr/lib/hadoop-mapreduce/hadoop-streaming.jar
+ su -s /bin/bash hdfs -c '/usr/bin/hadoop fs -put /usr/lib/hadoop-mapreduce/hadoop-streaming*.jar /user/oozie/share/lib/mapreduce-streaming'
作成されたディレクトリの確認
[root@cdh-train ~]# sudo -u hdfs hdfs dfs -ls -R /

YARNの起動

sudo service hadoop-yarn-resourcemanager start && \
sudo service hadoop-yarn-nodemanager start && \
sudo service hadoop-mapreduce-historyserver start

サンプルプログラムの実行(Word Count)

HDFSユーザに変更
sudo su - hdfs
データを作成し、HDFS上に配置
-bash-4.1$ echo "hoge fuga bar foo hoge fuga" > wordcount.txt
-bash-4.1$ hdfs dfs -mkdir -p /user/hdfs/input
-bash-4.1$ hdfs dfs -put wordcount.txt /user/hdfs/input
-bash-4.1$ hdfs dfs -cat /user/hdfs/input/wordcount.txt
hoge fuga bar foo hoge fuga
実行
-bash-4.1$ hadoop jar /usr/lib/hadoop-mapreduce/hadoop-mapreduce-examples.jar wordcount /user/hdfs/input/wordcount.txt /user/hdfs/output_wc
-bash-4.1$ hdfs dfs -text /user/hdfs/output_wc/*
bar 1
foo 1
fuga    2
hoge    2

Vagrantfileの編集

  • ブラウザから管理画面をみるための設定
#HDFS
config.vm.network "forwarded_port", guest: 50070, host: 50070
#ResourceManager
config.vm.network "forwarded_port", guest: 8088, host: 8088
#NodeManager
config.vm.network "forwarded_port", guest: 8042, host: 8042
#JobHistory
config.vm.network "forwarded_port", guest: 19888, host: 19888
問題

JobHistoryのポート、アドレスが違うのか、接続できない...

Vagrantを使ってMac上に仮想マシン(VM)を立ち上げる

仮想マシンMacなどから立ち上げるツールであるVagrantの備忘録。 なお、今回はVirtual Boxw使って仮想環境を構築していく。

参考

https://dotinstall.com/lessons/basic_vagrant

【Vagrantドキュメント意訳】09.プロビジョニング

Vagrantの便利機能メモ

virtualbox,Vagrantのインストール

Home Brewを使ったインストール
$ brew cask install virtualbox
$ brew cask install vagrant
サイトからインストール

f:id:okuya-KAZAN:20180924223715p:plain

立てたいOSのBOXを取得

使えるvagrant boxのコマンド確認
vagrant box -h
Usage: vagrant box <subcommand> [<args>]

Available subcommands:
     add
     list
     outdated
     prune
     remove
     repackage
     update

For help on any individual subcommand run `vagrant box <subcommand> -h`
例として今回はCentOS6とCentOS7を落とす
  • まだboxはない
$ vagrant box list
There are no installed boxes! Use `vagrant box add` to add some.
  • URLを指定してCentOS6を取得
$ vagrant box add centos6 https://github.com/2creatives/vagrant-centos/releases/download/v6.4.2/centos64-x86_64-20140116.box
  • Providerを指定してCentOS7を取得
    • VirtualBoxを使っているのでproviderは3を選択
$ vagrant box add centos/7
==> box: Loading metadata for box 'centos/7'
    box: URL: https://vagrantcloud.com/centos/7
This box can work with multiple providers! The providers that it
can work with are listed below. Please review the list and choose
the provider you will be working with.

1) hyperv
2) libvirt
3) virtualbox
4) vmware_desktop

Enter your choice: 3
==> box: Adding box 'centos/7' (v1804.02) for provider: virtualbox
    box: Downloading: https://vagrantcloud.com/centos/boxes/7/versions/1804.02/providers/virtualbox.box
    box: Download redirected to host: cloud.centos.org
==> box: Successfully added box 'centos/7' (v1804.02) for 'virtualbox'!
  • 確認
vagrant box list
centos/7 (virtualbox, 1804.02)
centos6  (virtualbox, 0)

仮想マシン初期化

$ mkdir centos6 && cd $_
$ vagrant init centos6
  • Vagrantfileができている
$ ls
Vagrantfile

仮想マシンの操作

  • Vagrantfileがあるフォルダでvagrant [コマンド]
    • halt, up
      • シャットダウン、起動
    • status
      • 起動中か確認
    • suspend、resume
      • 一旦停止、再起動
    • reload
      • 再起動
    • destroy

仮想マシンに接続、抜け出す

接続
vagrant ssh

または

ssh vagrant@127.0.0.1 -p 2222
  • @127.0.0.1は立ち上げ時にログで出力される
抜け出す
[vagrant@vagrant-centos64 ~]$ exit

データの同期

$ vagrant ssh

[vagrant@vagrant-centos64 ~]$ cd /vagrant
[vagrant@vagrant-centos64 vagrant]$ ls
Vagrantfile
[vagrant@vagrant-centos64 vagrant]$ touch hoge.txt
[vagrant@vagrant-centos64 vagrant]$ exit
$ ls
Vagrantfile hoge.txt

自作のBOXを作る

パッケージ作成
centos6_v2 $ vagrant package
==> default: Attempting graceful shutdown of VM...
==> default: Clearing any previously set forwarded ports...
==> default: Exporting VM...
==> default: Compressing package to: /Users/okuyamatakashi/workspace/vagrant/centos6_v2/package.box
  • package.boxができている
centos6_v2 $ ls
Vagrantfile  package.box  provision.sh
パッケージをもとにBox(my_box)作成
centos6_v2 $ vagrant box add my_box package.box
==> box: Box file was not detected as metadata. Adding it directly...
==> box: Adding box 'my_box' (v0) for provider:
    box: Unpacking necessary files from: file:///Users/okuyamatakashi/workspace/vagrant/centos6_v2/package.box
==> box: Successfully added box 'my_box' (v0) for 'virtualbox'!
  • Boxができたか確認
centos6_v2 $ ls ~/.vagrant.d/boxes
centos-VAGRANTSLASH-7 centos6               my_box
  • 確認できたのでpackage.boxはもういらない
centos6_v2 $ rm package.box
  • my_boxを基にVMを立てる & 起動
$ mkdir myBox && cd $_
$ vagrant init my_box

sahara

  • sundboxが作れるplugin「sahara」を使ってバージョン管理を行う

  • saharaをインストール

$ vagrant plugin install sahara
  • 他に使えるpluginのコマンド確認したい場合
$ vagrant plugin -h
Usage: vagrant plugin <command> [<args>]

Available subcommands:
     expunge
     install
     license
     list
     repair
     uninstall
     update

For help on any individual command run `vagrant plugin COMMAND -h`

$ vagrant plugin list
sahara (0.0.17, global)
  • sandboxモードオン
    • 今後のVMの変更が気にくわなかったらrollback
    • 気に入ったらcommit
$ vagrant sandbox on
[default] Starting sandbox mode...
0%...10%...20%...30%...40%...50%...60%...70%...80%...90%...100%
  • sandboxモードがオンになっているか確認
$ vagrant sandbox status
[default] Sandbox mode is on
  • VMに接続し、変更を加える
$ vagrant ssh
[vagrant@vagrant-centos64 ~]$ touch test
[vagrant@vagrant-centos64 ~]$ exit
  • 変更が気にくわなかったのでrollback
$ vagrant sandbox rollback
[default] Rolling back the virtual machine...
0%...10%...20%...30%...40%...50%...60%...70%...80%...90%...100%
0%...10%...20%...30%...40%...50%...60%...70%...80%...90%...100%
  • VMに接続し、変更を加える
$ vagrant ssh
[vagrant@vagrant-centos64 ~]$ ls # testファイルはなくなっている!
[vagrant@vagrant-centos64 ~]$ touch test2
[vagrant@vagrant-centos64 ~]$ exit
  • 変更が気に入ったのでcommit
    • 以下の作業はVMを止めて行うこと!!
    • 処理に時間がかかってしまう
$ vagrant suspend
$ vagrant sandbox commit
[default] Committing the virtual machine...
0%...10%...20%...30%...40%...50%...60%...70%...80%...90%...100%
0%...10%...20%...30%...40%...50%...60%...70%...80%...90%...100%

プロビジョニング

  • プロビジョニングで、仮想マシンが立ち上がったあとに実行される処理を指定できる
プロビジョニングの実行
設定方法
  • Vagrantfileのconfig.vm.provisionメソッドを使用して設定
  • シェルの実行
# Vagrantfile
config.vm.provision :shell, inline: "echo hello world"
  • ファイルを読み込ませる
# Vagrantfile
config.vm.provision :shell, path: "start.sh"

# start.sh
echo "Hello this is Start file"
結果
$ vagrant up
~ 中略 ~
default: hello world
~ 中略 ~
default: Hello this is Start file

おまけ

  • 試しにWebサーバーを立てた(ドットインストールの#6,7「Webページを表示させよう」)
[vagrant@vagrant-centos64 ~]$ sudo service httpd start
Starting httpd: httpd: apr_sockaddr_info_get() failed for vagrant-centos64.vagrantup.com
httpd: Could not reliably determine the server's fully qualified domain name, using 127.0.0.1 for ServerName
                                                           [  OK  ]
[vagrant@vagrant-centos64 ~]$ sudo chkconfig httpd on
[vagrant@vagrant-centos64 ~]$ sudo service iptables stop
[vagrant@vagrant-centos64 ~]$ sudo chkconfig iptables off
[vagrant@vagrant-centos64 ~]$ exit
atom Vagrantfile

#config.vm.network "private_network", ip: "192.168.33.10"という行のコメントアウトを外し、リロード

$ vagrant reload
  • ブラウザで 192.168.33.10 を見てみる f:id:okuya-KAZAN:20180924223738p:plain

  • local からサイトの内容を変えよう

[vagrant@vagrant-centos64 ~]$ sudo rm -rf /var/www/html
[vagrant@vagrant-centos64 ~]$ # シンボリックリンクを貼る
[vagrant@vagrant-centos64 ~]$ sudo ln -fs /vagrant /var/www/html
[vagrant@vagrant-centos64 ~]$ exit
logout
Connection to 127.0.0.1 closed.
centos6 $ vi index.html
# 編集
  • ブラウザで 192.168.33.10 を見てみる f:id:okuya-KAZAN:20180924223750p:plain

Unity, Python, Deep Q Network Learningを使って自動運転

前回の更新からずいぶんと経ってしまった...
ドワンゴさんの人工知能研究所が開発した超人工生命Life in Sillico(以下LIS)という、
AIエージェントを強化学習で鍛えられるモジュールを使用して、自動運転のエージェントを作成したので、それについてまとめていこうと思う。
このLISというのが本当に素晴らしいモジュールで、Unityの環境上で作成したエージェントを、AIの学習によく使われるPythonを使って鍛え上げることができる。


LISの導入はこちらの記事で取り上げている。

参考にさせていただいたサイト、論文、記事

  1. Life in Silico : http://github.com/wbap/lis
  2. Lillicrap, et al. Continuous control with Deep Reinforcement Learning
  3. https://yanpanlau.github.io/2016/10/11/Torcs-Keras.html

手法

LISのドキュメントを直接見てくれた方が早い気もするが、AIエージェントがどう最適な行動を学んでいくのか(LISのシステム)について記述していく。

距離センサーは入力情報に使っていない

まず、今回作成したエージェントの入力情報だが、距離センサーに頼らず、車載カメラに映る画像のみを入力情報としている。
エージェントは車載カメラに映る画像からコースアウトしたり、障害物と衝突しない行動を選択していく。

Deep Q Network

エージェントを制御するアルゴリズムとして、LISではDeep Q Network(以下DQN)を用いている。
DQNは、環境から与えられた状態を入力として受け取り、車が選択できる各行動の価値(Q値)を出力するニューラルネットワークのこと。
ここでは、車載カメラの映像が状態となる。

強化学習

車の自律走行の学習方法として、強化学習を用いている。
強化学習とは、エージェントが環境との相互作用を通して、得られる報酬の総和を最大にする行動を学習していく学習法のこと。
ここでいう相互作用というのは、

  • エージェントが今どんな状態なのかを環境から受け取る
  • エージェントは状態を基に行動を選択して環境に返す
  • 環境はエージェントから受け取った行動を基に状態を変化させる
  • 環境は新しい状態と行動に対する報酬をエージェントに返す

といったサイクルのことである。 f:id:okuya-KAZAN:20180524144052p:plain
この学習方法で、車はコースアウトしたり、障害物とぶつからない最適な運転技術を学んでいく。
強化学習の詳細については、こちらの記事でまとめている。

学習の流れ

強化学習を用いて、DQNを学習させていく流れは以下のようになる。

  1. 車載カメラの映像をDQNに入力
  2. 行動の選択
    → ε-greedy法と呼ばれる手法で行動を選択する。
    確率εでランダムな行動を選択し、確率1-εで、DQNから出力(各行動のQ値)が最大となる行動を選択する。
    ε-greedy法を採用することで、他に価値の高い行動がないかを探索でき、様々な行動に対する適切なQ値の学習が可能となる。
  3. 選択された行動の評価
    → 2.で選択した行動に対して報酬を与える。
    例えば、コースに沿って走れていたならプラスの報酬、コースアウトしたり障害物と衝突してしまったらマイナスの報酬といった感じ。
  4. 経験の蓄積
    → 「ある状態である行動をとったらどれだけの報酬を得て、環境がどんな状態に変化した」といった経験を、メモリーと呼ばれる場所にストック。
  5. DQNの学習
    → メモリーに記録されている情報からランダムに経験を選び出し、ディープラーニングによってDQNをチューニングしていく。 f:id:okuya-KAZAN:20180524144123p:plain

実験

ここまで理論的なことを述べてきたが、ここからは実際にLISを使ってどんな実験をしたかを書いていく。

実験の目的

実験の最終的な目的としては、実際の車の1/10スケールの「RoboCar1/10(以下RoboCar)」が、研究室内に作成したコースを自律走行できるよう鍛え上げること。

実験の流れ

シミュレーション環境上でDQNモデルを訓練し、訓練されたモデルを実世界に適用させていく。 行った主な実験は以下の3つ。 1. Virtual Simulation →直線しかないシンプルな道路で自律走行できるよう学習。 2. Laboratry Simulation →シミュレーション環境上に、研究室とRoboCarとドライビングコースを作成し、RoboCarが自律走行できるよう学習。 3. RoboCar verification →Laboratry Simulationでの学習モデルを現実世界に適用。

※ 1,2がシミュレーション環境、3が現実世界での学習となる。

Virtual Simulation

環境

一般道路とほぼ同じ道幅の直線道路を用意して、30mごとに障害物を発生させる。
どちらの車線に障害物が発生するかはランダムに決定。

エージェント

一般的な軽自動車をイメージして作成し、DQNの出力は、右に10度ハンドル切る場合のQ値、左に10度ハンドル切る場合のQ値、直進する場合のQ値の3種類とした。

報酬設計

障害物と衝突するかコースアウトした場合、報酬として-1を与え、障害物を避けるごと(30m進むごと)に+1の報酬を与える。

結果

学習過程、学習の結果を動画にまとめて、YouTubeにアップしているので、ぜひご覧ください。

www.youtube.com

Laboratry Simulation

環境

以下の画像のようなコースを作成し、1エピソードにつき1つ、コース上のストレートの部分のランダムな位置に障害物を発生させる。

エージェント

実際のRoboCarと同じエージェントをシミュレーション環境上作成した。
DQNの出力は、右に25度ハンドル切る場合のQ値、左に25度ハンドル切る場合のQ値、直進する場合のQ値の3種類とした。

報酬設計


よりコースの真ん中を、よりコースの軸に沿って進むと多くの報酬がもらえるよう報酬設計した。 コースの軸に対する車体の傾きによる報酬から、|trackPos|による減点をし、最後に車のスピードと比例定数をかけたものを報酬とする。 |trackPos|はコースの真ん中を最小値0とし、真ん中からズレるだけ値が上昇して、コースの端が最大値の1となるよう正規化。

結果

こちらも学習過程、学習の結果を動画にまとめて、YouTubeにアップしているのでぜひご覧ください。

www.youtube.com

RoboCar verification

環境

Laboratory Simulationでの環境と同じになるようドライビングコースを設計した。

エージェント

実際の車の1/10スケールの「RoboCar1/10」をエージェントとした。
こちらもLaboratory Simulation上のAgentと同じプロパティ。

System Architecture

どんなかんじでRoboCarが動くのかを図にまとめる。

ZeroMQというのはネットワークを介して他言語、他OS間でもデータのやりとりができるインターフェース。 Distributed Messaging - zeromq

+αの実験

+αの実験として、信号認識モジュールとの連携を行った。
信号認識モジュールとは同じ研究室の同僚が作成したニューラルネットワークのモジュールで、車載カメラの映像から信号認識を行い、認識結果(Stop, Go)を出力するようチューニングされている。

結果

しつこいようだが、こちらも学習過程、学習の結果を動画にまとめて、YouTubeにアップしているのでぜひご覧ください。

www.youtube.com

まとめ

今回3つの実験を大雑把に書いてしまったが、今後はこれらの実験のより詳細を書き留められたらなと思う次第です。

強化学習,DQNについて自分なりにまとめてみた

強化学習に関連するキーワードや手法についてのメモ。
最終的にはDeep Q-Learning、いわゆるDQNにも触れていこうと思う。

参考にさせていただいた記事、書籍

DQNの生い立ち + Deep Q-NetworkをChainerで書いた - Qiita
ゼロからDeepまで学ぶ強化学習 - Qiita
これからの強化学習 | 森北出版株式会社
正直参考記事、書籍を読めばこの記事はいらないくらいめちゃくちゃ詳しく書いてあるので、ぜひ参考にしてほしい。

強化学習

強化学習とは何かを簡潔にまとめると、「環境に置かれたエージェントが、環境との相互作用を通して、環境に応じた最適な方策を学習する手法」である。

エージェントが環境に行う作用を行動(action)といい、エージェントの行動によって変化する環境の要素を状態(state)という。

エージェントは環境から状態を受け取り、行動を決定し、環境がその行動に応じて変化。
さらに新しい状態をエージェントが受け取る。
このような環境とエージェントの相互作用を繰り返していく。

f:id:okuya-KAZAN:20171019134826p:plain


強化学習は他の機械学習と異なり、教師による答えがない。
その代わり、行動に対する「報酬」というものが存在する。
例えば、ある状態Sで行動A1をとったら報酬+1pt、行動A2をとったら報酬-1ptといった具合だ。

強化学習のキーワード

強化学習を勉強する上で欠かせないキーワードをまとめておく。

エージェント

行動する主体。環境から得られた観測結果をもとに行動を決定する。

環境

エージェントが決定した行動を受けて、次なる状態と報酬を決定し、エージェントに引き渡す。
環境の挙動は設計者の設計対象外。
ただし、報酬に関しては問題設定の一部として設計者が与える必要がある。

時間ステップとエピソード

時間ステップ

エージェントと環境の相互作用における基本的時間単位。1時間ステップの間に、エージェントは環境から状態や報酬を受け取り、それをもとに行動を決定して環境に引き渡す。

エピソード

タスクの開始から終了までの時間。
囲碁であれば碁盤に石がない状態から、試合が終了するまでがエピソードである。

マルコフ性

t+1ステップ目における状態は、tステップ目での状態と行動を使って、
P\bigl(S_{t+1}\mid S_t,A_t\bigr)で定められる。この時S_{t+1}は、S_{t-1}A_{t-1}などには依存せず、S_{t}A_{t}のみに依存して定まる。このように直前の状態のみで遷移確率が決まる性質をマルコフ性と呼ぶ。

方策

状態を入力として、行動を出力する行動決定のための関数。
方策は\piで表し、方策\piのもとである状態sにおけるある行動aが選択される確率を\pi\bigl(a\mid s\bigr)と表す。

最終目標は、エージェントが置かれた状態において、できるだけ多くの報酬得られるよう方策を設計していくこと。
もっと詳しく言うと、環境から得られる「報酬」「状態」から方策を改善していくこと(DQNでは方策の改善 -> NNのパラメータのチューニング)。

報酬、収益

ここでいう報酬というのは、ある行動を取った直後に得られる報酬(即時報酬)のことであるが、即時報酬の大きさだけを行動の指標にしてしまうと局所解に落ち込んでしまう危険性がある。

例えば、無人島に漂着した場合、周囲を探索するより、じっと座り込んでいる方が、体力の消耗が少ないので即時報酬は高い。
しかし、体力を使うという負の即時報酬を得ながらも探索を続けることで、食料を見つけ、結果的にじっと座ってるよりも大きな報酬を得られる可能性がある。

つまり、即時的には小さな報酬しか得られない行動でも、後にとても大きな報酬を得ることに繋がれば、その行動は全体としてみれば良い行動とみなせる。

よって、即時報酬だけでなく、その後に得られる全ての報酬の累積である収益を考える必要があり、強化学習においてエージェントは、「行動の選択を通して得られる収益の総和の最大化」を目的として学習していく。

収益にはそのまま報酬を足し合わせて定義されるものも存在するが、主には、未来にいくに従って割引されて定義されていく割引報酬和で収益が多く採用されているらしい。
割引報酬和とは、報酬を足し合わせる際に、未来の報酬ほど減衰させることで未来の不確実性を表現したものであり、時間ステップtでの収益G_tを、

G_t = \sum_{\tau=0}^{\infty} \gamma^{\tau}R_{t+1+\tau}=R_{t+1}+\gamma R_{t+2}+\gamma^{2}R_{t+3}+\cdots
のように定義する。
ここで\gamma\left(0\le\gamma\leq1\right)は割引率と呼ばれ、どれだけ未来を割引くかを表す定数である。

価値,行動価値関数

エージェントと環境との相互作用の中身は確率的に決定されてしまうため、収益も確率的に変動してしまう値となってしまう。
そのため収益は、方策を設計する際の行動の評価指標としては扱いづらい。

そこで、ある状態sから方策に従って行動を決定していった時に得られる収益の期待値を求め、それを価値と呼び、良い方策を設計する際の指標とする。

期待値をとることで、確率的に変動する報酬の平均的な値を算出することができる。
価値の中でも、ある状態sで行動aを選択した場合の収益の期待値を状態sにおける、行動aの行動価値と呼び、さらに行動価値を求める関数を行動価値関数をとし、

Q^\pi \left(s,a\right)
のように定義する。

行動価値を求める手法

ベルマン方程式

実際に行動価値関数を推定していくためのアルゴリズムの1つである、ベルマン最適方程式について述べていく。

この方程式では、未来の収益の期待値である行動価値関数を算出するために、異なる時刻における行動価値関数の間に成り立つ再帰的な関係を利用している。

ある方策\piの元での行動価値関数Q^\pi \left(s,a\right)は、ある状態sで行動aを選択した場合の収益の期待値であるので、

Q^\pi \left(s,a\right)=\mathbb{E}^\pi\left[G_t|S_t=s,A_t=a \right]
と表せる。
ここで収益は、

G_t = \sum_{\tau=0}^{\infty} \gamma^{\tau}R_{t+1+\tau}=R_{t+1}+\gamma R_{t+2}+\gamma^{2}R_{t+3}+\cdots
と表せるので、

\begin{eqnarray}
Q^\pi \left(s,a\right)&=&\mathbb{E}^\pi\left[\sum_{\tau=0}^{\infty}\gamma^{\tau}R_{t+1+\tau}\mid S_t=s,A_t=a \right] \\
&=&\mathbb{E}^\pi\left[R_{t+1}+\gamma R_{t+2}+\gamma^{2}R_{t+3}+\cdots\mid S_t=s,A_t=a \right]
\end{eqnarray}
となる。

\mathbb{E}^\pi\left[\cdot\right]は線形な演算なので、

Q^\pi \left(s,a\right)=\mathbb{E}^\pi\left[R_{t+1}\mid S_t=s,A_t=a \right]+\gamma\mathbb{E}^\pi\left[R_{t+2}+\gamma R_{t+3}+\cdots\mid S_t=s,A_t=a \right]
とすることができる。

右辺の第一項については、

\mathbb{E}^\pi\left[R_{t+1}\mid S_t=s,A_t=a \right]=\sum_{s'}P\left(s'\mid s,a\right)r\left(s,a,s'\right)
となる。
P\left(s'\mid s,a\right)は状態sで行動aを選択した際に状態s’に遷移する確率を表す状態遷移確率であり、r\left(s,a,s'\right)はその時に得られる報酬である。

続いて第二項は、

\begin{eqnarray}
&\gamma&\mathbb{E}^\pi\left[R_{t+2}+\gamma R_{t+3}+\cdots\mid S_t=s,A_t=a \right]\\
=&\gamma&\sum_{s'}P\left(s'\mid s,a\right)\sum_{a'}\pi\left(a'\mid s'\right)\mathbb{E}^\pi\left[R_{t+2}+\gamma R_{t+3}+\cdots\mid S_{t+1}=s',A_{t+1}=a'\right]
\end{eqnarray}
となる。
このうち\mathbb{E}^\pi\left[R_{t+2}+\gamma R_{t+3}+\cdots\mid S_{t+1}=s',A_{t+1}=a'\right]の部分はQ^\pi\left(s',a'\right)とみることができるので、
第二項については、

\gamma\sum_{s'}P\left(s'\mid s,a\right)\sum_{a'}\pi\left(a'\mid s'\right)Q^\pi\left(s',a'\right)
となる。

これらをまとめると、

Q^\pi\left(s,a\right)=\sum_{s'}P\left(s'\mid s,a\right)\Bigl(r\bigl(s,a,s'\bigr)+\gamma\sum_{a'}\pi\bigl(a'\mid s'\bigr)Q^\pi\bigl(s',a'\bigr)\Bigr)
を得る。この方程式をベルマン方程式という。

ここで\sum_{a'}\pi\bigl(a'\mid s'\bigr)Q^\pi\bigl(s',a'\bigr)を見ると、状態s’にて、選択できる全ての行動a'から期待値を求めていることがわかる。

ベルマン最適方程式

状態s’での行動を方策で選択する代わりに、価値関数の値が最大となる行動a’を選択した場合の期待値を求めると、ベルマン方程式は、

Q\left(s,a\right)=\sum_{s'}P\left(s'\mid s,a\right)\Bigl(r\bigl(s,a,s'\bigr)+\gamma\max_{a'}Q^\pi\bigl(s',a'\bigr)\Bigr)
となる。この方程式をベルマン最適方程式という。

この方程式にはベルマン方程式とは違い、計算に直接的に行動選択確率が含まれていない。
実際にこのような問題を解く際には、全ての状態に対する行動価値関数の表を用意し、表の全ての要素をベルマン最適方程式で計算していく。

Q-Learning

ベルマン最適方程式を使って最適行動価値関数を求める方法は、状態遷移確率が既知であれば最も効率良く最適価値関数を計算することができる。
しかし、強化学習において、エージェントは環境に関して事前の知識を持っていない。
よって、不完全な知識の上で、知識を収集しながら最適な行動を学習していかなくてはならない。
Q-Learningとは、このような不完全な知識の中での探索結果から行動価値関数を近似し、最適方策を学習していく学習手法である。

例えば状態sで行動aを選択して状態s'に遷移した場合、
遷移時に得た即時報酬と、状態s'からずっと最適な行動を選び続けた時に得ることのできる収益の期待値の和を教師(target)として、Q\bigl(s,a\bigl)とtargetの誤差から新たにQ\bigl(s,a\bigl)を更新していく。
ちなみにこのQ\bigl(s,a\bigl)とtargetの誤差をTD誤差という。



\begin{align*}
&target=r\bigl(s,a,s'\bigr)+\gamma\max_{a'}Q^\pi\bigl(s',a'\bigr)\\
&loss=target-Q\bigl(s,a\bigr)\\
&Q\bigl(s,a\bigr)\leftarrow Q\bigl(s,a\bigr)+\alpha\times loss
\end{align*}


行動aによってエピソードが終了した場合、
targetはただ単にr\bigl(s,a,s'\bigr)となる。

エージェントが行動を選択する方法(方策)

先ほどのQ-Learningの説明で、「状態sで行動aを選択」と書いたが、
実際にどのように行動を選択していくか(方策)について述べる。

greedy

まず、これまでの相互作用の結果から、最も価値の高い行動を選択していくという方策がある。これをgreedy方策という。

ε-greedy

greedy方策は際的行動価値観数が既知であれば最適な方策となる。しかし、現実には最適行動価値観数は始めからわかっておらず、最適な行動価値を探索して推定していく必要がある。
その際に、これまでで得られた最も価値の高い行動を選択するだけでは、他の選択肢がどのくらい良い結果をもたらすか知ることができない。

そこで、今までの学習結果を利用するだけでなく、たまにランダムに探索する必要がある。
このように利用と探索を織り交ぜていく手法としてε-greedy方策が存在する。
確率εでランダムに行動し、確率1-εでgreedy方策をとる。

行動価値関数の近似

状態数が少なければ、テーブル関数Q\bigl(s,a\bigr)を作ることができるが、
例えばカメラの画像などを状態として扱うと、状態数が莫大になり、テーブル関数では表現できなくなってしまう。
そこでQ関数をパラメータ\thetaで近似する。

DQN(Deep Q-Network)

以上の手法にDeep Learningの技術を適用したもの。
関数近似ニューラルネットワークを用いて、Q関数を学習させていく。
ひと昔前までの関数近似は人手で構築された特徴を用いるて性能を向上させてきたが、Deep Q-Network(以下DQN)は入力として画面のデータなどの状態のみを用いて、同じ学習アルゴリズムを適用するだけで、囲碁やチェス、昔の家庭用ゲームなど様々なゲームにおいて人間以上の強さを発揮できるようになったことで注目を集めた。

DQNのネットワーク構造

DQNは行動価値関数を推定するための多層ニューラルネットワークである。
エージェントの視界や画面データなど画素情報を入力とするため、ただの多層ニューラルネットワークでなく畳み込みニューラルネットワークが利用されることが多い。
畳み込みニューラルネットワークの詳しい説明は今回省略する。

DQNの出力は行動価値関数Q\bigl(s,a\bigl)である。
つまり行動aの取りうる種類がN通りの場合、DQNはN通りの出力をもつニューラルネットワークとなる。

DQNの学習におけるテクニック

DQNディープラーニングを用いてネットワークのパラメータを更新していくのだが、上手く学習されるよう下記の工夫がなされている。

  • ターゲットの固定化(neural fitted Q)
  • 学習に用いるサンプルの偏りの抑制(Experience Replay)
neural fitted Q

先ほどQ-Learningの説明のところでも述べたが、
DQNのパラメータを更新してくには、TD誤差r_{t+1}+\gamma\max_{a_{t+1}}Q\bigl(s_{t+1},a_{t+1}:\theta_t\bigr)-Q\bigl(s_t,a_t:\theta_t\bigr)を求める必要がある。

第一項のr_{t+1}+\gamma\max_{a_{t+1}}Q\bigl(s_{t+1},a_{t+1}:\theta_t\bigr)ディープラーニングでいうtargetにあたるのだが、
このtargetはパラメータ\thetaに依存するため収束が安定しないとのこと。

そこでtargetにおけるパラメータ\thetaをある時点でのパラメータ\theta^-で固定し、
r_{t+1}+\gamma\max_{a_{t+1}}Q\bigl(s_{t+1},a_{t+1}:\theta^-\bigr)とする。
そしてある程度学習を繰り返した後に、targetとなるQ関数のパラメータ\theta^-を更新する。

Experience Replay

関数近似に多層ニューラルネットワークなどを用いた場合、
「サンプル取得 -> 学習 -> サンプル取得 -> 学習」という学習法だと、どうしても学習が遅くなってしまう。
そこで、サンプル\bigl(s,a,r,s'\bigr)が無限回得られるなら、それらをどのような順番で学習しても最適な価値関数が得られるという特徴を生かして、経験したサンプルを全てメモリーとして記録しておき、学習を行う際はそこからランダムサンプリングして利用するExperience Replayという手法を使う。
この手法は、時系列的に連続であるサンプル間の相関をバラすというメリットもある。

もっと噛み砕いて書くと、
「ある状態でどう行動をとったら、どんな結果を得て、次にどんな状態になった」という経験をエージェントは蓄積していく。
そしてたまった経験から「あの時、右に曲がればぶつからずに済んだんだな」といった風に、ディープラーニングで最適な行動を学習し、ニューラルネットワークをチューニングしていく。

f:id:okuya-KAZAN:20171019135101p:plain


まとめ

強化学習のキーワードをまとめ、最適な行動価値関数を求める手法についてまとめた。
実際にPythonのChainerでDQNの実装もしたので今度改めてブログにまとめようと思う。

Pythonの標準モジュール「argparse」の使い方についてのメモ

コマンドラインからPythonプログラムに引数を渡す際に便利な、標準モジュール「argparse」に関する備忘録。

環境

Mac Book Air : OS X EI Caption(version 10.11.6)
Python 2.7.12 |Anaconda custom (x86_64)|

Pythonプログラムの中身

コマンドラインから引数を受け取るPythonプログラムを以下のような流れで書いていく。

  • argparseモジュールをimport
  • argparseモジュールのArgumentParserクラスを呼び出しパーサーを生成
  • ArgumentParserクラスのadd_argumentメソッドで引数を追加
  • ArgumentParserクラスのparse_argsメソッドで引数の解析

※プログラムは「Parser_practice.py」という名前のファイルで実行していく。

argparseモジュールをimport

これをしないとはじまらないので早速import

import argparse

ArgumentParserクラスを呼び出しパーサーを生成

argparseモジュールのArgumentParserクラスを呼び出す。
このArgumentParserクラスのコンストラクタを使用することで、構文解析の処理を行うパーサーが生成される。

import argparse
parser = argparse.ArgumentParser()

ここから、add_argumentメソッドで引数を追加していくのだが、
実はコンストラクタが呼び出された時点で[-h/--help]という引数がデフォルトで追加されている。
先ほど記述した順番とは異なるが、parse_argsメソッドで引数の解析を行い、実際に[-h]引数を指定してコマンドラインにヘルプを表示させてみる。

import argparse
parser = argparse.ArgumentParser()
args = parser.parse_args()



実行結果
f:id:okuya-KAZAN:20170927155339p:plain

「usage」はプログラムの利用方法であり(後述)、
「optional arguments:」以下には追加されているオプションが表示されるのだが、今表示されているのは、コンストラクタによって追加された[-h],[--help]引数のみである。
さらに、コンストラクタに引数を指定することによって、ヘルプの表示内容を設定することができる。
設定できる引数は以下の通り

  • prog : プログラム名
  • usage : プログラムの利用方法
  • description :「optional arguments」の前に表示される説明文
  • epilog :「optional arguments」後に表示される文字列
  • add_help : [-h],[--help]オプションをデフォルトで追加するか

実際にこれらを指定してみる。

import argparse

parser = argparse.ArgumentParser(
            prog="aaaaaaaaaa", #プログラム名
            usage="bbbbbbbbbb", #プログラムの利用方法
            description="cccccccccc", #「optional arguments」の前に表示される説明文
            epilog = "dddddddddd", #「optional arguments」後に表示される文字列
            add_help = True #[-h],[--help]オプションをデフォルトで追加するか
            )



実行結果
f:id:okuya-KAZAN:20170927155353p:plain

ちなみに、引数のadd_helpをFalseにして実行すると、


f:id:okuya-KAZAN:20170927155409p:plain


[-h]なんて引数はないと怒られエラーとなる。

add_argumentメソッドで引数を追加

続いてArgumentParserクラスのadd_argumentメソッドで引数を追加していく。
こちらのメソッドは、parse_argsメソッドで引数の解析を行う前に使用する。
add_argumentメソッドの引数は以下の通り

  • name or flags : 位置引数もしくはオプション引数の指定
  • help : 引数の説明
  • required : 引数の省略を不可にするか
  • type : 引数が変換される型
  • choises : 引数の選択肢を指定
  • nargs : 引数の数の指定
  • default : 引数がなかった場合に生成される値
  • metavar : メッセージで表示される引数の名前
  • action : 引数の取り扱いの指定

何がなんだかわからなくなりそうなので1個ずつ見ていく。

name or flags

引数が"位置引数"か"オプション引数"かを指定する。

  • 位置引数 : 関数に対して必須となる引数
  • オプション引数 : 与えても与えなくても良く、接頭辞に「-」または「--」を指定する必要がある
位置引数の例

位置引数を受け取るプログラムを以下のように記述する。

import argparse

parser = argparse.ArgumentParser(
            prog="aaaaaaaaaa", #プログラム名
            usage="bbbbbbbbbb", #プログラムの利用方法
            description="cccccccccc", #「optional arguments」の前に表示される説明文
            epilog = "dddddddddd", #「optional arguments」後に表示される文字列
            add_help = True #[-h],[--help]オプションをデフォルトで追加するか
            )
# 引数の追加
parser.add_argument("test", #位置引数
                    help="eeeeeeeeee" #引数の説明)
args = parser.parse_args() # 引数の解析
print args.test

そして、位置引数「test」に値を入れて出力させてみる。


実行結果
f:id:okuya-KAZAN:20170927155420p:plain


[-h]引数でヘルプを表示すると[positional arguments:]以下に位置引数「test」追加されていることがわかる。さらにhelpで指定した内容も表示されている。
最後の実行結果は位置引数を指定しなかったためにエラーとなる。

オプションの例

オプション引数を受け取るプログラムを以下のように記述する。

import argparse

parser = argparse.ArgumentParser(
            prog="aaaaaaaaaa", #プログラム名
            usage="bbbbbbbbbb", #プログラムの利用方法
            description="cccccccccc", #「optional arguments」の前に表示される説明文
            epilog = "dddddddddd", #「optional arguments」後に表示される文字列
            add_help = True #[-h],[--help]オプションをデフォルトで追加するか
            )
# 引数の追加
parser.add_argument("-t","--test", #オプション引数
                    help="eeeeeeeeee" #引数の説明
                    )
args = parser.parse_args() # 引数の解析
print args.test



実行結果
f:id:okuya-KAZAN:20170927155428p:plain


[-h]引数でヘルプを表示すると[optional arguments:]以下に位置引数「test」追加されていることがわかる。
最後の実行結果はオプションtestには値が入っていないため「None」となっている。

required

オプション引数も位置引数のように、省略を不可にすることができる。
それにはadd_argumentメソッドの引数であるrequiredをTrueにする必要がある。

import argparse

parser = argparse.ArgumentParser(
            prog="aaaaaaaaaa", #プログラム名
            usage="bbbbbbbbbb", #プログラムの利用方法
            description="cccccccccc", #「optional arguments」の前に表示される説明文
            epilog = "dddddddddd", #「optional arguments」後に表示される文字列
            add_help = True #[-h],[--help]オプションをデフォルトで追加するか
            )
# 引数の追加
parser.add_argument("-t","--test", #オプション引数
                    help="eeeeeeeeee", #引数の説明
                    required = True #引数の省略を不可にするか
                    )
args = parser.parse_args() # 引数の解析
print args.test



実行結果
f:id:okuya-KAZAN:20170927155440p:plain


オプション引数「-t」を指定しないと「error: argument -t/--test is required」というエラーが出る。

type

引数は何も指定しないとstr型として扱われるが、add_argumentメソッドの引数であるtypeで引数の型を指定できる。
異なる型の引数を指定するとエラーが出る。

import argparse

parser = argparse.ArgumentParser(
            prog="aaaaaaaaaa", #プログラム名
            usage="bbbbbbbbbb", #プログラムの利用方法
            description="cccccccccc", #「optional arguments」の前に表示される説明文
            epilog = "dddddddddd", #「optional arguments」後に表示される文字列
            add_help = True #[-h],[--help]オプションをデフォルトで追加するか
            )
# 引数の追加
parser.add_argument("-t","--test", #オプション引数
                    help = "eeeeeeeeee",#引数の説明
                    required = True ,#引数の省略を不可にするか
                    type = int
                    )
args = parser.parse_args() # 引数の解析
print args.test



実行結果
f:id:okuya-KAZAN:20170927161718p:plain


choices

引数に指定する値を限定したい場合に使う、こちらの引数で指定した値以外を指定するとエラーとなる。

import argparse

parser = argparse.ArgumentParser(
            prog="aaaaaaaaaa", #プログラム名
            usage="bbbbbbbbbb", #プログラムの利用方法
            description="cccccccccc", #「optional arguments」の前に表示される説明文
            epilog = "dddddddddd", #「optional arguments」後に表示される文字列
            add_help = True #[-h],[--help]オプションをデフォルトで追加するか
            )
# 引数の追加
parser.add_argument("-t","--test", #オプション引数
                    help = "eeeeeeeeee",#引数の説明
                    required = True ,#引数の省略を不可にするか
                    type = int,
                    choices = [0,1] #このオプション引数の値は0か1にしか指定できない
                    )
args = parser.parse_args() # 引数の解析
print args.test



実行結果
f:id:okuya-KAZAN:20170927155505p:plain

nargs

引数に配列を指定したい場合に用いる。
nargsで指定する値は、配列の大きさ。
コマンドラインで指定した引数の数と、nargsの値が異なるとエラーが出る。

import argparse

parser = argparse.ArgumentParser(
            prog="aaaaaaaaaa", #プログラム名
            usage="bbbbbbbbbb", #プログラムの利用方法
            description="cccccccccc", #「optional arguments」の前に表示される説明文
            epilog = "dddddddddd", #「optional arguments」後に表示される文字列
            add_help = True #[-h],[--help]オプションをデフォルトで追加するか
            )
# 引数の追加
parser.add_argument("-t","--test", #オプション引数
                    help = "eeeeeeeeee",#引数の説明
                    required = True ,#引数の省略を不可にするか
                    type = int,
                    nargs = 4
                    )
args = parser.parse_args() # 引数の解析
print args.test



実行結果
f:id:okuya-KAZAN:20170927155514p:plain

default

コマンドラインで引数を指定しなかった場合に、オプション引数に格納される値を設定できる。

import argparse

parser = argparse.ArgumentParser(
            prog="aaaaaaaaaa", #プログラム名
            usage="bbbbbbbbbb", #プログラムの利用方法
            description="cccccccccc", #「optional arguments」の前に表示される説明文
            epilog = "dddddddddd", #「optional arguments」後に表示される文字列
            add_help = True #[-h],[--help]オプションをデフォルトで追加するか
            )
# 引数の追加
parser.add_argument("-t","--test", #オプション引数
                    help = "eeeeeeeeee",#引数の説明
                    default = 5
                    )
args = parser.parse_args() # 引数の解析
print args.test



実行結果
f:id:okuya-KAZAN:20170927155522p:plain

metavar

ヘルプやエラーの出力時の引数名はmetavarという項目で変更することができる。

import argparse

parser = argparse.ArgumentParser(
            prog="aaaaaaaaaa", #プログラム名
            usage="bbbbbbbbbb", #プログラムの利用方法
            description="cccccccccc", #「optional arguments」の前に表示される説明文
            epilog = "dddddddddd", #「optional arguments」後に表示される文字列
            add_help = True #[-h],[--help]オプションをデフォルトで追加するか
            )
# 引数の追加
parser.add_argument("-t","--test", #オプション引数
                    )
parser.add_argument("-o","--origin" ,#オプション引数
                    metavar = "My Optional argument"
                    )
args = parser.parse_args() # 引数の解析
print args.test



実行結果
f:id:okuya-KAZAN:20170927155530p:plain

action

今までは引数の後に値を指定して引数に格納していたが、
actionという項目を使うことで、引数の後に値を入力せずとも引数の指定のみで値を格納することができる。

まず、actionで指定できるものの一部を見てみる。

  • store : 今まで通り引数の後に指定された値を格納する。
  • store_const : constキーワード引数で指定された値を格納
  • store_true : 引数の指定があればtrueを格納、なければfalseを格納
  • store_false : 引数の指定があればfalseを格納、なければtrueを格納
import argparse

parser = argparse.ArgumentParser(
            prog="aaaaaaaaaa", #プログラム名
            usage="bbbbbbbbbb", #プログラムの利用方法
            description="cccccccccc", #「optional arguments」の前に表示される説明文
            epilog = "dddddddddd", #「optional arguments」後に表示される文字列
            add_help = True, #[-h],[--help]オプションをデフォルトで追加するか
            )



# 引数の追加
parser.add_argument("-a","--aaa", #オプション引数
                    action = "store"
                    )
parser.add_argument("-b","--bbb", #オプション引数
                    action = "store_const",const=42
                    )
parser.add_argument("-c","--ccc", #オプション引数
                    action = "store_true"
                    )
parser.add_argument("-d","--ddd", #オプション引数
                    action = "store_false"
                    )

args = parser.parse_args() # 引数の解析

print args.aaa
print args.bbb
print args.ccc
print args.ddd



実行結果
f:id:okuya-KAZAN:20170927155540p:plain

さらに以下の設定をすればactionで引数の取り扱いの指定もできる。

  • append : オプション引数にリストを格納し、そのリストに引数の値を格納する。
  • count : キーワード引数の数を格納
import argparse

parser = argparse.ArgumentParser(
            prog="aaaaaaaaaa", #プログラム名
            usage="bbbbbbbbbb", #プログラムの利用方法
            description="cccccccccc", #「optional arguments」の前に表示される説明文
            epilog = "dddddddddd", #「optional arguments」後に表示される文字列
            add_help = True, #[-h],[--help]オプションをデフォルトで追加するか
            )



# 引数の追加
parser.add_argument("-a","--aaa", #オプション引数
                    action = "append"
                    )
parser.add_argument("-b","--bbb", #オプション引数
                    action = "count"
                    )

args = parser.parse_args() # 引数の解析

print args.aaa
print args.bbb
print args.ccc
print args.ddd



実行結果
f:id:okuya-KAZAN:20170927155550p:plain

まとめ

Pythonの標準モジュールのargparseについてほんの一部だけまとめた。
さらにリファレンスを読めば深い知識が得られそうだが、今回は疲れてしまったのでここまで...

Unityでオブジェクトをスクリプトで動かし視界に映る画像をファイル保存

Unity上でエージェントをキー入力で動かし、エージェントの視界に映る映像をPNGファイルとして保存できるようにした。

環境

Mac Book Air : OS X EI Caption(version 10.11.6)
Unity : 2017.1.0f3

流れ

  • エージェントと、エージェントを動かす環境を作成
  • エージェントの視界に映った画像をPNGファイルとして保存するスクリプトの作成

シーンの環境構築

早速Unityを起動し、新しくプロジェクトを作成し、シーン上に環境を作っていく。
まずHierarchy上にて[Create Empty]で空オブジェクトを作成し、名前を「Agent」とする。
f:id:okuya-KAZAN:20170830174158p:plain
そして、Hierarchy上に最初からある「Main Camera」を「Agent」の子オブジェクトにし、名前を「Eye Camera」に変更。
f:id:okuya-KAZAN:20170830174214p:plain
続いて、エージェントが移動する地面を構築する。
シーン上にTerrainを配置し、名前を「Ground」に変更し、適当に隆起させたりテクスチャをつけた。
f:id:okuya-KAZAN:20170830174335p:plain
※地面のテクスチャはProjectビュー上のAssetsフォルダ内で右クリック->[Import Package]->[Environment]と進みインポートした。
f:id:okuya-KAZAN:20170830174355p:plain


Agentを動かす

キー入力を受けつけ、上向き矢印キーでエージェントが前に進み、左右の矢印キーで方向転換、スペースキーでエージェントがジャンプするようなスクリプトを作成する。
スクリプトを作成する前にまず、
「Character Controllerコンポーネント」をAgentオブジェクトに追加しておく。
f:id:okuya-KAZAN:20170830174454p:plain


Projectビュー上にて[Create]->[C# Script]で新規スクリプトを作成、名前をPlayerController.csとし、中身は以下のようにした。


PlayerController.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerController : MonoBehaviour {
  CharacterController controller;
  Vector3 moveDirection;

  public float gravity;
  public float speed;
  public float amountRotate;
  public float speedJump;

  void Start () {
    controller = GetComponent<CharacterController>();
    moveDirection = Vector3.zero;
  }

  void Update () {
    //地上にいる場合のみ操作を行う
    if(controller.isGrounded){
      //Inputの検知->0未満となってバックすることを避ける
      if(Input.GetAxis("Vertical") > 0.0f){
        moveDirection.z = Input.GetAxis("Vertical")*speed;
      }
      else{
        moveDirection.z = 0;
      }

      //方向転換
      transform.Rotate(0,Input.GetAxis("Horizontal")*amountRotate ,0);

      //ジャンプ
      if(Input.GetButton("Jump")){
        moveDirection.y = speedJump;
      }
    }
    //毎フレーム重力を加算
    moveDirection.y -= gravity * Time.deltaTime;

    //移動の実行
    Vector3 globalDirection = transform.TransformDirection(moveDirection);
    controller.Move(globalDirection * Time.deltaTime);

    //移動後地面についてるなら重力をなくす
    if(controller.isGrounded){
      moveDirection.y = 0;
    }
  }
}



スクリプトの詳しい解説は割愛する(疲れてしまったので...)
作成したPlayerController.csをAgentオブジェクトに追加し、Inspectorでpublic変数の値を設定。
f:id:okuya-KAZAN:20170830174552p:plain


プレビューを開始するとAgentを動かすことができた。
f:id:okuya-KAZAN:20170830174611p:plain


エージェントの視界に映った画像をPNGファイルとして保存

というわけでこの記事のメインに入っていく。
流れとしてはこんな感じ。
1.ProjectViewでRenderTextureを作成
2.EyeCameraにRenderTextureを設定
3.RenderTextureをTexture2D変換し、さらにPNG画像として保存するスクリプトを作成

RenderTextureを作成し、TargetTextureに設定

RenderTextureとは、カメラで撮影した映像をTextureとして使える機能である。
まず、Projectビュー上にて、[Create]->[Render Texture]でRenderTextureを作成、名前をMyRenderTextureに変更。
続いて、EyeCameraオブジェクトのInspector上にある[Target Texture]に作成したMyRenderTextureを設定。
これでEyeCameraで描画したものはMyRenderTextureに描画されるようになる。
f:id:okuya-KAZAN:20170830174750p:plain


f:id:okuya-KAZAN:20170830174859p:plain


視界を保存するためのスクリプトの作成

Projectビュー上にて[Create]->[C# Script]で新規スクリプトを作成し、名前をSaveImage.csとした。
実際のスクリプトは以下。


SaveImage.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//File.~を使うため
using System.IO;

public class SaveImage : MonoBehaviour {
  public Camera eyeCamera;
  private Texture2D texture;

  private int photoNumber = 1;


// Use this for initialization
void Start () {
    texture = new Texture2D (eyeCamera.targetTexture.width, eyeCamera.targetTexture.height,
                            TextureFormat.RGB24, false);
                          }
// Update is called once per frame
void Update () {
    //キーボードの「s」を押したら画像を保存
    if(Input.GetKeyDown("s")){
      SaveCameraImage();
    }
  }

  public void SaveCameraImage() {
    // Remember currently active render textureture
    RenderTexture currentRT = RenderTexture.active;
    // Set the supplied RenderTexture as the active one
    RenderTexture.active = eyeCamera.targetTexture;
    eyeCamera.Render();
    // Create a new Texture2D and read the RenderTexture texture into it
    texture.ReadPixels(new Rect(0, 0, eyeCamera.targetTexture.width, eyeCamera.targetTexture.height), 0, 0);
    //転送処理の適用
    texture.Apply();
    // Restorie previously active render texture to avoid errors
    RenderTexture.active = currentRT;
    //PNGに変換
    byte[] bytes =  texture.EncodeToPNG ();
    //保存
    File.WriteAllBytes("/Users/okuyamatakashi/Desktop/Unity/photo/Hoge" + photoNumber + ".png", bytes);
    photoNumber++;
  }
}



まず、視界が描画されたRenderTextureの転送先であるTexture2Dを作成している。

texture = new Texture2D (eyeCamera.targetTexture.width, eyeCamera.targetTexture.height,
                        TextureFormat.RGB24, false);


キーボードの「S」キーを押したら、画像が保存されるようにした。

void Update () {
  //キーボードの「s」を押したら画像を保存
  if(Input.GetKeyDown("s")){
    SaveCameraImage();
  }
}



RenderTextureからピクセル値を取得するには、RenderTexture.activeに取得元のRenderTextureを指定する必要があるらしい。
そして、RenderTexture.activeに指定された映像をキャプチャするReadPixels()で、ピクセルデータを転送。
しかし、このままではピクセル値をスクリプトから参照できないので、targetTexture.Apply()で転送処理を適用している。

// Set the supplied RenderTexture as the active one
RenderTexture.active = eyeCamera.targetTexture;
eyeCamera.Render();
// Create a new Texture2D and read the RenderTexture texture into it
texture.ReadPixels(new Rect(0, 0, eyeCamera.targetTexture.width, eyeCamera.targetTexture.height), 0, 0);
//転送処理の適用
texture.Apply();



Texture2DをPNGに変換し。
フォルダを指定し保存。

//PNGに変換
byte[] bytes =  texture.EncodeToPNG ();
//保存
File.WriteAllBytes("/Users/okuyamatakashi/Desktop/Unity/photo/Hoge" + photoNumber + ".png", bytes);
photoNumber++;

※File.○○を使用するにはスクリプト冒頭に

using System.IO;

と記述する必要がある。


作成したPlayerController.csをAgentオブジェクトに追加し、eyeCamera変数にEyeCameraオブジェクトを設定。
プレビューを開始し、「S」キーを押すと指定したフォルダにエージェントの視界に映る画像が保存されていることが確認できる。
f:id:okuya-KAZAN:20170830175303p:plain


f:id:okuya-KAZAN:20170830175310p:plain