「Programming」カテゴリーアーカイブ

MacでTypeScripのAWS CDKを試す

本記事では、AWSのGetting Started with the AWS CDKのチュートリアルを参考にAWS CDKを使ったTypeScriptを試してみた記録です。

  • AWS CDKのインストール
  • TypeScriptテンプレートのダウンロード
  • TypeScriptの作成
    • ライブラリファイルの作成
    • 実行ファイルの作成
  • TypeScriptのコンパイル
  • デプロイ環境の準備
  • AWSリソースのデプロイ

AWS CDKのインストール

以下のコマンドを利用してaws-cdkをグローバルインストールします。cdkコマンドをプロジェクトごとに管理したい場合は、-gオプションを外してローカルインストールする必要があります。

$ npm install -g aws-cdk
$ cdk --version
2.4.0 (build 993f14d)

TypeScriptテンプレートのダウンロード

利用できるTypeScriptのテンプレートは以下の通りで、app、lib、sample-appが選択できます。今回はappを選択します。

Available templates:
* app: Template for a CDK Application
   └─ cdk init app --language=typescript
* lib: Template for a CDK Construct Library
   └─ cdk init lib --language=typescript
* sample-app: Example CDK Application with some constructs
   └─ cdk init sample-app --language=typescript

プロジェクト用のディレクトリを作成し、appテンプレートをダウンロードします。ダウンロードが完了するとTypeScriptのコンパイルに必要なファイルが作成されます。

$ mkdir aws-cdk
$ cd aws-cdk/
$ cdk init app --language typescript
~~省略~~
✅ All done!

TypeScriptの作成

ライブラリファイルの作成

lib配下には、TypeScriptの雛形ファイルが作成されます。チュートリアルのS3サンプルコードを利用して以下のように編集します。これは、aws-cdk-test-bucketという名前のS3バケットを作成するだけのコードです。なお、クラス名であるTestS3StackはCloudFormationのスタック名になります。

import { Stack, StackProps } from 'aws-cdk-lib';
import { Construct } from 'constructs';
import { aws_s3 as s3 } from 'aws-cdk-lib';

export class TestS3Stack extends Stack {
  constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props);
    const bucket = new s3.Bucket(this, 'CdkTestBucket', {
        bucketName: 'aws-cdk-test-bucket',
        versioned: true,
        websiteRedirect: {hostName: 'aws.amazon.com'}});
  }
}

実行ファイルの作成

bin配下には、lib配下で作成したクラスのインスタンを生成して実行するためのTypeScriptコードがあります。以下のように編集してTestS3Stackを実行するように設定します。

#!/usr/bin/env node
import 'source-map-support/register';
import * as cdk from 'aws-cdk-lib';
import { TestS3Stack } from '../lib/aws-cdk-stack';

const app = new cdk.App();
new TestS3Stack(app, 'TestS3Stack', {});

TypeScriptのコンパイル

編集したTypeScriptをビルドします。ビルドが完了するとJavaScriptファイルが生成されます。

$ npm run build

> aws-cdk@0.1.0 build
> tsc

$ cdk ls
TestS3Stack

作成したTypeScriptをCloudFormationの形式に変換したい場合は以下のコマンドを利用します。

$ cdk synth
Resources:
  CdkTestBucket3821C40C:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: aws-cdk-test-bucket
      VersioningConfiguration:
        Status: Enabled
      WebsiteConfiguration:
        RedirectAllRequestsTo:
          HostName: aws.amazon.com
    UpdateReplacePolicy: Retain
    DeletionPolicy: Retain
    Metadata:
      aws:cdk:path: TestS3Stack/CdkTestBucket/Resource
~~省略~~

デプロイ環境の準備

bootstrapコマンドを使用して、cdkを利用してデプロイできる環境を構築します。ここでは、CDKToolkitという名前のCloudFormationスタックが実行されます。

# aws cliの設定をしていない場合はあらかじめ設定しておきます。
$ aws configure
$ cdk bootstrap
~~省略~~
 ✅  Environment aws://[AWSのアカウントID]/[CLIで設定しているリージョン] bootstrapped.

実行が完了するとS3のバケットが作成されます。間違ってこのS3バケットを削除してしまうと、デプロイ時にエラーが発生します。その際は、CloudFormationのStackを削除して再度cdk bootstrapを実行してください。

$ aws s3 ls
2022-01-09 14:38:26 [バケット名]

#上記のバケットを削除してcdk deployを実行すると以下のエラーが発生します。
[100%] fail: No bucket named ‘バケット名’. Is account ‘アカウント名’ bootstrapped?

AWSリソースのデプロイ

deployコマンドを使用してTypeScriptに記述したバケットを作成します。一度CloudFormationの形式に変換された後、TestS3Stackという名前のスタックが作成されて実行されます。

$ cdk deploy

✨  Synthesis time: 8.98s

TestS3Stack: deploying...
[0%] start: Publishing ~~省略~~
[100%] success: Published ~~省略~~
TestS3Stack: creating CloudFormation changeset...

 ✅  TestS3Stack

✨  Deployment time: 50.63s

Stack ARN:
~~省略~~

✨  Total time: 59.6s

AWS上で確認すると、以下の通りバケットが作成されていました。

$ aws s3 ls | grep aws-cdk-test-bucket
2022-01-09 15:58:02 aws-cdk-test-bucket

以上です。

MacでTypeScriptをインストールしてコンパイルを試す

  • TypeScriptとは
  • Node.jsのインストール
  • TypeScriptのインストール
  • TypeScriptでコンパイルを試す

TypeScriptとは

TypeScriptを一言で説明すると、独自の構文からJavaScriptにコンパイルするためのツールです。JavaScriptとの大きな違いは型定義が必要な点です。

https://www.typescriptlang.org

JavaScriptは型を定義しないで開発できますが、開発する上で多くの弊害があります。例えば、ソースコードを読む時に何の変数かが一目で分かりませんし、その変数に間違った型を定義するとプログラムが実行されるまで気付くことができません。

TypeScriptでは独自の構文からJavaScriptを生成することで、それらの欠点を補います。JavaScriptの可読性を向上したり、定義を強制したり、型エラーを実行前に検知したりすることで開発生産性を向上します。TypeScriptはJavaScriptのコードを綺麗に保つ上で今後重宝されていくと思います。

Node.jsのインストール

以下のコマンドでNode.jsの推奨バージョンをインストールします。推奨バージョンは、こちらから確認できます。

$ brew install nodebrew
# nodebrew installで"Failed to create the file"のエラーが出た場合は以下のコマンドを実行
$ mkdir -p .nodebrew/src
# 推奨バージョンのインストール
$ nodebrew install-binary v16.13.1
Fetching: https://nodejs.org/dist/v16.13.1/node-v16.13.1-darwin-x64.tar.gz
########################################################################################################################## 100.0%
Installed successfully

インストールが完了したら、以下のコマンドでパスを通します。毎回パスを通すのが面倒な方は~/.bash_profileに登録しておくと便利です。

$ nodebrew use v16.13.1
use v16.13.1
$ export PATH=$HOME/.nodebrew/current/bin:$PATH
$ node -v
v16.13.1

TypeScriptのインストール

TypeScriptをプロジェクトで管理するため、ディレクトリを作成して以下のコマンドでローカルインストールを実行します。

$ mkdir getting-start
$ cd getting-start/
$ npm install --save-dev typescript tslint @types/node

TypeScriptでコンパイルを試す

TypeScriptでコンパイルし、型定義のエラーを出力するところまで実施します。

String型で定義した変数に文字列を代入するだけのindex.tsを作成します。なお、TypeScriptの拡張子はtsです。

let message: string
message = "Hello World"

以下のコマンドでコンパイルします。コンパイルするときには、コンパイルオプションを指定する”tsconfig.json”が必要です。今回は何も指定せずにコンパイルするので、中身を空にしています。

$ touch tsconfig.json
$ ./node_modules/.bin/tsc

index.tsファイルと同じディレクトリにindex.jsファイルが生成されています。中身を確認すると、JavaScriptに変換されていることがわかります。

$ cat index.js
var message;
message = "Hello World";

JavaScriptではmessage変数の型が定義されていませんので、messageに数値を代入してもJavaScriptでは問題ありません。しかしTypeScriptでは型を定義しており、数値を代入することができません。この挙動を確認してみます。

index.tsファイルを以下のように編集します。

let message: string
message = 1

再度コンパイルすると、messageに異なる型を代入しているのでエラーが出力されます。

$ ./node_modules/.bin/tsc
index.ts:2:1 - error TS2322: Type 'number' is not assignable to type 'string'.

2 message = 1
  ~~~~~~~

Found 1 error.

本記事では以上とします。

PythonのPyGithubでコンテンツをPushする

PyGithubを使って、PythonプログラムからGitHubに対してコンテンツをPushする方法について解説します。

  • 環境情報
  • PyGithubをインストールする
  • PyGithubを使ってコンテンツをPushする
    • create_fileメソッドを利用する
    • create_git_commitメソッドを利用する

環境情報


本記事は、以下の環境を基に解説します。

  • OS・・・MacOS Catalina 10.15.5
  • Python・・・Python 3.9.1
  • PyGithub・・・1.54.1

PyGithubをインストールする


pipコマンドを利用して、PyGithubをインストールします。

$ pip install PyGithub

PyGithubを使ってコンテンツをPushする


PyGithubでGithubにPushする方法として、以下二つがあります。本節では、これらの方法について紹介します。

  • create_fileメソッドを利用する
  • create_git_commitメソッドを利用する

create_fileメソッドを利用する


create_fileメソッドを利用すると、コンテンツをGitHubにPushするところまで実施します。

PyGithubのRepositoryクラスのメソッドで、引数は以下の通りです。branch、comitter、authorは必須ではなく、レポジトリのファイルパス(path)、Commit時のメッセージ(message)、ファイルの内容(content)が必須の引数です。

create_file(path, message, content, branch=NotSet, committer=NotSet, author=NotSet)

プログラムとして利用する場合は、以下のようにGithub.get_repoメソッドを利用してRepositoryクラスを定義する必要があります。

github_source = Github( base_url="https://github.com/api/v3", login_or_token=[GithubのToken] )
repo = github_source.get_repo( [GithubのOrganization/GitHubのRepository] )
repo.create_file( [ファイルパス],  [Commitのメッセージ], [ファイルの内容] )

しかし、create_fileメソッドは一つのファイルをPushします。従って、複数のファイルをPushする場合はファイルごとにPushするので、処理時間が長くなります。複数のファイルをまとめてPushしたい場合は、create_git_commitを利用します。

create_git_commitメソッドを利用する


create_git_commitを利用して、複数のファイルを一括でCommitしてPushする方法について解説します。

PyGithubのRepositoryクラスのメソッドで、引数は以下の通りです。author、comitterは必須ではなく、Commit時のメッセージ(message)、新たにアタッチするGitTree(tree)、GitTreeを追加したいCommit情報(parents)が必須の引数です。

create_git_commit(message, tree, parents, author=NotSet, committer=NotSet)

GitTreeとは、ファイルのコンテンツ(GitTreeElement)のリストです。つまり、既に存在するCommit情報がparents、新たにListに登録するファイルのコンテンツがtreeに該当します。

プログラムとして利用する場合は、以下の流れで記述します。

  1. InputGitTreeElementメソッドで登録したファイルコンテンツをlist(elements)に登録します(6~7行目)。ここでPushしたいファイルを複数まとめて登録できます。
  2. 既存のGitTree(base_tree)を取得し、ファイルコンテンツのlist(elements)をマージしたGitTree(new_tree)を作成します(9~12行目)。
  3. 最新のCommit情報(latest_commit)を取得します(13行目)。
  4. ファイルコンテンツをPushします(14~15行目)。
github_source = Github( base_url="https://github.com/api/v3", login_or_token=[GithubのToken] )
repo = github_source.get_repo( repo_path )
elements = list()

#ここでPushしたいファイルをfor文などを利用して複数登録する。
element = InputGitTreeElement( [ファイルパス], '100644', 'blob', [ファイルの内容] )
elements.append( element )

master_ref = repo.get_git_ref( [対象のブランチ名(masterの場合は、’heads/master’)] )
master_sha = master_ref.object.sha
base_tree = repo.get_git_tree( master_sha )
new_tree = repo.create_git_tree( elements, base_tree )
latest_commit = repo.get_git_commit( master_sha )
new_commit = repo.create_git_commit( [Commitのメッセージ], new_tree, [ latest_commit ] )
master_ref.edit( new_commit.sha )

Referenceを読み解くのは、意外に調査に時間がかかりますよね。。本記事がその時間を短縮してくれることを願います。

以上です。

Mavenのアーキテクチャについて

本記事では、Mavenのアーキテクチャを紹介します。

  • Mavenのアーキテクチャ
  • Mavenのビルドステップ
    • コンパイル
    • テスト
    • パッケージング
    • インストール
    • デプロイ

Mavenのアーキテクチャ


Mavenのアーキテクチャは、「pom.xml」「ローカルレポジトリ」「リモートレポジトリ」の三つがあります。それぞれの役割は以下の通りです。

  • pom.xml・・・ビルドから成果物の管理までのプロセスを記述したファイル
  • ローカルレポジトリ・・・リモートレポジトリからダウンロードしたライブラリやプラグインを保存しておく場所
  • リモートレポジトリ・・・ローカルレポジトリに必要なライブラリやプラグインが存在しない場合に、参照するレポジトリ

それぞれの関係を図示すると以下の通りです。pom.xmlには、「コンパイル」「テスト」「パッケージング」「インストール」「デプロイ」のステップがあります。各ステップについては、次節で説明します。

Mavenのビルドステップ


pom.xmlに記述する各ビルドステップについて説明します。

コンパイル


Javaソースコードをコンパイルして実行可能なファイルを生成をするステップで、以下のコマンドで単独実行できます。

$ mvn compile

コンパイルステップでは、target/classesにクラスファイルが生成されます。

テスト


ユニットテストを実行するステップで、以下のコマンドで単独実行できます。

$ mvn test

デフォルトでは以下のファイル名をテストクラスとして実行します。他のファイル名で実行したい場合は、pom.xmlのbuildタグで定義する必要があります。

  • **/Test*.java
  • **/*Test.java
  • **/*Tests.java
  • **/*TestCase.java

デフォルトで以下のAppTest.javaが作成されています。assertTrueは引数の値をtrueかfalseを確認するメソッドなので、必ず成功するテストクラスです。

package com.example;

import static org.junit.Assert.assertTrue;

import org.junit.Test;

/**
 * Unit test for simple App.
 */
public class AppTest 
{
    /**
     * Rigorous Test :-)
     */
    @Test
    public void shouldAnswerWithTrue()
    {
        assertTrue( true );
    }
}

junitをimportしているので、junitのライブラリが必要になります。デフォルトのpom.xmlではdependenciesタグにjunitが定義されていて、ライブラリを取得するよう定義しています。

~~ pom.xmlより抜粋 ~~
  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
~~ pom.xmlより抜粋 ~~

パッケージング


JARファイルを作成するステップで、以下のコマンドで単独実行できます。

$ mvn package

デフォルトではtarget/hello-world-1.0-SNAPSHOT.jarが作成されます。ファイル名は、pom.xmlのartifactIdversionタグに指定した名前になります。

~~ pom.xmlより抜粋 ~~
  <artifactId>hello-world</artifactId>
  <version>1.0-SNAPSHOT</version>
~~ pom.xmlより抜粋 ~~

インストール


作成したJARファイルをローカルレポジトリに登録するステップで、以下のコマンドで単独実行できます。

$ mvn install

インストールしたJARファイルは他のプロジェクトで利用することができます。

デプロイ


作成したJARファイルをリモートレポジトリに登録するステップで、以下のコマンドで単独実行できます。

$ mvn deploy

インストールとの違いは、登録したJARファイルを他のマシンでも利用できるかどうかです。デプロイステップで登録したJARファイルは、以下のように他のマシンでも利用が可能になります。

リモートレポジトリはpom.xmlで設定できます。以下のようにdistributionManagementタグで李モートレポジトリのURLを指定します。

~~ pom.xmlより抜粋 ~~
<project>
  <distributionManagement>
    <repository>
      <id>[Remote Repository ID]</id>
      <name>[Remote Repository name]</name>
      <url>[Remote Repository URL]</url>
    </repository>
  </distributionManagement>
</project>
~~ pom.xmlより抜粋 ~~

以上です。次はpom.xmlの内容について詳しく調べていきたいとお思います。

Apache MavenをMacにインストールしてHello World

前から気になっていたMavenの使い勝手を試そうと、まずはMacにインストールしてHello Worldを表示してみました。

  • Apache Mavenとは
  • Apache Mavenのインストール
    • 事前準備
    • インストール
    • Hello World

Apache Mavenとは


Apache Mavenとは、Javaソースコードをビルドするためのオープンソースソフトウェア(OSS)です。
ソースコードのコンパイルやユニットテストに加え、アーティファクト(WAR, EARなど)の生成が可能です。

Apache Mavenのインストール


事前準備


Apache Mavenのインストールを始める前に、以下の準備を行います。

  • Apache Mavenのダウンロード
  • JAVA_HOMEの設定

Apache Mavenのダウンロード


公式サイトから、Apache Mavenのモジュールをダウンロードします。本記事では、「Binary tar.gz archive」を選択します。

JAVA_HOMEの設定


インストールするには、JAVA_HOMEの設定が必要です。以下のように設定済みです。

$ echo $JAVA_HOME
/Library/Java/JavaVirtualMachines/jdk1.8.0_77.jdk/Contents/Home

インストール


ファイルの解凍


以下のコマンドでファイルを解凍します。

$ tar xzvf apache-maven-3.6.3-bin.tar

解凍後に作成されるディレクトリを/opt配下に移動します。

$ sudo mv apache-maven-3.6.3 /opt/

環境パスへの追加


コマンド実行できるように、以下のコマンドでPATH環境変数に/opt/apache-maven-3.6.3/binを追加します。

$ export PATH=/opt/apache-maven-3.6.3/bin:$PATH

mvnコマンドが実行できることを確認します。

$ mvn -v
Apache Maven 3.6.3 (cecedd343002696d0abb50b32b541b8a6ba2883f)
Maven home: /opt/apache-maven-3.6.3
Java version: 1.8.0_77, vendor: Oracle Corporation, runtime: /Library/Java/JavaVirtualMachines/jdk1.8.0_77.jdk/Contents/Home/jre
Default locale: ja_JP, platform encoding: UTF-8
OS name: "mac os x", version: "10.15.5", arch: "x86_64", family: "mac"

Hello World


Apache Mavenを使って、Javaプログラムのコンパイルを試します。

ソースコードの作成


以下のコマンドでJavaプログラムの雛形を作成します。

$ mkdir hello-world
$ cd hello-world/
$ mvn archetype:generate \
   -DgroupId=com.example \
   -DartifactId=hello-world

途中で質問されますが、全てEnterでデフォルトを選択すると、以下のようにBUILD SUCCESSが表示されます。

[INFO] ----------------------------------------------------------------------------
[INFO] Using following parameters for creating project from Archetype: maven-archetype-quickstart:1.4
[INFO] ----------------------------------------------------------------------------
[INFO] Parameter: groupId, Value: com.example
[INFO] Parameter: artifactId, Value: hello-world
[INFO] Parameter: version, Value: 1.0-SNAPSHOT
[INFO] Parameter: package, Value: com.example
[INFO] Parameter: packageInPathFormat, Value: com/example
[INFO] Parameter: version, Value: 1.0-SNAPSHOT
[INFO] Parameter: package, Value: com.example
[INFO] Parameter: groupId, Value: com.example
[INFO] Parameter: artifactId, Value: hello-world
[INFO] Project created from Archetype in dir: /Users/XXX/Downloads/hello-world/hello-world
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  36.709 s
[INFO] Finished at: 2020-09-28T00:53:14+09:00
[INFO] ------------------------------------------------------------------------

以下のコマンドで確認すると、ディレクトリ構造が作成されていることがわかります。

$ tree
.
└── hello-world
    ├── pom.xml
    └── src
        ├── main
        │   └── java
        │       └── com
        │           └── example
        │               └── App.java
        └── test
            └── java
                └── com
                    └── example
                        └── AppTest.java

10 directories, 3 files

App.javaを確認すると、デフォルトでHello Worldプログラムのソースコードであることがわかります。今回はこのまま使用します。

$ cat hello-world/src/main/java/com/example/App.java 
package com.example;

/**
 * Hello world!
 *
 */
public class App 
{
    public static void main( String[] args )
    {
        System.out.println( "Hello World!" );
    }
}

Jarファイルを作成する


作成したソースコードをJarファイルに圧縮します。

$ cd hello-world/
$ mvn package
[INFO] Scanning for projects...
[INFO] 
[INFO] ----------------------< com.example:hello-world >-----------------------
[INFO] Building hello-world 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
〜〜省略〜〜
[INFO] Building jar: /Users/XXX/Downloads/hello-world/hello-world/target/hello-world-1.0-SNAPSHOT.jar
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  53.662 s
[INFO] Finished at: 2020-09-28T01:07:29+09:00
[INFO] ------------------------------------------------------------------------

targetディレクトリ配下にJarファイルが作成されていることがわかります。

$ tree
.
├── pom.xml
├── src
│   ├── main
│   │   └── java
│   │       └── com
│   │           └── example
│   │               └── App.java
│   └── test
│       └── java
│           └── com
│               └── example
│                   └── AppTest.java
└── target
    ├── classes
    │   └── com
    │       └── example
    │           └── App.class
    ├── generated-sources
    │   └── annotations
    ├── generated-test-sources
    │   └── test-annotations
    ├── hello-world-1.0-SNAPSHOT.jar
    ├── maven-archiver
    │   └── pom.properties
    ├── maven-status
    │   └── maven-compiler-plugin
    │       ├── compile
    │       │   └── default-compile
    │       │       ├── createdFiles.lst
    │       │       └── inputFiles.lst
    │       └── testCompile
    │           └── default-testCompile
    │               ├── createdFiles.lst
    │               └── inputFiles.lst
    ├── surefire-reports
    │   ├── TEST-com.example.AppTest.xml
    │   └── com.example.AppTest.txt
    └── test-classes
        └── com
            └── example
                └── AppTest.class

28 directories, 13 files

実行する


作成したJarファイルを使って、Hello Worldプログラムを実行します。

$ java -cp target/hello-world-1.0-SNAPSHOT.jar com.example.App
Hello World!

以上です。とりあえず要領が分かったので、これから色々と使ってみたいと思います。

TensorFlowのチュートリアル から回帰による予測方法を学ぶ

TensorFlowのチュートリアル から回帰分析による予測について勉強したのでメモします。これから学習する人の参考になれば幸いです。

  • 回帰分析とは
    • 単回帰分析とは
    • 重回帰分析とは
  • TensorFlowのチュートリアル
    • チュートリアルの概要 
    • データの準備
    • モデルの構築
    • モデルの検証

回帰分析とは


回帰分析とは、連続するデータを関数モデルに適用し、変数からデータの値を分析・予測することです。つまり、y=f(x)というモデルを適用することで、説明変数 xから目的変数 yを予測することです。

連続するデータというのがキーワードで、機械学習の中では連続的な値を予測するために用います。例えば、価格や確率を予測するのに利用します。今回のチュートリアルでは自動車の燃費量を予測しています。

一方、分類法は目的によって離散的な値を予測することもあります。以下の「TensorFlowのチュートリアルから2クラス分類について学ぶ」で例に出した2クラス分類法はシグモイド関数を使っているので連続的な値ですが、出力が0か1(否定的か肯定的か)のみの場合は離散的な値の予測となります。

単回帰分析とは


単回帰分析とは、一つの説明変数から一つの目的変数で成り立つ回帰分析です。

例) y = ax + b

重回帰分析とは


重回帰分析とは、二つ以上の説明変数から一つの目的変数で成り立つ回帰分析です。

例)y = a1x1 + a2x2 + b

TensorFlowのチュートリアル


TensorFlowのチュートリアル 「回帰:燃費を予測する」を実施して、回帰による予測について勉強します。詳細については、実際にチュートリアルを実施するのが一番理解を深めると思いますので、あくまでも学習の補佐として読んでいただければと思います。

チュートリアルの概要


このチュートリアルでは、1970年代後半から1980年代初めの自動車の燃費を予測するモデルを構築し、自動車の排気量や馬力などから燃費を予測します。

このチュートリアルも大きく以下の流れに沿って進みます。

  • データの準備
  • モデルの構築
  • モデルの検証

データの準備


データのダウンロード


データは、UCI Machine Learning Repositoryから取得しており、それぞれの自動車に対して以下の情報が含まれています。

  • MPG・・・自動車の走行燃費量(miles per gallonの略)
  • Cylinders・・・自動車のシリンダー数
  • Displacement・・・自動車の排気量
  • Horsepower・・・自動車の馬力
  • Weight・・・自動車の重さ
  • Acceleration・・・自動車の加速性能
  • Model Year・・・自動車の製造年
  • Origin・・・自動車の製造元(1~3で区別しており、1がUSA、2がEurope、3がJapan)

データのクレンジング


ここでは以下を実施しています。

  • データが欠損している自動車のデータを削除
  • 製造元の”Origin”列を”USA”, “Europe”, “Japan”に変換して0と1で表現

ラベルと特徴量の分離


ここでは目的変数である燃費量(MPG)を説明変数から削除しています。燃費量を予測したいのに、予測するデータの中に燃費量が入っていると意味ないからです。

データの正規化


 データの特徴量の範囲(min~maxの間)が広いとモデルの訓練が難しくなるそうで、( データの特徴量 – 代表値 ) / 標準偏差で正規化を行っています。

モデルの構築


krasのSequentialモデルを利用してモデルを構築しています。

  model = keras.Sequential([
    layers.Dense(64, activation='relu', input_shape=[len(train_dataset.keys())]),
    layers.Dense(64, activation='relu'),
    layers.Dense(1)
  ])

Sequentialモデルとは、層を積み重ねたモデルのことです。このモデルを利用する場合は、最初のレイヤーに入力のshapeについて情報を与える必要があります。情報を与える方法として以下の3通りあり、チュートリアルでは以下の方法でinput_shape引数にデータセットの列数を指定しています。

  • 最初のレイヤーの input_shape引数を指定する.この引数にはshapeを示すタプルを与えます(このタプルの要素は整数か Noneを取ります.Noneは任意の正の整数を期待することを意味します).

参照元:https://keras.io/ja/getting-started/sequential-model-guide/

また、ここではモデルの活性化関数(activation)にreluを利用しています。
reluは正規化線形関数で、0と説明変数を比較して大きい値を目的変数とする関数です。0以上の値が返ってくる、かつ勾配が一定になることから、勾配消失に有効な関数です。

reluを活性化関数にすることで、ニューラルネットワークのイメージは以下のようになります。

モデルのコンパイルは以下のように記述しています。

  optimizer = tf.keras.optimizers.RMSprop(0.001)

  model.compile(loss='mse',
                optimizer=optimizer,
                metrics=['mae', 'mse'])

損失関数がmse(Mean Squared Error:平均二乗誤差)で、オプティマイザーには学習率0.001のRMSpropアルゴリズムが利用されています。評価関数には、mae(Mean Absolute Error: 平均絶対誤差)とmse(Mean Squared Error:平均二乗誤差)が利用されています。

モデルの全体図は以下の通りです。

モデルの検証


モデルの訓練


訓練用データを引数にmodel.fit関数を使って、モデルを訓練しています。

チュートリアルではモデルを1000エポック訓練しますが、100エポックを過ぎてから検証スコアが悪化するので、検証スコアが10エポック以内に改善しなかったら訓練を止めるようにEary Stoppingを使っています。

# patience は改善が見られるかを監視するエポック数を表すパラメーター
early_stop = keras.callbacks.EarlyStopping(monitor='val_loss', patience=10)

history = model.fit(normed_train_data, train_labels, epochs=EPOCHS,
                    validation_split = 0.2, verbose=0, callbacks=[early_stop, PrintDot()])

最後の結論にもありますが、Early Stoppingは過学習を防止するのに有効な手法です。しかしチュートリアルでは、Early Stoppingを使うことで、40エポック辺りで訓練が終了してしまい訓練用データが少なくなってしまうという事態に陥っています。この辺りはチューニングが必要になってくると思われます。

モデルのテスト


テストデータを引数にmodel.predict関数を使って、燃費量を予測しています。誤差は少ないようですが、訓練用データが少ないために正規頒布の形にはなってないみたいですね。

以上です。

TensorFlowのチュートリアルから2クラス分類について学ぶ

TensorFlowのチュートリアルである「映画レビューのテキスト分類」から2クラス分類(二値分類とも呼ばれる)について勉強したことをメモします。これから学習する人の参考になれば幸いです。

  • 2クラス分類について
    • 2クラス分類(二値分類)とは
    • 多クラス分類とは
    • 2クラス分類と多クラス分類の違い
  • チュートリアル
    • データの準備
    • モデルの構築
    • モデルの訓練と検証

2クラス分類について


2クラス分類(二値分類)とは


2クラス分類とは、任意のオブジェクトを二つのオブジェクトに分類することです。例えば、TensorFlowのチュートリアルでは「映画のレビュー」を「肯定的レビュー」か「否定的レビュー」のどちらかに分類しています。

多クラス分類とは


チュートリアルとは関係ないですが、2クラス分類の理解をより深めるために多クラス分類についても学んでおきます。

二つよりも多いオブジェクトに分類する場合は多クラス分類と呼ばれます。例えば、フルーツの写真を「りんご」「バナナ」「いちご」に分類する場合は多クラス分類です。

2クラス分類と多クラス分類の違い


チュートリアルとは関係ないですが、ここで2クラス分類との違いについて学んでおきます。

2クラス分類の大きな特徴は、出力層のニューロンが一つであることです。出力結果を0~1の範囲にして、二つのオブジェクトのパーセンテージを表すことが多いです。TensorFlowのチュートリアルでは、0が「否定的レビュー」、1が「肯定的レビュー」として表現しています。2クラス分類の出力結果にはシグモイド関数が利用されます。

一方、多クラス分類はカテゴリ毎に出力層のニューロンが存在します。フルーツの写真の例だと「りんご」「バナナ」「いちご」毎にニューロンがあり、それぞれ0~1の範囲で出力します。ここで重要なのは、出力結果の合計が必ず1になることです。ソフトマックス関数が利用され、「りんごである確率60%」「バナナである確率10%」「いちごである確率20%」といったように、出力の合計が1になります。

チュートリアル


チュートリアルは、大きく以下の流れで進みます。

  • データの準備
  • モデルの構築
  • モデルの訓練と検証

データの準備


レビューデータについては、Internet Movie Databaseから抽出した整形済みの IMDB datasetをダウンロードして利用しています。利用時には全てのレビューを同じデータ長にしています。

モデルの構築


モデルの各層の説明については、以下の通りチュートリアルに詳しく記載されているので、割愛します。

  1. 最初の層はEmbedding(埋め込み)層です。この層は、整数にエンコードされた語彙を受け取り、それぞれの単語インデックスに対応する埋め込みベクトルを検索します。埋め込みベクトルは、モデルの訓練の中で学習されます。ベクトル化のために、出力行列には次元が1つ追加されます。その結果、次元は、(batch, sequence, embedding)となります。
  2. 次は、GlobalAveragePooling1D(1次元のグローバル平均プーリング)層です。この層は、それぞれのサンプルについて、シーケンスの次元方向に平均値をもとめ、固定長のベクトルを返します。この結果、モデルは最も単純な形で、可変長の入力を扱うことができるようになります。
  3. この固定長の出力ベクトルは、16個の隠れユニットを持つ全結合(Dense)層に受け渡されます。
  4. 最後の層は、1個の出力ノードに全結合されます。シグモイド(sigmoid)活性化関数を使うことで、値は確率あるいは確信度を表す0と1の間の浮動小数点数となります。

映画レビューのテキスト分類より抜粋

compile関数を使って上記のモデルをコンパイルしています。

model.compile(optimizer='adam',
              loss='binary_crossentropy',
              metrics=['accuracy'])

各引数の説明は以下の通りです。

  • optimizer・・・重みの修正を行うアルゴリズムを指定します。指定できるアルゴリズムの詳細についてはこちら
  • loss・・・予測データと正解データの誤差を表す損失関数を指定します。指定できる損失関数の詳細についてはこちら
  • metrics・・・モデル全体の評価を行う関数を指定します。accuracyの場合は正解率で評価します。指定できる関数についての詳細はこちら

ニューラルネットワークのモデルを訓練する時は、予測データと正解データの誤差に従って層間の重みを修正していきます。上記のcompile関数における引数の役割を図示しました。

モデルの訓練と検証


本チュートリアルでは、上記で作成したモデルの訓練と検証まで実施しています。

訓練と検証の結果をエポック数と損失、エポック数と正解率のグラフで図示しています。エポック数は、一つの訓練データの学習回数を示します。

訓練中にエポック数が増える度に損失が減少し、正解率が上昇していることがわかります。しかし、検証では20エポック過ぎた辺りから損失も正解率も横ばいになります。

ニューラルネットワークはモデルを訓練すればするほど精度が上がるというわけではありません。学習して欲しくないパターンもあるからです。学習して欲しくないパターンを学習することによって、訓練用データでの正解率が上がる一方で、検証用データでの正解率が上がらない現象が発生します(過学習またはオーバーフッティングと呼びます)。チュートリアルでは、最適なエポック数が20程度で、それ以降は過学習ということがグラフからわかります。

以上です。

TensorFlowのチュートリアルをDockerで動かしてみる

AIOpsなどの波に押し寄せられ、機械学習について最初の一歩を恐る恐る踏み出してみました。これから始める方のために一応残しておきます。

  • TensorFlowとは
  • TensorFlowをローカルで動かす
  • TensorFlowのチュートリアルを試す

TensorFlowとは


公式ドキュメントでは、以下の通り記載があります。

TensorFlow は、機械学習向けに開発されたエンドツーエンドのオープンソース プラットフォームです。
引用元:https://www.tensorflow.org

Machine Learning(ML)を使ったアプリケーションを開発・デプロイするために作成されました。

TensorFlowをローカルで動かす


TensorFlowをローカルで動かすには、以下二通りの方法があります。本記事ではリンク先を参考に、コンテナとして実行する方法で動かします。

  • pipコマンドによるインストール
  • Dockerによるコンテナ実行

1. コンテナイメージをpullする


以下のコマンドでTensorFlowのコンテナイメージを取得します。

$ docker pull tensorflow/tensorflow:nightly-py3-jupyter

コンテナイメージにおけるタグ情報のオプションはリンク先をご参照ください。ここでは、python3のサポートとJupyterを含むナイトリービルド(最新ソースコード)のコンテナイメージを取得しています。

ちなみにJupyterとは、プログラミング言語をウェブブラウザ上で実行できるドキュメントツールです。ソースコードと説明文が混在したドキュメントを作成し、ソースコードとその実行結果をインタラクティブに表示することができます。

2. コンテナを起動する


以下のコマンドで、手順1で取得したコンテナイメージからコンテナを起動します。

$ docker run -it -p 8888:8888 tensorflow/tensorflow:nightly-py3-jupyter
[I 14:54:21.897 NotebookApp] Writing notebook server cookie secret to /root/.local/share/jupyter/runtime/notebook_cookie_secret
jupyter_http_over_ws extension initialized. Listening on /http_over_websocket
[I 14:54:22.459 NotebookApp] Serving notebooks from local directory: /tf
[I 14:54:22.459 NotebookApp] The Jupyter Notebook is running at:
[I 14:54:22.461 NotebookApp] http://453c162dd9e3:8888/?token=d233277b7f66020bbc79ea155a906ed4c41fc3846634511d
[I 14:54:22.461 NotebookApp]  or http://127.0.0.1:8888/?token=d233277b7f66020bbc79ea155a906ed4c41fc3846634511d
[I 14:54:22.463 NotebookApp] Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).
[C 14:54:22.471 NotebookApp] 
    
    To access the notebook, open this file in a browser:
        file:///root/.local/share/jupyter/runtime/nbserver-1-open.html
    Or copy and paste one of these URLs:
        http://453c162dd9e3:8888/?token=d233277b7f66020bbc79ea155a906ed4c41fc3846634511d
     or http://127.0.0.1:8888/?token=d233277b7f66020bbc79ea155a906ed4c41fc3846634511d

3. ブラウザからログインする


標準出力されたOr copy and paste one of these URLs配下のURLをコピーしてブラウザアクセスすると、以下の画面が表示されます。

TensorFlowのチュートリアルを試す


Dockerで動かしたJupyterを使って、TensorFlowのチュートリアル(分類問題の初歩)を試してみます。このチュートリアル自体は、機械学習のHello Worldに該当するそうです。

1. ipynbファイルを表示する


「tensorflow-tutorials」→「classification.ipynb」をクリックします。

2. 「Run」ボタンをクリックする


「Run」ボタンをクリックし、上から順にソースコードを実行していきます。それぞれの出力結果については、チュートリアルに記載されているので省略します。

チュートリアルで実施している内容をざっくり要約すると以下になります。

以上です。チュートリアルを一通りやるだけでも勉強になりました。

SwaggerEditorで生成したGo API Serverの処理を実装する

  • はじめに
  • APIの全体像
  • MongoDBの作成
    • Dockerfileの作成
    • Dockerビルド
  • Go-Serverの作成
    • APIの実装
      • CreateUserメソッドの実装
      • GetUserByNameメソッドの実装
      • go/api_user.goの全体
    • Dockerfileの作成
    • Dockerビルド
  • APIの確認
    • docker-compose.yamlの作成
    • コンテナの起動
    • curlによるAPIの確認

はじめに


SwaggerEditorはOpenAPI specを様々な言語に変換することができます。以下の記事では、SwaggerEditorを使って簡単なOpenAPI specを作成し、Go-Serverに変換するところまで紹介しました。

MacにSwaggerEditorをインストールしてOpenAPI specを実装する

しかし、あくまでもAPIの雛形であって、中身の処理が実装されているわけではありません。本記事では、上記の記事で作成したGo-Serverに処理を実装します。

APIの全体像


本記事で作成するAPIの全体像について紹介します。

APIの仕様は以下の通りです。DBにはMongoDBを利用し、apiとdbはそれぞれコンテナで起動します。

  • POST /v2/user/create・・・Context-TypeでJson形式のユーザー情報を受け付け、DBに保管します。
  • GET /v2/user/{username}・・・URLに指定したusernameとマッチするレコードをDBから取得し、表示します。

MongoDBの作成


Dockerfileの作成


MongoDBのDockerfileを作成します。

ディレクトリ構造は以下の通りです。

$ tree mongo/
mongo/
├── Dockerfile
├── init.sh
└── users.json

今回は揮発性のDBコンテナにします。コンテナ起動時に初期データ(users.json)を流し込むinit.shも併せて作成します。

1. 初期データの作成


コンテナ起動時に流し込む初期データをjson形式で作成します。

[
  { "username":	"koratta", "email": "koratta@example.com" },
  { "username":	"koratta2", "email": "koratta2@example.com" }
]

2. 初期実行ファイルの作成


1で作成したJsonファイルをMongoDBに保管するためのスクリプトinit.shを作成します。

mongoimport --authenticationDatabase admin --username root --password password --db api --collection users --drop --file /docker-entrypoint-initdb.d/users.json --jsonArray

3. Dockerfileの作成


init.shをコンテナ起動時に実行するmongoベースのDockerfileを作成します。コンテナ起動時に実行するため、/docker-entrypoint-initdb.dにコピーします。

FROM mongo:latest
COPY users.json /docker-entrypoint-initdb.d/
COPY init.sh /docker-entrypoint-initdb.d/

Dockerビルド


上記で作成したDockerfileをビルドし、コンテナイメージを作成します。

$ docker build -t api-mongo-db:latest mongo/

Go-Serverの作成


APIの実装


APIリクエストのルーティング情報は、go/routers.goに記述されています。

~~go/router.goより抜粋~~
var routes = Routes{ Route{ "Index", "GET", "/v2/", Index, }, Route{ "CreateUser", strings.ToUpper("Post"), "/v2/user/create", CreateUser, }, Route{ "GetUserByName", strings.ToUpper("Get"), "/v2/user/{username}", GetUserByName, }, }

/v2/user/createに対してPOSTされたらCreateUserメソッドを、/v2/user/{username}に対してGETされたらGetUserBynameメソッドを呼び出すようになっています。それぞれのメソッドはgo/api_user.goで定義されているので、それぞれを実装します。

CreateUserメソッドの実装


CreateUserメソッドを以下に示します。

func CreateUser(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json; charset=UTF-8")

	//ヘッダー情報の取得
	if r.Header.Get("Content-Type") != "application/json" {
		w.WriteHeader(http.StatusBadRequest)
		return
	}
	length, err := strconv.Atoi(r.Header.Get("Content-Length"))
	body := make([]byte, length)
	length, err = r.Body.Read(body)
	var insert_record User
	err = json.Unmarshal(body[:length], &insert_record)

	//DBへのInsert
	connect := getConnect()
	_, err = connect.Database("api").Collection("users").InsertOne(context.Background(), insert_record)
	if err != nil {
		w.WriteHeader(http.StatusBadRequest)
		return
	}

	w.WriteHeader(http.StatusOK)
}

9~11行目でPOSTされたJson形式のユーザー情報を取得しています。

	length, err := strconv.Atoi(r.Header.Get("Content-Length"))
	body := make([]byte, length)
	length, err = r.Body.Read(body)

12~13行目でUser構造体のinsert_recordに変換しています。

	var insert_record User
	err = json.Unmarshal(body[:length], &insert_record)

このUser構造体は、OpenAPI specから生成されていて、go/model_user.goに記述されています。

package swagger

type User struct {

	Username string `json:"username,omitempty"`

	Email string `json:"email,omitempty"`
}

16~17行目でMongoDBに保管しています。getConnectメソッドはMongoDBとの接続を確立するメソッドで、go/api_user.goの全体に載せています。

	connect := getConnect()
	_, err = connect.Database("api").Collection("users").InsertOne(context.Background(), insert_record)

GetUserByNameメソッドの実装


GetUserByNameメソッドを以下に示します。

func GetUserByName(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json; charset=UTF-8")

	//検索ユーザー情報の取得
	user := strings.Replace(r.URL.Path, "/v2/user/", "", 1)

	//user情報の取得
	var output User
	connect := getConnect()
	filter := bson.D{{"username", user }}
	err := connect.Database("api").Collection("users").FindOne(context.Background(), filter).Decode(&output)
	if err != nil {
		w.WriteHeader(http.StatusBadRequest)
		return
	}

	//Jsonの出力
	json.NewEncoder(w).Encode(output)

	w.WriteHeader(http.StatusOK)
}

5行目で検索条件となるユーザー名を取得しています。今回のAPIの仕様は、URLのパスに指定されたユーザー名の情報を表示するので、URLパスからユーザー名を取得しています。

user := strings.Replace(r.URL.Path, "/v2/user/", "", 1)

9~11行目で、URLに指定されたユーザー名を基にMongoDBから情報を取得しています。

	connect := getConnect()
	filter := bson.D{{"username", user }}
	err := connect.Database("api").Collection("users").FindOne(context.Background(), filter).Decode(&output)

go/user_api.goの全体


user_api.goの全体は以下の通りです。

/*
 * Koratta Test API
 *
 * This is sample api
 *
 * API version: 1.0.0
 * Contact: koratta@example.com
 * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git)
 */

package swagger

import (
	"net/http"
	"log"
	"time"
	"encoding/json"
	"context"
	"strconv"
	"strings"
	"go.mongodb.org/mongo-driver/mongo"
	"go.mongodb.org/mongo-driver/mongo/options"
	"go.mongodb.org/mongo-driver/bson"
)

func CreateUser(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json; charset=UTF-8")

	//ヘッダー情報の取得
	if r.Header.Get("Content-Type") != "application/json" {
		w.WriteHeader(http.StatusBadRequest)
		return
	}
	length, err := strconv.Atoi(r.Header.Get("Content-Length"))
	body := make([]byte, length)
	length, err = r.Body.Read(body)
	var insert_record User
	err = json.Unmarshal(body[:length], &insert_record)

	//DBへのInsert
	connect := getConnect()
	_, err = connect.Database("api").Collection("users").InsertOne(context.Background(), insert_record)
	if err != nil {
		w.WriteHeader(http.StatusBadRequest)
		return
	}

	w.WriteHeader(http.StatusOK)
}

func GetUserByName(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json; charset=UTF-8")

	//検索ユーザー情報の取得
	user := strings.Replace(r.URL.Path, "/v2/user/", "", 1)

	//user情報の取得
	var output User
	connect := getConnect()
	filter := bson.D{{"username", user }}
	err := connect.Database("api").Collection("users").FindOne(context.Background(), filter).Decode(&output)
	if err != nil {
		w.WriteHeader(http.StatusBadRequest)
		return
	}

	//Jsonの出力
	json.NewEncoder(w).Encode(output)

	w.WriteHeader(http.StatusOK)
}

func getConnect() *mongo.Client{
	//MongoDBの認証情報
	credential := options.Credential{
		Username: "root",
		Password: "password",
	}
	//MongoDBへの接続
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()
	connect, err := mongo.Connect(ctx, options.Client().ApplyURI("mongodb://db:27017").SetAuth(credential))
	if err != nil { log.Fatal(err) }

	return connect
}

Dockerfileの作成


Dockerfileを作成します。

ディレクトリ構造は以下の通りです。

$ tree api
api
├── Dockerfile
└── src
    ├── api
    │   └── swagger.yaml
    ├── go
    │   ├── README.md
    │   ├── api_user.go
    │   ├── logger.go
    │   ├── model_user.go
    │   └── routers.go
    └── main.go

src配下のファイルは、MacにSwaggerEditorをインストールしてOpenAPI specを実装するで作成したソースコードです。

Dockerfileは以下の通りです。

FROM golang:latest
# gouserの作成
RUN useradd -m -s /bin/bash gouser
# コンテナ作業ディレクトリの変更
WORKDIR /go/src/api
RUN chown gouser:gouser /go/src/api
# モジュールのダウンロード
RUN go get -u github.com/gorilla/mux &&\
    go get -u "go.mongodb.org/mongo-driver/mongo"
# ホストOSの ./src の中身を作業ディレクトリにコピー
COPY --chown=gouser:gouser ./src .
# go build
RUN go build -o koratta-api &&\
    chown gouser:gouser api
# gouserに変更
USER gouser
# API実行コマンドの実行
CMD ["/go/src/api/koratta-api"]

必要なモジュールのgorilla/muxとmongo-driver/mongoをインストールし、gouserに実行権限を与えてgouserで実行するようにしています。root権限でも当然実行できます。

注意点は、main.goのimportパッケージのsw "go"部分です。コンテナの場合デフォルトで$GOPATH=/go/srcなので、上記のDockerfileの場合sw "api/go"になります。

APIの確認


docker-compose.yamlの作成


MongoDBとAPIのコンテナを起動するためのdocker-compose.yamlを作成します。

version: '3'
services:
  db:
    container_name: db
    image: api-mongo-db:latest
    environment:
      MONGO_INITDB_ROOT_USERNAME: root
      MONGO_INITDB_ROOT_PASSWORD: password
    ports:
      - "27017:27017"
  api:
    container_name: api
    image: api:latest
    depends_on:
      - db
    links:
      - db
    ports:
      - "8080:8080"

コンテナの起動


docker-composeを使ってコンテナを起動します。

$ docker-compose up -d

curlによるAPIの確認


作成したAPIの挙動を確認します。

初期データとして保管されているレコードをAPI経由で確認すると、以下の通り、指定したユーザー名に該当する情報を取得できます。

$ curl -X GET "http://localhost:8080/v2/user/koratta"
{"username":"koratta","email":"koratta@example.com"}

保管されていないユーザーは、当然取得できません。

$ curl -X GET "http://localhost:8080/v2/user/koratta5
$

従って、APIを使ってユーザー情報を保管します。

$ curl -X POST "http://localhost:8080/v2/user/create" -H "accept: application/xml" -H "Content-Type: application/json" -d "{ \"username\": \"koratta5\", \"email\": \"koratta5@example.com\"}"

再度確認すると、ユーザー情報を確認することができます。/v2/user/createのAPI経由でDBに保管できていることが確認できました。

$ curl -X GET "http://localhost:8080/v2/user/koratta5"
{"username":"koratta5","email":"koratta5@example.com"}

以上です。