pyhaya’s diary

機械学習系の記事をメインで書きます

Raspberry Pi 4 に Ubuntu Desktop を入れる

Raspberry Pi 4 に Ubuntu Desktop を入れるときに苦労したので備忘録として書いておきます。

最近、Raspberry Pi に OSを入れるためのアプリケーションとして Raspberry Pi Imager が出てきて簡単に SD カードをフォーマットしたり OS をSD カードに入れられるようになりました。しかし、そんな中何故か Ubuntu が正常にインストールできないという状況に遭遇しました。ここでは発生したエラーとその対処法について書いていきます。

インストール手順

www.raspberrypi.org
上のリンクから Imager をダウンロードしてきて、OSを選択、Write をするだけです。手順はここのサイトにもありますが非常にシンプルでわかりやすいです。今回は Ubuntu Desktop 21.04, 64bit を選択しました

OSの書き込みは正常に終了したという通知が出て、microSDRaspberry Pi に入れてさあ起動!と意気込みましたがそうは行きません。画面の下の方に 「Ubuntu」という文字(ロゴ?)が数秒出たあと消えます。その後はスクリーンには No signal という表示。。。その後は何分待ってもシグナルは復活しません。

状況の確認

これだと何が起きたのか全くわからないので Ubuntu Server 20.04LTS を microSD に入れ直してメッセージを見てみます。さっきと同じ手順で「Ubuntu Server 20.04LTS, 64bit」を選択してインストールします。Raspberry Pi で起動してみると先ほどと同様についたと思ったらすぐに「No signal」になります。消える直前に一瞬エラーメッセージが表示されていたのでそれをスマホで録画して確認してみると、

[FAILED] Failed to start Command from Kernel Command Line.
See 'systemctl status kernel-command-line.service' for details.
[DEPEND] Dependency failed for Command from Kernel Command Line
(中略)
[OK] Reached target Power-Off.

systemctl status kernel-command-line.service で状況を確認しろって言うなら電源落とすな!と言いたくなりますが、このエラーでぐぐってみると、次のようなページが引っかかります。
bugs.launchpad.net

このタイムラインで#3の人がこのエラーについてコメントしています。どうやら Raspberry Pi 4 のバグのようですね。

どうやって解決するか

解決策について探してみると、下の方に

  • dd 使ってイメージを焼く
  • Raspberry Pi Imager の Advanced Option をリセットしてためす

という解決法が上げられていました。Advanced Option というのは OS を入れたときに SSH を有効にしておいたり、WiFiの情報を設定しておいたりできるRaspberry Pi Imager のオプションです。私はこれを設定しておいたのでもしかしてと思い、Raspberry Pi Imager を入れ直して試してみたところ正常に起動しました!

データベースの学習環境をDockerで作った話

データベースの勉強をするときに書いたクエリを実際に試すことはとても重要ですよね。しかし、私はこれまで書いたクエリを試すための環境構築が面倒で下のサイトを利用して SQL を実行していました。
www.db-fiddle.com

このサイトはとても便利なのですが、毎回 CREATE TABLE から始めなければならなくてこれはこれで面倒なのと、やはりローカルに入れて勉強した方が深いところまで学ぶことができるのではないかと感じたので試してみることにしました。

動作環境

動作確認は WSL 内で行っています。

  • Windows 10 Home
  • WSL2 (Ubuntu 18.04)
  • Docker 19.03.13
  • docker-compose 1.27.4

Docker image の取得

PostgreSQL は DockerHub に公式イメージが公開されているのでそれを使います。バージョンに特にこだわりはないのと、最近 PostgreSQL の大規模なバク修正があったと聞いたので latest を使います。

news.mynavi.jp

$ docker pull postgres:latest

docker-compose.yaml の作成

毎回コンテナを起動するときにいろいろ指定するのは面倒なので全部 docker-compose.yaml に書いておきます。

version: '3'

services:

  db:
    image: postgres
    container_name: db
    restart: always
    environment:
      POSTGRES_PASSWORD: your-password
    volumes:
      - ./data:/var/lib/postgresql/data
      - ./sql:/home

コンテナデータを永続化するためにホストのディレクトリ(./data)をコンテナ内でデータベース情報の入るディレクトリ(/var/lib/postgresql/data)にマウントします。また、SQLファイルを入れるディレクトリ(./sql)もマウントしています。

構築した環境を使う

準備はできたのでコンテナを起動します。

$ docker-compose up -d
$ docker-compose ps  # コンテナが無事起動しているか確認

Name              Command              State    Ports
-------------------------------------------------------
db     docker-entrypoint.sh postgres   Up      5432/tcp

コンテナが無事に起動出来たら学習用のデータベースを作りましょう。

$ docker exec -it db /bin/bash
root@92913b69db39:/# createdb -U postgres testdb
root@92913b69db39:/# psql -U postgres -d testdb
psql (13.1 (Debian 13.1-1.pgdg100+1))
Type "help" for help.

testdb=# \q
root@92913b69db39:/# exit
$ 

今回は単に学習用なのでユーザーは postgres を使ってしまいます。

次に、このデータベースにテーブルを作成する SQL を実行します。

create_table.sql

CREATE TABLE testtable (
    id INTEGER PRIMARY KEY,
    name VARCHAR(100) NOT NULL
);

テーブルを作成するには

$ docker exec -it db psql -U postgres -d testdb -f /home/create_table.sql

を実行します。CREATE TABLE という出力が出れば成功です。

最後に、SQL を実行するコマンドの効率化をしましょう。毎回実行するたびに上のコマンドを実行するのは面倒なのでシェルスクリプトを作ります。

runner.sh

#!/bin/bash                                                                                                                                                               
docker exec -it db psql -U postgres -d testdb -f /home/$1

chmod +x runnser.sh で実行権限を付与しておけば、

$ ./runner.sh create_table.sql

のように実行することができます。これで勉強がはかどりますね!


Docker/Kubernetes 実践コンテナ開発入門

Docker/Kubernetes 実践コンテナ開発入門

Python + Docker でデータ解析環境の管理

今回は実験データ自体の解析の話ではなく、どのような環境で解析を行ったのかという「環境」の管理について書きたいと思います。大学などで研究をしていて論文としてその成果をまとめる場合、関連するデータや解析プログラムは所属する大学もしくは研究室に整理して保存しておくことが一般的です。これはもちろん、論文に示されているデータがどのように解析されているのかあとから確認することができるようにしておくためです。

実験データの解析を例えば Python で行っている場合、解析の再現性を担保するためにはデータファイルのみならず解析を行っている環境(ライブラリのバージョンなど)も重要になってきます。そこで、この記事では Docker を使ってこの環境の管理をしたという話をします。

動作環境

  • Windows10 Home
  • WSL2 (Ubuntu 18.04)

管理したい環境

以下のような環境で実験データの解析を行うことを目指して環境を作ります

Docker image の作成

FROM jupyter/base-notebook:python-3.8.6

COPY ./requirements.txt .

RUN pip install -r requirements.txt

EXPOSE 8888

base image としては jupyter notebook のイメージが Docker Hub にあるのでこれを使います。タグは latest のものを使うと Docker Hub が更新されたときに中身が変わってしまうので python-3.8.6 を指定します。

解析に使うライブラリは requirements.txtバージョンを明記して書いておきます。

docker-compose.yaml の作成

実際にこのコンテナを運用するときには、ホストから Jupyter Notebook を編集するためにポートフォワーディングをしたり、解析データをマウントしたりします。これらはコンテナを起動するときに指定することは出来ますが、いちいち書くのは面倒なので docker-compose を使います。

version: "3"                                                                                                                                                                                               
services:                                                                                                                                                                                                  
  my-jupyter:                                                                                                                                                                                           
    build: .                                                                                                                                                                                               
    image: my-jupyter:1.0.0                                                                                                                                                                             
    ports:                                                                                                                                                                                                 
      - 8888:8888                                                                                                                                                                                          
    volumes:                                                                                                                                                                                               
      - .:/home/jovyan/                                                                                                                                                                                    
    command: jupyter lab 

コンテナの実行

コンテナを動かすためには、

docker-compose up

を実行します。もしも Docker image がビルドされていないときにはビルドから開始します。http://localhost:8888にアクセスしてJupyter Lab が起動すれば成功です。

GCP の Credential の読み込み

Google Cloud Platform では認証情報をJSON形式などでダウンロードすることで外部のアプリケーションから BigQuery などのサービスにアクセスすることができます。しかし、認証情報を読み込ませるのに苦労したので解決までのメモを残しておきます。

環境

何をやろうとしていたか

Go を用いて BigQuery にアクセスをしようと考えてました。そのためにGCPから認証情報が入ったJSONをダウンロードした

どこで詰まったのか

ドキュメントにあるようにJSONまでのパスを環境変数にセットしました。

export GOOGLE_APPLICATION_CREDENTIALS=/home/.../credential.json

さあ、これでアクセスできると考えプログラムを走らせたところ

Response: {
  "error": "invalid_grant",
  "error_description": "Token has been expired or revoked."
}

。。。いや、今発行したばかりなのに期限切れとか取り消されたとかなんだよ。試しに、.bashrcにこの環境変数をセットして試してみましたがダメでした。echo $GOOGLE_APPLICATION_CREDENTIALSで確認するとちゃんとセットした値が出てくるので環境変数には正しく設定されているんだよな。。。

どう解決したか

最終的に、これはプロジェクトのルートディレクトリに .env を作ってそこに

GOOGLE_APPLICATION_CREDENTIALS=/home/.../credential.json

とセットし、main()の中で.envファイルを読み込むとエラーがでなくなりました。なぜかはわかりません。


GCPの教科書

GCPの教科書

Django で Chart.js を使ってグラフを描く

DjangoPython を使うことで簡単に Web アプリケーションを作ることができることから非常に人気のあるフレームワークです。しかし、高度なアニメーションなどは Python だけでは表現することが難しく、JavaScript の力を借りることが多い印象を受けます。この記事では、グラフ描画でよく用いられる Chart.js を Django のプロジェクト内で使う方法について紹介したいと思います。特に、Python 側で処理したデータを JavaScript に渡して、Chart.js で描画するという部分について書きます。

開発環境

Django と Chart.js を結ぶ django-chartjs というライブラリがありますが、正直ここまでやらなくていいという感じを受けたので今回はこのライブラリ無しで実装を進めます。

準備

Django プロジェクトの準備はこれまでに何度か書いていますので、割愛します。詳しく知りたい方は以下の記事の「準備」までを参考にして作ってみてください。
pyhaya.hatenablog.com

Chart.js を使ってみる

ひとまず、Django の存在は最初にはできるだけ意識せず、HTML と JavaScript を使って適当なグラフを作ってみます。

{% load static %}
<html lang="ja">
    <header>
        <meta charset="utf-8">
        <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.2/Chart.bundle.js"></script>
        <script type="text/javascript" src="{% static 'visualize/js/draw.js' %}" defer></script>
    </header>
    <body>
        <div style="width: 50%; heigh: 50%;">
            <canvas id="graph"></canvas>
        </div>
    </body>
</html>

グラフだけを表示する HTML です。JavaScript のコードは

var ctx = document.getElementById("graph");

var myLineChart = new Chart(ctx, {
  type: "line",
  data: {
    labels: [
        "Jan.", "Feb.", "Mar.", "Apr.", "May", "Jun.", "Jul.", "Aug.",
        "Sep.", "Oct.", "Nov.", "Dec."
    ],
    datasets: [
      {
        data: [1, 2, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121],
        borderColor: "salmon",
        backgroundColor: "rgba(0,0,0,0)",
      },
    ],
  },
  options: {
    title: {
      display: false,
    },
    scales: {
      yAxes: [
        {
          ticks: {
            suggestedMax: 130,
            suggestedMin: 0,
            stepSize: 10,
            callback: function (value, index, values) {
              return value;
            },
          },
        },
      ],
    },
  },
});

x軸を labels に書いて、y軸の値を dataset に書いて、それ以外の軸の設定を options に書くというチュートリアルに載っていそうなコードです。

Django からデータを渡してグラフを描画する

では次に、Django からデータをもらってグラフを書くということをしてみます。これはユーザーがなにか操作をしてグラフを更新するような状況で、グラフにプロットされている値が不変でない状況です。

Django では HTML には context という形でデータを渡すことができます。そのため、JavaScript のコードを HTML 内の script タグの中に全部入れるという解決策がまず思い浮かびます。しかし、JavaScript のコードが長くなると、ソースコードがごちゃごちゃになってしまいます。そこで、データを JavaScript の変数に受け渡すところだけ script タグ内に入れるようにすればコードはスッキリします。

{% load static %}
<html lang="ja">
    <header>
        <meta charset="utf-8">
        <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.2/Chart.bundle.js"></script>
        <script type="text/javascript" src="{% static 'visualize/js/draw.js' %}" defer></script>
        <script type="text/javascript">var data = {{ data }}</script>
    </header>
    <body>
        <div style="width: 50%; heigh: 50%;">
            <canvas id="graph"></canvas>
        </div>
    </body>
</html>

var data = {{ data }}Django から JavaScript へデータの受け渡しが行われます。

var ctx = document.getElementById("graph");

var myLineChart = new Chart(ctx, {
  type: "line",
  data: {
    labels: [
        "Jan.", "Feb.", "Mar.", "Apr.", "May", "Jun.", "Jul.", "Aug.",
        "Sep.", "Oct.", "Nov.", "Dec."
    ],
    datasets: [
      {
        data: data,  // ここで data を使っている
        borderColor: "salmon",
        backgroundColor: "rgba(0,0,0,0)",
      },
    ],
  },
  options: {
    title: {
      display: false,
    },
    scales: {
      yAxes: [
        {
          ticks: {
            suggestedMax: 130,
            suggestedMin: 0,
            stepSize: 10,
            callback: function (value, index, values) {
              return value;
            },
          },
        },
      ],
    },
  },
});

肝心の Python コードは

views.py

from django.shortcuts import render
from django.views import View

# Create your views here.
class Index(View):
    def get(self, request):
        context = {"data": [i*i for i in range(12)]}
        return render(request, "visualize/index.html", context)

です。これで画面をリフレッシュすると先程のグラフと同様のグラフが表示されます。
f:id:pyhaya:20200930222012p:plain

GraphQL から GitHub API を叩く

最近、GitHub API を使ってリポジトリ情報を取ることが多いのですが、REST API より GraphQL を使ったほうが必要な情報だけを一気に取れるということを耳にし、試してみることにしました。

作業環境

Python の requests パッケージを使って GitHub API の graphql エンドポイントを叩きます。

準備

GraphQL を使う場合には Access Token が必須になるので発行しておきます。

docs.github.com

Query を作成

GraphQL はクエリの書き方が特殊なので REST API を普段使う人にとっては最初とっつきにくい感じを受けます。最初に指定できるのは Query という分類をされているもので、GitHub API のドキュメントに一覧が載っています。

developer.github.com

この中から欲しい情報を選ぶのが最初のステップです。ここでは user にしてみましょう。上のリンクの一番下に userの説明があります。変数として login のみを指定できるようですね。これは String 型です。試しに私の情報を取ってみます。

{
  user(login: "Hayashi-Yudai")
}

指定できました。これだけでは実行しても指定した情報が足りずエラーが出てしまいますので情報を追加していきます。userUser型を返すと書いてある(user の横に青文字で User と書いてある)のでリンクをクリックして User 型がどのような情報をもっているのか見てみます。

commitCommentsfollowers など色々あります。issuesというのがあるのでこれを取ってみましょう。引数として firstが取れるのでこれを10にして最初の10個を取ってみます。

{
  user(login: "Hayashi-Yudai") {
    issues(first: 10)
  }
}

issues の中には edgesnodesなどが入っています。こんな調子でどんどん深くまでたどっていくと、

{
  user(login: "Hayashi-Yudai") {
    issues(first: 10) {
      nodes {
        body
      }
    }
  }
}

というクエリを書くことができます。一番深いところにある body は型が String なのでこれ以上深いところには何もありません。

Query を実行する

では最後に上で作成したクエリを Python で実行してみます。

import requests
import os

headers = {"Authorization": f"Bearer {os.environ.get('GITHUB_TOKEN')}"}


def run(query): 
    request = requests.post(
        'https://api.github.com/graphql', 
        json={'query': query}, 
        headers=headers
    )
    if request.status_code == 200:
        return request.json()
    else:
        raise Exception(f"Query failed to run by returning code of {request.status_code}. {query}")

       
query = """
{
  user(login: "Hayashi-Yudai") {
    issues(first: 10) {
      nodes {
        body
      }
    }
  }
}
"""

result = run(query)

GitHub Access Token は私の場合には環境変数に指定しているので os.environ.getで取ってきてヘッダに入れています。result の中身を見てみると

{'data': {
  'user': {
    'issues': {
      'nodes': [
        {'body': 'When I run some codes, text garbling happened. I tried hello world program by\r\n\r\n* Python3\r\n* Java\r\n* C\r\n* C++ \r\n\r\nbut, only in C and C++, text garbling happened.'},
        {'body': 'In HTML, the width of `th` component is assigned like "5%", but it does not reflected in the page.'},
        {'body': 'add regularization and batch-normalization to transposed convolution layer'},
        {'body': 'add the pooling layer'},
        {'body': 'create UNet by components I prepared.'},
        {'body': 'To training UNet model, I should change input images to one-hot representation'},
        {'body': 'create training session'},
        {'body': 'To evaluate the model, I should set metrics such that accuracy or loss'},
        {'body': 'show training result as images'},
        {'body': 'To split training and validation of the model, I should give the is_training parameter when running the session.'}
      ]
    }
  }
}}

これを見てみると user -> issues -> nodes -> body という構造になっていてクエリで書いた構造と同じ構造でレスポンスが返ってきていることが分かります。

まとめ

GraphQL を使うと自分のほしい要素だけを取り出せるのでレスポンスがREST API よりスッキリしますね。さらに、上の例ですと、Issue の一覧からそれぞれの body を一回のクエリで取れていますが、REST API ですと users/Hayashi-Yudai/eventsで イベントの一覧を取ってからその中の type: "IssueEvent" のものを取ってそれぞれに対して対応する URL にリクエスト投げて。。。と非常にリクエスト回数が多くなります。アプリケーションの中で使うときにはレスポンスの改善に非常に効果がありそうです。

Django でログインページを作る

PythonフルスタックWebフレームワークとして有名な Django を使ってログインページを簡単に作ってみます。

開発環境

準備

雛形の生成

Django のプロジェクトをセットアップします。

$ mkdir myapp
$ cd myapp
$ django-admin startproject config .
$ python manage.py startapp myauth
$ python manage.py startapp appmain
$ python manage.py makemigrations
$ python manage.py migrate
$ python manage.py createsuperuser  # ユーザー名とパスワードを設定する

これらを実行することで以下のようなディレクトリ構成が得られます。

$ tree -L 2
.
├── appmain    # 内部ページ
│   ├── __init__.py
│   ├── admin.py
│   ├── apps.py
│   ├── migrations
│   ├── models.py
│   ├── tests.py
│   ├── views.py
├── config    # プロジェクト全体の設定
│   ├── __init__.py
│   ├── asgi.py
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
├── db.sqlite3
├── manage.py
└── myauth    # 今回ログインページをつくるところ
    ├── __init__.py
    ├── admin.py
    ├── apps.py
    ├── migrations
    ├── models.py
    ├── tests.py
    └── views.py
基本的な設定

config/settings.py

# ...
LOGIN_REDIRECT_URL = "/"  # ログイン後にどこに飛ばすか
LOGIN_URL = "/myauth"  # ログインページの URLを設定

# ...

INSTALLED_APPS = [
    "myauth.apps.MyauthConfig",  # <- 追加
    "appmain.apps.AppmainConfig",  # <- 追加
    "django.contrib.admin",
    "django.contrib.auth",
    "django.contrib.contenttypes",
    "django.contrib.sessions",
    "django.contrib.messages",
    "django.contrib.staticfiles",
]

# ...

config/urls.py

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path("admin/", admin.site.urls),
    path("", include("appmain.urls")),
    path("myauth/", include("myauth.urls")),
]

appmain/urls.py

from django.urls import path
from appmain import views

urlpatterns = [path("", views.index, name="index")]

myauth/urls.py

from django.urls import path
from myauth import views

app_name = "myauth"
urlpatterns = [path("", views.Login.as_view(), name="login")]
内部ページの実装

ログインしたあとに飛ぶページを作ります。これは今回の主題ではないので簡単に作ります。上の appmain/urls.py に設定したように、ルートページとして views.index を表示するようにしているので、これを作ります。

appmain/views.py

from django.shortcuts import render
from django.contrib.auth.decorators import login_required


@login_required
def index(request):
    return render(request, "appmain/index.html")

呼ばれたら index.html を表示するだけです。シンプルですね。

appmain/templates/appmain/index.html

<html>
  <head></head>
  <body>This is inner page</body>
</html>

Django では HTML ファイルは慣習的に (app名)/templates/(app名)/ ディレクトリに入れるということになっていますので少しパス名が長くなります。

ログインページの実装

まずはログイン情報を入力するフォームを作ります。

myauth/forms.py

from django.contrib.auth.forms import AuthenticationForm


class LoginForm(AuthenticationForm):
    def __init__(self, *args, **kwargs):
        super(LoginForm, self).__init__(*args, **kwargs)

Django には認証用のフォームを作るためのクラスが用意されているのでそれを継承すれば良いです。このフォームをビューに組み込みます。

myauth/views.py

from django.contrib.auth.views import LoginView

from myauth.forms import LoginForm


class Login(LoginView):
    form_class = LoginForm
    template_name = "myauth/index.html"

これでログインページの実装は9割方終わりです。あとは HTML ファイルを作ります。

myauth/templates/myauth/index.html

<html>
  <head>
    <title>Login Page</title>
  </head>
  <body>
    <h2>Login</h2>
    <form method="post" action="{% url 'myauth:login' %}">
      {% csrf_token %}
      {{ form.username }}
      {{ form.password }}
      {% if form.errors %}
      <p>ユーザー名またはパスワードが一致しません</p>
      {% endif %}
      <input type="submit" value="login"/>
    </form>
  </body>
</html>

ビューの中でフォームを取り込んでいるので、HTML内では LoginFormの持つ変数を使うことができます。この記事では明示的にフォーム内で変数を定義することはしていませんが、AuthenticationFormusernamepasswordを定義しているのでそれを HTML 内で {{ form.username }}のようにして使うことができます。

github.com

フォームのアクションとして myauth:loginを指定しています。これは「myauth」という名前空間内の「login」というビューにフォームの内容を送信するということを指定しています。「myauth」の urls.py 内を見直してみると

path("", views.Login.as_view(), name="login")

という記述があります。myauth:loginlogin は、この name="login" を指しています。

実行してみる

では、今まで実装してきたコードを試してみます。

$ python manage.py runserver

として http://localhost:8000/myauth にアクセスしてみます。

f:id:pyhaya:20200922202208p:plain

CSS は何も書いていないので非常にシンプルですがきちんとログインページが出来ています。一番最初に管理者としてユーザーを1人登録していますのでそのユーザーのユーザー名とパスワードを入力すれば内部ページに飛べます。間違っていれば ユーザー名またはパスワードが一致しません という文字が表示されるはずです。