Cloud Functions + PubSub のローカル開発環境をdocker-composeで構築

Cloud Function でhttpリクエストを受け取りPubSubを経由して別のFunctionを起動する、という機能を開発した際のローカル環境構築メモ。

Function用のイメージのビルド

Gemfile

source "https://rubygems.org"

gem "functions_framework", "~> 0.7"
gem "google-cloud-pubsub"

docker/function/Dockerfile

# Build: docker build -t function -f docker/function/Dockerfile .
FROM ruby:3.2.2
WORKDIR /function

COPY Gemfile /function/Gemfile
RUN bundle config --local without "development test" \
    && bundle install

ソースコードをvolumeで取り込んで bundle exec functions-framework-ruby --target xxxx でターゲットを切り替えることで同じイメージで http 側とサブスクライバ側のコンテナを起動する想定。

http側のFunction

protobuf形式のメッセージを送信したいので定義。

message.proto

syntax = "proto3";
option ruby_package = "Message::";

message Request {
  int64 id = 1;
  string body = 2;
}

下記コマンドでrbファイルを生成

mkdir lib
protoc --ruby_out=./lib ./message.proto

http.rb

require "functions_framework"
require "google/cloud/pubsub"
require_relative "lib/message_pb"

TOPIC = ENV["TOPIC_ID"]

FunctionsFramework.http "hello" do |request|
  FunctionsFramework.logger.info("hello http")
  pubsub = ENV["PUBSUB_EMULATOR_HOST"].nil? ? Google::Cloud::Pubsub.new : Google::Cloud::Pubsub.new(project_id: "emulator")
  topic = pubsub.topic(TOPIC)
  message = Message::Message.new(id: 123, body: "hello!")
  message = Message::Message.encode(message)
  topic.publish(message)

  { "message" => "OK" }
end

イベント側のファンクション

event.rb

require "base64"
require "functions_framework"
require_relative "lib/message_pb"

FunctionsFramework.cloud_event "hello-event" do |event|
  FunctionsFramework.logger.info("hello event")

  message = Message::Message.decode(Base64.decode64(event.data["message"]["data"]))
  FunctionsFramework.logger.info("id: #{message.id}, body: #{message.body}")
end

PubSubエミュレータ

zenn.dev

この記事が良さそうなので参考にさせてもらう。

docker/pubsub/Dockerfile

# Build: docker build -t pubsub -f docker/pubsub/Dockerfile .
FROM gcr.io/google.com/cloudsdktool/cloud-sdk:367.0.0-emulators

RUN apt-get update && \
    apt-get install -y git python3-pip netcat && \
    git clone https://github.com/googleapis/python-pubsub.git

WORKDIR /python-pubsub/samples/snippets
RUN pip3 install virtualenv && \
    virtualenv env && \
    . env/bin/activate && \
    pip3 install -r requirements.txt

COPY ./docker/pubsub/entrypoint.sh ./
EXPOSE 8085
ENTRYPOINT ["./entrypoint.sh"]

docker/pubsub/entrypoint.sh

#!/bin/bash

set -em

export PUBSUB_PROJECT_ID=$PROJECT_ID
export PUBSUB_EMULATOR_HOST=0.0.0.0:8085

gcloud beta emulators pubsub start --project=$PUBSUB_PROJECT_ID --host-port=$PUBSUB_EMULATOR_HOST --quiet &

while ! nc -z localhost 8085; do
  sleep 0.1
done

. env/bin/activate
python3 publisher.py $PUBSUB_PROJECT_ID create $TOPIC_ID
python3 subscriber.py $PUBSUB_PROJECT_ID create-push $TOPIC_ID $SUBSCRIPTION_ID $PUSH_ENDPOINT

fg %1
chmod +x docker/pubsub/entrypoint.sh 

を忘れずに

docker-compose でまとめる

docker-compose.yaml

version: '3.8'
services:
  pubsub:
    image: pubsub:latest
    restart: always
    environment:
      - PROJECT_ID=emulator
      - TOPIC_ID=event-topic
      - SUBSCRIPTION_ID=event-subscription
      - PUSH_ENDPOINT=http://event:8080
  http:
    image: function:latest
    environment:
      - TOPIC_ID=event-topic
      - PUBSUB_EMULATOR_HOST=pubsub:8085
      - PUBSUB_PROJECT_ID=emulator
    ports:
      - 8080:8080
    volumes:
      - .:/function
    entrypoint: ["bundle", "exec", "functions-framework-ruby", "--source=http.rb","--target=hello"]
  event:
    image: function:latest
    volumes:
      - .:/function
    entrypoint: ["bundle", "exec", "functions-framework-ruby", "--source=event.rb","--target=hello-event"]

docker-compose up で起動

これで、http://localhost:8080/ にアクセスすると http側のfunctionが呼ばれ、pubsubを経由してイベント側のfunctionが呼ばれることがログから確認できる

nuxt.js + BalmUI

nuxt.js のプロジェクトを作成

$ npx nuxi init nuxt-balm-ui

balm-ui をインストール

$ cd nuxt-balm-ui
$ npm install
$ npm install --save balm-ui

cssファイルの読み込みのため nuxt.config.ts を修正

// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
  devtools: { enabled: true },
  css: [
    'balm-ui/dist/balm-ui.css',
  ]
})

下記の内容で plugins/balm-ui.js を作成

import BalmUI from "balm-ui";
import { defineNuxtPlugin } from "nuxt/app";

export default defineNuxtPlugin((nuxtApp) => {
  nuxtApp.vueApp.use(BalmUI);
});

TypeScript + Vue.js で Chrome拡張機能を開発する

前回とほぼ同じ話です。 Chrome拡張機能の開発ではアイコンクリック時に表示されるポップアップと、開発者ツールに追加するタブでHTMLページを利用します。 このHTMLページの開発でVue.jsを利用したい、というのがモチベーションです。

Vue.jsでの開発ではよくvue-cliが利用されると思いますが、何をしているかよくわからない感じがあまり好きじゃないのでvue-cliは利用せずに行きたいと思います。

参考 :

github.com

まずはnpmのプロジェクトを作成します

$ cd my-extension
$ npm init
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.
See `npm help init` for definitive documentation on these fields
and exactly what they do.
Use `npm install <pkg>` afterwards to install a package and
save it as a dependency in the package.json file.
Press ^C at any time to quit.
package name: (chrome-extension-vue-typescript)
version: (1.0.0)
description:
entry point: (index.js)
test command:
git repository: (https://github.com/TakahashiShuuhei/chrome-extension-vue-typescript.                                         git)
keywords:
author:
license: (ISC)
About to write to C:\Users\shuhei.takahashi\dev\chrome-extension-vue-typescript\packa                                         ge.json:
{
  "name": "chrome-extension-vue-typescript",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/TakahashiShuuhei/chrome-extension-vue-typescript.g                                         it"
  },
  "author": "",
  "license": "ISC",
  "bugs": {
    "url": "https://github.com/TakahashiShuuhei/chrome-extension-vue-typescript/issue                                         s"
  },
  "homepage": "https://github.com/TakahashiShuuhei/chrome-extension-vue-typescript#re                                         adme"
}
Is this OK? (yes)

とりあえず全部デフォルトでやってます。

次に、必要なライブラリをインストールしていきます。

$ npm install --save-dev @babel/core @babel/preset-env babel-loader clean-webpack-plugin copy-webpack-plugin css-loader html-webpack-plugin postcss-loader vue vue-loader vue-style-loader vue-template-compiler webpack webpack-cli

この時点でpackage.jsonは下記のようになりました。(ビルド用に scripts に build を追加しています)

{
  "name": "chrome-extension-vue-typescript",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack --mode production"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/TakahashiShuuhei/chrome-extension-vue-typescript.git"
  },
  "author": "",
  "license": "ISC",
  "bugs": {
    "url": "https://github.com/TakahashiShuuhei/chrome-extension-vue-typescript/issues"
  },
  "homepage": "https://github.com/TakahashiShuuhei/chrome-extension-vue-typescript#readme",
  "devDependencies": {
    "@babel/core": "^7.12.10",
    "@babel/preset-env": "^7.12.11",
    "babel-loader": "^8.2.2",
    "clean-webpack-plugin": "^3.0.0",
    "copy-webpack-plugin": "^7.0.0",
    "css-loader": "^5.0.1",
    "postcss-loader": "^4.1.0",
    "vue": "^2.6.12",
    "vue-loader": "^15.9.6",
    "vue-style-loader": "^4.1.2",
    "vue-template-compiler": "^2.6.12",
    "webpack": "^5.11.1",
    "webpack-cli": "^4.3.1"
  }
}

次にwebpack.config.jsを追加します。

const path = require('path');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const VueLoaderPlugin = require('vue-loader/lib/plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');

module.exports = {
  context: path.resolve(__dirname),
  entry: {
    popup: './src/popup/main.js',
    devtools: './src/devtools/main.js',
    background: './src/background/main.js',
    content: './src/content/main.js'
  },
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].js'
  },
  resolve: {
    alias: {
      vue$: 'vue/dist/vue.esm.js',
      '@': path.resolve(__dirname, 'src'),
      '~': path.resolve(__dirname)
    }
  },
  devServer: {
    open: true,
    hot: true
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        use: 'babel-loader'
      },
      {
        test: /\.vue$/,
        use: 'vue-loader'
      },
      {
        test: /\.css$/,
        use: [
          'vue-style-loader',
          'css-loader',
          'postcss-loader'
        ]
      }
    ]
  },
  plugins: [
    new CleanWebpackPlugin(),
    new VueLoaderPlugin(),
    new webpack.HotModuleReplacementPlugin(),
    new CopyWebpackPlugin({
      patterns: [
        {
          from: '**/*',
          context: 'public'
        }
      ]
    })
  ]
};

src 配下に popup や devtools 等のそれぞれのディレクトリを用意しておいて、その下の main.js をそれぞれのエントリーポイントにすることにしました。 コンパイル結果のファイルは ./dist/popup.js や ./dist/devtools.js のような形で出力されます。

ここからはソースコードを書いていきます。

public/popup.html

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Popup</title>
</head>
<body>
  <div id="app"></div>
  <script src="./popup.js"></script>
</body>
</html>

public/devtools.html

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Devtools</title>
</head>
<body>
  <div id="app"></div>
  <script src="./devtools.js"></script>
</body>
</html>

ポップアップと開発者ツールそれぞれに表示するページです。

src/popup/main.js

import Vue from 'vue';
import App from './App.vue';

new Vue({
  el: '#app',
  render: h => h(App)
});

src/popup/App.vue

<template>
  <div>
    <h1>This Is Popup! {{ now }} </h1>
  </div>
</template>

<script>
export default {
  computed: {
    now: () => {
      return new Date();
    }
  }
}
</script>

src/devtools/main.js

import Vue from 'vue';
import App from './App.vue';

new Vue({
  el: '#app',
  render: h => h(App)
});

src/devtools/App.vue

<template>
  <div>
    <h1>This Is Devtools! {{ now }} </h1>
  </div>
</template>

<script>
export default {
  computed: {
    now: () => {
      return new Date();
    }
  }
}
</script>

src/background/main.js src/content/main.js

console.log('hello');

backgroudとcontentは画面が無いので今回は適当にしておきます。

ここまでくるとビルドが可能になります

$ npm run build

dist配下にdevtools.htmlやpopup.htmlが出力されているのでブラウザで開くと "This Is Devtools! Fri Jan 08 2021 17:52:25 GMT+0900 (日本標準時)" のように表示されると思います。

これでひとまずポップアップと開発者ツールそれぞれのVue.jsアプリのビルドが可能になりました。

今度はTypeScriptへ対応していきます。

$ npm install --save-dev typescript ts-loader

webpack.config.js の module.rules に以下を追加します

      {
        test: /\.ts$/,
        loader: 'ts-loader',
        exclude: /node_modules/,
        options: {
          appendTsSuffixTo: [/\.vue$/],
        }
      }

あと resolve に以下を追加しておきます

    extensions: ['.ts', '.js', '.vue', '.json'],

entryの各main.js を main.ts に変更しておきます

  entry: {
    popup: './src/popup/main.ts',
    devtools: './src/devtools/main.ts',
    background: './src/background/main.ts',
    content: './src/content/main.ts'
  },

あとtsconfig.jsonを追加しておきます

{
  "compilerOptions": {
      "outDir": "./dist/",
      "sourceMap": true,
      "strict": true,
      "module": "es2015",
      "moduleResolution": "node",
      "target": "es5",
      "skipLibCheck": true,
      "esModuleInterop": true,
      "experimentalDecorators": true
  },
  "include": [
      "./src/**/*"
  ]
}

あと src に 以下の内容のvue-shims.d.tsをおいておきます 参考 : github.com

declare module "*.vue" {
  import Vue from "vue";
  export default Vue;
}

次にjsファイルをtsに変えていきます。 各 main.js のファイル名を main.ts に変更します。 App.vue の script を <script lang="ts"> に変更しておきます。

これでTypeScript化できたと思います。npm run build でビルドできることを確認します。

ここまでできればほぼ完了です。

あとは、Chrome拡張機能用のプロジェクトなのでpublicに必要なファイルを置いていきます。

public/manifest.json

{
  "manifest_version": 2,
  "name": "my chrome extension",
  "version": "0.1.0",
  "description": "My Chrome Extension",
  "icons": {
    "16": "icons/icon_16.png"
  },
  "background": {
    "scripts": [
      "background.js"
    ],
    "persistent": false
  },
  "browser_action": {
    "default_title": "popup title",
    "default_popup": "popup.html"
  },
  "permissions": [
    "storage"
  ],
  "content_scripts": [
    {
      "matches": [
        "<all_urls>"
      ],
      "js": [
        "content.js"
      ]
    }
  ],
  "devtools_page": "panelcreate.html"
}

アイコンも適当に public/icons においておいて下さい。

開発者ツールのパネルは devtools_page に指定した html が直接表示される訳ではなく、chrome.devtools.panel.create で追加しなければならないのでこれを実行してくれるファイルを用意しておきます。 public/panelcreate.js

chrome.devtools.panels.create('My Extension', null, 'devtools.html');

public/panelcreate.html

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Panel Create</title>
  <script type="text/javascript" src="./panelcreate.js"></script>
</head>
<body>
  
</body>
</html>

chrome.devtools.panels.create の第一引数はタブのタイトルになります。

npm run build で dist にファイルが生成されます。 Chrome でアドレスバーに chrome://extensions を入力して、デベロッパーモードをオンにして、"パッケージ化されていない拡張機能を読み込む"からdistを選択するとVue.js + TypeScriptで作成した拡張機能を読み込むことができると思います。

今回のコードは下記で確認できます。

github.com

TypeScriptでChrome拡張機能を開発

Chrome拡張機能をちょっと作ってみたときにTypeScriptを使ってみたかったので、その時の手順をメモ。

基本的な方針はChrome拡張機能の雛形を作成してくれる GitHub - dutiyesh/chrome-extension-cli: 🚀 The CLI for your next Chrome Extension を利用しつつ、これはTypeScriptに現在対応していないためそこだけ修正する。 ※ TypeScript対応のPRが出ているようなのでそちらがマージされればこの手順自体が不要となる。

chrome-extension-cli のインストール

|| $ npm install -g chrome-extension-cli ||<

プロジェクトの雛形の作成

|| $ chrome-extension-cli my-project ||<

これで my-project フォルダにChrome拡張機能のプロジェクトの雛形が作成されます。

TypeScript へ対応

必要なパッケージのインストール

|| $ npm install --save-dev @types/chrome @types/node ts-loader typescript ||<

config/webpack.common.js の修正

common の module.rules に下記を追加

|| { rules: [ { test: /.ts$/, use: 'ts-loader', } ] }, ||<

common に下記を追加

|| resolve: { extensions: [ '.ts', '.js' ] }, ||<

webpack.config.js の修正

entry の各ファイルの拡張子を .js から .ts へ変更

|| // Merge webpack configuration files const config = merge(common, { entry: { popup: PATHS.src + '/popup.ts', contents: PATHS.src + '/contentScript.ts', background: PATHS.src + '/background.ts', }, }); ||<

src/ 内のファイルの拡張子を変更

src/popup.js → src/popup.ts contentScript.js, background.js も同様

tsconfig.json の追加

my-project ディレクトリに下記の内容で tsconfig.json を追加

|| { "compilerOptions": { "sourceMap": true, "target": "es6", "module": "es6" } } ||<

PyTorchの基本的なところ

テンソルの添字

>>> t = torch.zeros(2, 3, 4)

>>> t
tensor([[[0., 0., 0., 0.],
         [0., 0., 0., 0.],
         [0., 0., 0., 0.]],

        [[0., 0., 0., 0.],
         [0., 0., 0., 0.],
         [0., 0., 0., 0.]]])

>>> t.shape
torch.Size([2, 3, 4])

>>> t[0][1][2] = 1
>>> t
tensor([[[0., 0., 0., 0.],
         [0., 0., 1., 0.],
         [0., 0., 0., 0.]],

        [[0., 0., 0., 0.],
         [0., 0., 0., 0.],
         [0., 0., 0., 0.]]])

t.shape が [2, 3, 4] であるということは、4つのまとまりが3つあるまとまりが2つある、みたいな意味

view による変形

>>> t = torch.Tensor([[[1,2,3], [4, 5, 6]], [[7, 8, 9], [10, 11, 12]]])
>>> t
tensor([[[ 1.,  2.,  3.],
         [ 4.,  5.,  6.]],

        [[ 7.,  8.,  9.],
         [10., 11., 12.]]])

>>> t.shape
torch.Size([2, 2, 3])

>>> t2 = t.view(6, 2) # 2個のまとまりを6個に変換
>>> t2
tensor([[ 1.,  2.],
        [ 3.,  4.],
        [ 5.,  6.],
        [ 7.,  8.],
        [ 9., 10.],
        [11., 12.]])
>>> t2.shape
torch.Size([6, 2])

>>> t3 = t.view(4, -1) # X個のまとまりを4個に変換
>>> t3
tensor([[ 1.,  2.,  3.],
        [ 4.,  5.,  6.],
        [ 7.,  8.,  9.],
        [10., 11., 12.]])
>>> t3.shape
torch.Size([4, 3])

>>> t.view(9, -1)
RuntimeError: shape '[9, -1]' is invalid for input of size 12

テンソルの形を要素数が同じという条件下で変形する -1を使うと他に指定された数値からかってに埋めてくれる

squeeze

素数が1の軸を削除してくれる

>>> t = torch.zeros(2, 1, 3, 1)
>>> t
tensor([[[[0.],
          [0.],
          [0.]]],

        [[[0.],
          [0.],
          [0.]]]])

>>> t.squeeze() # [2, 1, 3, 1] を [2, 3] に整形してくれる
tensor([[0., 0., 0.],
        [0., 0., 0.]])

>>> # 引数(dim)を指定すると dim番めの軸のみに着目
>>> t.squeeze(0) # 0番目は2なのでsqueezeされない
tensor([[[[0.],
          [0.],
          [0.]]],


        [[[0.],
          [0.],
          [0.]]]]) 

>>> t.squeeze(1) # 1番目は1なので [2, 3, 1] に整形
tensor([[[0.],
         [0.],
         [0.]],

        [[0.],
         [0.],
         [0.]]])

>>> t.squeeze(3) # 3番目は1なので[2, 1, 3]に整形
tensor([[[0., 0., 0.]],

        [[0., 0., 0.]]])

unsqueeze

squeezeの逆バージョン 要素数が1の軸を足してくれる dimが必須

>>> t = torch.zeros(2, 3)
>>> t
tensor([[0., 0., 0.],
        [0., 0., 0.]])
>>> t.unsqueeze(1)
tensor([[[0., 0., 0.]],

        [[0., 0., 0.]]])

>>> t.unsqueeze(2)
tensor([[[0.],
         [0.],
         [0.]],

        [[0.],
         [0.],
         [0.]]])

permute

>>> t = torch.Tensor([[[1,2,3], [4, 5, 6]], [[7, 8, 9], [10, 11, 12]]])
>>> t
tensor([[[ 1.,  2.,  3.],
         [ 4.,  5.,  6.]],

        [[ 7.,  8.,  9.],
         [10., 11., 12.]]])

>>> t2 = t.permute(2, 0, 1)
>>> t2
tensor([[[ 1.,  4.],
         [ 7., 10.]],

        [[ 2.,  5.],
         [ 8., 11.]],

        [[ 3.,  6.],
         [ 9., 12.]]])

>>> t3 = t.permute(2, 1, 0)
>>> t3
tensor([[[ 1.,  7.],
         [ 4., 10.]],

        [[ 2.,  8.],
         [ 5., 11.]],

        [[ 3.,  9.],
         [ 6., 12.]]])

>>> t[1][0][2]
tensor(9.)

>>> t2[2][1][0]
tensor(9.)

>>> t3[2][0][1]
tensor(9.)

spacy と Multi30k

from torchtext.datasets import TranslationDataset, Multi30k
from torchtext.data import Field, BucketIterator
import spacy

!python -m spacy download en

spacy_en = spacy.load('en')

!python -m spacy download de

spacy_de = spacy.load('de')

l = spacy_en.tokenizer('This is a pen!')
type(l) # => spacy.tokens.doc.Doc
type(l[0]) # => spacy.tokens.token.Token
l[0].text # => 'This'

def tokenize_de(text):
    """
    Tokenizes German text from a string into a list of strings (tokens) and reverses it
    """
    return [tok.text for tok in spacy_de.tokenizer(text)]

def tokenize_en(text):
    """
    Tokenizes English text from a string into a list of strings (tokens)
    """
    return [tok.text for tok in spacy_en.tokenizer(text)]

SRC = Field(tokenize = tokenize_de, 
            init_token = '<sos>', 
            eos_token = '<eos>', 
            lower = True)

TRG = Field(tokenize = tokenize_en, 
            init_token = '<sos>', 
            eos_token = '<eos>', 
            lower = True)


train_data, valid_data, test_data = Multi30k.splits(exts = ('.de', '.en'), 
                                                    fields = (SRC, TRG))

type(train_data) # => torchtext.datasets.translation.Multi30k

print(vars(train_data.examples[0]))
"""
=> {
  'src': [
    'zwei',
    'junge',
    'weiße',
    'männer',
    'sind',
    'im',
    'freien',
    'in',
    'der',
    'nähe',
    'vieler',
    'büsche',
    '.'
  ],
  'trg': [
    'two',
    'young',
    ',',
    'white',
    'males',
    'are',
    'outside',
    'near',
    'many',
    'bushes',
    '.'
  ]
}
"""

SRC.build_vocab(train_data, min_freq = 2)
TRG.build_vocab(train_data, min_freq = 2)


BATCH_SIZE = 128

train_iterator, valid_iterator, test_iterator = BucketIterator.splits(
    (train_data, valid_data, test_data), 
    batch_size = BATCH_SIZE, 
    device = device)

for b in train_iterator:
  break

type(b) # => torchtext.data.batch.Batch
type(b.src) # => torch.Tensor
type(b.trg) # => torch.Tensor

b.src.shape # => [27, 128]
b.trg.shape # => [27, 128]
b.trg
"""
tensor([[   2,    2,    2,  ...,    2,    2,    2],
        [   4,   16,    4,  ...,    4,    4,    4],
        [   9,   50, 4224,  ...,   14,    9,   61],
        ...,
        [   1,    1,    1,  ...,    1,    1,    1],
        [   1,    1,    1,  ...,    1,    1,    1],
        [   1,    1,    1,  ...,    1,    1,    1]], device='cuda:0')
"""
# b.trg[:, n] が n文目のはず
b.trg[:, 0]
"""
tensor([  2,   4,   9,  11,  74, 589,  17,   6,  43,  12,   4,  59,  77,   5,
          3,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1],
       device='cuda:0')
"""

for t in b.trg[:, 0]:
  print(TRG.vocab.itos[t])

"""
<sos>
a
man
and
some
bicycles
are
in
front
of
a
large
building
.
<eos>
<pad>
<pad>
<pad>
<pad>
<pad>
<pad>
<pad>
<pad>
<pad>
<pad>
<pad>
<pad>
"""

Bashのプロンプトをカスタマイズする

やりたいこと

カレントディレクトリと時刻とgitのブランチとgcloudに設定されているプロジェクトを表示したいです

そもそもどこで設定されている?

環境変数のPS1に設定されています

echo $PS1
${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$

f:id:uhiaha888:20200228215406p:plain

意味がわからない

カスタマイズする前に、現状を把握しておきたいが意味がわからないです

Bash/プロンプトのカスタマイズ - ArchWiki を参考に読んで見ましょう

debian_chroot ???
${debian_chroot:+($debian_chroot)}

早速謎の debian_chroot なるなにかが現れました

Ubuntuのデフォルト設定から学ぶbashプロンプト - Qiita にある通り chroot でルートディレクトリを変えている場合にそれを表示してくれるらしいです

普段 chroot を使うことがないのでこの部分はとりあえず無視しておきます

ユーザー名@ホスト名
\[\033[01;32m\]\u@\h

\[ と \] はエスケープに使われる記号で、この間に書かれた文字は文字数をカウントしないとかそんな感じみたいです

\033[01;32m

\033 は \e と書いてもいいみたいです \033[ と m の間に書体や文字色等の設定ができるみたいです 複数同時に指定したい場合は ; で区切るようです

コード 意味
00 装飾なし
01 太字
02 細字
03 イタリック
04 下線
30 文字色 黒
31 文字色 赤
32 文字色 緑
33 文字色 黄色
34 文字色 青
35 文字色 マゼンタ
36 文字色 シアン
37 文字色 白
40 背景色 黒
41 背景色 赤
42 背景色 緑
43 背景色 黄色
44 背景色 青
45 背景色 マゼンタ
46 背景色 シアン
47 背景色 白

なので、 [\033[01;32m]\ は ここからは 太字 かつ 文字色 緑 で表示します という意味になるようです

\u はユーザー名, @はただの文字列で \h がホスト名になります

コロン
\[\033[00m\]:

装飾をすべてなしにして : を表示します

カレントディレクト
\[\033[01;34m\]\w

\w はカレントディレクトリ(フルパス) さきほどの表によると、01は太字、34は文字色 青 です

$
\[\033[00m\]\$

再度装飾をすべてなしにしています \$ は一般ユーザーのときは $, root のときは # を表示してくれるようです

読めたね!

ここからはやりたいことを実現していきます

日時を表示

\D を使えば良いみたいです

\D{%Y/%m/%d %T}

ちょっと表示してみましょう

export PS1="[\D{%Y/%m/%d %T}]${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ "

f:id:uhiaha888:20200228215731p:plain

日付が表示できましたね!!

git ブランチ

プロンプトをカスタマイズしてgitブランチを表示する - Qiita を参考に、以下でブランチ名が取得できるようです

git branch --no-color 2> /dev/null | sed -e '/^[^*]/d' -e 's/* \(.*\)/\1/'

ということでこれを.bashrcに記載していきましょう

function branch () {
    git branch --no-color 2> /dev/null | sed -e '/^[^*]/d' -e 's/* \(.*\)/\1/'
}

if [ "$color_prompt" = yes ]; then
    PS1='$(branch)${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ '
else
    PS1='$(branch)${debian_chroot:+($debian_chroot)}\u@\h:\w\$ '
fi

f:id:uhiaha888:20200228221317p:plain

表示できた!!

gcloud のプロジェクト名

zsh-gcloud-prompt/gcloud.zsh at master · ocadaruma/zsh-gcloud-prompt · GitHub を参考に、以下でプロジェクト名が取得できます

gcloud config get-value project 2>/dev/null

ということでこれも.bashrcに記載します

f:id:uhiaha888:20200228221822p:plain

f:id:uhiaha888:20200228221856p:plain

YES!!

レイアウト

欲しい情報は一通り取得できましたが、プロンプトが長くなりすぎて使いにくそうです 2行に分けたり右端に置いたりして整理しましょう

改行

\n でいけるみたいです

右寄せ

ややこしいんですが

Bash/プロンプトのカスタマイズ - ArchWiki を参考にしたらできました

f:id:uhiaha888:20200228225442p:plain

まとめ

意外とできましたね

.bashrc の関連しそうな部分は最終的に下記のような形になりました

function branch () {
    git branch --no-color 2> /dev/null | sed -e '/^[^*]/d' -e 's/* \(.*\)/\1/'
}

function project () {
    gcloud config get-value project 2>/dev/null
}

function right () {
    printf "%*s" $COLUMNS "$(branch) $(project) "
}

if [ "$color_prompt" = yes ]; then
    PS1='\[$(tput sc; right; tput rc)\]${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\n\D{%Y/%m/%d %T}\[\033[00m\] \$ '
else
    PS1='${debian_chroot:+($debian_chroot)}\u@\h:\w\$ '
fi
unset color_prompt force_color_prompt

Keras に入門する 文書生成 Part.1 サンプルを動かしてみる

文書生成のサンプルを動かしてみる

keras/lstm_text_generation.py at master · keras-team/keras · GitHub

Colabはこれ

https://colab.research.google.com/drive/1lbSMhWEUxenIXVPSCl8MaYcz_rJbuB6o

もとの文書は

https://s3.amazonaws.com/text-datasets/nietzsche.txt

まず1~40文字, 4~43文字, 7~46文字 のように、3字ずつずらしながら40文字ずつに切り分けていく & 41文字目、44文字目、47文字目 のように それぞれの40文字の次の1文字をnext_charsに格納していく

maxlen = 40
step = 3
sentences = []
next_chars = []
for i in range(0, len(text) - maxlen, step):
    sentences.append(text[i: i + maxlen])
    next_chars.append(text[i + maxlen])
print('nb sequences:', len(sentences))

つぎにベクトル化をしているらしい

print('Vectorization...')
x = np.zeros((len(sentences), maxlen, len(chars)), dtype=np.bool)
y = np.zeros((len(sentences), len(chars)), dtype=np.bool)
for i, sentence in enumerate(sentences):
    for t, char in enumerate(sentence):
        x[i, t, char_indices[char]] = 1
    y[i, char_indices[next_chars[i]]] = 1

各文字のindexは indices_char に格納されている

{0: '\n',
 1: ' ',
 2: '!',
 3: '"',
 4: "'",
 5: '(',
 6: ')',
 7: ',',
 8: '-',
 9: '.',
 10: '0',
 11: '1',
 12: '2',
 13: '3',
 14: '4',
 15: '5',
 16: '6',
 17: '7',
 18: '8',
 19: '9',
 20: ':',
 21: ';',
 22: '=',
 23: '?',
 24: '[',
 25: ']',
 26: '_',
 27: 'a',
 28: 'b',
 29: 'c',
 30: 'd',
 31: 'e',
 32: 'f',
 33: 'g',
 34: 'h',
 35: 'i',
 36: 'j',
 37: 'k',
 38: 'l',
 39: 'm',
 40: 'n',
 41: 'o',
 42: 'p',
 43: 'q',
 44: 'r',
 45: 's',
 46: 't',
 47: 'u',
 48: 'v',
 49: 'w',
 50: 'x',
 51: 'y',
 52: 'z',
 53: 'ä',
 54: 'æ',
 55: 'é',
 56: 'ë'}

x は (len(sentences), maxlen, len(chars)) の形になっている

x[i][j][k] は i文目 の j文字目 が id=k の文字なら True, そうじゃなければ False となっている (便宜上、"文"と書いたけど、先程40文字ずつに分けた1まとまりを文としている)

たとえば

x[0][0]

→ array([False, False, False, False, False, False, False, False, False,
       False, False, False, False, False, False, False, False, False,
       False, False, False, False, False, False, False, False, False,
       False, False, False, False, False, False, False, False, False,
       False, False, False, False, False, False,  True, False, False,
       False, False, False, False, False, False, False, False, False,
       False, False, False])

これは最初の文 (preface\n\n\nsupposing that truth is a woma) の 1文字目が、id:42(=p)であることを表している

つぎにLSTMのモデルを構築する

# build the model: a single LSTM
print('Build model...')
model = Sequential()
model.add(LSTM(128, input_shape=(maxlen, len(chars))))
model.add(Dense(len(chars), activation='softmax'))

optimizer = RMSprop(lr=0.01)
model.compile(loss='categorical_crossentropy', optimizer=optimizer)

学習途中の経過表示用


def sample(preds, temperature=1.0):
    # helper function to sample an index from a probability array
    preds = np.asarray(preds).astype('float64')
    preds = np.log(preds) / temperature
    exp_preds = np.exp(preds)
    preds = exp_preds / np.sum(exp_preds)
    probas = np.random.multinomial(1, preds, 1)
    return np.argmax(probas)


def on_epoch_end(epoch, _):
    # Function invoked at end of each epoch. Prints generated text.
    print()
    print('----- Generating text after Epoch: %d' % epoch)

    start_index = random.randint(0, len(text) - maxlen - 1)
    for diversity in [0.2, 0.5, 1.0, 1.2]:
        print('----- diversity:', diversity)

        generated = ''
        sentence = text[start_index: start_index + maxlen]
        generated += sentence
        print('----- Generating with seed: "' + sentence + '"')
        sys.stdout.write(generated)

        for i in range(400):
            x_pred = np.zeros((1, maxlen, len(chars)))
            for t, char in enumerate(sentence):
                x_pred[0, t, char_indices[char]] = 1.

            preds = model.predict(x_pred, verbose=0)[0]
            next_index = sample(preds, diversity)
            next_char = indices_char[next_index]

            generated += next_char
            sentence = sentence[1:] + next_char

            sys.stdout.write(next_char)
            sys.stdout.flush()
        print()

学習の実行

print_callback = LambdaCallback(on_epoch_end=on_epoch_end)

model.fit(x, y,
          batch_size=128,
          epochs=60,
          callbacks=[print_callback])

学習結果は以下

多分変な文が生成されてるけど、英語が苦手だからどのくらい不自然なのか判別できない...

Epoch 60/60
200285/200285 [==============================] - 148s 741us/step - loss: 1.3291

----- Generating text after Epoch: 59
----- diversity: 0.2
----- Generating with seed: "ar as it is desired and demanded by wome"
ar as it is desired and demanded by women and and all the sense and subject of the problems of the power the sense to the most sense of the present the former the strength, the strength of the sense of the power the sense of the sense of the sense of the power of the participated to the sense and account the consciousness of the power of the power of the problemse--and the problemse--he wishes to the problemse--he who would not the prob
----- diversity: 0.5
----- Generating with seed: "ar as it is desired and demanded by wome"
ar as it is desired and demanded by women and believe to the unhered in the stands and entirely but all in his learned to extent as a standard new and affation of a power the result of the instances, and fatherly, which is the contempt in the person the power and a consciousness of the standate of a participation of the
man and entire power, which may be present is he was personal of the self-sake and struggle and our acts and instinct 
----- diversity: 1.0
----- Generating with seed: "ar as it is desired and demanded by wome"
ar as it is desired and demanded by women; the counter" in pitts. the
latter hand, in. a notainly ragulour, in the goodan; ard dogmes really who
experience by the "made of a liuitest power--from the cosgrable oh, an true.--we really, indeed and outicated of every such a book of its "me the one's act of worl! for hthe personal
grancts, to position--have will, well to cause about  self-my tereomtic grow where the lightness, with its choie
----- diversity: 1.2
----- Generating with seed: "ar as it is desired and demanded by wome"
ar as it is desired and demanded by women, he some good utiesnely it cleards who gif and purtant, numblinariation, ourselves. the secret curioe,
in emess,--above
spiritually tuld
insiestiaing make. this kanishons this kind of at personne--it
not "let peraportly aginives ever also, thanc
easists of delusion fseeses: suth on wind nothin! herguimed portagh highle
eighlesing, any
kundod
systems the svenses its power overwary acvedges? w) as