スマレジエンジニアyushiのブログ

スマレジエンジニアのブログ

【GraphQL入門 #2】チュートリアル

前回より、GraphQLに入門しています。

yushi-dev.hatenablog.com

今回は、下記のApollo Serverの公式チュートリアルに取り組みます。

Get started with Apollo Server - Apollo GraphQL Docs

型の定義

APIで扱う型を定義します。

  type Book {
    title: String
    author: String
  }

  type Query {
    books: [Book]
  }

type Bookは書籍のタイトル(title)と著者(author)を持つ型です。
REST APIにおけるリソースであり、多くはDBのテーブルで管理されるようなものです。

type Queryは、APIでのリクエスト方法を表します。
REST APIにおいては /booksというエンドポイントを定義しているイメージです。

JS上は、下記のように定義します。

const typeDefs = gql`
  type Book {
    title: String
    author: String
  }

  type Query {
    books: [Book]
  }
`;

型の詳しい説明は、下記のページで参照できます。

GraphQL schema basics - Apollo GraphQL Docs

Resolversの定義

次は、APIへのリクエストを処理するロジックを定義します。

GraphQLではこれをResolversと言います。

const books = [
  {
    title: 'The Awakening',
    author: 'Kate Chopin',
  },
  {
    title: 'City of Glass',
    author: 'Paul Author',
  },
];

const resolvers = {
  Query: {
    books: () => books,
  },
};

const booksは、ただのデータ管理用の配列です。本来はDBなどで管理するものですが、今回は簡易にメモリ管理することにします。

const resolversがResolversの本体です。

「型定義」で用意したtype Queryと構造が対応しています。

Resolversの詳しい情報は、下記より参照できます。

Resolvers - Apollo GraphQL Docs

サーバの初期化・起動

先に用意した型とResolversを利用して、GraphQLサーバを初期化します。

const server = new ApolloServer({
  typeDefs,
  resolvers,
});

最後に、サーバを起動します。

server.listen().then(({ url }) => {
  console.log(`🚀 Server ready at ${url}`);
});

動作確認

実際に動かしてみます。

まずはサーバを起動します。

node index.js

curlでリクエストしてみます。

# リクエスト
curl --request POST \
  --header 'content-type: application/json' \
  --url http://localhost:4000/ \
  --data '{"query":"query { books { title }}"}' | jq

# レスポンス
{
  "data": {
    "books": [
      {
        "title": "The Awakening"
      },
      {
        "title": "City of Glass"
      }
    ]
  }
}

事前に定義した const booksの内容が返却されています。

開発者ツール

次に、開発者ツールにアクセスしてみます。

ブラウザで http://localhost:4000 を開きます。

「Query your server」のボタンをクリックします。

OperationというConsoleで下記のようにクエリを記述します。

query GetBooks {
  books {
    title
    author
  }
}

GetBooks という実行ボタンが表示されるので、これをクリックすると、Responseが表示されます。

Pull Request

https://github.com/nek0meshi/graphql-learn/pull/2

あとがき

GraphQLの概要がわかりました。

【GraphQL入門 #1】GraphQLに入門する

今回より数回に分けて、GraphQLに入門してみようと思います。

GraphQLとは、APIのための言語・またランタイムです。

graphql.org

今特に注目されている技術の1つであり、導入事例も多々聞きますし、かのBackend Developer Roadmapでも紹介されています。

Backend Developer Roadmap: Learn to become a modern backend developer

Apollo

GraphQLを実装するライブラリは、多数あります。

GraphQL Code Libraries, Tools and Services

その中でも、注目されている1つであり、Backend Developer Roadmapでも紹介されているものに、node.jsのライブラリであるApolloがあります。

www.apollographql.com

今回は、これを活用して入門しようと思います。

環境構築

npm環境を用意します。

npm init -y

.gitignoreにて、npmで導入したモジュールをgit管理から除外します。

(.gitignore)
node_modules

editorconfigを利用して、大まかな記述ルールを制御できるようにします。

(.editorconfig)
root = true

[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

[Makefile]
indent_style = tab

[*.md]
trim_trailing_whitespace = false

editorconfig.org

利用するnode.jsは、nodenvを利用して簡単にバージョン変更できるようにします。

# install
nodenv install [最新のLTSのnodeバージョン]

# 当環境で利用するバージョンを決める
nodenv local [最新のLTSのnodeバージョン]

# -> .node-versionというファイルが作成され、指定したバージョンが記録される。

Pull Request

https://github.com/nek0meshi/graphql-learn/pull/1

あとがき

次回より、実際に動かしながら勉強していきます。

TODOリストを作る(まとめ)

Go言語やVue3の勉強を兼ねて、TODOリストを実装中です。

本記事はそのまとめです。

過去の記事はこちらをご確認ください。

TODOリスト カテゴリーの記事一覧 - スマレジエンジニアyushiのブログ

技術スタック

バックエンド

  • Go言語
  • gin

フロントエンド

  • Vue.js3
  • Vite
  • SCSS
  • Bulma

その他

  • Docker

開発状況(機能)

タスクの表示・登録・編集・完了できるようになりました。

一覧画面

編集モーダル

開発状況(技術)

Dockerで開発環境を構築

Vue.js3・vite・SCSS・Bulmaでフロントエンドを制作

Go言語でバックエンドを制作

Go言語のバージョンを1.18に更新

HTTPフレームワークとして、ginを導入

Rest APIを作成(タスクの一覧・追加・編集・完了)

本番デプロイ用のDocker環境を構築(multi stage buildなど)

GitHub

github.com

あとがき

ブログ記事が増えてきたので、制作状況のまとめ記事を作ってみました!
本ページは随時更新できたらと思っています。

【TODOリスト 第10回】Golangのバージョン更新

久しぶりにTODOリストを作っていきます。

yushi-dev.hatenablog.com

今回は、Golangのバージョンを更新します。

Golangのマイナーバージョン固定

久しぶりにプロジェクトをビルドしようとしたら、エラーが出てしまいました。

docker compose build --pull

=> ERROR [3/3] RUN go get -u github.com/go-sql-driver/mysql   gith  0.3s
------
 > [3/3] RUN go get -u github.com/go-sql-driver/mysql   github.com/gin-gonic/gin   github.com/gin-contrib/cors:
#6 0.332 go: go.mod file not found in current directory or any parent directory.
#6 0.332        'go get' is no longer supported outside a module.
#6 0.332        To build and install a command, use 'go install' with a version,
#6 0.332        like 'go install example.com/cmd@latest'
#6 0.332        For more information, see https://golang.org/doc/go-get-install-deprecation
#6 0.332        or run 'go help get' or 'go help install'.
------
failed to solve: rpc error: code = Unknown desc = executor failed running [/bin/sh -c go get -u github.com/go-sql-driver/mysql   github.com/gin-gonic/gin   github.com/gin-contrib/cors]: exit code: 1

golangの1.18より、go getが外部モジュール導入に利用できなくなったことが原因のようです。

そもそも、golangではマイナーバージョン更新で仕様が変わっていっているため、マイナーバージョンを固定してあげる必要があるようです。

- FROM golang:1-alpine
+ FROM golang:1.16-alpine

これで無事ビルドが通るようになります。

Golangのバージョンを最新化

改めて、バージョンを最新の1.18に更新してみます。

- FROM golang:1.16-alpine
+ FROM golang:1.18-alpine

もちろん、前項のエラーが発生してしましますので、修正します。

- FROM golang:1.16-alpine

- RUN apk add --no-cache git

- RUN go get -u github.com/go-sql-driver/mysql \
-   github.com/gin-gonic/gin \
-   github.com/gin-contrib/cors
+ FROM golang:1.18-alpine

Dockerfileで外部モジュールを導入しようとしていますが、これが間違いでした。
go.modの記述によって、golangのビルド時に自動でモジュール導入してくれるようです。

これを削除することで、無事エラーを解決できました。

go.mod, go.sumの更新

最後に、go.mod, go.sumのバージョン更新を行います。

go mod edit -go=1.18
go mod tidy

Pull Request

https://github.com/nek0meshi/todo-list/pull/17

まとめ

久しぶりにGolangを触ってみました。
go.mod等の仕様を全然把握できておらず、少しずつ勉強していきたいです。

【プロフィールページ改修 第9回】GitHub Actionsの導入

久しぶりに、プロフィールページを作成していきます。

yushi-dev.hatenablog.com

技術スタックは下記の通りです。

  • nuxt.js
  • SCSS
  • Tailwind CSS

今回はGitHub Actionsの導入です。

GitHub Actionsとは

GitHub Actionsとは、GitHubに搭載されているCIの仕組みです。
標準搭載のため導入も非常に簡単です。

なお私自身は、業務ではGitlabを利用していることもあり、Gitlab CIは使っていても、GitHub Actionsの経験はほとんどなく、今回導入しながら勉強してみました。

Linterの導入

今回は、JS・CSSのLinterを導入します。

  • JS
    • Linter ... ESLint
    • Formatter ... Prettier
  • CSS
    • Linter ... StyleLint
    • Formatter ... Prettier

Linterについては、現状有力な代替案を知りません(教えて欲しいです)。

Prettierについては、良くも悪くも強制力が強くて運用の幅が少ないため、評価が分かれるんじゃないかと思っています。
とは言え無難な選択肢なのかなと考えています。

GitHub Actionsの導入

.github/workflows/ci.yml というファイルを作成・編集することで、導入できます。

name: Node.js CI

on:
  pull_request:
    branches: [ master ]

jobs:
  lint:
    runs-on: ubuntu-latest
    defaults:
      run:
        working-directory: 'v2'
    steps:
    - uses: actions/checkout@v2
    - name: Set up Node.js
      uses: actions/setup-node@v2
      with:
        node-version-file: 'v2/.node-version'
        cache: 'yarn'
        cache-dependency-path: '**/yarn.lock'
    - run: yarn
    - run: yarn lint

nameでは、単にCI名を設定しています。

on: pull_request: branches では、CIの実行対象のbranch(今回はmaster)を指定しています。 masterブランチにcommitが追加された時に、CIが実行されます。 今回はPull Request運用なので、PRのMerge時のみの想定です。

jobs: lint にて、Linterを実行するようなCIを導入します。

jobs: lint: runs-on にて、実行環境を指定します。今回は、ubuntu-latest(Ubuntuの最新バージョン)を指定しました。 jobs: lint: stepsにて、CIの1つ1つの手順を記述していきます。

uses: actions/checkout@v2 では、ソースコードをcheckoutします。

https://github.com/actions/checkout

uses: actions/setup-node@v2 では、node.js環境を導入しています。
with: にてactions/setup-node@v2に渡す変数を設定します。

最後のrun: yarn, run: yarn lintにて、yarnのパッケージ導入とlintの実行を行います。

実行結果

無事に実行され、Lintも成功しました。

https://github.com/nek0meshi/profile/runs/5069475818?check_suite_focus=true

Pull Request

まとめ

GitHubについてはまだ使い慣れていないので、少しずつ触ってみたいと思っています。

【機械学習入門 #6】Perceptronの性能検証

機械学習に入門しています。

yushi-dev.hatenablog.com

今回は、前回までに実装したPerceptronの性能を確認していきます。

性能検証

Perceptronの性能を確認するために、どの値に対してPerceptronがIris-setosaかIris-versicolorのどちらとして判定するかを確かめていきます。

まず最初に、作成したグラフを見てみます。

この赤い丸と青いバツは、下記の記事で表示したのと同じものです。

yushi-dev.hatenablog.com

上記の記事と同様に、図の意味は下記の通りです。

  • x軸: がくの長さ
  • y軸: 花弁の長さ
  • 赤点: Iris-setosa
  • バツ: Iris-versicolor

x軸とy軸の範囲は、それぞれトレーニングデータの最小値-1、最大値+1です。

今回新たに表示されているのは、赤と青色の背景色です。

これは、図内の一点一点について、それがIris-setosaかIris-versicolorのどちらに判定されるかを示しています。
レーニングデータはもちろんそれに近い値についても、正しく判定されていそうなことがわかります。
十分な性能のPerceptronができていそうですね。

ソースコードは以下です。

"""
グラフ表示関数
"""

from matplotlib.colors import ListedColormap

def plot_decision_regions(X, y, classifier, resolution=0.02):
    markers = ('o', 'x')
    colors = ('red', 'blue')
    cmap = ListedColormap(colors[:len(np.unique(y))])
    
    print(cmap)
    
    x1_min, x1_max = X[:, 0].min() - 1, X[:, 0].max() + 1
    x2_min, x2_max = X[:, 1].min() - 1, X[:, 1].max() + 1
    
    xx1, xx2 = np.meshgrid(
        np.arange(x1_min, x1_max, resolution),
        np.arange(x2_min, x2_max, resolution)
    )
    Z = classifier.predict(
        np.array([
            xx1.ravel(),
            xx2.ravel(),
        ]).T
    )
    
    Z = Z.reshape(xx1.shape)
    plt.contourf(xx1, xx2, Z, alpha=0.4, cmap=cmap)
    
    plt.xlim(xx1.min(), xx1.max())
    plt.ylim(xx2.min(), xx2.max())
    
    for idx, cl in enumerate(np.unique(y)):
        plt.scatter(
            x=X[y == cl, 0],
            y=X[y == cl, 1],
            alpha=0.8,c=cmap(idx),
            marker=markers[idx],
            label=cl
        )
"""
処理結果のグラフ表示
赤背景のデータだとsetosa、青背景のデータだとversicolorとして分類される
"""

plot_decision_regions(X, y, classifier=ppn)
plt.xlabel('sepal length [cm]')
plt.ylabel('petap length [cm]')

plt.legend(loc='upper left')

plt.show()

ソースコード

これまでのソースコードです。

https://github.com/nek0meshi/data-science-learn/blob/master/PerceptronLearn.ipynb

まとめ

数回に渡り、Perceptronの実装や解説をしてみました。

短いコードで機械学習が実現できてしまうのは驚きでしたし、面白かったです。

【機械学習入門 #5】Perceptron実装後半 学習ロジック

機械学習に入門しています。

yushi-dev.hatenablog.com

今回はPerceptronの実装後半です。

学習ロジックの実装

まずはコンストラクタの実装です。

class Perceptron(object):
    ...
    # コンストラクタ
    def __init__(self, eta=0.01, n_iter=10):
        # 学習率 0.0〜1.0
        self.eta = eta
        
        # トレーニング回数
        self.n_iter = n_iter

学習率とトレーニング回数は、詳しくは理解できていませんが、大きすぎると過学習になり小さすぎるとトレーニングが足りず、
どちらにしてもうまくいきません。

過学習とは、利用したトレーニングデータに特化してしまうような現象です。 トレーニングデータに対しては、高精度で判定できるようになります。 一方で、本来の目的であるそれ以外のデータに対してはうまく判定できなくなってしまいます。

続いて、学習ロジックの本体です。

class Perceptron(object):
    ...
    # トレーニング
    def fit(self, X, y):
        # X: トレーニング変数 今回は、2 x N
        # y: 目的変数

        # 重み(トレーニング結果)
        self.w_ = np.zeros(1 + X.shape[1])
        
        # トレーニングN回目のエラー回数(誤分類回数)
        self.errors_ = []
        
        for _ in range(self.n_iter):
            errors = 0
            for xi, target in zip(X, y):
                # 予測値が正解していたら0, 外していたら符号 * 2 * 学習率
                update = self.eta * (target - self.predict(xi))
                
                # 重みの更新
                self.w_[1:] += update * xi
                self.w_[0] += update
                
                errors += int(update != 0.0)
            self.errors_.append(errors)

重みは前記事で紹介した通りです。

具体的なロジック内容としては、まずself.n_iter回ループを回します。
ループの各回では、更に一つ一つのレコード(花ひとつ分の特徴量)について、ループを回します。
このループ内では、特徴量1つ1つを元にどちらの種類の花かを判定します。
この判定が正しかった場合は、何も行わず次のループに進みます。 この判定が間違っていた場合には、重みを補正し、エラー回数を加算します。
この重みの補正が繰り返されることで、段々と正しい判定ができるように改善されていく(学習)、という仕組みです。
この重みの補正は、self.etaの数値が大きいほど程度が大きくなります。

学習の実行

学習を実行します。この際の、エラー回数をグラフ表示します。

ppn = Perceptron(eta=0.1, n_iter=10)
ppn.fit(X, y)
plt.plot(range(1, len(ppn.errors_) + 1), ppn.errors_, marker='o')

このグラフでは、数値が大きいほど花の種類の判定に失敗していることがわかります。

見てわかる通り、6回目以降では判定間違いは0回になっています。
そもそも1回目時点でも2回しか判定間違いしていませんが、 それだけデータの傾向がはっきりしているということなのかなと考えています。 3回目で一度判定間違いが増えているのが興味深い所ですね。

まとめ

Perceptronの実装が完了しました。

次回は、完成したPercptronを使って、データの判定をしてみます。