最近、生成モデルを利用して何か作ってみようかと、生成モデルを学んでいます。 今回は最も基本的な敵対的生成ネットワーク(GAN)の基本的な仕組みについて説明します。

GANとは生成モデルの一種であるため、まずは生成モデルについて説明します。

生成モデルとは

生成モデルとは、写真やイラスト、音楽など実際のデータそのものを出力することができる機械学習モデルです。

機械学習では、画像にうつっているものを識別したり、真偽を判定したりと、未来数値の変化を予測したりといった用途でよく利用されています。

画像の分類などを行うモデルは識別モデルと呼ばれていて、2値分類の場合は、分類ラベルを表す「0」、「1」となります。

生成モデルはこれらのモデルとは異なり、入力はランダムなベクトルをとり、出力は画像などのデータそのものとなります。

Image

機械によって存在しないデータを作り出すことができる、まさに人工知能的な技術といえると思います。

敵対的生成ネットワーク(GAN)とは

生成モデルの中で、近年成功を収めている手法が、敵対的生成ネットワーク(GAN)です。

現在成功をおさめているGANは生成モデルに深層学習技術を利用しており、従来よりも精度の高い出力を可能にしました。

たとえば、こちらはGANの一種のStyleGANという手法で生成した画像ですが、 かなり写真に近い画像を生成することができています。

Image
引用:https://en.wikipedia.org/wiki/StyleGAN

GANの基本

GANはデータを作り出す生成モデルと本物と偽物を見分ける識別モデルの2つを組み合わせて学習を行います。

識別モデルは生成モデルのデータ(ラベル: 0)と本物のデータ(ラベル: 1)を使って学習を行います。学習を行うことで識別モデルはデータが本物か偽物かを見分けられるようになります。

Image

生成モデルはランダムな数値ベクトルを入力として、出力を行いますが、学習時は識別モデルと連結し、 識別モデルの出力が本物と判定されるように生成モデルの学習を行います。 つまり、生成モデルは後段の識別モデルが本物の画像だと間違えてしまう画像を作るように学習します。

Image

生成モデルの学習時に注意が必要なのは、識別モデルのパラメータは変更しないことです。 識別モデルは忖度せずに厳しく判定を行なってほしいですが、学習時に識別モデルを変化させてしまうと、 入力画像が似ていなくても本物と識別してしまう弱い識別モデルになってしまいます。

TensorFlowを使ったGANの実装

今回はTensorFlowを使ってGAN(DCGAN)を実装してみたいと思います。 GANの学習は時間がかかるため、GPUが利用できる環境で実行したほうが良いです。 今回は無料でGPUが利用できるGoogleのColaboratory で学習を行います。

  • (1) データの読み込み
  • (2) 生成器の作成
  • (3) 識別器の作成
  • (4) モデルのコンパイル
  • (5) モデルの学習

の順に説明します。実際に動かしたコードはこちらで公開しています。

(1) データの読み込み

TensorFlowのパッケージに付属するmnistのデータを利用して学習を行ってみます。 データのshapeは「(60000, 28, 28)」で返ってきますが、モデルへの入力を「(28, 28, 1)」にしたいため「reshape」します。

from tensorflow.keras.datasets import mnist

(x_train, y_train), (x_test, y_test) = mnist.load_data()

x_train = x_train.reshape(-1, 28, 28, 1)
x_test = x_test.reshape(-1, 28, 28, 1)

また、このデータは「0~255」まで整数を取りますが、入力値を適切にスケーリングしないと上手く学習できないため、 「-1 ~ 1」の範囲を取るように変換します。

x_train = (x_train - 127.5) / 127.5
x_test = (x_test - 127.5) / 127.5

(2) 生成器の作成

KerasのFunctional APIを利用してモデルを作成します。まずモデルで利用するパッケージをimportします。

from tensorflow.keras.models import Model
from tensorflow.keras.layers import Input, \
   Dense, Conv2D, LeakyReLU, BatchNormalization, \
   Reshape, UpSampling2D, Activation

後で利用する定数を定義します。 kernel_size はモデルの畳み込み層で利用するカーネルサイズです。 noize_vector_dim は生成モデルの入力ベクトルのサイズです。

kernel_size = 5
noize_vector_dim = 100

今回は最終的に 28 x 28 のサイズの画像を作成したいため、 アップサンプリング層 で倍々に増やしたときに、 ちょうど28となるように最初の畳み込み層への入力サイズを決めます。

アップサンプリング層は、行と列を繰り返してサイズを大きくする層です。 参考: https://keras.io/api/layers/reshaping_layers/up_sampling2d/

7 x 7とした場合、 7 x 714 x 1428 x 28 となるため、 最初のサイズは 7 x 7 x N とします。今回は N = 64 としました。

input_layer = Input(shape=(noize_vector_dim,))

x = Dense(units=7*7*64)(input_layer)
x = BatchNormalization(momentum=0.8)(x)
x = Reshape(target_shape=(7, 7, 64))(x)

アップサンプリング層畳み込み層バッチ正規化層活性化層を順に適用し繰り返します。

x = UpSampling2D()(x)
x = Conv2D(filters=128, strides=1,
          kernel_size=kernel_size, padding='same')(x)
x = BatchNormalization(momentum=0.8)(x)
x = LeakyReLU()(x)

x = Conv2D(filters=64, strides=1,
          kernel_size=kernel_size, padding='same')(x)
x = BatchNormalization(momentum=0.8)(x)
x = LeakyReLU()(x)

x = Conv2D(filters=64, strides=1,
          kernel_size=kernel_size, padding='same')(x)
x = BatchNormalization(momentum=0.8)(x)
x = LeakyReLU()(x)

バッチ正規化層を入れると学習が安定することが知られています。

最後に畳み込み層のフィルタの数を1にして、 (28 x 28 x 1) の出力にします。 最後に tanh を活性化関数とします。これにより出力が -1 ~ 1 の範囲を取るようになります。

DCGANの論文では、全ての層で正規化層を入れるとモデルが不安定になるため、 生成器の出力層ではバッチ正規化を行っておらず、ここでも正規化層を入れていないです。 参考: https://arxiv.abs/1511.06434

x = Conv2D(filters=1, strides=1,
          kernel_size=kernel_size, padding='same')(x)
output_layer = Activation("tanh")(x)

入力層と出力層を Model に渡し、生成器を作成します。

generative_model = Model(input_layer, output_layer)

modelのサマリーは以下のようになります。

generative_model.summary()

Image

(3) 識別器の作成

識別用のモデルは、畳み込み層活性化層ドロップアウト層で構成します。 出力層の手前で平坦化層で平坦化し、出力層はシグモイド関数を使って0~1の範囲で出力を行います。

DCGANの論文 では識別器でも入力層以外はバッチ正規化を入れていたようですが、一般に識別器では利用しない方が良いようです。

識別器の学習ではバッチ正規化は本物のバッチと偽物のバッチでパラメータを決定しますが、 生成器では全て偽物のバッチの正規化となってしまい、学習がうまくいかなくなるようです。 (こちらのコメントを参考 )

識別器では、学習の安定化のためにドロップアウト層を利用します。

kernel_size = 5
input_layer = Input(shape=(28, 28, 1))

x = Conv2D(filters=64, strides=2, kernel_size=kernel_size, padding='same')(input_layer)
x = LeakyReLU()(x)

x = Conv2D(filters=64, strides=2, kernel_size=kernel_size, padding='same')(x)
x = LeakyReLU()(x)
x = Dropout(rate=0.3)(x)

x = Conv2D(filters=128, strides=2, kernel_size=kernel_size, padding='same')(x)
x = LeakyReLU()(x)
x = Dropout(rate=0.4)(x)

x = Conv2D(filters=128, strides=1, kernel_size=kernel_size, padding='same')(x)
x = LeakyReLU()(x)
x = Dropout(rate=0.4)(x)

x = Flatten()(x)
output_layer = Dense(units=1, activation='sigmoid')(x)

discriminator_model = Model(input_layer, output_layer)

モデルのサマリーは以下のようになります。

discriminator_model.summary()

Image

(4) モデルのコンパイル

まず識別器のコンパイルを行います。識別器は2値分類を行うため、損失に binary_crossentropy を指定します。

discriminator_model.compile(
   optimizer=Adam(learning_rate=1e-4),
   loss='binary_crossentropy',
   metrics=['accuracy']
)

生成器の学習のためには二つのモデルを連結したモデルを作成します。ただし、生成器の学習時に識別器の学習をストップする必要があります。

Kerasではモデルの trainable という属性を False にすると、コンパイル後に学習が行われなくなります。コンパイルする必要があるので、先にコンパイルした識別器には影響がないです。

生成器も損失に binary_crossentropy を指定します。

input_layer = Input(shape=(noize_vector_dim,))
discriminator_model.trainable = False

img = generative_model(input_layer)
output_layer = discriminator_model(img)

combined_model = Model(input_layer, output_layer)
combined_model.compile(
   optimizer=Adam(learning_rate=1e-4),
   loss='binary_crossentropy',
   metrics=['accuracy'],
)

(5) モデルの学習

モデルの定義が完了したので、学習を行います。 ただし、GANは学習に時間がかかるため、学習がうまくいっているのか途中経過を確認したいです。

TensorBoardを利用すると学習時の様子が観察できます。 損失を文字で出力して眺めても状況が分かりにくいので、こちらを利用することをお勧めします。

ColaboratoryではTensorBoardを以下のマジックコマンドで起動することができます。

%load_ext tensorboard
%tensorboard --logdir logs

Image
実際にTensorBoardで学習を眺めた様子

学習のログをTensorBoardを反映するため、 tf.keras.callbacks.TensorBoard を初期化します。

batch_size = 64

from tensorflow.keras.callbacks import TensorBoard

tensorboard = TensorBoard(log_dir="logs", histogram_freq=0, batch_size=batch_size, write_graph=True)

各種パラメータと、生成器の入力で必要なランダムベクトルを出力するヘルパー関数を定義します。

batch_size = 64
noize_vector_dim = 100
num_epoch = 50000

def get_random_vector():
   return np.random.normal(0, 1, (batch_size, noize_vector_dim))

まず、生成器の出力画像を利用して、識別器の学習を行います。その後連結したモデルを用いて生成器の学習を行います。 これを指定したエポック数で学習を繰り返します。

for epoch in range(num_epoch):
   # 入力ベクトル
   input_vector = get_random_vector()
   # 生成画像
   fake_image = generative_model.predict(input_vector)
   # 生成画像のラベル (全て0)
   fake_label = np.zeros((batch_size, 1))

   # 本物の画像をサンプリング
   index = np.random.randint(0, x_train.shape[0], size=batch_size),
   valid_image = x_train[index]
   # 本物の画像のラベル (すべて1)
   valid_label = np.ones((batch_size, 1))

   # 学習のログ
   epoch_logs = {}

   # 識別器の学習
   fake_loss, fake_acc = discriminator_model.train_on_batch(fake_image, fake_label)
   valid_loss, valid_acc = discriminator_model.train_on_batch(valid_image, valid_label)
   epoch_logs.update({'disc_loss': 0.5*(fake_loss + valid_loss), 'disc_acc': 0.5*(fake_acc + valid_acc)})

   # 生成器の訓練
   input_vector = get_random_vector()
   label = np.ones((batch_size, 1))
   logs = combined_model.train_on_batch(input_vector, label, return_dict=True)
   epoch_logs.update({'gen_'+key: logs[key] for key in logs})

   tensorboard.on_epoch_end(epoch, epoch_logs)

tensorboard.on_train_end(None)

学習経過はTensorBoardで確認を行います。lossやaccuracyは激しく振動しているため、Smoothingを「0.95~0.98」など強めにすると分かりやすいです。

以下はそれぞれ、識別器のaccuracy、識別器のloss、生成器のaccuracy、生成器のlossの経過です。accuracyやlossなどが途中で常に一定の値をとってしまったりすると学習が失敗しています。

Image

Image

Image

Image

学習の注意点

入力画像のスケールを「-1~1」に変更することを忘れないようにしましょう。 私は何度も学習が失敗し続けていたのですが、モデルにミスがあるのかと思いずっとハマってました。

また、最適化の学習率で結果が変わるので、失敗する場合は、学習率を変更してみると改善するかもしれないです。

精度が悪い場合は、単にエポック数が足りないかもしれないです。最初は1000~2000程度でなかなか上手くいかないなと悩んでいたのですが、こちらのリポジトリでは18kエポックほど学習していたので学習回数を増やしてみたところ改善しました。

実験結果

以下のように画像をグレースケールで描画します。

for i in range(200):
    v = get_random_vector()
    y = generative_model.predict(v)
    plt.imshow(y[0, :, :, 0] * 0.5 + 0.5, cmap='gray')
    plt.figure()

50000エポックほど学習した際の出力画像です。手描きの数字画像を緺麗に生成できていると思います。

Image

同じ3という画像をとってみても、色々な出力が行われていることがわかります。

Image

参考

参考書籍

iOS14からIDFAを収集する場合、アプリユーザーの同意が必要になります。広告の計測などを行う場合、事前に許可を求める必要があります。

IDFA (Identifier for Advertisers)

Appleが端末に割り当てるデバイスIDのことです。このIDを利用して広告配信のエンゲージメントやコンバージョンの測定などに利用します。

参考: https://www.adjust.com/ja/glossary/idfa/

ATT (App Tracking Transparency)

iOS14では、許可していないユーザーのIDFAの取得ができなくなりました。そのため、広告のトラッキングなどを行いたい場合、IDFA取得の同意を求める必要があります。

■ オプトイン

広告配信の際に事前に許可を求めること。

■ ATTオプトイン率

トラッキングを許可したユーザー数。 AppsFlyerのこの記事によると、 アプリ全体で41%程度、アプリ一つあたりだと28%とのこと。また、非ゲームアプリの方がオプトイン率が高くなるとのことです。

参考: https://www.appsflyer.com/jp/ios-14-att-dashboard/

LAT (Limit Ad Tracking)

iOS14以前は、追跡型広告の制限はLimit Ad Tracking (LAT) という機能で行ってました。 これはオプトアウト方式、すなわちデフォルトではIDFAを取得できるものの、後から制限する方式でした。

■ LAT ONユーザー

LAT機能をONにしているユーザーのことです。

• • •

開発者としての対応

もし、IDFAの取得を行う場合は、info.plistにTracking Usage Descriptionの設定を行う必要があります。

この設定値として、許可を求める際の文言を設定します。

オプトイン率は3~4割とのことのため、ユーザー目線に立って文章を考えましょう。 (参考: ATTオプトイン率を上げる5つの方法

また、アプリ側の実装で、ATT許可の要求を必要な場面で行います。

参考: https://zenn.dev/yorifuji/articles/ios-app-tracking-transparency

参考 React Nativeの場合

React NativeでAppsFlyerなど利用して広告の効果測定を行っている場合は、 iOS14以降はATT対応のための設定を行う必要があります。行わない場合、非オーガニックの判定ができないです。

AppsFlyerでは起動時にIDFAを取得し送信しようとしますが、ATT許可を行うまで送信を待つ必要があります。 送信を延期するためのtimeToWaitForATTUserAuthorizationを設定する必要があります。

パーミッションの許可を求める際は、react-native-tracking-transparencyなどを利用して行います。

PromQLとは

PromQL (Prometheus Query Language) とは、Prometheusで利用する、データ取得、集約のためのクエリ言語のことです。

データ型

Prometheusで扱うデータの型は以下の4つがあります。(参考: Expression language data types

Instant vector時系列データ
Range vector範囲指定された時系列データ
Scalarスカラー値
String文字列

ラベルマッチャ

Prometheusはメトリクスにラベルをつけて時系列を保存します。ラベルを指定することで、特定の時系列データを選択することができます。

例えば、~httprequeststotal~ がメトリクス名の場合、ラベルenvironmentstagingのものだけを選択したい場合、 以下のように記載します。

http_requests_total{job="prometheus"}

選択する際には、以下のオペレータを利用できます。

=一致する
!=一致しない
=~正規表現で一致
!~正規表現に一致しない

メトリクスの種類

Prometheusで扱うメトリクスは以下の4つがあります。

Guageゲージ
Counterカウンター
Histgramヒストグラム
Summaryサマリー

HistogramとSummaryの違いは、 公式ドキュメントのHISTOGRAMS AND SUMMARIES を参照してください。

実際の場面ではレイテンシの計算でHistgramやSummaryが利用されますが、次のリンクの説明が詳しいです。 レイテンシーを計算する技術の話

集約

ベクトルを集計する場合は、集計の演算子を利用できます。

例えば、Instant vectorを集約するのに、以下のような集計演算子を利用できます。

  • sum
  • min
  • max
  • avg

例えば、httpリクエストの合計を求める場合は以下のように記載します。

sum(http_requests_total)

参考: Prometheus ~ Aggregation operator

関数

時系列に対して様々な処理を行う関数が存在します。

関数は~instant vector~か~range vector~のどちらかを受け取ります。 どちらを受け取るかは、ドキュメントに型として記載されています。

例えば、要素があるか判定を行う~absent(v instant-vector)~は、instant-vectorを受け取り、 ある範囲で要素があるか判定を行う~absentovertime(v range-vector)~は、range-vectorを受け取ります。

例えば、~exp(v instant-vector)~は指数関数を計算します。

他の関数は以下の公式ドキュメントを参照してください。

参考: Prometheus ~ functions

rate / irate関数

rate関数やirate関数を利用すると、1秒間の間にカウンターのメトリクスがどれだけ増加したかを計算することができます。

rate(v range-vector) 動きが遅いカウンターの平均増加率を計算します。

irate(v range-vector) 動きが早いカウンターの瞬間増加率を平均します。極端に値が上下する場合は、rateでは観測できなくなることがあるため、こちらを利用します。

カウンターのリセット対策 カウンターは再起動でリセットしてしまうことがありますが、rateやirateを使う場合は自動的に調整されます。集計演算子を利用する場合は、rateかirateを利用して、カウンターリセットの影響を受けないようにします。

例: sum

sum(rate(http_request_total[5m]))

時系列の集約関数

集約名overtimeの形式の関数が定義されており<aggregation>overtimeの形式で時間軸で集約処理を行って~instant vector~を返却することができます。

avg_over_time(range_vector)指定した範囲の平均値
min_over_time(range-vector)指定した範囲の最小値
max_over_time(range-vector)指定した範囲の最大値
sum_over_time(range-vector)指定した範囲の合計値

参考

Prometheus公式ドキュメント

Alertmanagerとは

Alertmanagerを利用すると、Prometheusで監視しているメトリクスが一定の条件を満たしたとき、メールやSlackなどにアラートを通知することができます。

インストール

こちらのリポジトリを利用すると、Docker ComposeからPrometheusを起動可能です。 → docker-composeのインストール

以下で起動します。

$ git clone https://github.com/vegasbrianc/prometheus
$ cd prometheus
$ docker-compose up

それぞれ以下のポートで起動します。

Prometheushttp://localhost:9090/
Alertmanagerhttp://localhost:9093/

Slackの通知設定

以下を参考に、Prometheus用のAppsを作成し、Incoming Webhookを有効にして、Webhook URLを取得します。

alertmanager/config.yml~に~username, channel, apiurlを指定します。

route:
 receiver: 'slack'

receivers:
 - name: 'slack'
   slack_configs:
       - send_resolved: true
         username: '<username>'
         channel: '<channel name>'
         api_url: '<webhook url>'

Alertmanagerの設定

アラートのルールは以下のように設定を行います。

groups:
- name: <ルール名>
  rules:
  - alert: <アラート名>
    expr: <クエリ式>
    for: <条件の期間>
    severity: <深刻度>
    annotations:
      description: "アラートの説明"
      summary: "サマリー"
alertアラート名を記載
exprクエリ式を指定 (PromQLで書く)
for条件の継続時間を指定 (秒:s, 分:m, 時間:h, 日:d など)
severityアラートの深刻度を指定(critical, warningなど)
annotationsアラートの説明、サマリーなど

今回のリポジトリでは、prometheus/alert.rulesにアラートのルールが記載されています。

以下のように設定を変更すると、exprが1のためアラートが発火します。

groups:
- name: rule-example
  rules:
  - alert: alert-example
    expr: 1
    for: 1m

設定の変更が終わったら、反映を行うためにDockerコンテナの再起動を行います。

再度開いてしばらく待つと、http://localhost:9090/alerts でアラートがactiveとなり発火していることがわかります。

Image

statusは、PENDING、FIRINGのどちらかとなり、FIRINGは発火を表します。

Alertmanagerの方では発火したアラートが表示されます。

Image

SlackにもPrometheusから通知が行われます。

Image

参考

目的

GitHubのIssueは古いものが残りがちなので、古くなったIssueを自動でクローズしたい。

Stale

Staleを利用すると、放置されたissueを自動的に閉じることができます。

以下のようにgithub-actionsのbotがstaleします。(動作確認用にすぐにstaleとcloseされるようにしています)

Image

インストール手順

GitHubのリポジトリのActionsタブから、テンプレートを作成できます。

Image

画面の下の方の Automate every step in your process 以下にStaleが存在します。

Image

Set up this workflow のボタンを押すと、テンプレート作成画面に遷移します。

Image

デフォルトで60日でstale、7日でstaleされたissueがcloseされます。

オプションで各種設定を変更することが可能です。 例えば、staleまで30日、closeまで5日に変更したい場合は.github/workflows/stale.ymlに以下のように記載します。

name: Mark stale issues and pull requests

on:
  schedule:
  - cron: "30 1 * * *"

jobs:
  stale:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/stale@v3
      with:
        repo-token: ${{ secrets.GITHUB_TOKEN }}
        stale-issue-message: 'Stale issue message'
        stale-pr-message: 'Stale pull request message'
        stale-issue-label: 'no-issue-activity'
        stale-pr-label: 'no-pr-activity'
        days-before-stale: 30
        days-before-close: 5

その他の設定は、リポジトリのREADME.mdに記載されています。

はじめに

定期的に実行されるジョブを作りたいとき、Cloud Functions for Firebaseを利用することができます。 プログラムを決められた時刻に定期的に実行したい場合に役に立ちます。

利用例

  • Firestoreで1日1回のユーザーデータの集計を行う
  • 不要なデータを消去する

仕組み

内部的には、cronジョブサービスのCloud Schedulerによって、Pub/Subトピックにメッセージが送信されます。Cloud Functionはこのトピックをサブスクライブすることで関数実行のスケジューリングを行います。

インストール

Blazeプランの移行

まず、Firebaseの無料プランでは利用できないため、従量課金のBlazeプランに以降する必要があります。

ツールのインストール

$ npm install -g firebase-tools

ログイン

$ firebase login

初期化

$ firebase init functions

記述方法

regionを指定する場合は、functions.regionに対象の地域を指定します。以下は1分毎に実行する例です。

間隔の指定はcronジョブ形式App Engineのcron構文で行います。

exports.scheduledFunction = functions.region('asia-northeast1')
  .pubsub.schedule('every 1 minutes')
  .timeZone('Asia/Tokyo')
  .onRun(() => {
    console.info('hello world');
  });

デプロイ

$ firebase deploy --only functions

デプロイすると以下のようにFunctionsに定期実行ジョブが追加されます。

Image

参考

Firebase公式ドキュメント

Percona XtraDB Clusterとは

Percona社のオープンソースのデーターベースクラスタシステム。 MySQLをGalera Clusterでクラスタ化しており、全てのノードがmaster/masterの構成、すなわちマルチマスターとなり、どのノードもリードとライトが可能。

テーブル変更など、DDL実行に注意する

PXCはマルチマスターであるため、DDLの実行に注意が必要。 DDLの実行方法には、TOI (Total Order Isolation)とRSU (Rolling Schema Upgrade) がある。

TOIでは、全ノードでDDLが同時に実行されるが、DDLが完了するまではトランザクションが待ち状態になる。テーブルが巨大な場合、ALTER TABLEをサービス稼働中に実行しないほうが良い。

RSUでは、DDL実行中のノードがクラスタから分離されDDLを実行する。復帰した後に更新差分が適用されて、クラスタに復帰する。1ノードずつ更新することでサービス停止せずにDDLを実行できるが、DDLが反映されたカラムの削除や変更などは実行できない。

サービス停止せずに、削除や変更などを行いたいとき、pt-online-schema-changeを利用することも可能。ただしサーバーの負荷が高くなる。

参考

購入したもの

FLEXISPOT EN1B

  • 定価: 33100円
  • 当時定価の10%OFFで、30000円だった

https://amzn.asia/d/eNWTOYv

電動ドライバー

  • FlexiSpotに天板を取り付ける際に必要になる。
  • あまり大きいドリルを買っても持て余しそうなので、こちらを購入。
  • 2980円

https://amzn.asia/d/3yu75sk

天板 アカシア

  • ホームセンターに行って購入した
  • サイズ: 縦65cm 横140cm (カット後)
  • 約10000円
  • カット代: 160円
  • 送料: 2000円
  • 木材: 7000円

サンドペーパー

  • 木材をやすりがけする際に必要
  • 主に600を利用した
  • 980円

https://amzn.asia/d/cjK8AGH

ワトコワックス

塗るために「ブリキの小物いれ」と「はけ」も同時に購入した

組み立て

ヤスリがけ

  • オイルを塗る前に全体をヤスリがけする
  • 表面と側面を手で触ってすべすべになるまでヤスリがけする

オイル塗り

  • ヤスリがけが終わったら、ワトコオイルを塗っていく
  • 全体に塗ったら15分程度時間をおき、余分なオイルを拭く、その後1時間程度乾かす
  • 乾いたらもう一度同じ手順で、オイル塗りを行う
  • 最後にサンドペーパーでやすりがけを行い、オイルを拭き取る
オイル塗り

オイル塗り

乾燥

乾燥

オイル後

オイル後

組み立て

  • 天板の取り付けでは、電動ドライバーを利用する
  • 一人だと大変なので、できるだけ二人以上で組み立てた方が良い

完成

椅子に座るとき

スタンディングのとき

28件中 21 - 28件を表示