TadaoYamaokaの開発日記

個人開発しているスマホアプリや将棋AIの開発ネタを中心に書いていきます。

LinuxでC#からC++で作成した共有ライブラリを呼び出す

cmakeでC++の共有ライブラリプロジェクトを作成する

CMakeLists.txt
cmake_minimum_required(VERSION 3.8)
project(SampleDll)

enable_language(CXX)
add_library(SampleDll SHARED
  sample_dll.cpp
)


sample_dll.cppに、C++で処理を記述する。
Windowsでも使えるように、ディレクティブを使用している。

sample_dll.cpp
#include <iostream>

#ifdef _MSC_VER
#define DLL_EXPORT __declspec(dllexport)
#else
#define DLL_EXPORT
#endif

extern "C" {
	DLL_EXPORT DLL_EXPORT int Add(int n1, int n2);
}

DLL_EXPORT int Add(int n1, int n2)
{
	std::cout << n1 << "," << n2 << std::endl;
	return n1 + n2;
}

ビルドする

mkdir build
cd build
cmake ..
make

ビルドに成功するとlibSampleDll.soが作成される。

共有ライブラリを使用可能にする

Ubuntuの共有ライブラリの格納パスは、/etc/ld.so.conf.d/libc.confに以下の通り設定されている。

# libc default configuration
/usr/local/lib

したがって、共有ライブラリを/usr/local/libにコピーする。

cp libSampleDll.so /usr/local/lib/

ただし、このままではロードできない。
キャッシュの更新が必要なため、ldconfigコマンドを実行する。

ldconfig

以上で、共有ライブラリが使用可能となる。

.NET CoreでC#のプロジェクトを作成する

dotnet new console

共有ライブラリを呼び出すコードを記述する。

Program.cs
using System;
using System.Runtime.InteropServices;

namespace Sample
{
    class Program
    {
        [DllImport("SampleDll")]
        extern static int Add(int n1, int n2);

        static void Main(string[] args)
        {
            int a = Add(1, 2);
            Console.WriteLine(a);
        }
    }
}

共有ライブラリの名前には接頭辞の「lib」と、拡張子の「.so」は含める必要はない。

実行する

dotnet run

成功すれば以下の通り表示される。

1,2
3


GitHubソースコードを公開しました。
GitHub - TadaoYamaoka/csnative

また、Google Colabでノートブックを公開しました。
https://colab.research.google.com/drive/15Yr-JeYuUJUa08jsDPOI3BUOBMxX3Rh-

WindowsでPyTorchをC++(Visual C++)で動かす

WindowsでPyTorchをC++のサンプル(MNIST)をVisual Studio 2017でビルドして動かす手順のメモです。

LibTorchのインストール

https://pytorch.orgのQUICK START LOCALLYから、Stable/Windows/LibTorch/C++/10.0(CUDAのバージョンが10.0の場合)を選んでlibtorch-win-shared-with-deps-latest.zipをダウンロードする。
適当な場所に解凍する(以下、C:\に解凍したとして説明)。

環境変数PATHに

C:\libtorch\lib

を追加する。

最も単純なサンプルをビルドして実行

Installing C++ Distributions of PyTorch — PyTorch master documentation
の説明の通り、適当な場所に

  • CMakeLists.txt
  • example-app.cpp

を作成して、上記のページの内容の通り内容を記述する。

ビルド

スタートメニューから「VS 2017 用 x64_x86 Cross Tools コマンド プロンプト」を起動する。
サンプルコードを配置したディレクトリに移動して、以下のコマンドを実行する。

mkdir build
cd build
cmake -G "Visual Studio 15 2017 Win64" -DCMAKE_PREFIX_PATH=C:\libtorch ..

cmakeのオプションは、Visual Studio 2017とLibTorchを解凍した場所に合わせている。
(cmakeはVisual Studioのインストール時のオプションで選択すればインストールされる。)

buildディレクトリに「example-app.sln」が作成されるので、エクスプローラから開いて、Visual Studio 2017でビルドして、スタートアッププロジェクトを「example-app」に設定して実行する。
成功すれば、以下のように表示される。

 0.3171  0.7950  0.6067
 0.1094  0.7421  0.8496
[ Variable[CPUType]{2,3} ]

もしくは、msbuildでビルドする。
ソリューションの構成が「RelWithDebInfo」の場合の例)

msbuild example-app.sln /t:build /p:Configuration=RelWithDebInfo;Platform="x64"

MNISTサンプルをビルドして実行

The C++ Frontend — PyTorch master documentation
の説明の通り、サンプルのGitHubレポジトリのcppブランチを取得する。

git clone -b cpp https://github.com/goldsborough/examples.git

以下の通り実行する。

cd examples\cpp\mnist
mkdir build
cd build
cmake -G "Visual Studio 15 2017 Win64" -DCMAKE_PREFIX_PATH=C:\libtorch ..

buildディレクトリにmnist.slnが作成されるので、エクスプローラから開いてビルドする。
スタートアッププロジェクトを「mnist」に設定して実行する。
ソリューションの構成が「Debug」の場合、
register_module("conv1", conv1);
の箇所で、vectorライブラリ内で例外が発生して実行できなかった。
ソリューションの構成を「Release」または「RelWithDebInfo」にした場合は実行できた。
成功すれば、以下のように表示される。

CUDA available! Training on GPU.
Train Epoch: 1 [59584/60000] Loss: 0.4143
Test set: Average loss: 0.1984 | Accuracy: 0.939
Train Epoch: 2 [59584/60000] Loss: 0.1886
Test set: Average loss: 0.1356 | Accuracy: 0.959
Train Epoch: 3 [59584/60000] Loss: 0.1666
Test set: Average loss: 0.0983 | Accuracy: 0.969
Train Epoch: 4 [59584/60000] Loss: 0.1346
Test set: Average loss: 0.0870 | Accuracy: 0.973
Train Epoch: 5 [59584/60000] Loss: 0.1822
Test set: Average loss: 0.0770 | Accuracy: 0.975
Train Epoch: 6 [59584/60000] Loss: 0.0635
Test set: Average loss: 0.0696 | Accuracy: 0.978
Train Epoch: 7 [59584/60000] Loss: 0.1375
Test set: Average loss: 0.0676 | Accuracy: 0.978
Train Epoch: 8 [59584/60000] Loss: 0.0289
Test set: Average loss: 0.0625 | Accuracy: 0.980
Train Epoch: 9 [59584/60000] Loss: 0.0218
Test set: Average loss: 0.0553 | Accuracy: 0.982
Train Epoch: 10 [59584/60000] Loss: 0.0773
Test set: Average loss: 0.0530 | Accuracy: 0.983

Google Colabでビルドして実行する方法

Linuxの場合は、公式の説明の通り実行できる。
Google Colabのノートブックを公開したので参考にしてほしい。
https://colab.research.google.com/drive/1F8kSS1VLTAi0zVocIf7kCCMhSgM732PW

将棋AIの進捗 その32(MCTSの探索にAND/OR木を導入する)

Leela Chess Zeroの状況を定期的にウォッチしないとなと思って、issueを眺めていたら"Exact-Win Strategy for Overcoming AlphaZero" #799という投稿がされていた。
Leela Zeroのissue#2276にも同様の投稿がある。
ざっくり説明すると、子ノードが勝ちの場合、そのノードを再度探索しないという探索のアイディアである。

元の論文「Exact-Win Strategy for Overcoming AlphaZero」では、この方法によりLeela Zeroのオリジナルに対して61%の勝率になると報告されている。

元の論文は有料だが、Leela Zeroのissueに抜粋が掲載されている。
f:id:TadaoYamaoka:20190810094745p:plain

要約すると、

  1. 子ノードに1つでも確定した勝ち(囲碁の場合2回のパス)、負け、引き分けがあると、そのノードは選択しない。
  2. 子ノードに1つでも確定した負けがあると、その親ノードを勝ちとマークする。すべての子ノードが勝ちの場合、負けとマークする。すべて引き分けの場合、引き分けとしてマークする。

ということだ。

MCTSの探索に、負けが確定した局面を探索しないという枝刈りと、AND/OR木による詰み探索を導入するアイディアと解釈できる。

dlshogiでは、MCTSの探索中に短手数(7手)の詰み探索を導入している。
終盤では勝敗が確定するノードが現れやすいため、この探索の導入には効果があると思われる。
例えば、ノードが8手詰みの局面で、子ノードに詰み探索に勝ちが確定した局面あったとしても無駄な探索を行ってしまう。
この方法によって探索が効率化できる。
また、8手以上の詰みでも、探索によりAND/OR木を成長できるので、MCTSでも確実な詰みの判断ができる。

dlshogiでは、すでに詰み探索を導入しているため、論文のノードに確定した勝ち負けをマークするという部分はすでに実装されているため、論文の探索方法を容易に実装できるため、試してみた。

次のような10手詰め(後手負け)の局面で、
f:id:TadaoYamaoka:20190810210934p:plain
sfen lnS+N5/6Gg1/p1pp+R3p/4ppS2/1p1sk4/2P1LlP2/PP1P1+p2P/1KGS2+b2/LN1r1+n2+b w G4P 96

試したところ、探索の改良前は、3秒の探索で、27483ノードの探索して評価値-2956だったが、改良後は、230ノードの探索で詰み(負け)が確定できた。

終盤では、このような局面がゲーム木の途中に多数現れるため、探索の効率化につながると予測できる。

実際に勝率に効果が表れるかは、別途連続対局で効果を確認したい。

なお、Leela Zeroでは、勝敗が確定する局面はそれほど多くないという理由で保留されている。
Leela Chess Zeroは、効果を検証中のようだ。

2019/8/12追記

技巧2と1手3秒で100回対局して勝率を比較した。

勝敗数 勝率 信頼区間(95%)
改善前 35勝61負4分 36% 46.4%~27.5%
改善後 38勝58負9分 41% 52.0%~32.2%

勝率は高くなっているが、信頼区間95%では強くなったとは言えなそうだ。
さらに対局数を増やして確認する必要がある。

信頼区間の計算方法
import math
def UCB(X, N, Z):
    return (X+Z*Z/2+Z*math.sqrt(X*(N-X)/N+Z*Z/4))/(N+Z*Z)
def LCB(X, N, Z):
    return (X+Z*Z/2-Z*math.sqrt(X*(N-X)/N+Z*Z/4))/(N+Z*Z)
UCB(35, 100-4, 1.96)
LCB(35, 100-4, 1.96)
UCB(38, 100-9, 1.96)
LCB(38, 100-9, 1.96)

参考:Wilson score interval を使う。 - 中野智文のブログ

AlphaStarについて

次回の技術書典に、参加している強化学習の勉強会のメンバで合同誌として頒布を行うべく執筆を行っている。
自分は、ネタとしてAlphaStarについて選んだのだが、書く内容がまとまらないのでブログにもアウトプットすることにする。

AlphaStarについて

Google DeepMindが開発した、StarCraft IIで初めて人間のトッププロに5-0で勝利したAI。

StarCraft IIについて

StarCraft IIは、ブリザード・エンターテイメントが開発した1対1で対戦を行うリアルタイムストラテジー(RTS)ゲーム。
日本語版が発売されていないため、日本ではあまり知名度が高くないが、海外(欧米、アジア)ではeスポーツ大会が開かれるくらい人気*1
f:id:TadaoYamaoka:20190804150945p:plain

AlphaStarの技術情報

現時点では、まだ論文は公開されていないが、査読付き論文誌に向けて準備されている*2

現時点での情報源

AlphaStarのブレークスルー

従来のAIの課題

従来のAIは、以下のような課題に対処できなかった。

  • じゃんけんのような単一の戦略がないゲーム
  • 不完全情報(部分観測)
  • 長期的なプランニング
  • リアルタイム
  • 広い行動空間

StarCraftを、これらの課題に対処するためのAI研究の「グランドチャレンジ」と位置づけた。
リザードの協力のもとオープンソースStarCraftの学習環境PySC2(人間のゲームのリプレイを含む)をリリースした。
AlphaStarは、そこでの研究からの複数のブレークスルーを組み合わせることによって生み出された。
f:id:TadaoYamaoka:20190804150427p:plain

AlphaStarのアーキテクチャ

(ゲーム画面ではなく)ゲームの内部情報(ユニットリストとそれらのプロパティ)を入力として受け取り、行動(一連の命令)を出力する。
f:id:TadaoYamaoka:20190804154718p:plain

ニューラルネットワークの構成

以下の要素を組み合わせている。

一つ一つの詳細を理解するには、論文を読み込む必要がある。
f:id:TadaoYamaoka:20190804150322p:plain

エージェントの学習アルゴリズム

はじめに匿名の人間のリプレイデータから、基本的なミクロ戦略とマクロ戦略を模倣した。
それにより、ゲームに実装されている"エリート"レベルAI(人間のゴールドレベル相当)に95%勝つようになった。

その後、マルチエージェント強化学習により訓練する。
マルチエージェントをリーグ戦で戦わせて、自分とは異なるエージェントから学べるようにする。
既存のエージェントから分岐して新しいエージェントが動的にリーグに追加される。
population-based trainingmulti-agent reinforcement learningのアイディアを取り入れている。

リーグが進み新しいエージェントが作成されるとそれに対抗する新しい戦略が出現する。
いくつかの新しいエージェントが単に前の戦略を改良した戦略を実行する一方で、他のものは全く新しいビルドオーダー、ユニット構成、およびマイクロ管理計画からなる劇的に新しい戦略を発見する。
f:id:TadaoYamaoka:20190804162129p:plain

リーグの多様性を促進するために、各エージェントは個別の学習目標を持っている。
あるエージェントは特定の敵を倒すという目的を持っているが、他のあるエージェントは全体的に戦いを行い、別のエージェントはユニットの建築を行う。
f:id:TadaoYamaoka:20190804154001p:plain

パラメータの更新

ニューラルネットワークの重みは他のエージェントとの対戦から、強化学習によって更新され、自分自身の学習目標を最適化する。
重み更新則は、以下のアルゴリズムを使用する。

訓練環境

訓練は、分散環境で行われ、何千ものエージェントが並列で訓練された。
リーグは、エージェントにつきTPU v3を16個使用して、14日間行われた。
実時間では 200年に相当する。
最後のエージェントは、リーグのナッシュ分布(最も効果的な戦略の組み合わせ)の構成要素により選ばれた。
対戦は1GPUのデスクトップPCで行われた。


引用されている、それぞれの論文を理解するとなるとかなり大変そうです。
技術書典までには間に合いそうにもないので、上記の内容を肉付けして執筆することにします。
AlphaStarと同様にRTSで成果を出しているOpenAI Five(詳細Model Architecture)は、5対5の対戦に対応しています。
ユニットを埋め込み表現にしたり、LSTMを使ったり共通点はありますが、学習則はAlphaStarとは異なっておりそちらも気になっています。

Visual Studioを使わずにNuGetのパッケージを検索する

Microsoft系のプログラミング言語では、NuGetを使用するとパッケージインストールが楽にできる。
しかし、NuGetは基本的にVisual Studioから使うことが前提になっている。
.NET Coreは、Visual Studioを前提としていないため、Visual Studio使わずにNuGetの管理ができるとよい(特に、MacOSLinuxで使う場合)。
プロジェクトへのパッケージの追加は、「dotnet add package」で可能だが、ここのissueを見ると現時点でVisual Studioを使わずにコマンドラインからNuGetのパッケージを検索する方法は提供されていないようだ。

パッケージを検索する方法が他に方法がないか探したところ、「dotnet-search」という.NET Core 2.1に対応したツールが見つかった。
GitHub - billpratt/dotnet-search: Search for Nuget packages using the .NET Core CLI.
これを使えば、MacOSLinuxでも使用できる。

dotnet tool install」を使用してインストールすることで、コマンドラインから「dotnet search」コマンドを使用できるようになる。
dotnet tool installは.NET core グローバル ツールをインストールするコマンド)

インストール
>dotnet tool install --global dotnet-search
使用例
>dotnet search Grpc

結果

Name                    Description                 Authors            Version   Downloads   Verified
_____________________________________________________________________________________________________
Grpc                    Metapackage for gRPC C#     The gRPC Authors   1.22.0    1.60M          *
-----------------------------------------------------------------------------------------------------
Grpc.Core               C# implementation of        The gRPC Authors   1.22.0    3.98M          *
                        gRPC based on native
                        gRPC C-core library.
-----------------------------------------------------------------------------------------------------
Grpc.Auth               gRPC C#                     The gRPC Authors   1.22.0    1.75M          *
                        Authentication
                        Library
-----------------------------------------------------------------------------------------------------
Grpc.Core.Api           gRPC C# Surface API         The gRPC Authors   1.22.0    578.60K        *
-----------------------------------------------------------------------------------------------------
Grpc.Tools              gRPC and Protocol           The gRPC Authors   1.22.0    1.33M          *
                        Buffer compiler for
                        managed C# and
                        native C++ projects.
                         Add this package to
                        a project that
                        contains .proto
                        files to be compiled
                        to code. It contains
                        the compilers,
                        include files and
                        project system
                        integration for gRPC
                        and Protocol buffer
                        service description
                        files necessary to
                        build them on
                        Windows, Linux and
                        MacOS. Managed
                        runtime is supplied
                        separately in the
                        Grpc.Core package.
-----------------------------------------------------------------------------------------------------
Grpc.HealthCheck        gRPC C# Health              The gRPC Authors   1.22.0    185.71K        *
                        Checking (for
                        Grpc.Core)
-----------------------------------------------------------------------------------------------------
Grpc.Reflection         gRPC C# Server              The gRPC Authors   1.22.0    170.65K        *
                        Reflection (for
                        Grpc.Core)
-----------------------------------------------------------------------------------------------------
Grpc.Core.Testing       Miscellaneous code          The gRPC Authors   1.22.0    68.49K         *
                        for testing
                        Grpc.Core
-----------------------------------------------------------------------------------------------------
Grpc.Core.NativeDebug   Debug symbols for           The gRPC Authors   1.22.0    4.35K          *
                        the native library
                        contained in
                        Grpc.Core
-----------------------------------------------------------------------------------------------------
Bond.Grpc.CSharp        This package                Microsoft          8.1.0     18.35K
                        contains bindings
                        for using Bond with
                        gRPC. The
                        Bond-generated gRPC
                        code depends on
                        types in this
                        assembly.
                        Bond is an open
                        source,
                        cross-platform
                        framework for
                        working with
                        schematized data. It
                        supports
                        cross-language
                        serialization/deserialization
                        and powerful generic
                        mechanisms for
                        efficiently
                        manipulating data.
                                 Bond is
                        published on GitHub
                        at
                        https://github.com/Microsoft/bond/
                                   The C#
                        documentation is
                        available at
                        https://microsoft.github.io/bond/manual/bond_cs.html                                            

1 - 10 of 113 results

gRPCでC#とPythonを連携する

C#のプログラムから機械学習などの処理をPythonで実装したい場合がある。
C#Pythonの連携方法について調べたところいくつか方法があったが、gRPCが良さそうだったので試してみた。

調べた方法

Python for .NET

C#Pythonを直接連携させるには、「Python for .NET」があるが、リリースされているバージョンは最新のPythonと.NETのバージョンに追従できていないようなので、自分でビルドが必要になる(.NET Coreで動かそうと試したがうまくいかずあきらめた)。

リダイレクト

プロセスをリダイレクトで連携するという方法もあるが、受け渡すデータの構造が複雑な場合、データのシリアライズとデシリアライズの処理の作りこみが必要になる。
また、C#から起動したPythonプログラムのデバッグが行いにくいという課題がある。

ソケット通信

ソケット通信で連携する場合は、Pythonのサーバプログラムをデバッガで起動することができるが、データのシリアライズとデシリアライズの処理の作りこみは必要になる。

RESTサービス

RESTサービスにして連携する場合は、バイナリデータをテキストで渡すためオーバヘッドが大きくなり、呼び出し頻度によってはパフォーマンスが課題になる。

gRPC

gRPCを使用した場合、これらの課題をすべて解消できる。

gRPCについて

gRPCは、Googleが標準化を主導しているHTTP/2をベースにしたオープンソースのRPCライブラリだ。
様々な言語に対応しており、C#Pythonにも対応している。
インターフェースは、protobufで定義する。

プログラミング言語で使用できる標準的なスカラ型、配列や構造体など複雑なデータ構造が定義できる。
シリアライズとデシリアライズのコードは、使用するプログラミング言語向けに自動生成されるため、自分で作りこむ必要がない。

サンプルプログラム

C#Pythonの連携を行うサンプルプログラムを作成した。
GitHub - TadaoYamaoka/gRPCSample

公式でC#Pythonそれぞれのサンプルが提供されており、C#のクライアントとPythonのサーバを連携しただけなので、詳細は公式の説明を参照。
https://grpc.io/docs/quickstart/csharp/
https://grpc.io/docs/quickstart/python/

C#側(gRPCSample)

C#側の.NETのバージョンは、.NET Core 2.1を使用した。
Visual Studioで.NET Coreのコンソールアプリを作成して、NuGetで、「Grpc」(gRPCのAPI)と「Grpc.Toos」(protocを含む)と「Google.Protobuf」(protobufに必要なクラス)を参照に追加する。
Visual Studioを使用しないでCLIから追加するには以下の通り実行する。

>dotnet add package Grpc
>dotnet add package Grpc.Tools
>dotnet add package Google.Protobuf

作成した.protoファイルをVisual Studioで、右クリックしてプロパティを表示して、Build Actionを「protobuf compiler」に設定すると、プロジェクトをビルドすると自動で.protoからC#コードが生成される。
namespaceやクラスのプロパティなどの先頭は大文字になるため注意が必要。

クライアントの処理は、Program.csに実装している。
ほぼ公式のサンプル通りのため説明は割愛。

Python側(PyServerSample)

Python向けgrpcはpipからインストールする。

>pip install grpcio
>pip install grpcio-tools

.protoからPythonコードの生成は、手動でコマンドを実行して生成した。

python -m grpc_tools.protoc -I../gRPCSample --python_out=. --grpc_python_out=. ../gRPCSample/Sample.proto

サーバの処理は、PyServerSample.pyに実装している。
ほぼ公式のサンプル通りのため説明を割愛。
max_workersで、スレッド数を指定できるが、PythonにはGILがあるため、並列化のパフォーマンスはあまり期待できそうにない。
並列化する場合は、マルチプロセス構成にして、負荷分散の仕組みを検討した方がよさそうだ(詳しくないがNginxでHTTP/2の負荷分散ができるようだ)。

2019/8/7 追記

.porotoから生成したクラスに、C#のオブジェクトから値を詰め込む際に、JavaでいうDozerのような機能が欲しくなる。
C#では、AutoMapperというライブラリがデファクトのようだ。
フィールド名が一致する場合は、自動でフィールドの値をコピーしてくれる。
ただし注意点がいくつかある。

名前規則

.protoで単語間をアンダーバーで区切っている場合、C#ではキャメルケースで区切る規則に変換される。
そのため、名前が一致せずコピーされなくなる。
その際は、以下のように設定すればよい。

    Mapper.Initialize(cfg =>
    {
      cfg.CreateMap<Class1, Class2>();
      cfg.SourceMemberNamingConvention = new LowerUnderscoreNamingConvention();
      cfg.DestinationMemberNamingConvention = new PascalCaseNamingConvention();
    });

参考:c# - Automapper naming convention with underscore/PascalCase properties - Stack Overflow

repeated

.porotoで、repeatedとして定義したフィールドは、readonoyのコレクションとして生成される。
そのため、値がコピーされない。
AfterMapでカスタマイズする必要がある。
例:https://gist.github.com/TadaoYamaoka/7791fcfe274b9bb2fc5a4318e2f4dd15

Visual Studio 2017に.NET Core 2.1を追加する

ほぼ自分用のメモです。

Visual Studio 2017がサポートしている.NET Coreのバージョンは2.1.508だが、先により新しいバージョン2.1.801をインストールしていたため、アンインストールしてから、Visual Studio Installerから「.NET Core クロスプラットフォームの開発」を追加したところ、.NET Coreのプロジェクトの作成はできるが、ターゲットフレームワークに.NET Core 2.1が表示されないという問題が起きた。

解決方法

こちらのissuesを見て解決できた。
DotNet SDK Found, DotNet.DLL not.... but its there · Issue #6180 · dotnet/sdk · GitHub

コマンドライン

>dotnet --info

を実行すると、

Found dotnet SDK, but did not find dotnet.dll at [C:\Program Files\dotnet\sdk\2.1.801\dotnet.dll]

と表示される状態になっていた。

C:\Program Files\dotnet\sdk\2.1.801
はフォルダのみ残っていて中身は空になっていた。
アンインストーラで、完全にアンインストールができていなかったようだ。
2.1.801フォルダを削除すると、ターゲットフレームワークに表示されるようになった。