TECH PLAY

電通総研

電通総研 の技術ブログ

822

電通国際情報サービス 、オープン イノベーション ラボの 比嘉康雄 です。 Stable Diffusion(というよりdiffusers)でTPU(JAX / Flax)を使った並列実行バージョンがリリースされたので、早速試してみました。 オリジナルのNotebook はこちら。 僕が作ったNotebook はこちら。 今回は、TPUを使うので、 Google Colabに特化しています。自分で1から試す方は、メニューのEdit -> Notebook settingsでTPUを使うように設定してください。 Stable Diffusionのおすすめコンテンツはこちら。 Waifu Diffusion 1.3.5_80000 v2.1 金髪美女写真 v2.1 美少女アニメ画 v2.1 AUTOMATIC1111 v2.0 美少女イラスト v1.5 美少女画検証 美少女アニメ画改善版 美少女を高確率で出す呪文編 美少女アニメ画編 美少女写真編 女性イラスト編 魅惑的な女アニメ画(トゥーンレンダリング)編 長い呪文は切り捨てられる編 必要なモジュールのインストール 必要なモジュールのインポート huggingfaceにログイン pipelineとparamsの作成 show_images()の定義 show_images()の実行 まとめ 仲間募集 Stable Diffusionの全コンテンツ 必要なモジュールのインストール 次のようにして必要なモジュールをインストールします。 !pip install --upgrade jax jaxlib !pip install flax transformers ftfy !pip install diffusers==0.5.1 必要なモジュールのインポート 次のようにして必要なモジュールをインポートします。 import jax.tools.colab_tpu jax.tools.colab_tpu.setup_tpu('tpu_driver_20221011') import jax import numpy as np import jax import jax.numpy as jnp from pathlib import Path from jax import pmap from flax.jax_utils import replicate from flax.training.common_utils import shard from PIL import Image from IPython.display import display import random from huggingface_hub import notebook_login from diffusers import FlaxStableDiffusionPipeline huggingfaceにログイン huggingfaceにログインします。 まだ、huggingfaceの トーク ンを取得していない場合は、 huggingface でユーザー登録を行い、 トーク ンを取得してください。 if not (Path.home()/'.huggingface'/'token').exists(): notebook_login() pipelineとparamsの作成 次のようにして、pipelineとparamsを作成します。 pipeline, params = FlaxStableDiffusionPipeline.from_pretrained( "CompVis/stable-diffusion-v1-4", revision="bf16", dtype=jnp.bfloat16, ) show_images()の定義 呪文から、画像を生成するための関数を作成します。現状のColabでは、TPUは、8個まで並列実行できます。 def show_images(prompt): seed = random.randrange(1000000) rng = jax.random.PRNGKey(seed) rng = jax.random.split(rng, jax.device_count()) p_params = replicate(params) prompt = [prompt] * jax.device_count() prompt_ids = pipeline.prepare_inputs(prompt) prompt_ids = shard(prompt_ids) images = pipeline(prompt_ids, p_params, rng, jit=True)[0] images = images.reshape((jax.device_count(), ) + images.shape[-3:]) images = pipeline.numpy_to_pil(images) for image in images: display(image) オリジナルのNotebookでは、8つの画像がGridになっていて、個別にダウンロードできないので、個別にダウンロードできるようにしてみました。 show_images()の実行 次の呪文で、show_images()を実行してみました。 prompt = "illustration of beaultiful girl detailed beautiful face detailed perfect pupil of eyes detailed mouth detailed shoulders detailed chest highly detailed artstation deviantart concept art award winning fantasy scene fantasy composition cinematic lighting ray tracing 8k" show_images(prompt) 今回の呪文(横長、コピー&ペースト用) illustration of beaultiful girl detailed beautiful face detailed perfect pupil of eyes detailed mouth detailed shoulders detailed chest highly detailed artstation deviantart concept art award winning fantasy scene fantasy composition cinematic lighting ray tracing 8k 閲覧用呪文(改行版) illustration of beaultiful girl detailed beautiful face detailed perfect pupil of eyes detailed mouth detailed shoulders detailed chest highly detailed artstation deviantart concept art award winning fantasy scene fantasy composition cinematic lighting ray tracing 8k 画像出力結果 全く選別していない、出力されたそのままの画像です。 まとめ Stable Diffusionが8つの画像を同時生成できるようになったのは、画期的ではないでしょうか。しかも、TPUを使うので、 GPU を使い切っていても使うことができるのです。実際僕は、 Google Colab Proの GPU を現在使い切ってしまっていて、 GPU を使うことはできないのですが、TPUは使えました。 追記: その後、あっという間に、TPUの無料枠分を使い切ってしまいました。 仲間募集 私たちは同じグループで共に働いていただける仲間を募集しています。 現在、以下のような職種を募集しています。 ソリューションアーキテクト AIエンジニア Stable Diffusionの全コンテンツ 人物写真編 レンズ編 画像タイプ編 美少女アニメ画編 美少女写真編 女性イラスト編 美しい夜空を見渡す男編 魅惑的な女アニメ画(トゥーンレンダリング)編 美少女を高確率で出す呪文編 長い呪文は切り捨てられる編 蒸気機関が高度に発達したレトロなアニメ(スチームパンク)の世界観編 A as Bの呪文による画像合成編 かわいい動物の擬人化編 バベルの塔のイラスト編 TPU版の使い方 美少女アニメ画改善版 v1.5 美少女画検証 東京タワーの写真 折り紙合体変形ロボ v2.0 美少女イラスト v2.1 AUTOMATIC1111 v2.1 美少女アニメ画 v2.1 金髪美女写真 Waifu Diffusion 1.3.5_80000 執筆: @higa ( Shodo で執筆されました )
アバター
電通国際情報サービス 、オープン イノベーション ラボの 比嘉康雄 です。 Stable Diffusionシリーズ、今回のテーマは バベルの塔 のイラストです。 今回は建物のイラストの呪文を学びましょう。 Stable Diffusionのおすすめコンテンツはこちら。 Waifu Diffusion 1.3.5_80000 v2.1 金髪美女写真 v2.1 美少女アニメ画 v2.1 AUTOMATIC1111 v2.0 美少女イラスト v1.5 美少女画検証 美少女アニメ画改善版 美少女を高確率で出す呪文編 美少女アニメ画編 美少女写真編 女性イラスト編 魅惑的な女アニメ画(トゥーンレンダリング)編 長い呪文は切り捨てられる編 steampunk illustration of babel tower highly detailed shot from above steampunk illustration of babel tower shot from below steampunk illustration of babel tower highly detailed steampunk illustration of illuminated babel tower highly detailed top shot from below night まとめ 仲間募集 Stable Diffusionの過去コンテンツ steampunk illustration of babel tower highly detailed shot from above steampunkは、 蒸気機関 が高度に発達したレトロなアニメの世界を召喚する呪文です。詳しく知りたい方は 蒸気機関が高度に発達したレトロなアニメ(スチームパンク)の世界観編 を参照してください。 steampunkのイラストは、通常のイラストより、クオリティが高いことが多いため、描画対象がsteampunkの世界観にあっているときは、積極的に利用しましょう。 babel towerが バベルの塔 です。 highly detailedで、描画対象を詳細に描画します。 shot from aboveで、上の方から描画します。 建物は、一般的に地味であることが多い( バベルの塔 は派手ですが)ので、建物を描画するときは、通常と違うアングルを選びましょう。人は見たことのないものに心惹かれます。 特に高い建物を上から描画するためには、カメラを空中に配置する必要があります。ほとんどの人にとってリアルに体験したことのないアングルです。 今回の呪文(横長、コピー&ペースト用) steampunk illustration of babel tower highly detailed shot from above artstation deviantart concept art award winning fantasy scene fantasy composition fantasy lighting 閲覧用呪文(改行版) steampunk illustration of babel tower highly detailed shot from above artstation deviantart concept art award winning fantasy scene fantasy composition fantasy lighting 出力結果 steampunkをつけていない通常のイラスト これも良いですね。ただ、これを出力するのに、何十回もトライしました。 steampunk illustration of babel tower shot from below shot from belowで下から描画します。highly detailedがないことに注目してください。下から描画する場合、highly detailedがあると、 バベルの塔 に近づきすぎて、うまく描画できません。 今回の呪文(横長、コピー&ペースト用) steampunk illustration of babel tower shot from below artstation deviantart concept art award winning fantasy scene fantasy composition fantasy lighting 閲覧用呪文(改行版) steampunk illustration of babel tower shot from below artstation deviantart concept art award winning fantasy scene fantasy composition fantasy lighting 出力結果 shot from belowで塔を描画する場合、空が描画されます。空のクオリティが高いこともチェックポイントに入れてください。 highly detailed shot from belowの出力結果 highly detailedで バベルの塔 に近づきすぎているので、shot from belowの呪文の効果がほとんどないことがわかります。 steampunk illustration of babel tower highly detailed 今度は、shot from aboveもshot from belowもない、通常アングルのイラストも見てみましょう。 今回の呪文(横長、コピー&ペースト用) steampunk illustration of babel tower highly detailed artstation deviantart concept art award winning fantasy scene fantasy composition fantasy lighting 閲覧用呪文(改行版) steampunk illustration of babel tower highly detailed artstation deviantart concept art award winning fantasy scene fantasy composition fantasy lighting 出力結果 highly detailed shot from belowのものとほとんど同じですね。これが通常のアングルです。 shot from aboveとshot from belowの画像と比較して、アングルの違いを実感しましょう。 steampunk illustration of illuminated babel tower highly detailed top shot from below night 応用もやっておきましょう。 建物にilluminatedで照明を当てて、時間を夜(night)にしてみましょう。 今回の呪文(横長、コピー&ペースト用) steampunk illustration of illuminated babel tower highly detailed shot from below night artstation deviantart concept art award winning fantasy scene fantasy composition fantasy lighting 閲覧用呪文(改行版) steampunk illustration of illuminated babel tower highly detailed shot from below night artstation deviantart concept art award winning fantasy scene fantasy composition fantasy lighting 出力結果 まとめ 今回は、建物のイラストを退屈なものにしないために、shot from above(上から描画)、shot from below(下から描画)の呪文を学びました。 次回は、 美少女アニメ画改善版編 です。 仲間募集 私たちは同じグループで共に働いていただける仲間を募集しています。 現在、以下のような職種を募集しています。 ソリューションアーキテクト AIエンジニア Stable Diffusionの過去コンテンツ 人物写真編 レンズ編 画像タイプ編 美少女アニメ画編 美少女写真編 女性イラスト編 美しい夜空を見渡す男編 魅惑的な女アニメ画(トゥーンレンダリング)編 美少女を高確率で出す呪文編 長い呪文は切り捨てられる編 蒸気機関が高度に発達したレトロなアニメ(スチームパンク)の世界観編 A as Bの呪文による画像合成編 かわいい動物の擬人化編 バベルの塔のイラスト編 TPU版の使い方 美少女アニメ画改善版 v1.5 美少女画検証 東京タワーの写真 折り紙合体変形ロボ v2.0 美少女イラスト v2.1 AUTOMATIC1111 v2.1 美少女アニメ画 v2.1 金髪美女写真 Waifu Diffusion 1.3.5_80000 執筆: @higa ( Shodo で執筆されました )
アバター
電通国際情報サービス 、オープン イノベーション ラボの 比嘉康雄 です。 Stable Diffusionシリーズ、今回のテーマは バベルの塔 のイラストです。 今回は建物のイラストの呪文を学びましょう。 Stable Diffusionのおすすめコンテンツはこちら。 Waifu Diffusion 1.3.5_80000 v2.1 金髪美女写真 v2.1 美少女アニメ画 v2.1 AUTOMATIC1111 v2.0 美少女イラスト v1.5 美少女画検証 美少女アニメ画改善版 美少女を高確率で出す呪文編 美少女アニメ画編 美少女写真編 女性イラスト編 魅惑的な女アニメ画(トゥーンレンダリング)編 長い呪文は切り捨てられる編 steampunk illustration of babel tower highly detailed shot from above steampunk illustration of babel tower shot from below steampunk illustration of babel tower highly detailed steampunk illustration of illuminated babel tower highly detailed top shot from below night まとめ 仲間募集 Stable Diffusionの過去コンテンツ steampunk illustration of babel tower highly detailed shot from above steampunkは、 蒸気機関 が高度に発達したレトロなアニメの世界を召喚する呪文です。詳しく知りたい方は 蒸気機関が高度に発達したレトロなアニメ(スチームパンク)の世界観編 を参照してください。 steampunkのイラストは、通常のイラストより、クオリティが高いことが多いため、描画対象がsteampunkの世界観にあっているときは、積極的に利用しましょう。 babel towerが バベルの塔 です。 highly detailedで、描画対象を詳細に描画します。 shot from aboveで、上の方から描画します。 建物は、一般的に地味であることが多い( バベルの塔 は派手ですが)ので、建物を描画するときは、通常と違うアングルを選びましょう。人は見たことのないものに心惹かれます。 特に高い建物を上から描画するためには、カメラを空中に配置する必要があります。ほとんどの人にとってリアルに体験したことのないアングルです。 今回の呪文(横長、コピー&ペースト用) steampunk illustration of babel tower highly detailed shot from above artstation deviantart concept art award winning fantasy scene fantasy composition fantasy lighting 閲覧用呪文(改行版) steampunk illustration of babel tower highly detailed shot from above artstation deviantart concept art award winning fantasy scene fantasy composition fantasy lighting 出力結果 steampunkをつけていない通常のイラスト これも良いですね。ただ、これを出力するのに、何十回もトライしました。 steampunk illustration of babel tower shot from below shot from belowで下から描画します。highly detailedがないことに注目してください。下から描画する場合、highly detailedがあると、 バベルの塔 に近づきすぎて、うまく描画できません。 今回の呪文(横長、コピー&ペースト用) steampunk illustration of babel tower shot from below artstation deviantart concept art award winning fantasy scene fantasy composition fantasy lighting 閲覧用呪文(改行版) steampunk illustration of babel tower shot from below artstation deviantart concept art award winning fantasy scene fantasy composition fantasy lighting 出力結果 shot from belowで塔を描画する場合、空が描画されます。空のクオリティが高いこともチェックポイントに入れてください。 highly detailed shot from belowの出力結果 highly detailedで バベルの塔 に近づきすぎているので、shot from belowの呪文の効果がほとんどないことがわかります。 steampunk illustration of babel tower highly detailed 今度は、shot from aboveもshot from belowもない、通常アングルのイラストも見てみましょう。 今回の呪文(横長、コピー&ペースト用) steampunk illustration of babel tower highly detailed artstation deviantart concept art award winning fantasy scene fantasy composition fantasy lighting 閲覧用呪文(改行版) steampunk illustration of babel tower highly detailed artstation deviantart concept art award winning fantasy scene fantasy composition fantasy lighting 出力結果 highly detailed shot from belowのものとほとんど同じですね。これが通常のアングルです。 shot from aboveとshot from belowの画像と比較して、アングルの違いを実感しましょう。 steampunk illustration of illuminated babel tower highly detailed top shot from below night 応用もやっておきましょう。 建物にilluminatedで照明を当てて、時間を夜(night)にしてみましょう。 今回の呪文(横長、コピー&ペースト用) steampunk illustration of illuminated babel tower highly detailed shot from below night artstation deviantart concept art award winning fantasy scene fantasy composition fantasy lighting 閲覧用呪文(改行版) steampunk illustration of illuminated babel tower highly detailed shot from below night artstation deviantart concept art award winning fantasy scene fantasy composition fantasy lighting 出力結果 まとめ 今回は、建物のイラストを退屈なものにしないために、shot from above(上から描画)、shot from below(下から描画)の呪文を学びました。 次回は、 美少女アニメ画改善版編 です。 仲間募集 私たちは同じグループで共に働いていただける仲間を募集しています。 現在、以下のような職種を募集しています。 ソリューションアーキテクト AIエンジニア Stable Diffusionの過去コンテンツ 人物写真編 レンズ編 画像タイプ編 美少女アニメ画編 美少女写真編 女性イラスト編 美しい夜空を見渡す男編 魅惑的な女アニメ画(トゥーンレンダリング)編 美少女を高確率で出す呪文編 長い呪文は切り捨てられる編 蒸気機関が高度に発達したレトロなアニメ(スチームパンク)の世界観編 A as Bの呪文による画像合成編 かわいい動物の擬人化編 バベルの塔のイラスト編 TPU版の使い方 美少女アニメ画改善版 v1.5 美少女画検証 東京タワーの写真 折り紙合体変形ロボ v2.0 美少女イラスト v2.1 AUTOMATIC1111 v2.1 美少女アニメ画 v2.1 金髪美女写真 Waifu Diffusion 1.3.5_80000 執筆: @higa ( Shodo で執筆されました )
アバター
はいどーもー! X イノベーション 本部の宮澤響です! X イノベーション 本部では、有志メンバーで論文輪読会を月次開催しています。 (詳細は こちらの記事 をご参照ください) 本記事では、私が担当した先日の論文輪読会で紹介した論文について、箇条書きベースでごくごく簡単にまとめます! どんな論文? 論文内容紹介 目的 手法 実験参加者 実験に用いたテキスト 実験手順 結果 RQ1:ノートのとり方は文章理解にどのような影響を与えるか? RQ2:ノートのとり方はノートの内容にどのような影響を与えるか? RQ3:ノートのとり方はメタ理解の判断にどのような影響を与えるか? RQ4:音声ノートを学習活動に利用することの利点と課題は何か? 考察 RQ1:ノートのとり方は文章理解にどのような影響を与えるか? RQ2:ノートのとり方はノートの内容にどのような影響を与えるか? RQ3:ノートのとり方はメタ理解の判断にどのような影響を与えるか? RQ4:音声ノートを学習活動に利用することの利点と課題は何か? 限界と今後の課題 結論 個人的な感想など この論文を題材に選んだきっかけ 論文内容に関して まとめ どんな論文? 書誌情報は以下です。 Anam Ahmad Khan, Sadia Nawaz, Joshua Newn, Ryan M. Kelly, Jason M. Lodge, James Bailey, and Eduardo Velloso. 2022. To type or to speak? The efect of input modality on text understanding during note-taking. In CHI Conference on Human Factors in Computing Systems (CHI ’22), April 29-May 5, 2022, New Orleans, LA, USA. ACM , New York, NY, USA, 15 pages. https://doi.org/10.1145/3491102.3501974 一言で言えば、ノートのとり方(キーボードと音声のどちらでノートをとるか)が文章理解に与える影響について検討した論文です。 論文の全体像を1枚にまとめたスライドがこちらです。 なお、こちらは「落合メソッド」「落合フォーマット」などと呼ばれる論文のまとめ方( 参考 )を簡略化したものです。 論文内容紹介 ここから実際に論文の内容を大まかなセクションごとに紹介します。 目的 以下のリサーチク エス チョン(以下、RQ)を明らかにすることを目的としています。 RQ1:ノートのとり方は文章理解にどのような影響を与えるか? RQ2:ノートのとり方はノートの内容にどのような影響を与えるか? RQ3:ノートのとり方はメタ理解の判断にどのような影響を与えるか? RQ4:音声ノートを学習活動に利用することの利点と課題は何か? 手法 2つのテキストに対してキーボードまたは音声でノートをとってもらい、テキストの内容についての理解度テストを実施するという実験を実施しました。 実験参加者 実験参加者は、以下のような属性をもつ、オーストラリアの大学院生60名でした。 属性 内訳 年齢 21~35歳(平均年齢27.04歳) 性別 男性31名、女性29名 母語 英語16名、イタリア語3名、中国語15名、 ウルドゥー語 4名、 ヒンディー語 5名、 ペルシャ語 7名、 シンハラ語 6名、韓国語2名 学歴 修士 課程在籍者29名、博士課程在籍者31名 実験に用いたテキスト 実験には、以下の2つのテキストを使用しました。 ブレーキに関する英語の文章(792語) ポンプに関する英語の文章(850語) 実験手順 実験は、全てオンラインで、下図のような流れで実施されました。 Briefing・Demo 実験の説明 ノートをとる操作の実演、練習 Pre-test テキストに関する予備知識を測定するためのテスト 多肢選択問題6問 4つの正答候補+“I don’t know”の計5択 Note taking task テキストを読んでノートをとるタスク 2つのConditionで異なるテキストを使用 ブレーキ ポンプ 2つのConditionで異なる入力形式を利用 キーボード 音声 時間制限なし Survey 主観的な項目として以下を報告 確信度 Post-testで正答できる自信がどれくらいあるか(0~100) 関心度 テキストの内容にどれくらい関心をもてるか(1~7) 難易度 テキストの内容がどれくらい難しいと感じるか(1~7) Distracting task 指定された数から7ずつ減算していく過程を報告するタスク 2分間 Condition1では200から 200、193、186、… Condition2では203から 203、196、189、… Post-test テキストの理解度を測定するためのテスト 事実問題12問(多肢選択) 1つのア イデア ユニット(「軽自動車はこの種類をブレーキを使っている」、「この村ではこのポンプが使われている」などの「○○が××だ」のような命題の単位)に関する問題 4つの正答候補+“I don’t know”の計5択 推論問題6問(多肢選択) 2つ以上のア イデア ユニットに関する問題 4つの正答候補+“I don’t know”の計5択 Interview・Debrief 操作感などに関するインタビュー 全体のまとめ 結果 RQ1:ノートのとり方は文章理解にどのような影響を与えるか? キーボードでノートをとった場合と音声でノートをとった場合のPost-testの成績(以下、成績)の間に有意差は見られませんでした。 ノートのとり方(キーボード、音声)と問題種別(事実問題、推論問題)の間の交互作用が有意でした。そのため、一対比較を実施したところ、以下の結果が得られました。 ノートのとり方に関して キーボード:事実問題の成績が推論問題の成績よりも有意に高い 音声:事実問題と推論問題の成績の間に有意差なし 問題種別に関して 事実問題:キーボードと音声の成績の間に有意差なし 推論問題:音声の成績がキーボードの成績よりも有意に高い RQ2:ノートのとり方はノートの内容にどのような影響を与えるか? 音声でとったノートに含まれるア イデア ユニットの数が、キーボードでとったノートに含まれるア イデア ユニットの数よりも有意に多いことが分かりました。 音声でとったノートに含まれるエラボレーション(例、類推、個人的な経験)の数が、キーボードでとったノートに含まれるエラボレーションの数よりも有意に多いことが分かりました。 ノートのとり方が成績に与える直接効果は有意であることが分かりました。 ア イデア ユニットの数を媒介としてノートのとり方が成績に与える間接効果は有意であることが分かりました。 エラボレーションの数を媒介としてノートのとり方が成績に与える間接効果は有意ではないことが分かりました。 RQ3:ノートのとり方はメタ理解の判断にどのような影響を与えるか? キーボードでノートをとった場合と音声でノートをとった場合のメタ理解の正確性(確信度と成績の差の絶対値)の間に有意差は見られませんでした。 RQ4:音声ノートを学習活動に利用することの利点と課題は何か? 参加者の意見をグルーピングした結果、以下のように6カテゴリーの利点と5カテゴリーの課題が浮かび上がりました。 利点 簡便な入力 瞬間的な思考の記録 共同的なノート作成 リハーサルと エンコード 内容への注意 能動的な説明と反映 課題 録音と再生 編集 検索と見直し 構造化とスケッチ 共有空間での利用 考察 RQ1:ノートのとり方は文章理解にどのような影響を与えるか? 音声でノートをとることは、テキストの情報に基づく推論を手助けし、情報を横断してア イデア やコンセプトを結びつけている可能性が示唆されました。 推論問題では複数のア イデア ユニットを結びつける必要があり、テキストに対する概念的な理解が試される可能性が示唆されました。 音声でとったノートに関して、「音声でとったノートはより多くのア イデア ユニットを議論できる→ノートをとりながらテキストについてより良い推論を行える→テキストに対する概念的理解が深まる→推論問題の成績が高くなる」という一連の流れが示唆されました。 RQ2:ノートのとり方はノートの内容にどのような影響を与えるか? 音声でノートをとる際は人称直示詞(人称代名詞)を多く使用するため、仮想の聴衆に話しかけるような、社会的関与が高い内容になる可能性が示唆されました。 社会的関与の高さがより多くのア イデア ユニットの議論とエラボレーションに繋がる可能性が示唆されました。 RQ3:ノートのとり方はメタ理解の判断にどのような影響を与えるか? 音声でノートをとることは、テキストに対する概念的理解を増加させても、学習パフォーマンスの知覚には影響を与えない可能性が示唆されました。 RQ4:音声ノートを学習活動に利用することの利点と課題は何か? 仮想の聴衆に話しかけることで豊かな表現が生まれる可能性が示唆されました。 テキストを読みながら同時にノートをとれるため、学習内容自体に注意を向けやすい可能性が示唆されました。 音声でノートをとることのメリットは、ノートを見直すときよりもノートをとるときの方がより明確になる可能性が示唆されました。 音声でとるノートの利便性はデジタル学習環境で発揮されやすい可能性が示唆されました。 限界と今後の課題 実験の限界と、そこから考えられる今後検討すべき課題は以下のとおりです。 限界 課題 実験に使用したテキストがやや複雑(難易度が高い)だと認識されていた より複雑でないテキストに対して一般化されるかの調査 ノートのとり方が文章理解に与える短期的な影響を調査するに留まった より長期的な影響の調査 キーボードと音声で見直しに要する時間が異なり交絡因子となるおそれがあるため、Post-testの前にノートを見直す時間を設定しなかった 入力(キーボード/音声)と出力(文章/音声)の組み合わせによる実験 大学院生を対象として管理された環境で実施した 異なる参加者集団や、より現実的な環境での実験 音声でとるノートは空間的な構造化を必要としない情報のみしか記録できない 数式や マインドマップ など、視覚的な表現が有効な場合の調査 結論 ノートをとる際の入力形式をキーボードから音声へ変更することで、より多くのア イデア ユニットの議論とエラボレーションを行える可能性が示唆されました。 基本的な知識を得ることが目的であれば、キーボードと音声どちらでノートをとった場合も同じようなパフォーマンスを示す可能性が示唆されました。 推論することによってより深く概念を理解することが目的であれば、音声でノートをとる方がより効果的である可能性が示唆されました。 キーボードと音声どちらでノートをとった場合も、文章理解におけるメタ理解の判断には同程度の効果をもたらす可能性が示唆されました。 音声でとるノートはデジタルテキストの概念的な理解を可能にするため、デジタル学習環境に組み込める可能性が示唆されました。 個人的な感想など この論文を題材に選んだきっかけ この論文を題材に選んだ理由としては、タイトルに惹かれたというのが大きいです。 というのも、私は教育関係の事柄に強い興味をもっています。 また、私は学生時代(ほんの2年半前ですが)、音や音楽に関する研究をしていました。 そのため、「ノートをとる」という部分が私の教育センサーに、「音声」の部分が私の音・音楽センサーに引っかかり、 アブスト ラク トに目を通しても面白そうな内容だったので、今回の論文輪読会の題材としました。 論文内容に関して いわゆる典型的なIMRAD形式( 参考 )だったこともあり、論文の構成が分かりやすかったです。 また、なるほど確かにと思える部分が多かったです。 例えば、音声でノートをとる場合はテキストから視線を逸らさずにノートをとれる、音声でとったノートは編集、検索、見直しに課題がある、などは、確かにそうだなぁという感覚でした。 疑問点として、「社会的関与の高さがより多くのア イデア ユニットの議論とエラボレーションに繋がる」というのは、日本語の場合でも同様なのか気になりました。 これは、日本語は英語と違い主語や目的語を省略できるため、理由として考察されていた人称直示詞があまり登場しないのでは、と思ったためです。 まとめ 本記事では、論文輪読会で私が紹介した論文について簡単にまとめました。 論文輪読会は、知見のインプットとアウトプット、両者の力を伸ばせる取り組みですので、皆さんの会社や部署などでも実施してみてはいかがでしょうか? 最後までお読みいただき、本当にありがとうございました! 私たちは同じグループで共に働いていただける仲間を募集しています。 現在募集している職種は以下です。 ソリューションアーキテクト 論文輪読会をはじめとした各種勉強会に気軽に参加できるような環境で働くことに興味のある皆様、ぜひご応募お待ちしています! 執筆: @miyazawa.hibiki 、レビュー: @yamashita.tsuyoshi ( Shodo で執筆されました )
アバター
はいどーもー! X イノベーション 本部の宮澤響です! X イノベーション 本部では、有志メンバーで論文輪読会を月次開催しています。 (詳細は こちらの記事 をご参照ください) 本記事では、私が担当した先日の論文輪読会で紹介した論文について、箇条書きベースでごくごく簡単にまとめます! どんな論文? 論文内容紹介 目的 手法 実験参加者 実験に用いたテキスト 実験手順 結果 RQ1:ノートのとり方は文章理解にどのような影響を与えるか? RQ2:ノートのとり方はノートの内容にどのような影響を与えるか? RQ3:ノートのとり方はメタ理解の判断にどのような影響を与えるか? RQ4:音声ノートを学習活動に利用することの利点と課題は何か? 考察 RQ1:ノートのとり方は文章理解にどのような影響を与えるか? RQ2:ノートのとり方はノートの内容にどのような影響を与えるか? RQ3:ノートのとり方はメタ理解の判断にどのような影響を与えるか? RQ4:音声ノートを学習活動に利用することの利点と課題は何か? 限界と今後の課題 結論 個人的な感想など この論文を題材に選んだきっかけ 論文内容に関して おわりに どんな論文? 書誌情報は以下です。 Anam Ahmad Khan, Sadia Nawaz, Joshua Newn, Ryan M. Kelly, Jason M. Lodge, James Bailey, and Eduardo Velloso. 2022. To type or to speak? The efect of input modality on text understanding during note-taking. In CHI Conference on Human Factors in Computing Systems (CHI ’22), April 29-May 5, 2022, New Orleans, LA, USA. ACM , New York, NY, USA, 15 pages. https://doi.org/10.1145/3491102.3501974 一言で言えば、ノートのとり方(キーボードと音声のどちらでノートをとるか)が文章理解に与える影響について検討した論文です。 論文の全体像を1枚にまとめたスライドがこちらです。 なお、こちらは「落合メソッド」「落合フォーマット」などと呼ばれる論文のまとめ方( 参考 )を簡略化したものです。 論文内容紹介 ここから実際に論文の内容を大まかなセクションごとに紹介します。 目的 以下のリサーチク エス チョン(以下、RQ)を明らかにすることを目的としています。 RQ1:ノートのとり方は文章理解にどのような影響を与えるか? RQ2:ノートのとり方はノートの内容にどのような影響を与えるか? RQ3:ノートのとり方はメタ理解の判断にどのような影響を与えるか? RQ4:音声ノートを学習活動に利用することの利点と課題は何か? 手法 2つのテキストに対してキーボードまたは音声でノートをとってもらい、テキストの内容についての理解度テストを実施するという実験を実施しました。 実験参加者 実験参加者は、以下のような属性をもつ、オーストラリアの大学院生60名でした。 属性 内訳 年齢 21~35歳(平均年齢27.04歳) 性別 男性31名、女性29名 母語 英語16名、イタリア語3名、中国語15名、 ウルドゥー語 4名、 ヒンディー語 5名、 ペルシャ語 7名、 シンハラ語 6名、韓国語2名 学歴 修士 課程在籍者29名、博士課程在籍者31名 実験に用いたテキスト 実験には、以下の2つのテキストを使用しました。 ブレーキに関する英語の文章(792語) ポンプに関する英語の文章(850語) 実験手順 実験は、全てオンラインで、下図のような流れで実施されました。 Briefing・Demo 実験の説明 ノートをとる操作の実演、練習 Pre-test テキストに関する予備知識を測定するためのテスト 多肢選択問題6問 4つの正答候補+“I don’t know”の計5択 Note taking task テキストを読んでノートをとるタスク 2つのConditionで異なるテキストを使用 ブレーキ ポンプ 2つのConditionで異なる入力形式を利用 キーボード 音声 時間制限なし Survey 主観的な項目として以下を報告 確信度 Post-testで正答できる自信がどれくらいあるか(0~100) 関心度 テキストの内容にどれくらい関心をもてるか(1~7) 難易度 テキストの内容がどれくらい難しいと感じるか(1~7) Distracting task 指定された数から7ずつ減算していく過程を報告するタスク 2分間 Condition1では200から 200、193、186、… Condition2では203から 203、196、189、… Post-test テキストの理解度を測定するためのテスト 事実問題12問(多肢選択) 1つのア イデア ユニット(「軽自動車はこの種類をブレーキを使っている」、「この村ではこのポンプが使われている」などの「○○が××だ」のような命題の単位)に関する問題 4つの正答候補+“I don’t know”の計5択 推論問題6問(多肢選択) 2つ以上のア イデア ユニットに関する問題 4つの正答候補+“I don’t know”の計5択 Interview・Debrief 操作感などに関するインタビュー 全体のまとめ 結果 RQ1:ノートのとり方は文章理解にどのような影響を与えるか? キーボードでノートをとった場合と音声でノートをとった場合のPost-testの成績(以下、成績)の間に有意差は見られませんでした。 ノートのとり方(キーボード、音声)と問題種別(事実問題、推論問題)の間の交互作用が有意でした。そのため、一対比較を実施したところ、以下の結果が得られました。 ノートのとり方に関して キーボード:事実問題の成績が推論問題の成績よりも有意に高い 音声:事実問題と推論問題の成績の間に有意差なし 問題種別に関して 事実問題:キーボードと音声の成績の間に有意差なし 推論問題:音声の成績がキーボードの成績よりも有意に高い RQ2:ノートのとり方はノートの内容にどのような影響を与えるか? 音声でとったノートに含まれるア イデア ユニットの数が、キーボードでとったノートに含まれるア イデア ユニットの数よりも有意に多いことが分かりました。 音声でとったノートに含まれるエラボレーション(例、類推、個人的な経験)の数が、キーボードでとったノートに含まれるエラボレーションの数よりも有意に多いことが分かりました。 ノートのとり方が成績に与える直接効果は有意であることが分かりました。 ア イデア ユニットの数を媒介としてノートのとり方が成績に与える間接効果は有意であることが分かりました。 エラボレーションの数を媒介としてノートのとり方が成績に与える間接効果は有意ではないことが分かりました。 RQ3:ノートのとり方はメタ理解の判断にどのような影響を与えるか? キーボードでノートをとった場合と音声でノートをとった場合のメタ理解の正確性(確信度と成績の差の絶対値)の間に有意差は見られませんでした。 RQ4:音声ノートを学習活動に利用することの利点と課題は何か? 参加者の意見をグルーピングした結果、以下のように6カテゴリーの利点と5カテゴリーの課題が浮かび上がりました。 利点 簡便な入力 瞬間的な思考の記録 共同的なノート作成 リハーサルと エンコード 内容への注意 能動的な説明と反映 課題 録音と再生 編集 検索と見直し 構造化とスケッチ 共有空間での利用 考察 RQ1:ノートのとり方は文章理解にどのような影響を与えるか? 音声でノートをとることは、テキストの情報に基づく推論を手助けし、情報を横断してア イデア やコンセプトを結びつけている可能性が示唆されました。 推論問題では複数のア イデア ユニットを結びつける必要があり、テキストに対する概念的な理解が試される可能性が示唆されました。 音声でとったノートに関して、「音声でとったノートはより多くのア イデア ユニットを議論できる→ノートをとりながらテキストについてより良い推論を行える→テキストに対する概念的理解が深まる→推論問題の成績が高くなる」という一連の流れが示唆されました。 RQ2:ノートのとり方はノートの内容にどのような影響を与えるか? 音声でノートをとる際は人称直示詞(人称代名詞)を多く使用するため、仮想の聴衆に話しかけるような、社会的関与が高い内容になる可能性が示唆されました。 社会的関与の高さがより多くのア イデア ユニットの議論とエラボレーションに繋がる可能性が示唆されました。 RQ3:ノートのとり方はメタ理解の判断にどのような影響を与えるか? 音声でノートをとることは、テキストに対する概念的理解を増加させても、学習パフォーマンスの知覚には影響を与えない可能性が示唆されました。 RQ4:音声ノートを学習活動に利用することの利点と課題は何か? 仮想の聴衆に話しかけることで豊かな表現が生まれる可能性が示唆されました。 テキストを読みながら同時にノートをとれるため、学習内容自体に注意を向けやすい可能性が示唆されました。 音声でノートをとることのメリットは、ノートを見直すときよりもノートをとるときの方がより明確になる可能性が示唆されました。 音声でとるノートの利便性はデジタル学習環境で発揮されやすい可能性が示唆されました。 限界と今後の課題 実験の限界と、そこから考えられる今後検討すべき課題は以下のとおりです。 限界 課題 実験に使用したテキストがやや複雑(難易度が高い)だと認識されていた より複雑でないテキストに対して一般化されるかの調査 ノートのとり方が文章理解に与える短期的な影響を調査するに留まった より長期的な影響の調査 キーボードと音声で見直しに要する時間が異なり交絡因子となるおそれがあるため、Post-testの前にノートを見直す時間を設定しなかった 入力(キーボード/音声)と出力(文章/音声)の組み合わせによる実験 大学院生を対象として管理された環境で実施した 異なる参加者集団や、より現実的な環境での実験 音声でとるノートは空間的な構造化を必要としない情報のみしか記録できない 数式や マインドマップ など、視覚的な表現が有効な場合の調査 結論 ノートをとる際の入力形式をキーボードから音声へ変更することで、より多くのア イデア ユニットの議論とエラボレーションを行える可能性が示唆されました。 基本的な知識を得ることが目的であれば、キーボードと音声どちらでノートをとった場合も同じようなパフォーマンスを示す可能性が示唆されました。 推論することによってより深く概念を理解することが目的であれば、音声でノートをとる方がより効果的である可能性が示唆されました。 キーボードと音声どちらでノートをとった場合も、文章理解におけるメタ理解の判断には同程度の効果をもたらす可能性が示唆されました。 音声でとるノートはデジタルテキストの概念的な理解を可能にするため、デジタル学習環境に組み込める可能性が示唆されました。 個人的な感想など この論文を題材に選んだきっかけ この論文を題材に選んだ理由としては、タイトルに惹かれたというのが大きいです。 というのも、私は教育関係の事柄に強い興味をもっています。 また、私は学生時代(ほんの2年半前ですが)、音や音楽に関する研究をしていました。 そのため、「ノートをとる」という部分が私の教育センサーに、「音声」の部分が私の音・音楽センサーに引っかかり、 アブスト ラク トに目を通しても面白そうな内容だったので、今回の論文輪読会の題材としました。 論文内容に関して いわゆる典型的なIMRAD形式( 参考 )だったこともあり、論文の構成が分かりやすかったです。 また、なるほど確かにと思える部分が多かったです。 例えば、音声でノートをとる場合はテキストから視線を逸らさずにノートをとれる、音声でとったノートは編集、検索、見直しに課題がある、などは、確かにそうだなぁという感覚でした。 疑問点として、「社会的関与の高さがより多くのア イデア ユニットの議論とエラボレーションに繋がる」というのは、日本語の場合でも同様なのか気になりました。 これは、日本語は英語と違い主語や目的語を省略できるため、理由として考察されていた人称直示詞があまり登場しないのでは、と思ったためです。 おわりに 本記事では、論文輪読会で私が紹介した論文について簡単にまとめました。 論文輪読会は、知見のインプットとアウトプット、両者の力を伸ばせる取り組みですので、皆さんの会社や部署などでも実施してみてはいかがでしょうか? 最後までお読みいただき、本当にありがとうございました! 私たちは同じ事業部で共に働いていただける仲間を募集しています! 論文輪読会をはじめとした各種勉強会に気軽に参加できるような環境で働くことに興味のある皆様、ぜひご応募お待ちしています! フルサイクルエンジニア 執筆: @miyazawa.hibiki 、レビュー: @yamashita.tsuyoshi ( Shodo で執筆されました )
アバター
電通国際情報サービス 、オープン イノベーション ラボの 比嘉康雄 です。 Stable Diffusionシリーズ、今回は、かわいい動物の擬人化用の呪文です。 Stable Diffusionって、動物の擬人化が難しいんです。例えば犬にテニスをさせたいとします。 パッと思いつくのはこんな呪文です。 illustration of cute dog playing tennis ... 出力結果 コレジャナイ感が満載ですね。かわいい動物の擬人化に成功したので、その方法を紹介します。 Stable Diffusionのおすすめコンテンツはこちら。 Waifu Diffusion 1.3.5_80000 v2.1 金髪美女写真 v2.1 美少女アニメ画 v2.1 AUTOMATIC1111 v2.0 美少女イラスト v1.5 美少女画検証 美少女アニメ画改善版 美少女を高確率で出す呪文編 美少女アニメ画編 美少女写真編 女性イラスト編 魅惑的な女アニメ画(トゥーンレンダリング)編 長い呪文は切り捨てられる編 steampunk illustration of cute anthromorphic 動物名 costume steampunkを外す anthromorphicを外す steampunkとanthromorphicを外す costumeを外す steampunkとcostumeを外す まとめ 仲間募集 Stable Diffusionの過去コンテンツ steampunk illustration of cute anthromorphic 動物名 costume これが基本呪文です。 steampunkは、 蒸気機関 が高度に発達したレトロなアニメの世界観を召喚する呪文です。詳しくは、 蒸気機関が高度に発達したレトロなアニメ(スチームパンク)の世界観編 を御覧ください。 不思議なことに、steampunkの世界では、動物が擬人化しやすいのです。 anthromorphicの意味は擬人化。anthromorphic 動物名で、動物が擬人化できれば話は簡単なのですが、世の中そう甘くはありません。 擬人化に必要な3つ目の呪文がcostume(服装)。動物を人間にするには、服が必要だというのは、深い何かを感じますね。 今回の呪文(横長、コピー&ペースト用) steampunk illustration of cute anthromorphic ferret costume highly detailed artstation deviantart concept art digital painting award winning fantasy scene fantasy composition fantasy lighting 閲覧用呪文(改行版) steampunk illustration of cute anthromorphic ferret costume highly detailed artstation deviantart concept art digital painting award winning fantasy scene fantasy composition fantasy lighting 出力結果 かわいい フェレット の擬人化に成功しました。 steampunkを外す steampunkを呪文から外すとどうなるか実験してみましょう。 今回の基本呪文 illustration of cute anthromorphic ferret costume 出力結果 これも、かわいいですね。steampunkを外すと擬人化に失敗するケースが出てきます。 anthromorphicを外す anthromorphicを呪文から外すとどうなるか実験してみましょう。 今回の基本呪文 steampunk illustration of cute ferret costume 出力結果 あれっ、あまり変わらない。そうなんです、steampunkの呪文が指定されている場合、anthromorphicがなくてもあまり結果は変わりません。ごくまれに、擬人化に失敗することがあるくらいです。じゃ、anthromorphicは効果がないかというとそんな事はありません。 次の例を見てください。 steampunkとanthromorphicを外す それでは、steampunkとanthromorphicを両方外してみましょう。 今回の基本呪文 illustration of cute ferret costume 出力結果 擬人化に失敗してしまいました。 先程、steampunkなし、anthromorphicありのときは、擬人化に成功していたことを思い出してください。anthromorphicは確かに擬人化の効果があるということです。 steampunkにも擬人化の効果があるため、steampunkと組み合わせるときは、anthromorphicなしでもあまり影響はありませんが、基本つけるようにしたほうが良いでしょう。 costumeを外す 今回は、costumeを外してみましょう。 今回の基本呪文 steampunk illustration of cute anthromorphic ferret 出力結果 costumeを外しても、きれいに擬人化されていますね。今回の結果は予想していた人もいたかもしれません。steampunkとanthromorphicで、擬人化に必要な呪文の成分が足りているため、costumeを外してもほとんど影響がないのです。 しかし、costumeに擬人化に必要な呪文の成分がないかというとそんな事はありません。次の例を見てください。 steampunkとcostumeを外す それでは、steampunkとcostumeを両方外してみましょう。 今回の基本呪文 illustration of cute anthromorphic ferret 出力結果 完全に擬人化に失敗してますね。anthromorphic単独では、擬人化に必要な呪文の成分が足りていないということです。 まとめ かわいい動物を擬人化するときの基本呪文は steampunk illustration of cute anthromorphic 動物名 costume です。anthromorphicの意味は擬人化ですが、anthromorphic単独で擬人化できるほど強い呪文ではありません。steampunkやcostumeなどの擬人化成分を持った呪文と組み合わせましょう。 次回は、 バベルの塔のイラスト編 です。 仲間募集 私たちは同じグループで共に働いていただける仲間を募集しています。 現在、以下のような職種を募集しています。 ソリューションアーキテクト AIエンジニア Stable Diffusionの過去コンテンツ 人物写真編 レンズ編 画像タイプ編 美少女アニメ画編 美少女写真編 女性イラスト編 美しい夜空を見渡す男編 魅惑的な女アニメ画(トゥーンレンダリング)編 美少女を高確率で出す呪文編 長い呪文は切り捨てられる編 蒸気機関が高度に発達したレトロなアニメ(スチームパンク)の世界観編 A as Bの呪文による画像合成編 かわいい動物の擬人化編 バベルの塔のイラスト編 TPU版の使い方 美少女アニメ画改善版 v1.5 美少女画検証 東京タワーの写真 折り紙合体変形ロボ v2.0 美少女イラスト v2.1 AUTOMATIC1111 v2.1 美少女アニメ画 v2.1 金髪美女写真 Waifu Diffusion 1.3.5_80000 執筆: @higa ( Shodo で執筆されました )
アバター
電通国際情報サービス 、オープン イノベーション ラボの 比嘉康雄 です。 Stable Diffusionシリーズ、今回は、かわいい動物の擬人化用の呪文です。 Stable Diffusionって、動物の擬人化が難しいんです。例えば犬にテニスをさせたいとします。 パッと思いつくのはこんな呪文です。 illustration of cute dog playing tennis ... 出力結果 コレジャナイ感が満載ですね。かわいい動物の擬人化に成功したので、その方法を紹介します。 Stable Diffusionのおすすめコンテンツはこちら。 Waifu Diffusion 1.3.5_80000 v2.1 金髪美女写真 v2.1 美少女アニメ画 v2.1 AUTOMATIC1111 v2.0 美少女イラスト v1.5 美少女画検証 美少女アニメ画改善版 美少女を高確率で出す呪文編 美少女アニメ画編 美少女写真編 女性イラスト編 魅惑的な女アニメ画(トゥーンレンダリング)編 長い呪文は切り捨てられる編 steampunk illustration of cute anthromorphic 動物名 costume steampunkを外す anthromorphicを外す steampunkとanthromorphicを外す costumeを外す steampunkとcostumeを外す まとめ 仲間募集 Stable Diffusionの過去コンテンツ steampunk illustration of cute anthromorphic 動物名 costume これが基本呪文です。 steampunkは、 蒸気機関 が高度に発達したレトロなアニメの世界観を召喚する呪文です。詳しくは、 蒸気機関が高度に発達したレトロなアニメ(スチームパンク)の世界観編 を御覧ください。 不思議なことに、steampunkの世界では、動物が擬人化しやすいのです。 anthromorphicの意味は擬人化。anthromorphic 動物名で、動物が擬人化できれば話は簡単なのですが、世の中そう甘くはありません。 擬人化に必要な3つ目の呪文がcostume(服装)。動物を人間にするには、服が必要だというのは、深い何かを感じますね。 今回の呪文(横長、コピー&ペースト用) steampunk illustration of cute anthromorphic ferret costume highly detailed artstation deviantart concept art digital painting award winning fantasy scene fantasy composition fantasy lighting 閲覧用呪文(改行版) steampunk illustration of cute anthromorphic ferret costume highly detailed artstation deviantart concept art digital painting award winning fantasy scene fantasy composition fantasy lighting 出力結果 かわいい フェレット の擬人化に成功しました。 steampunkを外す steampunkを呪文から外すとどうなるか実験してみましょう。 今回の基本呪文 illustration of cute anthromorphic ferret costume 出力結果 これも、かわいいですね。steampunkを外すと擬人化に失敗するケースが出てきます。 anthromorphicを外す anthromorphicを呪文から外すとどうなるか実験してみましょう。 今回の基本呪文 steampunk illustration of cute ferret costume 出力結果 あれっ、あまり変わらない。そうなんです、steampunkの呪文が指定されている場合、anthromorphicがなくてもあまり結果は変わりません。ごくまれに、擬人化に失敗することがあるくらいです。じゃ、anthromorphicは効果がないかというとそんな事はありません。 次の例を見てください。 steampunkとanthromorphicを外す それでは、steampunkとanthromorphicを両方外してみましょう。 今回の基本呪文 illustration of cute ferret costume 出力結果 擬人化に失敗してしまいました。 先程、steampunkなし、anthromorphicありのときは、擬人化に成功していたことを思い出してください。anthromorphicは確かに擬人化の効果があるということです。 steampunkにも擬人化の効果があるため、steampunkと組み合わせるときは、anthromorphicなしでもあまり影響はありませんが、基本つけるようにしたほうが良いでしょう。 costumeを外す 今回は、costumeを外してみましょう。 今回の基本呪文 steampunk illustration of cute anthromorphic ferret 出力結果 costumeを外しても、きれいに擬人化されていますね。今回の結果は予想していた人もいたかもしれません。steampunkとanthromorphicで、擬人化に必要な呪文の成分が足りているため、costumeを外してもほとんど影響がないのです。 しかし、costumeに擬人化に必要な呪文の成分がないかというとそんな事はありません。次の例を見てください。 steampunkとcostumeを外す それでは、steampunkとcostumeを両方外してみましょう。 今回の基本呪文 illustration of cute anthromorphic ferret 出力結果 完全に擬人化に失敗してますね。anthromorphic単独では、擬人化に必要な呪文の成分が足りていないということです。 まとめ かわいい動物を擬人化するときの基本呪文は steampunk illustration of cute anthromorphic 動物名 costume です。anthromorphicの意味は擬人化ですが、anthromorphic単独で擬人化できるほど強い呪文ではありません。steampunkやcostumeなどの擬人化成分を持った呪文と組み合わせましょう。 次回は、 バベルの塔のイラスト編 です。 仲間募集 私たちは同じグループで共に働いていただける仲間を募集しています。 現在、以下のような職種を募集しています。 ソリューションアーキテクト AIエンジニア Stable Diffusionの過去コンテンツ 人物写真編 レンズ編 画像タイプ編 美少女アニメ画編 美少女写真編 女性イラスト編 美しい夜空を見渡す男編 魅惑的な女アニメ画(トゥーンレンダリング)編 美少女を高確率で出す呪文編 長い呪文は切り捨てられる編 蒸気機関が高度に発達したレトロなアニメ(スチームパンク)の世界観編 A as Bの呪文による画像合成編 かわいい動物の擬人化編 バベルの塔のイラスト編 TPU版の使い方 美少女アニメ画改善版 v1.5 美少女画検証 東京タワーの写真 折り紙合体変形ロボ v2.0 美少女イラスト v2.1 AUTOMATIC1111 v2.1 美少女アニメ画 v2.1 金髪美女写真 Waifu Diffusion 1.3.5_80000 執筆: @higa ( Shodo で執筆されました )
アバター
こんにちは、 電通国際情報サービス (ISID) 金融ソリューション事業部の大場です。 今回は、Rustでフロントエンドの実装ができるYewというライブラリを使って Markdown エディタを作った話をします。本記事は、Yewの内部実装に触れながらYewやRustのマクロの動作について理解を深めることを目的としています。これらについて詳しく知りたい方はぜひ本記事を参考にしていただければと思います。 また、本記事で紹介するコードはこちらの リポジトリ で公開しています。 https://github.com/ISID/wasm-md-editor 作った背景 採用した主要なCrate 全体像とフロー Yew そもそもWebAssembly(Wasm)とは Yew内部で使われる主要なCrate Yewの実装 手続き型マクロ マクロについての補足 #[function_component]実装 実際に画面を実装してみる Top画面とルーティング実装 エディタ画面の作成 最後に 補足 Trunkについて Markdownのスタイルについて 作った背景 WebAssemblyを触ってみたいと思っていたところ、Rustでフロントエンドの実装ができるライブラリを発見したのを機に「これは何か作ってみるしかない!」と一念発起して作り始めました。デザインに時間をかけたくなかったので、最もシンプルなUIで作れそうな Markdown を作ることにしました。( 逆にシンプル過ぎたかもしれません ) 採用した主要なCrate さっそく、 Markdown エディタに使用した主要なライブラリの簡単な紹介です。pulldown-cmarkの実装部分の説明は省いています。気になる方はgit リポジトリ をご覧ください。 Yew WebAssemblyによるRust製フロントエンド フレームワーク JSX記法を提供するHTMLマクロや、ReactライクなComponent(Class Component、Functional Component)の実装が可能 pulldown-cmark Markdown 記法のテキストをHTML形式に変換するParser 全体像とフロー 作ったアプリの全体像がこんな感じです。 ①ユーザーが画面に Markdown 記法でテキストを入力 ➁Yew コンポーネント は文字入力のイベントを受信し、pulldown-cmarkで作られたrendererへテキストを渡す ③rendererは Markdown 記法を解釈し、HTML形式のテキストに変換する ④Yew コンポーネント に返却し再 レンダリング ここからYewの紹介をしつつ、実際にYewの実装を見ていきます。 Yew そもそもWebAssembly(Wasm)とは ブラウザ上で動作するバイナリー形式の アセンブリ言語 で、ネイティブアプリに近いパフォーマンスで動作する言語と言われています。現在Wasmに コンパイル できる言語にはC、 C++ 、Rustがあります。 Wasmには「 JavaScript を補完し、並行して動作するための言語」という思想が根底にあり、 JavaScript と(今回の記事では)Rustが双方向にそれぞれの関数をexport, importして利用することができます。 Yew内部で使われる主要なCrate js-sys JavaScript 標準の ビルトインオブジェクト をRustに提供 web-sys ブラウザが提供するWeb API をRustに提供 wasm-bindgen Rustで書いたコード(関数)を JavaScript 側で利用するためのCrate。両者の関数を受け渡しするブリッジ的な役割を果たす。js-sysやweb-sysを使ったRustのコードもwasm-bindgenが最終的に javaScript にexportする wasm-bindgen-futures Rustと JavaScript 両者の非同期実装をブリッジするためのCrate。 JavaScript のPromiseをRustのFutureとして操作することが可能 Yewの実装 手続き型マクロ 簡易的なComponentを実装してみます。 use yew::prelude::*; // これはHomeコンポーネントとして認識される #[function_component(Home)] // 1. pub fn home() -> Html { html! { // 2. <h1>{"Welcome to my editor!"}</h1> } } #[function_component] アトリビュート を付与することで関数全体がComponentであるとYewが認識します。 アトリビュート の引数にある `home はComponentの名称を指しています。 home関数では、 html! を利用してインナーブロックで与えられたHTMLタグを処理しHTMLとして返却します。 マクロについての補足 1.2 で述べた記法は、Rustの世界ではどちらもマクロと呼ばれています。 1 のように関数や構造体に付与するものは手続き型マクロと呼ばれるのに対し、macro_rules!で定義され、呼び出し元からは関数呼び出しのように利用されるものは宣言的マクロ(println!など)に分類されます。 ここで注意ですが、 2 の html! は一見宣言的マクロに見えますが実は手続き型マクロです。これはhtml!マクロの内部の実装を見ると明らかになります。 #[proc_macro_error::proc_macro_error] #[proc_macro] // 1. pub fn html(input: TokenStream) -> TokenStream { let root = parse_macro_input!(input as HtmlRootVNode); TokenStream::from(root.into_token_stream()) } html!マクロの実装は #[proc_macro] によって定義されており、macro_rules!を使っていません。 #[proc_macro] を使った手続き型マクロは関数風マクロと呼ばれています。手続き型マクロがRustのバージョン1.15.0で追加されたため、従来から存在した宣言的マクロと後発の関数風マクロが共存してしまっているようです。 それでは、 #[function_component] マクロはどのように実装されているのか見ていきましょう。 #[function_component]実装 先に実装から。 #[proc_macro_attribute] // 1. pub fn function_component( attr: proc_macro::TokenStream, // 2. item: proc_macro::TokenStream, // 2. ) -> proc_macro::TokenStream { let item = parse_macro_input!(item as FunctionComponent); // 3. let attr = parse_macro_input!(attr as FunctionComponentName); // 3. function_component_impl(attr, item) // 4. .unwrap_or_else(|err| err.to_compile_error()) .into() } pub fn function_component_impl( name: FunctionComponentName, component: FunctionComponent, ) -> syn::Result<TokenStream> { let FunctionComponentName { component_name } = name; // ・・・省略・・・ let quoted = quote! { // 5. #[doc(hidden)] #[allow(non_camel_case_types)] #[allow(unused_parens)] #vis struct #function_name #impl_generics { _marker: ::std::marker::PhantomData<(#phantom_generics)>, } impl #impl_generics ::yew::functional::FunctionProvider for #function_name #ty_generics #where_clause { type TProps = #props_type; fn run(#arg) -> #ret_type { #block } } #(#attrs)* #[allow(type_alias_bounds)] #vis type #component_name #impl_generics = ::yew::functional::FunctionComponent<#function_name #ty_generics>; }; Ok(quoted) } コンポーネント を定義する際に利用した #[function_component] アトリビュート は下記のように動作します。 proc_macro_attribute はfunction_component()関数が Custom Attribute であることを示しており、利用側で #[function_component] を関数に付与した際には、定義元であるこの関数にリンクされます。 手続き型マクロを定義する関数は、TokenStreamを入力として受け取りTokenStreamを出力として返します。つまり、 アトリビュート を付与した関数のコードそのものが入力値としてTokenStreamに変換され、そのTokenStreamを基にマクロで生成されるコードがTokenStreamとして返却されます。 引数の1つ目である attr: proc_macro::TokenStream は呼び出し側( #[function_component(Home)] )のHomeを指しているのに対し、2つ目の item: proc_macro::TokenStream は #[function_component(Home)] を付与した関数の中身(Componentの実装)に対応しています。 itemがFunctionComponent型、attrはFunctionComponentName型にキャストされていることがわかります。 parse_macro_input!はTokenStreamの トーク ン列を 構文木 にパースし、Rustが解釈できるデータ構造に変換されます。その後、マクロ実装によってデータ構造に対して書き込みが行われていきます。書き込み後に生成されたコードをTokenStreamに再変換し、呼び出し元に返却します。 一般的な手続き型マクロの順番は上述の通りですが、 parse_macro_input! マクロによって 構文木 にパースされたTokenStreamはこのあとfunction_component_impl()関数内の処理で再度TokenStreamに変換されます。 構文木 にパースされると、マクロによってコードが生成されます。function_component_impl()関数を見ると quote! マクロが呼ばれ、 構文木 からTokenStreamへの変換が行われます。 コードを生成する処理については各手続き型マクロの固有処理になるので、マクロを定義するlib.rsからは分離して定義することが多いです。 これは、proc_macroで定義したマクロ内部のコードは、マクロが評価される実行時のタイミングでしか呼び出すことができません。つまり、マクロ内部の詳細な実装をテストすることも踏まえると、詳細な実装はlib.rsではない別のクレートに実装し、lib.rsから呼び出すように書く必要があるのです。 実際に画面を実装してみる Top画面とルーティング実装 Rustのマクロの説明で利用したHome コンポーネント をラップして、トップ画面を作成します。 use crate::{components::home::Home, Routing}; use stylist::style; use yew::prelude::*; use yew_router::{history::History, hooks::use_history}; #[function_component(Top)] // 1. pub fn top() -> Html { let container = style!( // 2. r#" display: flex; flex-direction: column; align-items: center; "# ) .expect("Failed to styled."); let button = style!( r#" color: #ffffff; width: 200px; padding: 10px; background-color: #1976d2; box-shadow: 0 3px 5px rgba(0, 0, 0, .3); -webkit-box-shadow: 0 3px 5px rgba(0, 0, 0, .3); :hover { background: #115293; margin-top: 3px; } "# ) .expect("Failed to styled."); let history = use_history().unwrap(); let onclick = Callback::once(move |_| history.push(Routing::Editor)); html! { <> <div class={container}> <Home /> <button class={button} {onclick}>{"Start"}</ button> </div> </> } Top コンポーネント は、Home コンポーネント を呼び出しており、Startボタンを配置するTopページを表している。 styleは stylist を利用しており、Reactのような宣言的なスタイルの定義が可能。 あとはmain.rsとしてルーティングの設定を行います。今回はHome画面をトップ画面に、Home画面に設置しているボタンを押下するとエディタ画面に遷移するようにします。(解説は省きます) #[derive(Clone, Routable, PartialEq)] pub enum Routing { #[at("/")] Home, #[at("/editor")] Editor, #[not_found] #[at("/404")] NotFound, } pub enum Msg { SetInput(String), } #[function_component(App)] fn app() -> Html { html! { <BrowserRouter> <Switch<Routing> render={Switch::render(switch)} /> </BrowserRouter> } } fn switch(routes: &Routing) -> Html { match routes { Routing::Home => html! { <Top /> }, Routing::Editor => html! { <Text /> }, Routing::NotFound => html! {<NotFound />}, } } fn main() { yew::start_app::<App>(); } サーバを起動すると下記のようなTop画面が表示されます。 エディタ画面の作成 エディタ画面の実装です。改めてですが、画面のデザインは驚くほど凝っていません。 Reactを書いてるような感覚でstyleや イベントハンドラ を実装できるのがわかるかと思います。 #[styled_component(Text)] pub fn text() -> Html { let style = style!( r#" background-color: #1e2126; color: #fff; font-family: inherit; margin: 2rem; "# ) .expect("Failed to styled."); let container = style!( r#" display: flex; "# ) .expect("Failed to styled."); let item = style!( r#" "# ) .expect("Failed to styled."); let value = use_state(|| String::from("")); let on_input = { // 1. let value = value.clone(); Callback::from(move |e: InputEvent| { // 2. let input: HtmlTextAreaElement = e.target_unchecked_into(); value.set(input.value()); }) }; let html = cmark(value.to_string()); // 3. let div = web_sys::window() .unwrap() .document() .unwrap() .create_element("div") .unwrap(); div.set_inner_html(&html); let node = Node::from(div); let vnode = VNode::VRef(node); // 5. html! { <> <div class="markdown-body"> <div class={container}> <div class={item}> <textarea class={style} rows="140" cols="100" value={value.to_string()} oninput={on_input} /> </div> <div class="item" > {vnode} </div> </div> </div> </> } } fn cmark(text: String) -> String { let mut options = Options::empty(); options.insert( Options::ENABLE_TABLES // 4. | Options::ENABLE_FOOTNOTES | Options::ENABLE_STRIKETHROUGH | Options::ENABLE_TASKLISTS | Options::ENABLE_SMART_PUNCTUATION, ); let parser = Parser::new_ext(&text, options); let mut html_output = String::new(); // parser changes rendered String for markdown html::push_html(&mut html_output, parser); html_output } テキストエリアに文字が入力された際に発火されるイベントを登録しておきます。 Callback を使ってComponentが保持する状態にテキストの値を渡します。 pulldown-cmarkの処理です。 Markdown 記法で書かれたテキストをHTMLに変換します。 Parserを作成する際に必要なオプションの有無を設定しています。 ここで述べるオプションというのは Markdown 記法のうち、表形式やタスクリストなどの記法をParserの変換対象にするかを指しています。 各Options内の変数は1をシフト演算したものであるため、上記の実装のように 論理和 をとることでまとめてオプションを有効化しています。 HTMLに変換されたテキストを VNode を使って仮想DOMにセットします。 エディタ画面はこんな感じになりました。 最後に 本記事では、Yewの実装を見ながらマクロの挙動やYewの実装方法について説明しました。WasmやRustに興味を持っていただけたら嬉しいです。 また、Rustでフロントエンド実装ができるライブラリはYew以外にもいくつかあるので興味のある方はぜひ見てみてください! 補足 Trunkについて ローカルでのWebサーバは Trunk を利用しました。TrunkはRustのコードを JavaScript モジュールに コンパイル するバンドルツールとしてwasm-packと大変似ていますが、Trunkの場合はwasm-packと違って JavaScript モジュールのほかその他のアセット(HTML, CSS , Image)も同時に自動生成します。また、TrunkにはビルトインでWebサーバが提供されているため、コマンド一発で開発時に利用するWebサーバを立ち上げることができます。 Yew公式でも、Trunkが推奨されています。 Markdown のスタイルについて 公開されているものがあったのでそのまま利用しました。 https://github.com/sindresorhus/github-markdown-css 執筆: 大場 進太郎 (@ShintaroOba) 、レビュー: @sato.taichi ( Shodo で執筆されました )
アバター
こんにちは、 電通国際情報サービス (ISID) 金融ソリューション事業部の大場です。 今回は、Rustでフロントエンドの実装ができるYewというライブラリを使って Markdown エディタを作った話をします。本記事は、Yewの内部実装に触れながらYewやRustのマクロの動作について理解を深めることを目的としています。これらについて詳しく知りたい方はぜひ本記事を参考にしていただければと思います。 また、本記事で紹介するコードはこちらの リポジトリ で公開しています。 https://github.com/ISID/wasm-md-editor 作った背景 採用した主要なCrate 全体像とフロー Yew そもそもWebAssembly(Wasm)とは Yew内部で使われる主要なCrate Yewの実装 手続き型マクロ マクロについての補足 #[function_component]実装 実際に画面を実装してみる Top画面とルーティング実装 エディタ画面の作成 最後に 補足 Trunkについて Markdownのスタイルについて 作った背景 WebAssemblyを触ってみたいと思っていたところ、Rustでフロントエンドの実装ができるライブラリを発見したのを機に「これは何か作ってみるしかない!」と一念発起して作り始めました。デザインに時間をかけたくなかったので、最もシンプルなUIで作れそうな Markdown を作ることにしました。( 逆にシンプル過ぎたかもしれません ) 採用した主要なCrate さっそく、 Markdown エディタに使用した主要なライブラリの簡単な紹介です。pulldown-cmarkの実装部分の説明は省いています。気になる方はgit リポジトリ をご覧ください。 Yew WebAssemblyによるRust製フロントエンド フレームワーク JSX記法を提供するHTMLマクロや、ReactライクなComponent(Class Component、Functional Component)の実装が可能 pulldown-cmark Markdown 記法のテキストをHTML形式に変換するParser 全体像とフロー 作ったアプリの全体像がこんな感じです。 ①ユーザーが画面に Markdown 記法でテキストを入力 ➁Yew コンポーネント は文字入力のイベントを受信し、pulldown-cmarkで作られたrendererへテキストを渡す ③rendererは Markdown 記法を解釈し、HTML形式のテキストに変換する ④Yew コンポーネント に返却し再 レンダリング ここからYewの紹介をしつつ、実際にYewの実装を見ていきます。 Yew そもそもWebAssembly(Wasm)とは ブラウザ上で動作するバイナリー形式の アセンブリ言語 で、ネイティブアプリに近いパフォーマンスで動作する言語と言われています。現在Wasmに コンパイル できる言語にはC、 C++ 、Rustがあります。 Wasmには「 JavaScript を補完し、並行して動作するための言語」という思想が根底にあり、 JavaScript と(今回の記事では)Rustが双方向にそれぞれの関数をexport, importして利用することができます。 Yew内部で使われる主要なCrate js-sys JavaScript 標準の ビルトインオブジェクト をRustに提供 web-sys ブラウザが提供するWeb API をRustに提供 wasm-bindgen Rustで書いたコード(関数)を JavaScript 側で利用するためのCrate。両者の関数を受け渡しするブリッジ的な役割を果たす。js-sysやweb-sysを使ったRustのコードもwasm-bindgenが最終的に javaScript にexportする wasm-bindgen-futures Rustと JavaScript 両者の非同期実装をブリッジするためのCrate。 JavaScript のPromiseをRustのFutureとして操作することが可能 Yewの実装 手続き型マクロ 簡易的なComponentを実装してみます。 use yew::prelude::*; // これはHomeコンポーネントとして認識される #[function_component(Home)] // 1. pub fn home() -> Html { html! { // 2. <h1>{"Welcome to my editor!"}</h1> } } #[function_component] アトリビュート を付与することで関数全体がComponentであるとYewが認識します。 アトリビュート の引数にある `home はComponentの名称を指しています。 home関数では、 html! を利用してインナーブロックで与えられたHTMLタグを処理しHTMLとして返却します。 マクロについての補足 1.2 で述べた記法は、Rustの世界ではどちらもマクロと呼ばれています。 1 のように関数や構造体に付与するものは手続き型マクロと呼ばれるのに対し、macro_rules!で定義され、呼び出し元からは関数呼び出しのように利用されるものは宣言的マクロ(println!など)に分類されます。 ここで注意ですが、 2 の html! は一見宣言的マクロに見えますが実は手続き型マクロです。これはhtml!マクロの内部の実装を見ると明らかになります。 #[proc_macro_error::proc_macro_error] #[proc_macro] // 1. pub fn html(input: TokenStream) -> TokenStream { let root = parse_macro_input!(input as HtmlRootVNode); TokenStream::from(root.into_token_stream()) } html!マクロの実装は #[proc_macro] によって定義されており、macro_rules!を使っていません。 #[proc_macro] を使った手続き型マクロは関数風マクロと呼ばれています。手続き型マクロがRustのバージョン1.15.0で追加されたため、従来から存在した宣言的マクロと後発の関数風マクロが共存してしまっているようです。 それでは、 #[function_component] マクロはどのように実装されているのか見ていきましょう。 #[function_component]実装 先に実装から。 #[proc_macro_attribute] // 1. pub fn function_component( attr: proc_macro::TokenStream, // 2. item: proc_macro::TokenStream, // 2. ) -> proc_macro::TokenStream { let item = parse_macro_input!(item as FunctionComponent); // 3. let attr = parse_macro_input!(attr as FunctionComponentName); // 3. function_component_impl(attr, item) // 4. .unwrap_or_else(|err| err.to_compile_error()) .into() } pub fn function_component_impl( name: FunctionComponentName, component: FunctionComponent, ) -> syn::Result<TokenStream> { let FunctionComponentName { component_name } = name; // ・・・省略・・・ let quoted = quote! { // 5. #[doc(hidden)] #[allow(non_camel_case_types)] #[allow(unused_parens)] #vis struct #function_name #impl_generics { _marker: ::std::marker::PhantomData<(#phantom_generics)>, } impl #impl_generics ::yew::functional::FunctionProvider for #function_name #ty_generics #where_clause { type TProps = #props_type; fn run(#arg) -> #ret_type { #block } } #(#attrs)* #[allow(type_alias_bounds)] #vis type #component_name #impl_generics = ::yew::functional::FunctionComponent<#function_name #ty_generics>; }; Ok(quoted) } コンポーネント を定義する際に利用した #[function_component] アトリビュート は下記のように動作します。 proc_macro_attribute はfunction_component()関数が Custom Attribute であることを示しており、利用側で #[function_component] を関数に付与した際には、定義元であるこの関数にリンクされます。 手続き型マクロを定義する関数は、TokenStreamを入力として受け取りTokenStreamを出力として返します。つまり、 アトリビュート を付与した関数のコードそのものが入力値としてTokenStreamに変換され、そのTokenStreamを基にマクロで生成されるコードがTokenStreamとして返却されます。 引数の1つ目である attr: proc_macro::TokenStream は呼び出し側( #[function_component(Home)] )のHomeを指しているのに対し、2つ目の item: proc_macro::TokenStream は #[function_component(Home)] を付与した関数の中身(Componentの実装)に対応しています。 itemがFunctionComponent型、attrはFunctionComponentName型にキャストされていることがわかります。 parse_macro_input!はTokenStreamの トーク ン列を 構文木 にパースし、Rustが解釈できるデータ構造に変換されます。その後、マクロ実装によってデータ構造に対して書き込みが行われていきます。書き込み後に生成されたコードをTokenStreamに再変換し、呼び出し元に返却します。 一般的な手続き型マクロの順番は上述の通りですが、 parse_macro_input! マクロによって 構文木 にパースされたTokenStreamはこのあとfunction_component_impl()関数内の処理で再度TokenStreamに変換されます。 構文木 にパースされると、マクロによってコードが生成されます。function_component_impl()関数を見ると quote! マクロが呼ばれ、 構文木 からTokenStreamへの変換が行われます。 コードを生成する処理については各手続き型マクロの固有処理になるので、マクロを定義するlib.rsからは分離して定義することが多いです。 これは、proc_macroで定義したマクロ内部のコードは、マクロが評価される実行時のタイミングでしか呼び出すことができません。つまり、マクロ内部の詳細な実装をテストすることも踏まえると、詳細な実装はlib.rsではない別のクレートに実装し、lib.rsから呼び出すように書く必要があるのです。 実際に画面を実装してみる Top画面とルーティング実装 Rustのマクロの説明で利用したHome コンポーネント をラップして、トップ画面を作成します。 use crate::{components::home::Home, Routing}; use stylist::style; use yew::prelude::*; use yew_router::{history::History, hooks::use_history}; #[function_component(Top)] // 1. pub fn top() -> Html { let container = style!( // 2. r#" display: flex; flex-direction: column; align-items: center; "# ) .expect("Failed to styled."); let button = style!( r#" color: #ffffff; width: 200px; padding: 10px; background-color: #1976d2; box-shadow: 0 3px 5px rgba(0, 0, 0, .3); -webkit-box-shadow: 0 3px 5px rgba(0, 0, 0, .3); :hover { background: #115293; margin-top: 3px; } "# ) .expect("Failed to styled."); let history = use_history().unwrap(); let onclick = Callback::once(move |_| history.push(Routing::Editor)); html! { <> <div class={container}> <Home /> <button class={button} {onclick}>{"Start"}</ button> </div> </> } Top コンポーネント は、Home コンポーネント を呼び出しており、Startボタンを配置するTopページを表している。 styleは stylist を利用しており、Reactのような宣言的なスタイルの定義が可能。 あとはmain.rsとしてルーティングの設定を行います。今回はHome画面をトップ画面に、Home画面に設置しているボタンを押下するとエディタ画面に遷移するようにします。(解説は省きます) #[derive(Clone, Routable, PartialEq)] pub enum Routing { #[at("/")] Home, #[at("/editor")] Editor, #[not_found] #[at("/404")] NotFound, } pub enum Msg { SetInput(String), } #[function_component(App)] fn app() -> Html { html! { <BrowserRouter> <Switch<Routing> render={Switch::render(switch)} /> </BrowserRouter> } } fn switch(routes: &Routing) -> Html { match routes { Routing::Home => html! { <Top /> }, Routing::Editor => html! { <Text /> }, Routing::NotFound => html! {<NotFound />}, } } fn main() { yew::start_app::<App>(); } サーバを起動すると下記のようなTop画面が表示されます。 エディタ画面の作成 エディタ画面の実装です。改めてですが、画面のデザインは驚くほど凝っていません。 Reactを書いてるような感覚でstyleや イベントハンドラ を実装できるのがわかるかと思います。 #[styled_component(Text)] pub fn text() -> Html { let style = style!( r#" background-color: #1e2126; color: #fff; font-family: inherit; margin: 2rem; "# ) .expect("Failed to styled."); let container = style!( r#" display: flex; "# ) .expect("Failed to styled."); let item = style!( r#" "# ) .expect("Failed to styled."); let value = use_state(|| String::from("")); let on_input = { // 1. let value = value.clone(); Callback::from(move |e: InputEvent| { // 2. let input: HtmlTextAreaElement = e.target_unchecked_into(); value.set(input.value()); }) }; let html = cmark(value.to_string()); // 3. let div = web_sys::window() .unwrap() .document() .unwrap() .create_element("div") .unwrap(); div.set_inner_html(&html); let node = Node::from(div); let vnode = VNode::VRef(node); // 5. html! { <> <div class="markdown-body"> <div class={container}> <div class={item}> <textarea class={style} rows="140" cols="100" value={value.to_string()} oninput={on_input} /> </div> <div class="item" > {vnode} </div> </div> </div> </> } } fn cmark(text: String) -> String { let mut options = Options::empty(); options.insert( Options::ENABLE_TABLES // 4. | Options::ENABLE_FOOTNOTES | Options::ENABLE_STRIKETHROUGH | Options::ENABLE_TASKLISTS | Options::ENABLE_SMART_PUNCTUATION, ); let parser = Parser::new_ext(&text, options); let mut html_output = String::new(); // parser changes rendered String for markdown html::push_html(&mut html_output, parser); html_output } テキストエリアに文字が入力された際に発火されるイベントを登録しておきます。 Callback を使ってComponentが保持する状態にテキストの値を渡します。 pulldown-cmarkの処理です。 Markdown 記法で書かれたテキストをHTMLに変換します。 Parserを作成する際に必要なオプションの有無を設定しています。 ここで述べるオプションというのは Markdown 記法のうち、表形式やタスクリストなどの記法をParserの変換対象にするかを指しています。 各Options内の変数は1をシフト演算したものであるため、上記の実装のように 論理和 をとることでまとめてオプションを有効化しています。 HTMLに変換されたテキストを VNode を使って仮想DOMにセットします。 エディタ画面はこんな感じになりました。 最後に 本記事では、Yewの実装を見ながらマクロの挙動やYewの実装方法について説明しました。WasmやRustに興味を持っていただけたら嬉しいです。 また、Rustでフロントエンド実装ができるライブラリはYew以外にもいくつかあるので興味のある方はぜひ見てみてください! 補足 Trunkについて ローカルでのWebサーバは Trunk を利用しました。TrunkはRustのコードを JavaScript モジュールに コンパイル するバンドルツールとしてwasm-packと大変似ていますが、Trunkの場合はwasm-packと違って JavaScript モジュールのほかその他のアセット(HTML, CSS , Image)も同時に自動生成します。また、TrunkにはビルトインでWebサーバが提供されているため、コマンド一発で開発時に利用するWebサーバを立ち上げることができます。 Yew公式でも、Trunkが推奨されています。 Markdown のスタイルについて 公開されているものがあったのでそのまま利用しました。 https://github.com/sindresorhus/github-markdown-css 執筆: 大場 進太郎 (@ShintaroOba) 、レビュー: @sato.taichi ( Shodo で執筆されました )
アバター
こんにちは!金融ソリューション事業部の山下です。 本記事では、 こちらの記事 でも紹介した Unreal Engine が提供する「Pixel Streaiming」 プラグイン を使って AWS サーバーからリアルタイムCGストリーミング配信を行います。 説明をシンプルにする為、 AWS の基本的な知識(EC2, セキュリティグループ、IAMロールなど)の説明は割愛いたします。 また、本検証を行う場合、使用する GPU インスタンス の利用料金が発生する点にご注意ください。 実施手順 手順は、以下のとおりです。 AWS EC2で GPU インスタンス を作成 インスタンス 環境構築 UEプロジェクトのビルド UEアプリケーション、WebRTCサーバーの起動 Webブラウザ で接続確認 前提知識 1. Pixel Streaming (画像: https://docs.unrealengine.com/5.0/ja/overview-of-pixel-streaming-in-unreal-engine/ ) UEアプリケーションをサーバーサイドで実行して、 レンダリング 結果をWebRTCでストリーム配信する、EpicGames公式 プラグイン です。 インターネットに繋がった Webブラウザ があれば、 スマホ や タブレット 、ノートPCなど特別な端末スペック不要でUEアプリを楽しむことができます。 キーボード、マウス、タッチスクリーン入力などユーザーによるコン トロール も提供可能です。 使用環境/ツール Unreal Engine 5.0.3 Pixel Streaming plugin 1.0 AWS EC2 Instance type g4dn.xlarge AMI Ubuntu 20.04 LTS AMI NVIDIA CUDAドライバが必須 Architecture x86 EBS 250GB NICE DCV 2022.1 Client 手順1. AWS EC2で GPU インスタンス を作成 AWS EC2コンソールから インスタンス を作成します。 AMIには、 Ubuntu 20.04 LTS AMIを選択します。 このAMIには、PixelStreamingの動作要件として必要となる NVIDIA のCUDAドライバーがプリインストールされています。 インスタンス タイプは、 NVIDIA GPU が利用可能な G4dnインスタンス を選択します。 今回は動作検証が目的の為、最小サイズのxlargeを選択しました。 これから実施する Unreal Engine の コンパイル やアプリケーションのビルドにあまり時間をかけたくない方は、より高性能な インスタンス タイプをおすすめします。 また、vCPU使用リミットの引き上げが必要になる場合、 AWS に事前に申請が必要になります(今回選択したg4dn.xlargeの場合、vCPUは"4"になります)。 インスタンス の作成が完了したら、IAMロールでS3へのRead Permisionを付与します( AWS がEC2向けに提供する NVIDIA ドライバをダウンロードする為に必要となります)。 また、HTTPと SSH アクセス以外に、NICE DCV接続用の UDP / TCP ポートも開放します。 (※上記は検証用の設定なので、本番環境には使用しないでください。一応最低限のセキュリティとして SSH 接続にはkey pair、およびNICE DCV接続にはユーザー名とパスワードおよびセッションIDが必要になります) 手順2. インスタンス 環境構築 インスタンス に SSH などで接続して、環境構築を実施します。 こちらの AWS for Games Blogのチュートリアル を参考に、以下を実施します。 Ubuntu desctop managerのインストール NVIDIA ドライバのインストール DCVサーバーの起動、セッションの開始 AWS SecretsManagerを使用した、 Ubuntu ユーザーのパスワード生成 実行コマンド: #!/bin/bash #Installing the Ubuntu desktop manager sudo apt update -y sudo apt install -y ubuntu-desktop sudo apt install x11-xserver-utils sudo apt install awscli -y sudo apt install -y dpkg-dev sudo systemctl restart gdm3 sudo apt-get install xorg-dev -y #............................ # Disable the Wayland protocol. NICE DCV doesn't support the Wayland protocol. sudo sed -i '/WaylandEnable/s/^#//g' /etc/gdm3/custom.conf # ............................. # Install NVIDIA Drivers #.............................. sudo apt-get install -y unzip gcc make linux-headers-$(uname -r) cat << EOF | sudo tee --append /etc/modprobe.d/blacklist.conf blacklist vga16fb blacklist nouveau blacklist rivafb blacklist nvidiafb blacklist rivatv EOF # ........ sudo sed -i -e '$a GRUB_CMDLINE_LINUX="rdblacklist=nouveau"' /etc/default/grub sudo update-grub # Make sure to give the EC2 instance S3 read permissions, otherwise this will fail sudo aws s3 cp --recursive s3://ec2-linux-nvidia-drivers/latest/ . sudo chmod +x NVIDIA-Linux-x86_64*.run sudo /bin/sh ./NVIDIA-Linux-x86_64*.run nvidia-smi -q | head# Configure the X Server sudo systemctl set-default graphical.target sudo systemctl isolate graphical.target sudo apt install -y mesa-utils sudo init 3 sudo init 5 sudo nvidia-xconfig --preserve-busid --enable-all-gpus sudo DISPLAY=:0 XAUTHORITY=$(ps aux | grep "X.*\-auth" | grep -v grep \ | sed -n 's/.*-auth \([^ ]\+\).*/\1/p') glxinfo | grep -i "opengl.*version" sudo systemctl isolate multi-user.target sudo systemctl isolate graphical.target# Install DCV wget https://d1uj6qtbmh3dt5.cloudfront.net/NICE-GPG-KEY gpg --import NICE-GPG-KEY sudo rm NICE-GPG-KEY sudo dcvstartx & sudo wget https://d1uj6qtbmh3dt5.cloudfront.net/2022.0/Servers/nice-dcv-2022.0-12123-ubuntu2004-x86_64.tgz sudo tar xvzf nice-dcv-*ubun*.tgz && cd nice-dcv-*64 sudo apt install -y ./nice-dcv-ser*.deb sudo apt install -y ./nice-x*.deb sudo apt install -y ./nice-dcv-web*.deb sudo usermod -aG video ubuntu sudo usermod -aG video dcv # Start the DCV Server and DCV session sudo systemctl enable dcvserver sudo systemctl start dcvserver sudo dcv create-session --type=console --owner=ubuntu newsession sudo dcv list-sessions #sudo dcv close-session newsession # Create random password for the user Ubuntu PASS=$(aws secretsmanager get-random-password --require-each-included-type --password-length 20 --query RandomPassword) INSTANCE=$(curl -H "X-aws-ec2-metadata-token: $TOKEN" -v http://169.254.169.254/latest/meta-data/public-hostname) aws secretsmanager create-secret --name DCV/$INSTANCE --description "Credentials for $INSTANCE." --secret-string "{\"user\":\"ubuntu\",\"password\":$PASS}" sudo systemctl isolate multi-user.target sudo systemctl isolate graphical.target ( 参考URL ) 環境構築が完了したら、DCVサーバーとセッションが立ち上がっているはずです。 事前にダウンロードした(DCV Client) https://download.nice-dcv.com/ を起動して、 インスタンス に リモートデスクトップ で接続しましょう。 NICE DCV接続に必要な情報として、以下の情報が必要になります。 サーバーのホスト名 or IP ポート番号 (手順2.で設定した)セッションID (手順2.で設定した) インスタンス ユーザー名/パスワード 以下の構文で入力します。 server_hostname_or_IP:port#session_id この リモートデスクトップ 接続は、UEプロジェクトのビルドの為に行うものです。 その為、もし事前に Linux 向けビルド済のUEアプリケーションをお持ちの方は、次の手順3.は不要です。 手順3. UEプロジェクトのビルド Unreal Engine の GitHubレポジトリ より、 Unreal Engine をダウンロードして コンパイル します。 クローン: git clone git@github.com:EpicGames/UnrealEngine.git コンパイル : cd UnrealEngine/ ./Setup.sh ./GenerateProjectFiles.sh make UnrealEngine GitHub レポジトリへのアクセスには、EpicGamesアカウントと GitHub アカウントとの紐付けが事前に必要になります。 もしお済みでない方は、 EpicGames公式サイトのFAQ よりアカウントの紐付けを行ってください。 こちらの コンパイル には物凄く時間がかかります。私が行った際は数時間程度かかったので、一旦放置しましょう。 無事 コンパイル が完了したら、 Unreal Editorを立ち上げます。 cd Engine/Binaries/Linux/ ./UnrealEditor デモ用に、Architectureテンプレートから「Collab Viewer」テンプレートを選択します。 PixelStreaming用に、プロジェクト設定とビルドを行います。 詳細な解説は、 こちらのUnreal Engine公式Documentation を参考にしてください。 Pixel Streaming プラグイン の選択、再起動 メニュー「Edit」 -> 「Plugins」より、Pixel Streamingを検索して、 チェックボックス をONにします。 再起動が求められるので、実施します。 Additional Launch Parametersの設定 メニュー「Edit」 -> 「Editor Preferences」 -> 「Level Editor」 -> 「Play」カテゴリより、 Additional Launch Parametersに以下を入力して、音声とIP/ポートを設定します。 AudioMixer -PixelStreamingIP=localhost -PixelStreamingPort=8888 UEアプリの Linux 向けビルド メニュー「Platforms」 -> 「 Linux 」 -> 「Package Project」より、プロジェクトのビルドを行います。 適切な ディレクト リを設定してください( このビルドにもかなり時間がかかるので、ご注意ください)。 ビルドが完了したら、指定した ディレクト リに「 Linux 」フォルダが作成されます。 手順4. UEアプリケーション、WebRTCサーバーの起動 まず、WebRTCサーバーを立ち上げます。 Unreal Engine が提供する「SignallingWebServer」(Node.jsベースの Cirrus.js を使用)を起動します。 セットアップ: cd ~/Desktop/UnrealEngine/Samples/PixelStreaming/WebServers/SignallingWebServer/platform_scripts/bash chmod +x ./setup.sh ./setup.sh SignallingServerの起動: chmod +x ./Start_SignallingServer.sh ./Start_SignallingServer.sh 次に、UEアプリケーションを立ち上げます。 ビルド先の ディレクト リに移動して、プロジェクト名のついたファイル(今回は「ps_test」)を実行します。 オプションとして、UEアプリのヘッドレス起動、Pixel StreamingのIP/ポートを指定します。   cd ~/Desktop/Linux chmod +x ./ps_test.sh ./ps_test.sh -RenderOffScreen -PixelStreamingIP=127.0.0.1 -PixelStreamingPort=888 無事に両方のサーバーが起動すると、以下のログ(「Streamer connected: ::ffff: 127.0.0.1 」)が表示されます。 無事起動できたら、接続確認をしましょう! 手順5. Webブラウザ で接続確認 インスタンス のグローパル IPアドレス を指定して、お好きな Webブラウザ からアクセスします。 モバイルでも問題なく動きます。 終わりに 今回、 Unreal Engine 5アプリケーションを、 Webブラウザ 向けにリアルタイムストリーミング配信を行いました。 以前の記事 でも紹介したように、 Unreal Engine は非常に高精細なCGをリアルタイム レンダリング できる為、いわゆる" ノンゲーム "領域への活用も今後進んでいくと思われます。 課題を挙げるとすると、サーバーにある程度スペックの高い GPU マシンが必要になる為、コストが一定かかってしまう点です(今回の検証では、 AWS 利用料金が約12ドル程かかりました)。 また、 ゲームエンジン 以外に クラウド やWebの専門知識を必要とする点も、課題として挙げられます。 現在ISIDは web3領域のグループ横断組織 を立ち上げ、Web3および メタバース 領域のR&Dを行っております(カテゴリー「3DCG」の記事は こちら )。 もし本領域にご興味のある方や、一緒にチャレンジしていきたい方は、ぜひお気軽にご連絡ください! 私たちと同じチームで働いてくれる仲間を、是非お待ちしております! ISID採用ページ(Web3/メタバース/AI) TODO コンテナ化 スケーリング セルフヒーリング ネットワーク構成、各種セキュリティ CI/CD 参考 White Paper: Streaming Unreal Engine content to multiple platforms AWS for Games Blog: Unreal Engine Pixel Streaming in AWS with Ubuntu OS Github: aws-samples/deploying-unreal-engine-pixel-streaming-server-on-ec2 執筆: @yamashita.yuki 、レビュー: @mizuno.kazuhiro ( Shodo で執筆されました )
アバター
こんにちは!金融ソリューション事業部の山下です。 本記事では、 こちらの記事 でも紹介した Unreal Engine が提供する「Pixel Streaiming」 プラグイン を使って AWS サーバーからリアルタイムCGストリーミング配信を行います。 説明をシンプルにする為、 AWS の基本的な知識(EC2, セキュリティグループ、IAMロールなど)の説明は割愛いたします。 また、本検証を行う場合、使用する GPU インスタンス の利用料金が発生する点にご注意ください。 実施手順 手順は、以下のとおりです。 AWS EC2で GPU インスタンス を作成 インスタンス 環境構築 UEプロジェクトのビルド UEアプリケーション、WebRTCサーバーの起動 Webブラウザ で接続確認 前提知識 1. Pixel Streaming (画像: https://docs.unrealengine.com/5.0/ja/overview-of-pixel-streaming-in-unreal-engine/ ) UEアプリケーションをサーバーサイドで実行して、 レンダリング 結果をWebRTCでストリーム配信する、EpicGames公式 プラグイン です。 インターネットに繋がった Webブラウザ があれば、 スマホ や タブレット 、ノートPCなど特別な端末スペック不要でUEアプリを楽しむことができます。 キーボード、マウス、タッチスクリーン入力などユーザーによるコン トロール も提供可能です。 使用環境/ツール Unreal Engine 5.0.3 Pixel Streaming plugin 1.0 AWS EC2 Instance type g4dn.xlarge AMI Ubuntu 20.04 LTS AMI NVIDIA CUDAドライバが必須 Architecture x86 EBS 250GB NICE DCV 2022.1 Client 手順1. AWS EC2で GPU インスタンス を作成 AWS EC2コンソールから インスタンス を作成します。 AMIには、 Ubuntu 20.04 LTS AMIを選択します。 このAMIには、PixelStreamingの動作要件として必要となる NVIDIA のCUDAドライバーがプリインストールされています。 インスタンス タイプは、 NVIDIA GPU が利用可能な G4dnインスタンス を選択します。 今回は動作検証が目的の為、最小サイズのxlargeを選択しました。 これから実施する Unreal Engine の コンパイル やアプリケーションのビルドにあまり時間をかけたくない方は、より高性能な インスタンス タイプをおすすめします。 また、vCPU使用リミットの引き上げが必要になる場合、 AWS に事前に申請が必要になります(今回選択したg4dn.xlargeの場合、vCPUは"4"になります)。 インスタンス の作成が完了したら、IAMロールでS3へのRead Permisionを付与します( AWS がEC2向けに提供する NVIDIA ドライバをダウンロードする為に必要となります)。 また、HTTPと SSH アクセス以外に、NICE DCV接続用の UDP / TCP ポートも開放します。 (※上記は検証用の設定なので、本番環境には使用しないでください。一応最低限のセキュリティとして SSH 接続にはkey pair、およびNICE DCV接続にはユーザー名とパスワードおよびセッションIDが必要になります) 手順2. インスタンス 環境構築 インスタンス に SSH などで接続して、環境構築を実施します。 こちらの AWS for Games Blogのチュートリアル を参考に、以下を実施します。 Ubuntu desctop managerのインストール NVIDIA ドライバのインストール DCVサーバーの起動、セッションの開始 AWS SecretsManagerを使用した、 Ubuntu ユーザーのパスワード生成 実行コマンド: #!/bin/bash #Installing the Ubuntu desktop manager sudo apt update -y sudo apt install -y ubuntu-desktop sudo apt install x11-xserver-utils sudo apt install awscli -y sudo apt install -y dpkg-dev sudo systemctl restart gdm3 sudo apt-get install xorg-dev -y #............................ # Disable the Wayland protocol. NICE DCV doesn't support the Wayland protocol. sudo sed -i '/WaylandEnable/s/^#//g' /etc/gdm3/custom.conf # ............................. # Install NVIDIA Drivers #.............................. sudo apt-get install -y unzip gcc make linux-headers-$(uname -r) cat << EOF | sudo tee --append /etc/modprobe.d/blacklist.conf blacklist vga16fb blacklist nouveau blacklist rivafb blacklist nvidiafb blacklist rivatv EOF # ........ sudo sed -i -e '$a GRUB_CMDLINE_LINUX="rdblacklist=nouveau"' /etc/default/grub sudo update-grub # Make sure to give the EC2 instance S3 read permissions, otherwise this will fail sudo aws s3 cp --recursive s3://ec2-linux-nvidia-drivers/latest/ . sudo chmod +x NVIDIA-Linux-x86_64*.run sudo /bin/sh ./NVIDIA-Linux-x86_64*.run nvidia-smi -q | head# Configure the X Server sudo systemctl set-default graphical.target sudo systemctl isolate graphical.target sudo apt install -y mesa-utils sudo init 3 sudo init 5 sudo nvidia-xconfig --preserve-busid --enable-all-gpus sudo DISPLAY=:0 XAUTHORITY=$(ps aux | grep "X.*\-auth" | grep -v grep \ | sed -n 's/.*-auth \([^ ]\+\).*/\1/p') glxinfo | grep -i "opengl.*version" sudo systemctl isolate multi-user.target sudo systemctl isolate graphical.target# Install DCV wget https://d1uj6qtbmh3dt5.cloudfront.net/NICE-GPG-KEY gpg --import NICE-GPG-KEY sudo rm NICE-GPG-KEY sudo dcvstartx & sudo wget https://d1uj6qtbmh3dt5.cloudfront.net/2022.0/Servers/nice-dcv-2022.0-12123-ubuntu2004-x86_64.tgz sudo tar xvzf nice-dcv-*ubun*.tgz && cd nice-dcv-*64 sudo apt install -y ./nice-dcv-ser*.deb sudo apt install -y ./nice-x*.deb sudo apt install -y ./nice-dcv-web*.deb sudo usermod -aG video ubuntu sudo usermod -aG video dcv # Start the DCV Server and DCV session sudo systemctl enable dcvserver sudo systemctl start dcvserver sudo dcv create-session --type=console --owner=ubuntu newsession sudo dcv list-sessions #sudo dcv close-session newsession # Create random password for the user Ubuntu PASS=$(aws secretsmanager get-random-password --require-each-included-type --password-length 20 --query RandomPassword) INSTANCE=$(curl -H "X-aws-ec2-metadata-token: $TOKEN" -v http://169.254.169.254/latest/meta-data/public-hostname) aws secretsmanager create-secret --name DCV/$INSTANCE --description "Credentials for $INSTANCE." --secret-string "{\"user\":\"ubuntu\",\"password\":$PASS}" sudo systemctl isolate multi-user.target sudo systemctl isolate graphical.target ( 参考URL ) 環境構築が完了したら、DCVサーバーとセッションが立ち上がっているはずです。 事前にダウンロードした(DCV Client) https://download.nice-dcv.com/ を起動して、 インスタンス に リモートデスクトップ で接続しましょう。 NICE DCV接続に必要な情報として、以下の情報が必要になります。 サーバーのホスト名 or IP ポート番号 (手順2.で設定した)セッションID (手順2.で設定した) インスタンス ユーザー名/パスワード 以下の構文で入力します。 server_hostname_or_IP:port#session_id この リモートデスクトップ 接続は、UEプロジェクトのビルドの為に行うものです。 その為、もし事前に Linux 向けビルド済のUEアプリケーションをお持ちの方は、次の手順3.は不要です。 手順3. UEプロジェクトのビルド Unreal Engine の GitHubレポジトリ より、 Unreal Engine をダウンロードして コンパイル します。 クローン: git clone git@github.com:EpicGames/UnrealEngine.git コンパイル : cd UnrealEngine/ ./Setup.sh ./GenerateProjectFiles.sh make UnrealEngine GitHub レポジトリへのアクセスには、EpicGamesアカウントと GitHub アカウントとの紐付けが事前に必要になります。 もしお済みでない方は、 EpicGames公式サイトのFAQ よりアカウントの紐付けを行ってください。 こちらの コンパイル には物凄く時間がかかります。私が行った際は数時間程度かかったので、一旦放置しましょう。 無事 コンパイル が完了したら、 Unreal Editorを立ち上げます。 cd Engine/Binaries/Linux/ ./UnrealEditor デモ用に、Architectureテンプレートから「Collab Viewer」テンプレートを選択します。 PixelStreaming用に、プロジェクト設定とビルドを行います。 詳細な解説は、 こちらのUnreal Engine公式Documentation を参考にしてください。 Pixel Streaming プラグイン の選択、再起動 メニュー「Edit」 -> 「Plugins」より、Pixel Streamingを検索して、 チェックボックス をONにします。 再起動が求められるので、実施します。 Additional Launch Parametersの設定 メニュー「Edit」 -> 「Editor Preferences」 -> 「Level Editor」 -> 「Play」カテゴリより、 Additional Launch Parametersに以下を入力して、音声とIP/ポートを設定します。 AudioMixer -PixelStreamingIP=localhost -PixelStreamingPort=8888 UEアプリの Linux 向けビルド メニュー「Platforms」 -> 「 Linux 」 -> 「Package Project」より、プロジェクトのビルドを行います。 適切な ディレクト リを設定してください( このビルドにもかなり時間がかかるので、ご注意ください)。 ビルドが完了したら、指定した ディレクト リに「 Linux 」フォルダが作成されます。 手順4. UEアプリケーション、WebRTCサーバーの起動 まず、WebRTCサーバーを立ち上げます。 Unreal Engine が提供する「SignallingWebServer」(Node.jsベースの Cirrus.js を使用)を起動します。 セットアップ: cd ~/Desktop/UnrealEngine/Samples/PixelStreaming/WebServers/SignallingWebServer/platform_scripts/bash chmod +x ./setup.sh ./setup.sh SignallingServerの起動: chmod +x ./Start_SignallingServer.sh ./Start_SignallingServer.sh 次に、UEアプリケーションを立ち上げます。 ビルド先の ディレクト リに移動して、プロジェクト名のついたファイル(今回は「ps_test」)を実行します。 オプションとして、UEアプリのヘッドレス起動、Pixel StreamingのIP/ポートを指定します。   cd ~/Desktop/Linux chmod +x ./ps_test.sh ./ps_test.sh -RenderOffScreen -PixelStreamingIP=127.0.0.1 -PixelStreamingPort=888 無事に両方のサーバーが起動すると、以下のログ(「Streamer connected: ::ffff: 127.0.0.1 」)が表示されます。 無事起動できたら、接続確認をしましょう! 手順5. Webブラウザ で接続確認 インスタンス のグローパル IPアドレス を指定して、お好きな Webブラウザ からアクセスします。 モバイルでも問題なく動きます。 終わりに 今回、 Unreal Engine 5アプリケーションを、 Webブラウザ 向けにリアルタイムストリーミング配信を行いました。 以前の記事 でも紹介したように、 Unreal Engine は非常に高精細なCGをリアルタイム レンダリング できる為、いわゆる" ノンゲーム "領域への活用も今後進んでいくと思われます。 課題を挙げるとすると、サーバーにある程度スペックの高い GPU マシンが必要になる為、コストが一定かかってしまう点です(今回の検証では、 AWS 利用料金が約12ドル程かかりました)。 また、 ゲームエンジン 以外に クラウド やWebの専門知識を必要とする点も、課題として挙げられます。 現在ISIDは web3領域のグループ横断組織 を立ち上げ、Web3および メタバース 領域のR&Dを行っております(カテゴリー「3DCG」の記事は こちら )。 もし本領域にご興味のある方や、一緒にチャレンジしていきたい方は、ぜひお気軽にご連絡ください! 私たちと同じチームで働いてくれる仲間を、是非お待ちしております! ISID採用ページ(Web3/メタバース/AI) TODO コンテナ化 スケーリング セルフヒーリング ネットワーク構成、各種セキュリティ CI/CD 参考 White Paper: Streaming Unreal Engine content to multiple platforms AWS for Games Blog: Unreal Engine Pixel Streaming in AWS with Ubuntu OS Github: aws-samples/deploying-unreal-engine-pixel-streaming-server-on-ec2 執筆: @yamashita.yuki 、レビュー: @mizuno.kazuhiro ( Shodo で執筆されました )
アバター
こんにちは。X(クロス) イノベーション 本部 ソフトウェアデザインセンター セキュリティグループの耿です。 AWS アカウントのセキュリティを向上させるために、IAMユーザー作成のイベントに即時反応して自動削除する仕組みを作りました。それに際し、マネジメントコンソールからIAMユーザーを作成・削除した時に裏側で起こっていることも調べてみました。 IAMユーザーをなるべく減らしたい理由 運用ルールだけではなく仕組みを 構成図 ユーザーを削除するために必要なこと コンソールからIAMユーザーを作成するときに起こっていること CreateUser (A)CreateLoginProfile (B)CreateAccessKey (G)PutUserPolicy (H)AttachUserPolicy (I)AddUserToGroup コンソールからIAMユーザーを削除するときに起こっていること (A)DeleteLoginProfile (B)ListAccessKeys > DeleteAccessKey (C)ListSigningCertificates > DeleteSigningCertificate (D)ListSSHPublicKeys > DeleteSSHPublicKey (E)ListServiceSpecificCredentials > DeleteServiceSpecificCredential (F)ListMFADevices > DeactivateMFADevice > DeleteVirtualMFADevice (G)ListUserPolicies > DeleteUserPolicy (H)ListAttachedUserPolicies > DetachUserPolicy (I)ListGroupsForUser > RemoveUserFromGroup DeleteUser 今回の仕組みでカバーする範囲 CDKテンプレート Lambda関数 パッケージからのインポートとIAMClientの生成 (A)ログインプロファイルの削除 (B)アクセスキーの削除 (G)インラインポリシーの削除 (H)管理ポリシーのデタッチ (I)IAMグループへの所属解除 Lambdaハンドラ さいごに IAMユーザーをなるべく減らしたい理由 IAMユーザーはログ インパス ワードやアクセスキーなどを利用して AWS リソースにアクセスできますが、いずれも長期的な認証情報です。有効期限が存在しないので退職者管理が必要となり、漏えいした際の影響も大きいため、 AWS アカウントに存在するIAMユーザーをなるべく減らすのが良いでしょう。IAMユーザーを使わずに AWS リソースにアクセスする方法には、例えば以下のようなものがあります。 AWS リソースから別の AWS リソースにアクセスする場合、短期的な認証情報を発行するIAMロールを割り当てる マネジメントコンソールにアクセスしたい場合、 SAML 2.0やIDフェデレーションでIAMロールを引き受けてシングルサインオンする 外部サービスから AWS リソースにアクセスする場合、サポートされていればOIDCなどでIAMロールを引き受ける(例: GitHubの場合 ) 開発中に一時的にアクセスキーを使いたいなど、IAMユーザーが欲しいケースもありますが、むやみやたらには増やさない方が良いでしょう。 運用ルールだけではなく仕組みを 決まったIAMユーザー以外は作成しないことを運用ルールとして定めている場合も多いと思いますが、ルールはあくまでもルール。あらゆる状況において徹底を保証するものではありません。新任者にルールを漏れなく周知することも課題になります。また何らかのルートで悪意ある第 三者 にアカウントに侵入されてしまった場合、 バックドア としてIAMユーザーを作られることもあります。運用ルールだけではなく、実際のリソースとしてIAMユーザーが作成されたときに仕組みで対応できると安心でしょう。今回は強行策として、ユーザーの作成を検知してほぼリアルタイムで自動削除を行う仕組みを作りました。 構成図 ユーザーの削除はEventBridgeのイベントをトリガーにLambda関数が行います。IAMイベントは直接EventBridgeイベントを生成しないため、 CloudTrailのAPIコールでイベントルールが発火 するようにします。IAMの API コールは us-east-1 リージョンに記録されるため、EventBridgeルールやLambda関数も us-east-1 リージョンに作ります。ユーザー作成イベントドリブンのため、既存のユーザーには影響がありません。 この記事の後半に、この仕組みのCDKコードとLambda関数の実装を掲載しています。 ユーザーを削除するために必要なこと IAMユーザーを削除するには DeleteUser API を使うだけで良さそうだと思っていましたが、実はそれほど単純ではありませんでした。 API のリンク先の説明にある通り、 DeleteUser の前にそのIAMユーザーに付属する以下のアイテムを全て削除する 必要があります。それぞれの説明と削除するための API 名を箇条書きします。また便宜上、(A)~(I)の記号を付け、この記事を通して同じ記号を使うようにしています。 (A)ログインプロファイル ( DeleteLoginProfile ) マネジメントコンソールにログインするためのパスワード。1ユーザーにつき1つのみ作成できる (B)アクセスキー ( DeleteAccessKey ) アクセスキーIDとシークレットアクセスキーのペア。1ユーザーあたり2つまで発行できる (C)署 名証 明書 ( DeleteSigningCertificate ) SOAP アクセスなどで使用するX.509証明書。1ユーザーあたり複数登録できる (D) SSH 公開鍵 ( DeleteSSHPublicKey ) CodeCommit 用の SSH 公開鍵。1ユーザーあたり複数登録できる (E)各サービス用のクレデンシャル ( DeleteServiceSpecificCredential ) CodeCommitや Amazon Keyspaces用のクレデンシャル。1ユーザーあたり複数発行できる (F)MFAデ バイス ( DeactivateMFADevice DeleteVirtualMFADevice ) サインイン時に使用するMFAデ バイス 。 DeactivateMFADevice で無効化してから DeleteVirtualMFADevice で削除する必要がある (G)インラインポリシー ( DeleteUserPolicy ) ユーザーに追加されているインラインポリシー。1ユーザーあたり複数作成できる (H)アタッチされている管理ポリシー ( DetachUserPolicy ) アタッチされている管理ポリシー。1ユーザーあたり複数アタッチできる (I)IAMグループへの所属 ( RemoveUserFromGroup ) 1ユーザーあたり複数のグループに所属できる なんと9種類もあります。(C)(D)(E)あたりは使われるケースが少ないと思いますが、他はいずれも馴染み深いものですね。先にこれらのアイテムを削除しないと、 DeleteUser の呼び出しは失敗してしまいます。 コンソールからIAMユーザーを作成するときに起こっていること IAMユーザーと9つのアイテムとの関連付けについての理解を深めるために、マネジメントコンソールからIAMユーザーを作成する時に、裏側でどのような API が呼ばれているのかを掘り下げてみます。コンソールでユーザーを作る時にはいろいろなオプションがありますが、us-east-1 リージョンの CloudTrail ログより実際に呼び出された API をそれぞれ調べました。参照系の API も多く呼ばれていますが、更新系 API のみに着目します。 CreateUser 最初に必ず CreateUser API が呼ばれます。コンソールからユーザーを作成するときはパスワードかアクセスキーのどちらかを同時に作らなければなりませんが、実はこの API 単体ではいずれも生成されません。この時点ではコンソールアクセスもプログラムアクセスもできないユーザー本体のみができあがります。 これ以降の API コールはコンソールで選択した内容によって変わります。いずれも独立したリソース作成のため、実行順は重要ではありません。 (A)CreateLoginProfile 認証情報として「パスワード - AWS マネジメントコンソールへのアクセス」を選択しているとこの API が呼ばれ、パスワードが設定されます。 (B)CreateAccessKey 認証情報として「アクセスキー - プログラムによるアクセス」を選択しているとこの API が呼ばれ、アクセスキーが発行されます。 (G)PutUserPolicy アクセス許可の設定で「アクセス権限を既存のユーザーからコピー」でコピー元のユーザーにインラインポリシーがあるとこの API が呼ばれ、同じインラインポリシーが追加されます。複数のポリシーを追加可能です。 (H)AttachUserPolicy アクセス許可の設定で「既存のポリシーを直接アタッチ」する、もしくは「アクセス権限を既存のユーザーからコピー」でコピー元のユーザーに管理ポリシーがアタッチされているとこの API が呼ばれ、ユーザーに管理ポリシーがアタッチされます。複数のポリシーをアタッチ可能です。 (I)AddUserToGroup アクセス許可の設定で「ユーザーをグループに追加」する、もしくは「アクセス権限を既存のユーザーからコピー」でコピー元のユーザーがIAMグループに所属しているとこの API が呼ばれ、ユーザーがグループに所属します。複数のグループに所属可能です。 今のところ、「(C)署 名証 明書」「(D) SSH 公開鍵」「(E)各サービス用のクレデンシャル」「(F)MFAデ バイス 」はコンソールからのユーザー作成時に同時には作成できませんでした。 コンソールからIAMユーザーを削除するときに起こっていること 次に、(A)~(I)を全て追加したもりもりのユーザーを作成しておき、コンソールから削除した時にどのような API が裏側で呼ばれているのかを見てみました。更新系 API と、それに関連する参照系 API について記載します。 (A)DeleteLoginProfile パスワードが削除されます。 (B)ListAccessKeys > DeleteAccessKey まずは ListAccessKeys で削除対象ユーザーのアクセスキー一覧を取得し、見つかればそれぞれについて DeleteAccessKey で削除されます。 (C)ListSigningCertificates > DeleteSigningCertificate ListSigningCertificates で証明書一覧を取得し、見つかればそれぞれについて DeleteSigningCertificate で削除されます。 (D)ListSSHPublicKeys > DeleteSSHPublicKey ListSSHPublicKeys で鍵一覧を取得し、見つかればそれぞれについて DeleteSSHPublicKey で削除されます。 (E)ListServiceSpecificCredentials > DeleteServiceSpecificCredential ListServiceSpecificCredentials でクレデンシャル一覧を取得し、見つかればそれぞれについて DeleteServiceSpecificCredential で削除されます。 (F)ListMFADevices > DeactivateMFADevice > DeleteVirtualMFADevice ListMFADevices でデ バイス 一覧を取得し、見つかればそれぞれについて DeactivateMFADevice で無効化したのち、 DeleteVirtualMFADevice で削除されます。 (G)ListUserPolicies > DeleteUserPolicy ListUserPolicies でインラインポリシー一覧を取得し、見つかればそれぞれについて DeleteUserPolicy で削除されます。 (H)ListAttachedUserPolicies > DetachUserPolicy ListAttachedUserPolicies でアタッチされている管理ポリシー一覧を取得し、見つかればそれぞれについて DetachUserPolicy でデタッチされます。管理ポリシー自体は削除されません。 (I)ListGroupsForUser > RemoveUserFromGroup ListGroupsForUser で所属しているIAMグループ一覧を取得し、見つかればそれぞれについて RemoveUserFromGroup で所属が解除されます。IAMグループ自体は削除されません。 DeleteUser 以上のアイテムが全て削除されると、最後に DeleteUser が呼ばれてユーザー本体が晴れて(?)削除されます。画面では数クリックで終わる操作ですが、実は多くの API が呼ばれていることがわかりますね。 今回の仕組みでカバーする範囲 ユーザー削除時の API コールを参考にして、IAMユーザーを自動削除するための仕組みを作ります。以下ではCDKとLambda関数のコードを解説しますが、今回はIAMユーザー本体の削除に先立って、以下の5つのアイテムの削除のみを行います。 (A)ログインプロファイル (B)アクセスキー (G)インラインポリシー (H)アタッチされている管理ポリシー (I)IAMグループへの所属 (C)(D)(E)(F)についてはコンソールでユーザーを作成する時に同時に作成できないため、今回は省略しています。コンソールでも CLI でも、これらのアイテムを作成するためにはユーザー作成とは別の操作が必要になりますので、その操作が実施される前にEventBridgeイベントが発火し、ユーザーが削除されることが期待できます。もちろん、 CLI などから CreateUser 実行後、EventBridgeイベントが発火する前に(C)(D)(E)(F)を作成する API が素早く実行されるとユーザーの削除に失敗しますので、より確実にユーザーを削除したい場合は(C)(D)(E)(F)の削除も対応した方が良いでしょう。 CDKテンプレート IAMユーザーの作成を検知し、自動削除する仕組みのCDKスタックは次のとおりです。 EventBridgeルールは detailType を AWS API Call via CloudTrail 、 eventName を CreateUser にすることで、ユーザー作成 API の呼び出しがCloudTrailに記録されるとイベントが起動します。 前述の通り、IAMの更新系 API は us-east-1 リージョンのCloudTrailに記録されるので、このスタックも us-east-1 リージョンにデプロイする必要があります。 import { Duration , Stack , StackProps } from "aws-cdk-lib" ; import * as events from "aws-cdk-lib/aws-events" ; import * as eventTargets from "aws-cdk-lib/aws-events-targets" ; import * as iam from "aws-cdk-lib/aws-iam" ; import { Runtime } from "aws-cdk-lib/aws-lambda" ; import * as lambdaNodejs from "aws-cdk-lib/aws-lambda-nodejs" ; import { Construct } from "constructs" ; export class DeleteUserStack extends Stack { constructor( scope: Construct , id: string , props?: StackProps ) { super( scope , id , props ); // Lambda関数 const deleteUserFunction = new lambdaNodejs.NodejsFunction ( this , "DeleteUserFunction" , { entry: "functions/delete-user.ts" , runtime: Runtime.NODEJS_16_X , timeout: Duration.minutes ( 15 ), } ); // 必要なActionを追加 deleteUserFunction.addToRolePolicy ( new iam.PolicyStatement ( { resources: [ "*" ] , actions: [ "iam:DeleteUser" , "iam:ListAccessKeys" , "iam:DeleteAccessKey" , "iam:ListGroupsForUser" , "iam:RemoveUserFromGroup" , "iam:GetLoginProfile" , "iam:DeleteLoginProfile" , "iam:ListAttachedUserPolicies" , "iam:DetachUserPolicy" , "iam:ListUserPolicies" , "iam:DeleteUserPolicy" , ] , effect: iam.Effect.ALLOW , } ) ); // EventBrdigeルール new events.Rule ( this , "CreateUserEventRule" , { ruleName: "create-user" , eventPattern: { source: [ "aws.iam" ] , detailType: [ "AWS API Call via CloudTrail" ] , detail: { eventSource: [ "iam.amazonaws.com" ] , eventName: [ "CreateUser" ] , } , } , targets: [ new eventTargets.LambdaFunction ( deleteUserFunction ) ] , } ); } } Lambda関数 functions/delete-user.ts のLambda関数の実装を説明します。 パッケージからのインポートとIAMClientの生成 IAM用の SDK をインポートします。Lambda関数が受け取るのはEventBridgeイベントなので、 aws-lambda パッケージから EventBridgeEvent もインポートします。 IAMClientは us-east-1 リージョン向けに生成します。 import * as iam from "@aws-sdk/client-iam" ; import { Handler , EventBridgeEvent } from "aws-lambda" ; const client = new iam.IAMClient ( { region: "us-east-1" } ); (A)ログインプロファイルの削除 ログインプロファイルを削除する関数です。最初に GetLoginProfileCommand でログインプロファイルが存在するかどうかを確認し、存在すれば while ループで削除を試みます。while ループにしている理由は、プロファイルの作成には時間がかかるようで、 CreateLoginProfile コール直後に DeleteLoginProfile をしても次のエラーになってしまうためです。 Login Profile for User <ユーザー名> cannot be modified while login profile is being created. そこで while ループで間に5秒間ずつ待機し、最大30回削除を試みる実装にしました。実際にやってみると、体感的にログインプロファイル作成後15秒ほどで削除できる状態になっていました。 export const deleteLoginProfile = async ( userName: string ) : Promise < void > => { try { const { LoginProfile } = await client.send (new iam.GetLoginProfileCommand ( { UserName: userName } )); if ( LoginProfile === undefined || LoginProfile.UserName === undefined ) return; } catch ( e: unknown ) { console .log ( ` ${ e instanceof Error ? e.message : "" } ` ); return; } let attempts = 30 ; while ( attempts > 0 ) { try { await client.send (new iam.DeleteLoginProfileCommand ( { UserName: userName } )); break; } catch ( e: unknown ) { console .log ( ` ${ e instanceof Error ? e.message : "" } ` ); } await new Promise (( resolve ) => setTimeout ( resolve , 5000 )); attempts --; } } ; (B)アクセスキーの削除 次の関数でアクセスキーを削除します。まずは ListAccessKeysCommand でアクセスキー一覧を取得します。アクセスキーの件数が多い場合(実際には各ユーザー2つまでなので起こらないと思いますが)、一回の ListAccessKeysCommand 呼び出しで全件取得できず、結果の IsTruncated プロパティが true で返ってきます。この時の Marker プロパティを次の ListAccessKeysCommand 呼び出しのパラメータに含めることで、続きの結果を取得できます。一覧取得後は DeleteAccessKeyCommand で1つずつ削除します。 export const deleteAccessKeys = async ( userName: string ) : Promise < void > => { let accessKeyIds: string [] = [] ; let shouldListNext = true ; let Marker: string | undefined = undefined ; while ( shouldListNext ) { const params: iam.ListAccessKeysCommandInput = { UserName: userName , Marker } ; const output = await client.send (new iam.ListAccessKeysCommand ( params )); if ( output.AccessKeyMetadata ) { accessKeyIds = accessKeyIds.concat ( output.AccessKeyMetadata.filter (( accessKeyMetadata ) => accessKeyMetadata.AccessKeyId ) .map ( ( accessKeyMetadata ) => accessKeyMetadata.AccessKeyId ! ) ); } output.IsTruncated ? ( Marker = output.Marker ) : ( shouldListNext = false ); } if ( accessKeyIds.length === 0 ) return; await Promise . all( accessKeyIds.map (async ( keyId ) => { await client.send (new iam.DeleteAccessKeyCommand ( { UserName: userName , AccessKeyId: keyId } )); } ) ); } ; (G)インラインポリシーの削除 (B)アクセスキーの削除 と同様の処理なので、説明は割愛します。 export const deleteUserPolicies = async ( userName: string ) : Promise < void > => { let policyNames: string [] = [] ; let shouldListNext = true ; let Marker: string | undefined = undefined ; while ( shouldListNext ) { const params: iam.ListUserPoliciesCommandInput = { UserName: userName , Marker } ; const output = await client.send (new iam.ListUserPoliciesCommand ( params )); if ( output.PolicyNames ) { policyNames = policyNames.concat ( output.PolicyNames.filter (( name ) => name )); } output.IsTruncated ? ( Marker = output.Marker ) : ( shouldListNext = false ); } if ( policyNames.length === 0 ) return; await Promise . all( policyNames.map (async ( name ) => { await client.send (new iam.DeleteUserPolicyCommand ( { UserName: userName , PolicyName: name } )); } ) ); } ; (H)管理ポリシーのデタッチ 同様の処理なので、説明は割愛します。 export const detachUserPolicies = async ( userName: string ) : Promise < void > => { let attachedPolicieArns: string [] = [] ; let shouldListNext = true ; let Marker: string | undefined = undefined ; while ( shouldListNext ) { const params: iam.ListAttachedUserPoliciesCommandInput = { UserName: userName , Marker } ; const output = await client.send (new iam.ListAttachedUserPoliciesCommand ( params )); if ( output.AttachedPolicies ) { attachedPolicieArns = attachedPolicieArns.concat ( output.AttachedPolicies.filter (( policy ) => policy.PolicyArn ) .map (( policy ) => policy.PolicyArn ! ) ); } output.IsTruncated ? ( Marker = output.Marker ) : ( shouldListNext = false ); } if ( attachedPolicieArns.length === 0 ) return; await Promise . all( attachedPolicieArns.map (async ( arn ) => { await client.send (new iam.DetachUserPolicyCommand ( { UserName: userName , PolicyArn: arn } )); } ) ); } ; (I)IAMグループへの所属解除 同様の処理なので、説明は割愛します。 export const removeUserFromGroups = async ( userName: string ) : Promise < void > => { let groupNames: string [] = [] ; let shouldListNext = true ; let Marker: string | undefined = undefined ; while ( shouldListNext ) { const params: iam.ListGroupsForUserCommandInput = { UserName: userName , Marker } ; const output = await client.send (new iam.ListGroupsForUserCommand ( params )); if ( output.Groups ) { groupNames = groupNames.concat ( output.Groups.filter (( group ) => group.GroupName ) .map (( group ) => group.GroupName ! )); } output.IsTruncated ? ( Marker = output.Marker ) : ( shouldListNext = false ); } if ( groupNames.length === 0 ) return; await Promise . all( groupNames.map (async ( groupName ) => { await client.send (new iam.RemoveUserFromGroupCommand ( { UserName: userName , GroupName: groupName } )); } ) ); } ; Lambdaハンドラ 作成されたIAMユーザー名をイベントの中身から取得するための型を定義し、上で説明した関数を利用してLambdaハンドラを実装します。各アイテムの削除後に DeleteUserCommand でユーザー本体を削除します。 type Detail = { responseElements: { user: { userName: string } } } ; export const handler: Handler = async ( event: EventBridgeEvent < string , Detail >) => { const userName = event.detail.responseElements.user.userName ; await Promise . all( [ deleteLoginProfile ( userName ), deleteAccessKeys ( userName ), deleteUserPolicies ( userName ), detachUserPolicies ( userName ), removeUserFromGroups ( userName ), ] ); await client.send (new iam.DeleteUserCommand ( { UserName: userName } )); } ; これで、IAMユーザーが作成されるイベントをトリガーに、即座にユーザーを削除する仕組みが完成しました。 さいごに 自前で自動化の仕組みを実装してみると、普段コンソールでしている操作の裏側でどのような API が動いているのかが分かり、とても面白かったです。 最後までお読みただいてありがとうございました。 私たちは同じチームで働いてくれる仲間を大募集しています!たくさんのご応募をお待ちしています。 - セキュリティエンジニア(セキュリティ設計) 執筆: @kou.kinyo2 、レビュー: @higa ( Shodo で執筆されました )
アバター
こんにちは。X(クロス) イノベーション 本部 ソフトウェアデザインセンター セキュリティグループの耿です。 AWS アカウントのセキュリティを向上させるために、IAMユーザー作成のイベントに即時反応して自動削除する仕組みを作りました。それに際し、マネジメントコンソールからIAMユーザーを作成・削除した時に裏側で起こっていることも調べてみました。 IAMユーザーをなるべく減らしたい理由 運用ルールだけではなく仕組みを 構成図 ユーザーを削除するために必要なこと コンソールからIAMユーザーを作成するときに起こっていること CreateUser (A)CreateLoginProfile (B)CreateAccessKey (G)PutUserPolicy (H)AttachUserPolicy (I)AddUserToGroup コンソールからIAMユーザーを削除するときに起こっていること (A)DeleteLoginProfile (B)ListAccessKeys > DeleteAccessKey (C)ListSigningCertificates > DeleteSigningCertificate (D)ListSSHPublicKeys > DeleteSSHPublicKey (E)ListServiceSpecificCredentials > DeleteServiceSpecificCredential (F)ListMFADevices > DeactivateMFADevice > DeleteVirtualMFADevice (G)ListUserPolicies > DeleteUserPolicy (H)ListAttachedUserPolicies > DetachUserPolicy (I)ListGroupsForUser > RemoveUserFromGroup DeleteUser 今回の仕組みでカバーする範囲 CDKテンプレート Lambda関数 パッケージからのインポートとIAMClientの生成 (A)ログインプロファイルの削除 (B)アクセスキーの削除 (G)インラインポリシーの削除 (H)管理ポリシーのデタッチ (I)IAMグループへの所属解除 Lambdaハンドラ さいごに IAMユーザーをなるべく減らしたい理由 IAMユーザーはログ インパス ワードやアクセスキーなどを利用して AWS リソースにアクセスできますが、いずれも長期的な認証情報です。有効期限が存在しないので退職者管理が必要となり、漏えいした際の影響も大きいため、 AWS アカウントに存在するIAMユーザーをなるべく減らすのが良いでしょう。IAMユーザーを使わずに AWS リソースにアクセスする方法には、例えば以下のようなものがあります。 AWS リソースから別の AWS リソースにアクセスする場合、短期的な認証情報を発行するIAMロールを割り当てる マネジメントコンソールにアクセスしたい場合、 SAML 2.0やIDフェデレーションでIAMロールを引き受けてシングルサインオンする 外部サービスから AWS リソースにアクセスする場合、サポートされていればOIDCなどでIAMロールを引き受ける(例: GitHubの場合 ) 開発中に一時的にアクセスキーを使いたいなど、IAMユーザーが欲しいケースもありますが、むやみやたらには増やさない方が良いでしょう。 運用ルールだけではなく仕組みを 決まったIAMユーザー以外は作成しないことを運用ルールとして定めている場合も多いと思いますが、ルールはあくまでもルール。あらゆる状況において徹底を保証するものではありません。新任者にルールを漏れなく周知することも課題になります。また何らかのルートで悪意ある第 三者 にアカウントに侵入されてしまった場合、 バックドア としてIAMユーザーを作られることもあります。運用ルールだけではなく、実際のリソースとしてIAMユーザーが作成されたときに仕組みで対応できると安心でしょう。今回は強行策として、ユーザーの作成を検知してほぼリアルタイムで自動削除を行う仕組みを作りました。 構成図 ユーザーの削除はEventBridgeのイベントをトリガーにLambda関数が行います。IAMイベントは直接EventBridgeイベントを生成しないため、 CloudTrailのAPIコールでイベントルールが発火 するようにします。IAMの API コールは us-east-1 リージョンに記録されるため、EventBridgeルールやLambda関数も us-east-1 リージョンに作ります。ユーザー作成イベントドリブンのため、既存のユーザーには影響がありません。 この記事の後半に、この仕組みのCDKコードとLambda関数の実装を掲載しています。 ユーザーを削除するために必要なこと IAMユーザーを削除するには DeleteUser API を使うだけで良さそうだと思っていましたが、実はそれほど単純ではありませんでした。 API のリンク先の説明にある通り、 DeleteUser の前にそのIAMユーザーに付属する以下のアイテムを全て削除する 必要があります。それぞれの説明と削除するための API 名を箇条書きします。また便宜上、(A)~(I)の記号を付け、この記事を通して同じ記号を使うようにしています。 (A)ログインプロファイル ( DeleteLoginProfile ) マネジメントコンソールにログインするためのパスワード。1ユーザーにつき1つのみ作成できる (B)アクセスキー ( DeleteAccessKey ) アクセスキーIDとシークレットアクセスキーのペア。1ユーザーあたり2つまで発行できる (C)署 名証 明書 ( DeleteSigningCertificate ) SOAP アクセスなどで使用するX.509証明書。1ユーザーあたり複数登録できる (D) SSH 公開鍵 ( DeleteSSHPublicKey ) CodeCommit 用の SSH 公開鍵。1ユーザーあたり複数登録できる (E)各サービス用のクレデンシャル ( DeleteServiceSpecificCredential ) CodeCommitや Amazon Keyspaces用のクレデンシャル。1ユーザーあたり複数発行できる (F)MFAデ バイス ( DeactivateMFADevice DeleteVirtualMFADevice ) サインイン時に使用するMFAデ バイス 。 DeactivateMFADevice で無効化してから DeleteVirtualMFADevice で削除する必要がある (G)インラインポリシー ( DeleteUserPolicy ) ユーザーに追加されているインラインポリシー。1ユーザーあたり複数作成できる (H)アタッチされている管理ポリシー ( DetachUserPolicy ) アタッチされている管理ポリシー。1ユーザーあたり複数アタッチできる (I)IAMグループへの所属 ( RemoveUserFromGroup ) 1ユーザーあたり複数のグループに所属できる なんと9種類もあります。(C)(D)(E)あたりは使われるケースが少ないと思いますが、他はいずれも馴染み深いものですね。先にこれらのアイテムを削除しないと、 DeleteUser の呼び出しは失敗してしまいます。 コンソールからIAMユーザーを作成するときに起こっていること IAMユーザーと9つのアイテムとの関連付けについての理解を深めるために、マネジメントコンソールからIAMユーザーを作成する時に、裏側でどのような API が呼ばれているのかを掘り下げてみます。コンソールでユーザーを作る時にはいろいろなオプションがありますが、us-east-1 リージョンの CloudTrail ログより実際に呼び出された API をそれぞれ調べました。参照系の API も多く呼ばれていますが、更新系 API のみに着目します。 CreateUser 最初に必ず CreateUser API が呼ばれます。コンソールからユーザーを作成するときはパスワードかアクセスキーのどちらかを同時に作らなければなりませんが、実はこの API 単体ではいずれも生成されません。この時点ではコンソールアクセスもプログラムアクセスもできないユーザー本体のみができあがります。 これ以降の API コールはコンソールで選択した内容によって変わります。いずれも独立したリソース作成のため、実行順は重要ではありません。 (A)CreateLoginProfile 認証情報として「パスワード - AWS マネジメントコンソールへのアクセス」を選択しているとこの API が呼ばれ、パスワードが設定されます。 (B)CreateAccessKey 認証情報として「アクセスキー - プログラムによるアクセス」を選択しているとこの API が呼ばれ、アクセスキーが発行されます。 (G)PutUserPolicy アクセス許可の設定で「アクセス権限を既存のユーザーからコピー」でコピー元のユーザーにインラインポリシーがあるとこの API が呼ばれ、同じインラインポリシーが追加されます。複数のポリシーを追加可能です。 (H)AttachUserPolicy アクセス許可の設定で「既存のポリシーを直接アタッチ」する、もしくは「アクセス権限を既存のユーザーからコピー」でコピー元のユーザーに管理ポリシーがアタッチされているとこの API が呼ばれ、ユーザーに管理ポリシーがアタッチされます。複数のポリシーをアタッチ可能です。 (I)AddUserToGroup アクセス許可の設定で「ユーザーをグループに追加」する、もしくは「アクセス権限を既存のユーザーからコピー」でコピー元のユーザーがIAMグループに所属しているとこの API が呼ばれ、ユーザーがグループに所属します。複数のグループに所属可能です。 今のところ、「(C)署 名証 明書」「(D) SSH 公開鍵」「(E)各サービス用のクレデンシャル」「(F)MFAデ バイス 」はコンソールからのユーザー作成時に同時には作成できませんでした。 コンソールからIAMユーザーを削除するときに起こっていること 次に、(A)~(I)を全て追加したもりもりのユーザーを作成しておき、コンソールから削除した時にどのような API が裏側で呼ばれているのかを見てみました。更新系 API と、それに関連する参照系 API について記載します。 (A)DeleteLoginProfile パスワードが削除されます。 (B)ListAccessKeys > DeleteAccessKey まずは ListAccessKeys で削除対象ユーザーのアクセスキー一覧を取得し、見つかればそれぞれについて DeleteAccessKey で削除されます。 (C)ListSigningCertificates > DeleteSigningCertificate ListSigningCertificates で証明書一覧を取得し、見つかればそれぞれについて DeleteSigningCertificate で削除されます。 (D)ListSSHPublicKeys > DeleteSSHPublicKey ListSSHPublicKeys で鍵一覧を取得し、見つかればそれぞれについて DeleteSSHPublicKey で削除されます。 (E)ListServiceSpecificCredentials > DeleteServiceSpecificCredential ListServiceSpecificCredentials でクレデンシャル一覧を取得し、見つかればそれぞれについて DeleteServiceSpecificCredential で削除されます。 (F)ListMFADevices > DeactivateMFADevice > DeleteVirtualMFADevice ListMFADevices でデ バイス 一覧を取得し、見つかればそれぞれについて DeactivateMFADevice で無効化したのち、 DeleteVirtualMFADevice で削除されます。 (G)ListUserPolicies > DeleteUserPolicy ListUserPolicies でインラインポリシー一覧を取得し、見つかればそれぞれについて DeleteUserPolicy で削除されます。 (H)ListAttachedUserPolicies > DetachUserPolicy ListAttachedUserPolicies でアタッチされている管理ポリシー一覧を取得し、見つかればそれぞれについて DetachUserPolicy でデタッチされます。管理ポリシー自体は削除されません。 (I)ListGroupsForUser > RemoveUserFromGroup ListGroupsForUser で所属しているIAMグループ一覧を取得し、見つかればそれぞれについて RemoveUserFromGroup で所属が解除されます。IAMグループ自体は削除されません。 DeleteUser 以上のアイテムが全て削除されると、最後に DeleteUser が呼ばれてユーザー本体が晴れて(?)削除されます。画面では数クリックで終わる操作ですが、実は多くの API が呼ばれていることがわかりますね。 今回の仕組みでカバーする範囲 ユーザー削除時の API コールを参考にして、IAMユーザーを自動削除するための仕組みを作ります。以下ではCDKとLambda関数のコードを解説しますが、今回はIAMユーザー本体の削除に先立って、以下の5つのアイテムの削除のみを行います。 (A)ログインプロファイル (B)アクセスキー (G)インラインポリシー (H)アタッチされている管理ポリシー (I)IAMグループへの所属 (C)(D)(E)(F)についてはコンソールでユーザーを作成する時に同時に作成できないため、今回は省略しています。コンソールでも CLI でも、これらのアイテムを作成するためにはユーザー作成とは別の操作が必要になりますので、その操作が実施される前にEventBridgeイベントが発火し、ユーザーが削除されることが期待できます。もちろん、 CLI などから CreateUser 実行後、EventBridgeイベントが発火する前に(C)(D)(E)(F)を作成する API が素早く実行されるとユーザーの削除に失敗しますので、より確実にユーザーを削除したい場合は(C)(D)(E)(F)の削除も対応した方が良いでしょう。 CDKテンプレート IAMユーザーの作成を検知し、自動削除する仕組みのCDKスタックは次のとおりです。 EventBridgeルールは detailType を AWS API Call via CloudTrail 、 eventName を CreateUser にすることで、ユーザー作成 API の呼び出しがCloudTrailに記録されるとイベントが起動します。 前述の通り、IAMの更新系 API は us-east-1 リージョンのCloudTrailに記録されるので、このスタックも us-east-1 リージョンにデプロイする必要があります。 import { Duration , Stack , StackProps } from "aws-cdk-lib" ; import * as events from "aws-cdk-lib/aws-events" ; import * as eventTargets from "aws-cdk-lib/aws-events-targets" ; import * as iam from "aws-cdk-lib/aws-iam" ; import { Runtime } from "aws-cdk-lib/aws-lambda" ; import * as lambdaNodejs from "aws-cdk-lib/aws-lambda-nodejs" ; import { Construct } from "constructs" ; export class DeleteUserStack extends Stack { constructor( scope: Construct , id: string , props?: StackProps ) { super( scope , id , props ); // Lambda関数 const deleteUserFunction = new lambdaNodejs.NodejsFunction ( this , "DeleteUserFunction" , { entry: "functions/delete-user.ts" , runtime: Runtime.NODEJS_16_X , timeout: Duration.minutes ( 15 ), } ); // 必要なActionを追加 deleteUserFunction.addToRolePolicy ( new iam.PolicyStatement ( { resources: [ "*" ] , actions: [ "iam:DeleteUser" , "iam:ListAccessKeys" , "iam:DeleteAccessKey" , "iam:ListGroupsForUser" , "iam:RemoveUserFromGroup" , "iam:GetLoginProfile" , "iam:DeleteLoginProfile" , "iam:ListAttachedUserPolicies" , "iam:DetachUserPolicy" , "iam:ListUserPolicies" , "iam:DeleteUserPolicy" , ] , effect: iam.Effect.ALLOW , } ) ); // EventBrdigeルール new events.Rule ( this , "CreateUserEventRule" , { ruleName: "create-user" , eventPattern: { source: [ "aws.iam" ] , detailType: [ "AWS API Call via CloudTrail" ] , detail: { eventSource: [ "iam.amazonaws.com" ] , eventName: [ "CreateUser" ] , } , } , targets: [ new eventTargets.LambdaFunction ( deleteUserFunction ) ] , } ); } } Lambda関数 functions/delete-user.ts のLambda関数の実装を説明します。 パッケージからのインポートとIAMClientの生成 IAM用の SDK をインポートします。Lambda関数が受け取るのはEventBridgeイベントなので、 aws-lambda パッケージから EventBridgeEvent もインポートします。 IAMClientは us-east-1 リージョン向けに生成します。 import * as iam from "@aws-sdk/client-iam" ; import { Handler , EventBridgeEvent } from "aws-lambda" ; const client = new iam.IAMClient ( { region: "us-east-1" } ); (A)ログインプロファイルの削除 ログインプロファイルを削除する関数です。最初に GetLoginProfileCommand でログインプロファイルが存在するかどうかを確認し、存在すれば while ループで削除を試みます。while ループにしている理由は、プロファイルの作成には時間がかかるようで、 CreateLoginProfile コール直後に DeleteLoginProfile をしても次のエラーになってしまうためです。 Login Profile for User <ユーザー名> cannot be modified while login profile is being created. そこで while ループで間に5秒間ずつ待機し、最大30回削除を試みる実装にしました。実際にやってみると、体感的にログインプロファイル作成後15秒ほどで削除できる状態になっていました。 export const deleteLoginProfile = async ( userName: string ) : Promise < void > => { try { const { LoginProfile } = await client.send (new iam.GetLoginProfileCommand ( { UserName: userName } )); if ( LoginProfile === undefined || LoginProfile.UserName === undefined ) return; } catch ( e: unknown ) { console .log ( ` ${ e instanceof Error ? e.message : "" } ` ); return; } let attempts = 30 ; while ( attempts > 0 ) { try { await client.send (new iam.DeleteLoginProfileCommand ( { UserName: userName } )); break; } catch ( e: unknown ) { console .log ( ` ${ e instanceof Error ? e.message : "" } ` ); } await new Promise (( resolve ) => setTimeout ( resolve , 5000 )); attempts --; } } ; (B)アクセスキーの削除 次の関数でアクセスキーを削除します。まずは ListAccessKeysCommand でアクセスキー一覧を取得します。アクセスキーの件数が多い場合(実際には各ユーザー2つまでなので起こらないと思いますが)、一回の ListAccessKeysCommand 呼び出しで全件取得できず、結果の IsTruncated プロパティが true で返ってきます。この時の Marker プロパティを次の ListAccessKeysCommand 呼び出しのパラメータに含めることで、続きの結果を取得できます。一覧取得後は DeleteAccessKeyCommand で1つずつ削除します。 export const deleteAccessKeys = async ( userName: string ) : Promise < void > => { let accessKeyIds: string [] = [] ; let shouldListNext = true ; let Marker: string | undefined = undefined ; while ( shouldListNext ) { const params: iam.ListAccessKeysCommandInput = { UserName: userName , Marker } ; const output = await client.send (new iam.ListAccessKeysCommand ( params )); if ( output.AccessKeyMetadata ) { accessKeyIds = accessKeyIds.concat ( output.AccessKeyMetadata.filter (( accessKeyMetadata ) => accessKeyMetadata.AccessKeyId ) .map ( ( accessKeyMetadata ) => accessKeyMetadata.AccessKeyId ! ) ); } output.IsTruncated ? ( Marker = output.Marker ) : ( shouldListNext = false ); } if ( accessKeyIds.length === 0 ) return; await Promise . all( accessKeyIds.map (async ( keyId ) => { await client.send (new iam.DeleteAccessKeyCommand ( { UserName: userName , AccessKeyId: keyId } )); } ) ); } ; (G)インラインポリシーの削除 (B)アクセスキーの削除 と同様の処理なので、説明は割愛します。 export const deleteUserPolicies = async ( userName: string ) : Promise < void > => { let policyNames: string [] = [] ; let shouldListNext = true ; let Marker: string | undefined = undefined ; while ( shouldListNext ) { const params: iam.ListUserPoliciesCommandInput = { UserName: userName , Marker } ; const output = await client.send (new iam.ListUserPoliciesCommand ( params )); if ( output.PolicyNames ) { policyNames = policyNames.concat ( output.PolicyNames.filter (( name ) => name )); } output.IsTruncated ? ( Marker = output.Marker ) : ( shouldListNext = false ); } if ( policyNames.length === 0 ) return; await Promise . all( policyNames.map (async ( name ) => { await client.send (new iam.DeleteUserPolicyCommand ( { UserName: userName , PolicyName: name } )); } ) ); } ; (H)管理ポリシーのデタッチ 同様の処理なので、説明は割愛します。 export const detachUserPolicies = async ( userName: string ) : Promise < void > => { let attachedPolicieArns: string [] = [] ; let shouldListNext = true ; let Marker: string | undefined = undefined ; while ( shouldListNext ) { const params: iam.ListAttachedUserPoliciesCommandInput = { UserName: userName , Marker } ; const output = await client.send (new iam.ListAttachedUserPoliciesCommand ( params )); if ( output.AttachedPolicies ) { attachedPolicieArns = attachedPolicieArns.concat ( output.AttachedPolicies.filter (( policy ) => policy.PolicyArn ) .map (( policy ) => policy.PolicyArn ! ) ); } output.IsTruncated ? ( Marker = output.Marker ) : ( shouldListNext = false ); } if ( attachedPolicieArns.length === 0 ) return; await Promise . all( attachedPolicieArns.map (async ( arn ) => { await client.send (new iam.DetachUserPolicyCommand ( { UserName: userName , PolicyArn: arn } )); } ) ); } ; (I)IAMグループへの所属解除 同様の処理なので、説明は割愛します。 export const removeUserFromGroups = async ( userName: string ) : Promise < void > => { let groupNames: string [] = [] ; let shouldListNext = true ; let Marker: string | undefined = undefined ; while ( shouldListNext ) { const params: iam.ListGroupsForUserCommandInput = { UserName: userName , Marker } ; const output = await client.send (new iam.ListGroupsForUserCommand ( params )); if ( output.Groups ) { groupNames = groupNames.concat ( output.Groups.filter (( group ) => group.GroupName ) .map (( group ) => group.GroupName ! )); } output.IsTruncated ? ( Marker = output.Marker ) : ( shouldListNext = false ); } if ( groupNames.length === 0 ) return; await Promise . all( groupNames.map (async ( groupName ) => { await client.send (new iam.RemoveUserFromGroupCommand ( { UserName: userName , GroupName: groupName } )); } ) ); } ; Lambdaハンドラ 作成されたIAMユーザー名をイベントの中身から取得するための型を定義し、上で説明した関数を利用してLambdaハンドラを実装します。各アイテムの削除後に DeleteUserCommand でユーザー本体を削除します。 type Detail = { responseElements: { user: { userName: string } } } ; export const handler: Handler = async ( event: EventBridgeEvent < string , Detail >) => { const userName = event.detail.responseElements.user.userName ; await Promise . all( [ deleteLoginProfile ( userName ), deleteAccessKeys ( userName ), deleteUserPolicies ( userName ), detachUserPolicies ( userName ), removeUserFromGroups ( userName ), ] ); await client.send (new iam.DeleteUserCommand ( { UserName: userName } )); } ; これで、IAMユーザーが作成されるイベントをトリガーに、即座にユーザーを削除する仕組みが完成しました。 さいごに 自前で自動化の仕組みを実装してみると、普段コンソールでしている操作の裏側でどのような API が動いているのかが分かり、とても面白かったです。 最後までお読みただいてありがとうございました。 私たちは同じチームで働いてくれる仲間を大募集しています!たくさんのご応募をお待ちしています。 - セキュリティエンジニア(セキュリティ設計) 執筆: @kou.kinyo2 、レビュー: @higa ( Shodo で執筆されました )
アバター
電通国際情報サービス 、オープン イノベーション ラボの 比嘉康雄 です。 Stable Diffusionシリーズ、今回は、A as Bの呪文による画像合成の呪文です。 やまかずさんの 日刊 画像生成AI (2022年9月29日) の記事で紹介されていた 「A as B」は有効 を今回は検証してみました。 Stable Diffusionのおすすめコンテンツはこちら。 Waifu Diffusion 1.3.5_80000 v2.1 金髪美女写真 v2.1 美少女アニメ画 v2.1 AUTOMATIC1111 v2.0 美少女イラスト v1.5 美少女画検証 美少女アニメ画改善版 美少女を高確率で出す呪文編 美少女アニメ画編 美少女写真編 女性イラスト編 魅惑的な女アニメ画(トゥーンレンダリング)編 長い呪文は切り捨てられる編 A as Bの呪文とは まとめ 仲間募集 Stable Diffusionの過去コンテンツ A as Bの呪文とは A as Bの呪文は、AをBとして描画するというものです。Aの画像にBの画像が合成されたような効果が出ます。 例えば、次のような beautiful girl as cat の呪文を試してみましょう。 今回の呪文(横長、コピー&ペースト用) illustration of beautiful girl as cat detailed beautiful face detailed hair detailed perfect pupil of eyes detailed mouth detailed shoulders detailed bust looking far away highly detailed artstation deviantart concept art digital painting award winning fantasy scene fantasy composition fantasy lighting 閲覧用呪文(改行版) illustration of beautiful girl as cat detailed beautiful face detailed hair detailed perfect pupil of eyes detailed mouth detailed shoulders detailed bust looking far away highly detailed artstation deviantart concept art digital painting award winning fantasy scene fantasy composition fantasy lighting トーク ン出力結果(改行版) 長い呪文は切り捨てられる編 参照 42 ['illustration</w>', 'of</w>', 'beautiful</w>', 'girl</w>', 'as</w>', 'cat</w>', 'detailed</w>', 'beautiful</w>', 'face</w>', 'detailed</w>', 'hair</w>', 'detailed</w>', 'perfect</w>', 'pupil</w>', 'of</w>', 'eyes</w>', 'detailed</w>', 'mouth</w>', 'detailed</w>', 'shoulders</w>', 'detailed</w>', 'bust</w>', 'looking</w>', 'far</w>', 'away</w>', 'highly</w>', 'detailed</w>', 'art', 'station</w>', 'deviantart</w>', 'concept</w>', 'art</w>', 'digital</w>', 'painting</w>', 'award</w>', 'winning</w>', 'fantasy</w>', 'scene</w>', 'fantasy</w>', 'composition</w>', 'fantasy</w>', 'lighting</w>'] 画像出力結果 実は、この結果は、50回試して一回出るくらいの奇跡的な画像です。Aを人間、Bを動物にした場合、動物が耳に特徴があると、3, 4回に一回くらい、耳だけが人間に取り込まれます。 Bが犬など、耳にそれほど特徴がない場合、BがAに取り込まれる可能性がかなり減ります。 まとめ A as Bは、再現性が低いですが、うまくいくと面白い画像が作成できると言ったところでしょうか。 次回は、 かわいい動物の擬人化編 です。 仲間募集 私たちは同じグループで共に働いていただける仲間を募集しています。 現在、以下のような職種を募集しています。 ソリューションアーキテクト AIエンジニア Stable Diffusionの過去コンテンツ 人物写真編 レンズ編 画像タイプ編 美少女アニメ画編 美少女写真編 女性イラスト編 美しい夜空を見渡す男編 魅惑的な女アニメ画(トゥーンレンダリング)編 美少女を高確率で出す呪文編 長い呪文は切り捨てられる編 蒸気機関が高度に発達したレトロなアニメ(スチームパンク)の世界観編 A as Bの呪文による画像合成編 かわいい動物の擬人化編 バベルの塔のイラスト編 TPU版の使い方 美少女アニメ画改善版 v1.5 美少女画検証 東京タワーの写真 折り紙合体変形ロボ v2.0 美少女イラスト v2.1 AUTOMATIC1111 v2.1 美少女アニメ画 v2.1 金髪美女写真 Waifu Diffusion 1.3.5_80000 執筆: @higa ( Shodo で執筆されました )
アバター
電通国際情報サービス 、オープン イノベーション ラボの 比嘉康雄 です。 Stable Diffusionシリーズ、今回は、A as Bの呪文による画像合成の呪文です。 やまかずさんの 日刊 画像生成AI (2022年9月29日) の記事で紹介されていた 「A as B」は有効 を今回は検証してみました。 Stable Diffusionのおすすめコンテンツはこちら。 Waifu Diffusion 1.3.5_80000 v2.1 金髪美女写真 v2.1 美少女アニメ画 v2.1 AUTOMATIC1111 v2.0 美少女イラスト v1.5 美少女画検証 美少女アニメ画改善版 美少女を高確率で出す呪文編 美少女アニメ画編 美少女写真編 女性イラスト編 魅惑的な女アニメ画(トゥーンレンダリング)編 長い呪文は切り捨てられる編 A as Bの呪文とは まとめ 仲間募集 Stable Diffusionの過去コンテンツ A as Bの呪文とは A as Bの呪文は、AをBとして描画するというものです。Aの画像にBの画像が合成されたような効果が出ます。 例えば、次のような beautiful girl as cat の呪文を試してみましょう。 今回の呪文(横長、コピー&ペースト用) illustration of beautiful girl as cat detailed beautiful face detailed hair detailed perfect pupil of eyes detailed mouth detailed shoulders detailed bust looking far away highly detailed artstation deviantart concept art digital painting award winning fantasy scene fantasy composition fantasy lighting 閲覧用呪文(改行版) illustration of beautiful girl as cat detailed beautiful face detailed hair detailed perfect pupil of eyes detailed mouth detailed shoulders detailed bust looking far away highly detailed artstation deviantart concept art digital painting award winning fantasy scene fantasy composition fantasy lighting トーク ン出力結果(改行版) 長い呪文は切り捨てられる編 参照 42 ['illustration</w>', 'of</w>', 'beautiful</w>', 'girl</w>', 'as</w>', 'cat</w>', 'detailed</w>', 'beautiful</w>', 'face</w>', 'detailed</w>', 'hair</w>', 'detailed</w>', 'perfect</w>', 'pupil</w>', 'of</w>', 'eyes</w>', 'detailed</w>', 'mouth</w>', 'detailed</w>', 'shoulders</w>', 'detailed</w>', 'bust</w>', 'looking</w>', 'far</w>', 'away</w>', 'highly</w>', 'detailed</w>', 'art', 'station</w>', 'deviantart</w>', 'concept</w>', 'art</w>', 'digital</w>', 'painting</w>', 'award</w>', 'winning</w>', 'fantasy</w>', 'scene</w>', 'fantasy</w>', 'composition</w>', 'fantasy</w>', 'lighting</w>'] 画像出力結果 実は、この結果は、50回試して一回出るくらいの奇跡的な画像です。Aを人間、Bを動物にした場合、動物が耳に特徴があると、3, 4回に一回くらい、耳だけが人間に取り込まれます。 Bが犬など、耳にそれほど特徴がない場合、BがAに取り込まれる可能性がかなり減ります。 まとめ A as Bは、再現性が低いですが、うまくいくと面白い画像が作成できると言ったところでしょうか。 次回は、 かわいい動物の擬人化編 です。 仲間募集 私たちは同じグループで共に働いていただける仲間を募集しています。 現在、以下のような職種を募集しています。 ソリューションアーキテクト AIエンジニア Stable Diffusionの過去コンテンツ 人物写真編 レンズ編 画像タイプ編 美少女アニメ画編 美少女写真編 女性イラスト編 美しい夜空を見渡す男編 魅惑的な女アニメ画(トゥーンレンダリング)編 美少女を高確率で出す呪文編 長い呪文は切り捨てられる編 蒸気機関が高度に発達したレトロなアニメ(スチームパンク)の世界観編 A as Bの呪文による画像合成編 かわいい動物の擬人化編 バベルの塔のイラスト編 TPU版の使い方 美少女アニメ画改善版 v1.5 美少女画検証 東京タワーの写真 折り紙合体変形ロボ v2.0 美少女イラスト v2.1 AUTOMATIC1111 v2.1 美少女アニメ画 v2.1 金髪美女写真 Waifu Diffusion 1.3.5_80000 執筆: @higa ( Shodo で執筆されました )
アバター
電通国際情報サービス 、オープン イノベーション ラボの 比嘉康雄 です。 Stable Diffusionシリーズ、今回は、 蒸気機関 が高度に発達したレトロなアニメ( スチームパンク )の世界観編です。 Steampunk( スチームパンク )って言葉を知ってますか。SFから派生していて、 蒸気機関 (Steam)が高度に発達したレトロな世界観です。有名な作品だと「 天空の城ラピュタ 」、「 ハウルの動く城 」、「 鋼の錬金術師 」などがあります。 pixiv百科事典 スチームパンク 今回は、Steampunkの世界に飛び込んでみましょう。 Stable Diffusionのおすすめコンテンツはこちら。 Waifu Diffusion 1.3.5_80000 v2.1 金髪美女写真 v2.1 美少女アニメ画 v2.1 AUTOMATIC1111 v2.0 美少女イラスト v1.5 美少女画検証 美少女アニメ画改善版 美少女を高確率で出す呪文編 美少女アニメ画編 美少女写真編 女性イラスト編 魅惑的な女アニメ画(トゥーンレンダリング)編 長い呪文は切り捨てられる編 steampunk illustration of huge battleship flying over city highly detailed artstation deviantart concept art digital painting award winning fantasy scene fantasy composition fantasy lighting clockwork machines machines with cogs flying above clouds illuminated fountain in town まとめ 仲間募集 Stable Diffusionの過去コンテンツ steampunk illustration of huge battleship flying over city highly detailed 今回もシンプルな呪文から始めて、徐々に呪文を足していきましょう。 Steampunkなイラストを描くには、steampunk illustration ofで始めます。 今回の描画対象は、街の上を飛んでいる巨大な戦艦(huge battleship flying over city)です。 長い呪文は切り捨てられる編 で、説明したようにaやtheは必要ないので、省いています。 highly detailedを足すと描画対象のクオリティが上がります。 今回の呪文 steampunk illustration of huge battleship flying over city highly detailed トーク ン出力結果(改行版) 長い呪文は切り捨てられる編 参照 10 ['steampunk</w>', 'illustration</w>', 'of</w>', 'huge</w>', 'battleship</w>', 'flying</w>', 'over</w>', 'city</w>', 'highly</w>', 'detailed</w>'] 画像出力結果 artstation deviantart concept art digital painting award winning 作風用の呪文を追加しましょう。 artstation、 deviantart でアートの投稿サイトを指定します。 concept art、digital painting、award winningはクオリティを上げるためのお約束の呪文。 今回の呪文(横長、コピー&ペースト用) steampunk illustration of huge battleship flying over city highly detailed artstation deviantart concept art digital painting award winning 閲覧用呪文(改行版) steampunk illustration of huge battleship flying over city highly detailed artstation deviantart concept art digital painting award winning トーク ン出力結果(改行版) 19 ['steampunk</w>', 'illustration</w>', 'of</w>', 'huge</w>', 'battleship</w>', 'flying</w>', 'over</w>', 'city</w>', 'highly</w>', 'detailed</w>', 'art', 'station</w>', 'deviantart</w>', 'concept</w>', 'art</w>', 'digital</w>', 'painting</w>', 'award</w>', 'winning</w>'] artstationがartとstationの2つの トーク ンに分かれてしまってますが、artstationが有効な呪文であることは、何度もあり/なしで確認しています。 画像出力結果 fantasy scene fantasy composition fantasy lighting 演出用の呪文を追加しましょう。scene(シーン)、composition(構図)、lighting(ライティング)を指定します。いろいろ試しましたが、Steampunkの世界観には、fantasyがあっているようです。 今回の呪文(横長、コピー&ペースト用) steampunk illustration of huge battleship flying over city highly detailed artstation deviantart concept art digital painting award winning fantasy scene fantasy composition fantasy lighting 閲覧用呪文(改行版) steampunk illustration of huge battleship flying over city highly detailed artstation deviantart concept art digital painting award winning fantasy scene fantasy composition fantasy lighting トーク ン出力結果(改行版) 25 ['steampunk</w>', 'illustration</w>', 'of</w>', 'huge</w>', 'battleship</w>', 'flying</w>', 'over</w>', 'city</w>', 'highly</w>', 'detailed</w>', 'art', 'station</w>', 'deviantart</w>', 'concept</w>', 'art</w>', 'digital</w>', 'painting</w>', 'award</w>', 'winning</w>', 'fantasy</w>', 'scene</w>', 'fantasy</w>', 'composition</w>', 'fantasy</w>', 'lighting</w>'] 画像出力結果 良い感じに仕上がりましたね。今度は、描画対象( サブジ ェクト)を変えてみましょう。 clockwork machines clockwork machines(ゼンマイ仕掛けの機械)を試してみましょう。 今回の呪文(横長、コピー&ペースト用) steampunk illustration of clockwork machines highly detailed artstation deviantart concept art digital painting award winning fantasy scene fantasy composition fantasy lighting 閲覧用呪文(改行版) steampunk illustration of clockwork machines highly detailed artstation deviantart concept art digital painting award winning fantasy scene fantasy composition fantasy lighting トーク ン出力結果(改行版) 22 ['steampunk</w>', 'illustration</w>', 'of</w>', 'clockwork</w>', 'machines</w>', 'highly</w>', 'detailed</w>', 'art', 'station</w>', 'deviantart</w>', 'concept</w>', 'art</w>', 'digital</w>', 'painting</w>', 'award</w>', 'winning</w>', 'fantasy</w>', 'scene</w>', 'fantasy</w>', 'composition</w>', 'fantasy</w>', 'lighting</w>'] 画像出力結果 machines with cogs flying above clouds 今度は、雲の上を飛んでいる歯車付き機械です。 今回の呪文(横長、コピー&ペースト用) steampunk illustration of machines with cogs flying above clouds highly detailed artstation deviantart concept art digital painting award winning fantasy scene golden hour fantasy composition fantasy lighting 閲覧用呪文(改行版) steampunk illustration of machines with cogs flying above clouds highly detailed artstation deviantart concept art digital painting award winning fantasy scene golden hour fantasy composition fantasy lighting トーク ン出力結果(改行版) 29 ['steampunk</w>', 'illustration</w>', 'of</w>', 'machines</w>', 'flying</w>', 'above</w>', 'clouds</w>', 'with</w>', 'co', 'gs</w>', 'highly</w>', 'detailed</w>', 'art', 'station</w>', 'deviantart</w>', 'concept</w>', 'art</w>', 'digital</w>', 'painting</w>', 'award</w>', 'winning</w>', 'fantasy</w>', 'scene</w>', 'golden</w>', 'hour</w>', 'fantasy</w>', 'composition</w>', 'fantasy</w>', 'lighting</w>'] cogs(歯車)がcoとgsに分割されてしまっていますが、cogsが有効な呪文であることは何度も試して確認済みです。 画像出力結果 illuminated fountain in town 最後は、街の中のライトアップされた噴水です。 今回の呪文(横長、コピー&ペースト用) steampunk illustration of illuminated fountain in town highly detailed artstation deviantart concept art digital painting award winning fantasy scene fantasy composition fantasy lighting 閲覧用呪文(改行版) steampunk illustration of illuminated fountain in town highly detailed artstation deviantart concept art digital painting award winning fantasy scene fantasy composition fantasy lighting トーク ン出力結果(改行版) 24 ['steampunk</w>', 'illustration</w>', 'of</w>', 'illuminated</w>', 'fountain</w>', 'in</w>', town</w>', 'highly</w>', 'detailed</w>', 'art', 'station</w>', 'deviantart</w>', 'concept</w>', 'art</w>', 'digital</w>', 'painting</w>', 'award</w>', 'winning</w>', 'fantasy</w>', 'scene</w>', 'fantasy</w>', 'composition</w>', 'fantasy</w>', 'lighting</w>'] 画像出力結果 まとめ 今回は、Steampunkのイラストを扱いました。 蒸気機関 が高度に発達したレトロなアニメ( スチームパンク )の世界観、好きな人は多いのではないでしょうか。 次回は、 A as Bの呪文による画像合成編 です。 仲間募集 私たちは同じグループで共に働いていただける仲間を募集しています。 現在、以下のような職種を募集しています。 ソリューションアーキテクト AIエンジニア Stable Diffusionの過去コンテンツ 人物写真編 レンズ編 画像タイプ編 美少女アニメ画編 美少女写真編 女性イラスト編 美しい夜空を見渡す男編 魅惑的な女アニメ画(トゥーンレンダリング)編 美少女を高確率で出す呪文編 長い呪文は切り捨てられる編 蒸気機関が高度に発達したレトロなアニメ(スチームパンク)の世界観編 A as Bの呪文による画像合成編 かわいい動物の擬人化編 バベルの塔のイラスト編 TPU版の使い方 美少女アニメ画改善版 v1.5 美少女画検証 東京タワーの写真 折り紙合体変形ロボ v2.0 美少女イラスト v2.1 AUTOMATIC1111 v2.1 美少女アニメ画 v2.1 金髪美女写真 Waifu Diffusion 1.3.5_80000 執筆: @higa ( Shodo で執筆されました )
アバター
電通国際情報サービス 、オープン イノベーション ラボの 比嘉康雄 です。 Stable Diffusionシリーズ、今回は、 蒸気機関 が高度に発達したレトロなアニメ( スチームパンク )の世界観編です。 Steampunk( スチームパンク )って言葉を知ってますか。SFから派生していて、 蒸気機関 (Steam)が高度に発達したレトロな世界観です。有名な作品だと「 天空の城ラピュタ 」、「 ハウルの動く城 」、「 鋼の錬金術師 」などがあります。 pixiv百科事典 スチームパンク 今回は、Steampunkの世界に飛び込んでみましょう。 Stable Diffusionのおすすめコンテンツはこちら。 Waifu Diffusion 1.3.5_80000 v2.1 金髪美女写真 v2.1 美少女アニメ画 v2.1 AUTOMATIC1111 v2.0 美少女イラスト v1.5 美少女画検証 美少女アニメ画改善版 美少女を高確率で出す呪文編 美少女アニメ画編 美少女写真編 女性イラスト編 魅惑的な女アニメ画(トゥーンレンダリング)編 長い呪文は切り捨てられる編 steampunk illustration of huge battleship flying over city highly detailed artstation deviantart concept art digital painting award winning fantasy scene fantasy composition fantasy lighting clockwork machines machines with cogs flying above clouds illuminated fountain in town まとめ 仲間募集 Stable Diffusionの過去コンテンツ steampunk illustration of huge battleship flying over city highly detailed 今回もシンプルな呪文から始めて、徐々に呪文を足していきましょう。 Steampunkなイラストを描くには、steampunk illustration ofで始めます。 今回の描画対象は、街の上を飛んでいる巨大な戦艦(huge battleship flying over city)です。 長い呪文は切り捨てられる編 で、説明したようにaやtheは必要ないので、省いています。 highly detailedを足すと描画対象のクオリティが上がります。 今回の呪文 steampunk illustration of huge battleship flying over city highly detailed トーク ン出力結果(改行版) 長い呪文は切り捨てられる編 参照 10 ['steampunk</w>', 'illustration</w>', 'of</w>', 'huge</w>', 'battleship</w>', 'flying</w>', 'over</w>', 'city</w>', 'highly</w>', 'detailed</w>'] 画像出力結果 artstation deviantart concept art digital painting award winning 作風用の呪文を追加しましょう。 artstation、 deviantart でアートの投稿サイトを指定します。 concept art、digital painting、award winningはクオリティを上げるためのお約束の呪文。 今回の呪文(横長、コピー&ペースト用) steampunk illustration of huge battleship flying over city highly detailed artstation deviantart concept art digital painting award winning 閲覧用呪文(改行版) steampunk illustration of huge battleship flying over city highly detailed artstation deviantart concept art digital painting award winning トーク ン出力結果(改行版) 19 ['steampunk</w>', 'illustration</w>', 'of</w>', 'huge</w>', 'battleship</w>', 'flying</w>', 'over</w>', 'city</w>', 'highly</w>', 'detailed</w>', 'art', 'station</w>', 'deviantart</w>', 'concept</w>', 'art</w>', 'digital</w>', 'painting</w>', 'award</w>', 'winning</w>'] artstationがartとstationの2つの トーク ンに分かれてしまってますが、artstationが有効な呪文であることは、何度もあり/なしで確認しています。 画像出力結果 fantasy scene fantasy composition fantasy lighting 演出用の呪文を追加しましょう。scene(シーン)、composition(構図)、lighting(ライティング)を指定します。いろいろ試しましたが、Steampunkの世界観には、fantasyがあっているようです。 今回の呪文(横長、コピー&ペースト用) steampunk illustration of huge battleship flying over city highly detailed artstation deviantart concept art digital painting award winning fantasy scene fantasy composition fantasy lighting 閲覧用呪文(改行版) steampunk illustration of huge battleship flying over city highly detailed artstation deviantart concept art digital painting award winning fantasy scene fantasy composition fantasy lighting トーク ン出力結果(改行版) 25 ['steampunk</w>', 'illustration</w>', 'of</w>', 'huge</w>', 'battleship</w>', 'flying</w>', 'over</w>', 'city</w>', 'highly</w>', 'detailed</w>', 'art', 'station</w>', 'deviantart</w>', 'concept</w>', 'art</w>', 'digital</w>', 'painting</w>', 'award</w>', 'winning</w>', 'fantasy</w>', 'scene</w>', 'fantasy</w>', 'composition</w>', 'fantasy</w>', 'lighting</w>'] 画像出力結果 良い感じに仕上がりましたね。今度は、描画対象( サブジ ェクト)を変えてみましょう。 clockwork machines clockwork machines(ゼンマイ仕掛けの機械)を試してみましょう。 今回の呪文(横長、コピー&ペースト用) steampunk illustration of clockwork machines highly detailed artstation deviantart concept art digital painting award winning fantasy scene fantasy composition fantasy lighting 閲覧用呪文(改行版) steampunk illustration of clockwork machines highly detailed artstation deviantart concept art digital painting award winning fantasy scene fantasy composition fantasy lighting トーク ン出力結果(改行版) 22 ['steampunk</w>', 'illustration</w>', 'of</w>', 'clockwork</w>', 'machines</w>', 'highly</w>', 'detailed</w>', 'art', 'station</w>', 'deviantart</w>', 'concept</w>', 'art</w>', 'digital</w>', 'painting</w>', 'award</w>', 'winning</w>', 'fantasy</w>', 'scene</w>', 'fantasy</w>', 'composition</w>', 'fantasy</w>', 'lighting</w>'] 画像出力結果 machines with cogs flying above clouds 今度は、雲の上を飛んでいる歯車付き機械です。 今回の呪文(横長、コピー&ペースト用) steampunk illustration of machines with cogs flying above clouds highly detailed artstation deviantart concept art digital painting award winning fantasy scene golden hour fantasy composition fantasy lighting 閲覧用呪文(改行版) steampunk illustration of machines with cogs flying above clouds highly detailed artstation deviantart concept art digital painting award winning fantasy scene golden hour fantasy composition fantasy lighting トーク ン出力結果(改行版) 29 ['steampunk</w>', 'illustration</w>', 'of</w>', 'machines</w>', 'flying</w>', 'above</w>', 'clouds</w>', 'with</w>', 'co', 'gs</w>', 'highly</w>', 'detailed</w>', 'art', 'station</w>', 'deviantart</w>', 'concept</w>', 'art</w>', 'digital</w>', 'painting</w>', 'award</w>', 'winning</w>', 'fantasy</w>', 'scene</w>', 'golden</w>', 'hour</w>', 'fantasy</w>', 'composition</w>', 'fantasy</w>', 'lighting</w>'] cogs(歯車)がcoとgsに分割されてしまっていますが、cogsが有効な呪文であることは何度も試して確認済みです。 画像出力結果 illuminated fountain in town 最後は、街の中のライトアップされた噴水です。 今回の呪文(横長、コピー&ペースト用) steampunk illustration of illuminated fountain in town highly detailed artstation deviantart concept art digital painting award winning fantasy scene fantasy composition fantasy lighting 閲覧用呪文(改行版) steampunk illustration of illuminated fountain in town highly detailed artstation deviantart concept art digital painting award winning fantasy scene fantasy composition fantasy lighting トーク ン出力結果(改行版) 24 ['steampunk</w>', 'illustration</w>', 'of</w>', 'illuminated</w>', 'fountain</w>', 'in</w>', town</w>', 'highly</w>', 'detailed</w>', 'art', 'station</w>', 'deviantart</w>', 'concept</w>', 'art</w>', 'digital</w>', 'painting</w>', 'award</w>', 'winning</w>', 'fantasy</w>', 'scene</w>', 'fantasy</w>', 'composition</w>', 'fantasy</w>', 'lighting</w>'] 画像出力結果 まとめ 今回は、Steampunkのイラストを扱いました。 蒸気機関 が高度に発達したレトロなアニメ( スチームパンク )の世界観、好きな人は多いのではないでしょうか。 次回は、 A as Bの呪文による画像合成編 です。 仲間募集 私たちは同じグループで共に働いていただける仲間を募集しています。 現在、以下のような職種を募集しています。 ソリューションアーキテクト AIエンジニア Stable Diffusionの過去コンテンツ 人物写真編 レンズ編 画像タイプ編 美少女アニメ画編 美少女写真編 女性イラスト編 美しい夜空を見渡す男編 魅惑的な女アニメ画(トゥーンレンダリング)編 美少女を高確率で出す呪文編 長い呪文は切り捨てられる編 蒸気機関が高度に発達したレトロなアニメ(スチームパンク)の世界観編 A as Bの呪文による画像合成編 かわいい動物の擬人化編 バベルの塔のイラスト編 TPU版の使い方 美少女アニメ画改善版 v1.5 美少女画検証 東京タワーの写真 折り紙合体変形ロボ v2.0 美少女イラスト v2.1 AUTOMATIC1111 v2.1 美少女アニメ画 v2.1 金髪美女写真 Waifu Diffusion 1.3.5_80000 執筆: @higa ( Shodo で執筆されました )
アバター
はじめまして、X イノベーション 本部 オープン イノベーション ラボの飯田です。 ISIDでは、有志で論文輪読会を実施しています。 tech.isid.co.jp 今回、その中で、私が読んだDNAストレージについてご紹介します。 DNAストレージという技術は、DNAを情報記録媒体として用いる技術です。 DNAの 塩基配列 をうまく活用することで、デジタルデータの読み書きが可能となります。 マイクロソフト 等が積極的に研究を行っているようです ( Microsoft Research:DNA Storage ) 輪読会では、以下2本の論文を紹介しました。 その内容を簡単にご紹介します。 Bornholt, J. et al. (2016) A DNA-based Archival Storage System DNAストレージが初登場したと言われる Clelland C. T. et al. (1999) Hiding messages in DNA microdots 前提知識 なぜDNAストレージ? ビックデータ時代のストレージの課題とDNAストレージ DNAストレージの簡単な原理 最初のDNAストレージ DNAストレージの課題 (冗長性・エラー耐性 と ランダムアクセス) 冗長性・エラー耐性 ランダムアクセス Bornholt, J. et al. (2016) のアーキテクチャ 書き込み処理 の流れ 読み出し処理 の流れ 必要であれば、DNAストレージの増幅 実例:実際に保存されたデータ例 最後に、バイオロジー×IT技術の可能性! 前提知識 DNA ヒトの細胞では、核の中の染色体にあり、A(アデニン)・T(チミン)・G(グアニン)・C(シトシン)の4種で構成されている。 DNA中ではAとT、GとCが結合していて、その結合の対を塩基対と言います。 DNA合成技術 狙った 塩基配列 でDNAを生み出す方法。DNAストレージではWriteに相当する手技。 シークエンシング DNAの構成成分であるATGC 4種の 塩基配列 を決定すること。 古典的なサンガー法から、NGSまでいくつかの手法がある。DNAストレージではReadに相当する手技。 なぜDNAストレージ? ビックデータ時代のストレージの課題とDNAストレージ 現在のビックデータ時代、現状の 記憶メディア では保存密度や寿命が課題になると言われています。 磁気メディア:185 TB のテープ・カートリッジ(密度:10 GB/ mm3 程度) 光学メディア:1PBを格納できる光ディスクの実現可能性(密度:約100GB/ mm3 ) 回転ディスクの寿命:3~5年 テープの寿命:10~30年 その課題を解決する方法として、DNAストレージが注目されています。 バイオテク ノロ ジー の標準的な手法で実現可能 非常に高密度:理論的な限界値 1EB/ mm3 (テープの8倍) 長寿命:500年以上の 半減期 DNAベースの生命が存在する限り、DNAを読み取り、操作可能 DNAストレージによって、より高密度・長期保存が可能になると期待されています。 生物として生きていれば、生命が続く限りデータは保存される?ということになると思います。 DNAストレージの簡単な原理 ものすごく単 純化 すると、デジタルデータを0/1のビットで情報表現するものが、DNAストレージではそれをATGCの4種で表現するイメージになります。 大きな流れとしては、以下のとおりです。 デジタルデータをDNAの ヌクレオチド 配列に マッピング し、対応するDNA分子を合成し、保存する データの読み取りは、DNA分子の 塩基配列 を解読して、元のデジタルデータに戻す DNAストレージは読み取りに PCR やシークエンシングが必要となるため、超 アーカイブ 向けのストレージ(数時間から数日の アクセス時間 、高密度で耐久性)の特性があります。 具体例でみてみましょう。 最初のDNAストレージ 暗号表と 塩基配列 を マッピング して、「J U N E 6 I N V A S I O N : N O R M A N D Y」23文字をDNA上に保存し、読み取りにも成功したという報告です。 こちらの暗号表をもとに、英数字と 塩基配列 を対応させて、DNAに保存読み取りを実現しました。 DNAストレージの課題 (冗長性・エラー耐性 と ランダムアクセス) 冗長性・エラー耐性 DNAストレージでは、下記のようにDNAとしての物質に由来するエラーと劣化があります。 実用化をするためには、それに対応するための冗長性・エラー耐性の仕組みが必須となります。 DNAの合成と読み取りの不完全さ:DNA合成・シークエンシングではエラーが入ってしまう 塩基配列 は保存中に劣化する可能性 (データの完全性の欠損) ランダムアクセス DNAシークエンシングは、DNA全体に対して行う必要があるため、DNAストレージから1バイトでも読みだすには、DNA全体を解読(シークエンシング)する必要があります。 キーバリュー方式の格納法と PCR を組み合せ、必要なデータのみを増幅することで、ランダムアクセスを実現する方法が提案されています。 Bornholt, J. et al. (2016) の アーキテクチャ 最初の報告では、単純なものでしたが、ランダムアクセス等に対応した アーキテクチャ がこちらです。 書き込み処理 の流れ キーと値を入力 いわゆるKey- Value ストアのように保存するイメージになります キーは PCR プライマー配列、格納される場所 値はデータの値 DNA配列の生成 データアドレス、 ペイロード 、エラー検出コード等も符号化し、 PCR プライマーのターゲット配列を添付して、合成 できあがったDNAは、保存用ライブラリに格納 読み出し処理 の流れ 読み取りたいキーを入力し、物理的に抽出 キーの PCR プライマーを取得 保存されているデータを含むDNAプールからサンプルを物理的に抽出 (その中には無関係なデータも含まれている) PCR によりDNAを増幅し、シークエンシング キーに指定された PCR プライマーにより、目的のDNAだけが増幅される シークエンシングにより、デジタルデータの読み出し 必要であれば、DNAストレージの増幅 読み出し処理後、サンプルが減ってしまうため、必要に応じて PCR で複製を行う 実例:実際に保存されたデータ例 Bornholt (2016) では、 5kBから84kBまでの4つの画像ファイルを保存し、ランダムアクセスができることを確認しています 3つのファイルはエラーなしで復元できました。cat.jpgのみ、 JPEG ヘッダーに1バイトのエラーが発生しましたが、修正して参照できました。 最後に、バイオロ ジー × IT技術 の可能性! 論文の最後に下記の言葉がありました。 Given the impending limits of silicon technology, we believe that hybrid silicon and biochemical systems are worth serious consideration: time is ripe for computer architects to consider incorporating biomolecules as an integral part of computer design. now is the time for the computer industry to borrow back from the biotechnology industry to advance the state of the art in computer systems. ムーアの法則 に限界がきていると言われるように、個人的には、既存の IT技術 の延長では限界があると思っています。 バイオ技術など、他の業界の技術を大胆に取り入れてブレイクスルーを目指すのは夢があります。 量子コンピュータ は 量子力学 をベースとしているように、バイオコンピュータの登場も夢ではないと思います。 執筆: @iida.michitaka 、レビュー: @sato.taichi ( Shodo で執筆されました )
アバター
はじめまして、X イノベーション 本部 オープン イノベーション ラボの飯田です。 ISIDでは、有志で論文輪読会を実施しています。 tech.isid.co.jp 今回、その中で、私が読んだDNAストレージについてご紹介します。 DNAストレージという技術は、DNAを情報記録媒体として用いる技術です。 DNAの 塩基配列 をうまく活用することで、デジタルデータの読み書きが可能となります。 マイクロソフト 等が積極的に研究を行っているようです ( Microsoft Research:DNA Storage ) 輪読会では、以下2本の論文を紹介しました。 その内容を簡単にご紹介します。 Bornholt, J. et al. (2016) A DNA-based Archival Storage System DNAストレージが初登場したと言われる Clelland C. T. et al. (1999) Hiding messages in DNA microdots 前提知識 なぜDNAストレージ? ビックデータ時代のストレージの課題とDNAストレージ DNAストレージの簡単な原理 最初のDNAストレージ DNAストレージの課題 (冗長性・エラー耐性 と ランダムアクセス) 冗長性・エラー耐性 ランダムアクセス Bornholt, J. et al. (2016) のアーキテクチャ 書き込み処理 の流れ 読み出し処理 の流れ 必要であれば、DNAストレージの増幅 実例:実際に保存されたデータ例 最後に、バイオロジー×IT技術の可能性! 前提知識 DNA ヒトの細胞では、核の中の染色体にあり、A(アデニン)・T(チミン)・G(グアニン)・C(シトシン)の4種で構成されている。 DNA中ではAとT、GとCが結合していて、その結合の対を塩基対と言います。 DNA合成技術 狙った 塩基配列 でDNAを生み出す方法。DNAストレージではWriteに相当する手技。 シークエンシング DNAの構成成分であるATGC 4種の 塩基配列 を決定すること。 古典的なサンガー法から、NGSまでいくつかの手法がある。DNAストレージではReadに相当する手技。 なぜDNAストレージ? ビックデータ時代のストレージの課題とDNAストレージ 現在のビックデータ時代、現状の 記憶メディア では保存密度や寿命が課題になると言われています。 磁気メディア:185 TB のテープ・カートリッジ(密度:10 GB/ mm3 程度) 光学メディア:1PBを格納できる光ディスクの実現可能性(密度:約100GB/ mm3 ) 回転ディスクの寿命:3~5年 テープの寿命:10~30年 その課題を解決する方法として、DNAストレージが注目されています。 バイオテク ノロ ジー の標準的な手法で実現可能 非常に高密度:理論的な限界値 1EB/ mm3 (テープの8倍) 長寿命:500年以上の 半減期 DNAベースの生命が存在する限り、DNAを読み取り、操作可能 DNAストレージによって、より高密度・長期保存が可能になると期待されています。 生物として生きていれば、生命が続く限りデータは保存される?ということになると思います。 DNAストレージの簡単な原理 ものすごく単 純化 すると、デジタルデータを0/1のビットで情報表現するものが、DNAストレージではそれをATGCの4種で表現するイメージになります。 大きな流れとしては、以下のとおりです。 デジタルデータをDNAの ヌクレオチド 配列に マッピング し、対応するDNA分子を合成し、保存する データの読み取りは、DNA分子の 塩基配列 を解読して、元のデジタルデータに戻す DNAストレージは読み取りに PCR やシークエンシングが必要となるため、超 アーカイブ 向けのストレージ(数時間から数日の アクセス時間 、高密度で耐久性)の特性があります。 具体例でみてみましょう。 最初のDNAストレージ 暗号表と 塩基配列 を マッピング して、「J U N E 6 I N V A S I O N : N O R M A N D Y」23文字をDNA上に保存し、読み取りにも成功したという報告です。 こちらの暗号表をもとに、英数字と 塩基配列 を対応させて、DNAに保存読み取りを実現しました。 DNAストレージの課題 (冗長性・エラー耐性 と ランダムアクセス) 冗長性・エラー耐性 DNAストレージでは、下記のようにDNAとしての物質に由来するエラーと劣化があります。 実用化をするためには、それに対応するための冗長性・エラー耐性の仕組みが必須となります。 DNAの合成と読み取りの不完全さ:DNA合成・シークエンシングではエラーが入ってしまう 塩基配列 は保存中に劣化する可能性 (データの完全性の欠損) ランダムアクセス DNAシークエンシングは、DNA全体に対して行う必要があるため、DNAストレージから1バイトでも読みだすには、DNA全体を解読(シークエンシング)する必要があります。 キーバリュー方式の格納法と PCR を組み合せ、必要なデータのみを増幅することで、ランダムアクセスを実現する方法が提案されています。 Bornholt, J. et al. (2016) の アーキテクチャ 最初の報告では、単純なものでしたが、ランダムアクセス等に対応した アーキテクチャ がこちらです。 書き込み処理 の流れ キーと値を入力 いわゆるKey- Value ストアのように保存するイメージになります キーは PCR プライマー配列、格納される場所 値はデータの値 DNA配列の生成 データアドレス、 ペイロード 、エラー検出コード等も符号化し、 PCR プライマーのターゲット配列を添付して、合成 できあがったDNAは、保存用ライブラリに格納 読み出し処理 の流れ 読み取りたいキーを入力し、物理的に抽出 キーの PCR プライマーを取得 保存されているデータを含むDNAプールからサンプルを物理的に抽出 (その中には無関係なデータも含まれている) PCR によりDNAを増幅し、シークエンシング キーに指定された PCR プライマーにより、目的のDNAだけが増幅される シークエンシングにより、デジタルデータの読み出し 必要であれば、DNAストレージの増幅 読み出し処理後、サンプルが減ってしまうため、必要に応じて PCR で複製を行う 実例:実際に保存されたデータ例 Bornholt (2016) では、 5kBから84kBまでの4つの画像ファイルを保存し、ランダムアクセスができることを確認しています 3つのファイルはエラーなしで復元できました。cat.jpgのみ、 JPEG ヘッダーに1バイトのエラーが発生しましたが、修正して参照できました。 最後に、バイオロ ジー × IT技術 の可能性! 論文の最後に下記の言葉がありました。 Given the impending limits of silicon technology, we believe that hybrid silicon and biochemical systems are worth serious consideration: time is ripe for computer architects to consider incorporating biomolecules as an integral part of computer design. now is the time for the computer industry to borrow back from the biotechnology industry to advance the state of the art in computer systems. ムーアの法則 に限界がきていると言われるように、個人的には、既存の IT技術 の延長では限界があると思っています。 バイオ技術など、他の業界の技術を大胆に取り入れてブレイクスルーを目指すのは夢があります。 量子コンピュータ は 量子力学 をベースとしているように、バイオコンピュータの登場も夢ではないと思います。 執筆: @iida.michitaka 、レビュー: @sato.taichi ( Shodo で執筆されました )
アバター
みなさんこんにちは、 電通国際情報サービス (ISID)コーポレート本部 システム推進部の佐藤太一です。 このエントリでは Google Dataflowを使ったデータ分析パイプライン構築において中心的な API の使い方について説明します。 Google Dataflowとはなにか Dataflowの開発環境構築 GradleによるDataflowプロジェクトの作り方 Apache Beamの基礎 Pipelineについて PCollectionについて ParDoを使った逐次処理の書き方 Dataflowによるユニットテストの書き方 フィルター フィルターのテスト 値の増幅処理 増幅処理のテスト PCollectionの分岐 PCollectionの分岐をテストする まとめ Google Dataflowとはなにか Google DataflowはいわゆるExtract/Transform/Load(ETL)ツールの一種です。 Apache Beam という バッチ処理 基盤を GCP の分散処理環境で動かしてくれます。 Apache Beam自体は、 Apache Flink や Apache Spark 、 Hazelcast Jet といったオンプレミスで動作する実行環境を利用することもできます。 Apache Beamでは、 Java や Python 、Goといった言語で処理を記述できますが、今回は Java を使って説明します。 Dataflowの開発環境構築 まずは、Dataflowの開発環境を作っていきましょう。 開発環境として使うマシンには、事前にJava17とGradle7.5以上をインストールしておいてください。 GradleによるDataflowプロジェクトの作り方 最初にプロジェクト全体を格納するための ディレクト リを作成しましょう。 ここからは、この記事内でシェルコマンドを実行するよう説明している部分では、必ずこのルート ディレクト リで実行してください。 作るプロジェクトは、説明のために dataflow-example とします。作った dataflow-example ディレクト リの中で、以下のコマンドを実行して最小限のプロジェクトを作成します。 gradle init --type basic --dsl kotlin --project-name dataflow-example --incubating 最小限とはいえ、Gradle Wrapperとなる シェルスクリプト やgit用の設定ファイルが生成されていますね。 この中から、 build.gradle.kts を以下のように編集します。 plugins { id("java") } group = "com.example.dataflow" version = "0.1.0-SNAPSHOT" java.toolchain { languageVersion.set(JavaLanguageVersion.of(17)) // 1. } repositories { mavenCentral() maven("https://packages.confluent.io/maven/") // 2. } dependencies { var beamVersion = "2.41.0" // 3. var slf4jVersion = "1.7.36" implementation(platform("com.google.cloud:libraries-bom:25.4.0")) // 4. implementation("org.apache.beam:beam-sdks-java-core:${beamVersion}") // 5. implementation("org.apache.beam:beam-sdks-java-io-google-cloud-platform:${beamVersion}") // 5. implementation("org.apache.beam:beam-runners-google-cloud-dataflow-java:${beamVersion}") // 5. implementation("org.apache.commons:commons-csv:1.9.0") implementation("org.slf4j:slf4j-api:${slf4jVersion}") implementation("org.slf4j:slf4j-jdk14:${slf4jVersion}") testImplementation("junit:junit:4.13.2") // 6. testImplementation("org.apache.beam:beam-runners-direct-java:${beamVersion}") // 7. } tasks.withType<JavaCompile>().configureEach { options.encoding = "UTF-8" } このビルド スクリプト で利用する コンパイラ やランタイムのバージョン番号を指定しています。 Gradleを実行している Java ランタイムのバージョンが開発者ごとにズレていても、 コンパイル やテストに使う Java ランタイムは統一できるということです。ビルドの再現性が高まりますので必ず設定しましょう。 この機能を使うと、必要に応じてGradleがビルド済みの JDK を自動的にダウンロードしてくれます。 デフォルトでは Adoptium を使います。 依存ライブラリをダウンロードする先を宣言しています。 最初に指定しているのは Maven のデフォルト アーティファクト リポジトリ である Maven Central Repository です。 二つ目に指定しているのはconfluentが公開している リポジトリ です。これはKafka関連のライブラリで、この リポジトリ 内にしかないものがあるからです。 Apache Beamのバージョンを参照する依存性がいくつかあるので、ここでは変数として切り出しています。 Dataflowを動かすために必要な GCP の SDK に対する依存性を宣言しています。 GradleのPlatform機能 を使っていますね。 Apache Beamに対する依存性を宣言しています。後半の二つは GCP でBeamを動かすために必要な依存性です。 ユニットテスト 用の依存性としてJUnit4を指定しています。 JUnit の最新版はJUnit5系ですが、記事執筆時点において Apache BeamはJUnit5をサポートしていません。cf. JUnit5 support Apache Beam用の ユニットテスト ライブラリに対する依存性を宣言しています。 これでDataflow用のローカルビルド環境の構築は完了です。 Apache Beamの基礎 Apache Beamを理解するなら、まずはPipelineとPCollectionをしっかり理解してください。 他のコンセプトについては、Dataflowのドキュメントを参照してください。 * Apache Beam のプログラミング モデル Pipelineについて Pipelineは複数のステップから構成される処理全体を表すオブジェクトで、データの読み取り処理から始まりフィルターや変換を経て、出力処理までを行います。一つのパイプラインが一つのジョブとなります。 Pipelineを構成する各ステップは、実行環境が必要に応じて分散処理してくれます。つまり、各ステップを効率よく動作させるには、それぞれのステップが全く違ったプロセスの上で非同期に実行されても問題がおきないようにしましょう。 具体的には、処理単位になるデータの独立性をできるかぎり高めるようにします。つまり、 RDB における非正規化を積極的に行うようなデータの持ち方をします。 順序に強い整合性を求める書き方もできますが、そうすると分散処理環境がもつ性能を十分に引き出せません。 PCollectionについて PCollectionはPipelineを流れるデータの集合を表すオブジェクトです。 逐次的に要素を扱えるので Java のCollection Frameworkと似ていますが、PCollectionの API だけではデータの開始と終了を明示的に調べられません。 また、PCollectionに格納されている要素は、分散処理環境内における実行環境の都合で シリアライズ されたりコピーされる可能性があります。つまり、処理に必要な情報は全て要素内に内包する必要があります。 ParDoを使った逐次処理の書き方 基本的な概念が分かった所で本題に入っていきましょう。 最初に作るのは、PCollectionを流れる要素を1:1で変換していく処理です。 この図は四角い枠がPCollectionで、〇が各要素、矢印が処理です。つまり、全体がPipelineとなります。 今回の記事では、全てのコードをテストコードとして実装しますので、以下のように ディレクト リを作成します。 mkdir src/test/java/com/example/dataflow 出来た ディレクト リに CsvFn.java というファイルを以下の内容で作成します。 package com.example.dataflow; import org.apache.beam.sdk.transforms.DoFn; import java.util.*; public class CsvFn extends DoFn<String, List<String>> { // 1. @ProcessElement // 2. public void processElement(ProcessContext c) throws Exception { // 3. var element = c.element(); // 4. var list = Arrays.asList(element.split(",")); // 5. c.output(list); // 6. } } この処理では、単一の文字列を入力すると、それをカンマ区切りで分割したリストとして後続の処理に引き渡します。 逐次処理を実装する際に使うクラスは DoFn を継承します。 一つ目の型パラメータは、各入力要素を表す型を設定します。ここでは String を設定しています。 二つ目の型パラメータは、各出力要素を表す型を設定します。ここでは List<String> を設定しています。 逐次処理を行うメソッドは @ProcessElement アノテーション を付与します。 逐次処理を行うメソッドのアクセス修飾子は public 、戻り値は void です。送出される例外としては Exception を定義しておきます。 なお、このメソッドの中から例外を送出するとジョブ全体が停止します。 ProcessContext の element メソッドを呼ぶと、 DoFn を継承する際に設定した一番目の型パラメータの変数が得られます。ここでは String 型の変数が得られるわけです。 String の split メソッドを呼びだして得られた配列を List に格納しています。 ProcessContext の output メソッドを呼ぶ際には、 DoFn を継承する際に設定した二番目の型パラメータの変数を渡します。ここでは既に作成済みの list を渡していますね。 Dataflowによる ユニットテスト の書き方 次は、逐次処理を ユニットテスト してみましょう。 CsvFn.java と同じ ディレクト リ内に CsvFnTest.java というファイルを以下の内容で作成します。 package com.example.dataflow; import org.apache.beam.sdk.testing.*; import org.apache.beam.sdk.transforms.*; import org.junit.*; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import java.util.List; @RunWith(JUnit4.class) public class CsvFnTest { static final List<String> values = List.of( "foo,bar,baz", "fo1,ba2,ba3", "ba1,ba2,ba3"); @Rule public TestPipeline pipeline = TestPipeline.create(); // 1. @Test public void testSimplePipeline() throws Exception { var output = pipeline .apply(Create.of(values)) // 2. .apply(ParDo.of(new CsvFn())); // 3. PAssert.that(output).containsInAnyOrder( // 4. List.of("foo", "bar", "baz"), List.of("ba1", "ba2", "ba3"), // 5. List.of("fo1", "ba2", "ba3") ); pipeline.run().waitUntilFinish(); // 6. } } ユニットテスト 用のパイプラインを生成しています。パイプラインを構成するための共通処理があるので @Rule を付与しています。 Create の of メソッドを使って文字列のリストをパイプラインに流せる形に変換しています。ここでは、 List の各要素がパイプラインを流れていきます。 ParDo の of メソッドに先ほど実装した CsvFn を インスタンス 化して渡しています。これによって、パイプラインを流れる各要素ごとに CsvFn の processElement メソッドが呼びだされます。 パイプラインを流れる要素が正しく変換されているか確認するには、 Apache Beamで用意されている専用の PAssert を使います。ここでは containsInAnyOrder メソッドを使ってそれぞれの要素が正しくカンマ区切りで分解されたか確認しています。 values 変数として定義した要素の順序とは違った順序で要素を検証しています。これは、パイプラインを流れる要素の処理順序は保証されておらず、実行環境の都合で任意に入れ替わる可能性があることを意図しています。つまり、 Create の of メソッドで作った要素がそのままの順序で CsvFn の processElement メソッドに入ってくるとは限りません。 TestPipeline の run メソッドを呼びだした上で、さらに waitUntilFinish メソッドを呼んでパイプラインの処理が終わるのを待っています。デバッガで実行する際に注意してほしいのは、この時点で初めてパイプラインの処理が動き始めることです。つまり、 4. の時点では、まだ CsvFn の processElement メソッドは呼びだされません。 フィルター CsvFnでは単純な1:1の変換処理を実装しましたので、次はフィルター処理を実装してみましょう。 フィルター処理として作るのは、指定した長さよりも長い文字列だけを後続の処理に流すフィルターです。 CsvFn.java と同じ ディレクト リ内に FilterFn.java というファイルを以下の内容で作成します。 package com.example.dataflow; import org.apache.beam.sdk.transforms.DoFn; public class FilterFn extends DoFn<String, String> { // 1. final int size; // 2. public FilterFn(int size) { this.size = size; } @ProcessElement public void processElement(ProcessContext c) { var element = c.element(); if (size < element.length()) { // 3. c.output(element); } } } ここで実装するのはフィルター処理なので、入力と出力の型は同じです。 コンスト ラク タで受け取った長さをメンバ変数として格納しています。 Apache BeamではPCollectionの要素だけでなく、各処理のステップを表すオブジェクトも実行環境の都合で直列化される可能性があります。つまり、メンバ変数としてはSerializableな型(もしくは、Externalizableな型)だけを定義できます。 条件分岐に基づいて ProcessContext の output メソッドを呼びだすかどうかを決めています。 フィルターのテスト では、フィルター処理を ユニットテスト してみましょう。 CsvFn.java と同じ ディレクト リ内に FilterFnTest.java というファイルを以下の内容で作成します。 package com.example.dataflow; import org.apache.beam.sdk.testing.*; import org.apache.beam.sdk.transforms.*; import org.junit.*; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import java.util.List; @RunWith(JUnit4.class) public class FilterFnTest { static final List<String> values = List.of( "alpha", "beta", "gamma"); @Rule public TestPipeline pipeline = TestPipeline.create(); @Test public void testSimplePipeline() throws Exception { var output = pipeline .apply(Create.of(values)) .apply(ParDo.of(new FilterFn(4))); PAssert.that(output).containsInAnyOrder("gamma", "alpha"); pipeline.run().waitUntilFinish(); } } 長さが4文字より大きい単語をフィルターできていますね。 値の増幅処理 次は、一つの入力から複数回の出力を行う処理を実装してみましょう。 増幅処理として作るのは、文字列をカンマ区切りで分割した各要素をそのまま後続に渡す処理です。 CsvFn.java と同じ ディレクト リ内に FlatValuesFn.java というファイルを以下の内容で作成します。 package com.example.dataflow; import org.apache.beam.sdk.transforms.DoFn; import java.util.Arrays; public class FlatValuesFn extends DoFn<String, String> { // 1. @ProcessElement public void processElement(ProcessContext c) { var element = c.element(); var list = Arrays.asList(element.split(",")); list.forEach(c::output); // 2. } } ここで実装するのは増幅処理なので、入力と出力の型は同じです。 文字列を分割して得られた要素全てについて ProcessContext の ouput メソッドを呼びだしています。 増幅処理のテスト では、増幅処理を ユニットテスト してみましょう。 CsvFn.java と同じ ディレクト リ内に FlatValuesFnTest.java というファイルを以下の内容で作成します。 package com.example.dataflow; import org.apache.beam.sdk.testing.*; import org.apache.beam.sdk.transforms.*; import org.junit.*; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import java.util.List; @RunWith(JUnit4.class) public class FlatValuesFnTest { static final List<String> values = List.of( "foo,bar,baz", "fo1,ba2,ba3", "ba1,ba2,ba3"); @Rule public TestPipeline pipeline = TestPipeline.create(); @Test public void testSimplePipeline() throws Exception { var output = pipeline .apply(Create.of(values)) .apply(ParDo.of(new FlatValuesFn())); PAssert.that(output).containsInAnyOrder( "foo", "bar", "baz", "ba1", "ba2", "ba3", "fo1", "ba2", "ba3" ); pipeline.run().waitUntilFinish(); } } カンマ区切りで3つずつに分割できる要素を3回 FlatValuesFn で処理したので9つの要素が出力されていますね。 PCollectionの分岐 ここまでの処理では、処理の流れであるPCollection自体は1つのまま要素が流れていきました。 しかし、データの1カラム目だけを見て後続の処理を切り替えるといった処理構造を実現したくなることはあります。 ここでは、入力された文字列の1文字目を使って後続の処理を切り替えるためにPCollectionを分岐してみましょう。 CsvFn.java と同じ ディレクト リ内に BranchFn.java というファイルを以下の内容で作成します。 package com.example.dataflow; import org.apache.beam.sdk.transforms.DoFn; import org.apache.beam.sdk.values.TupleTag; import java.util.*; public class BranchFn extends DoFn<String, List<String>> { static final TupleTag<List<String>> MAIN = new TupleTag<>() { // 1. }; static final TupleTag<List<String>> SUB = new TupleTag<>() { }; @ProcessElement public void processElement(ProcessContext c) { var element = c.element(); var list = Arrays.asList(element.split(",")); if (list.get(0).equals("M")) { c.output(MAIN, list.subList(1, list.size()));       // 2. } else { c.output(SUB, list.subList(1, list.size())); } } } TupleTag は実行環境全体で一意のIDを付与する必要があります。ここでは、ややトリッキーなテクニックを使ってそれを実現しています。コンスト ラク タ呼び出しの後ろについている中括弧 {} によってインナークラスを作成していることがポイントです。実装の詳細が気になる方は是非コードを読んでみてください。 ProcessContext の ouput メソッドを呼びだす際に、 TupleTag を渡しています。これによって各要素にタグ付けをすることで、PCollectionの分岐を実現しているのです。 PCollectionの分岐をテストする では、分岐したPCollectionをどのように扱うのかテストコードで確認してみましょう。 CsvFn.java と同じ ディレクト リ内に BranchFnTest.java というファイルを以下の内容で作成します。 package com.example.dataflow; import org.apache.beam.sdk.testing.*; import org.apache.beam.sdk.transforms.*; import org.apache.beam.sdk.values.TupleTagList; import org.junit.*; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import java.util.List; @RunWith(JUnit4.class) public class BranchFnTest { static final List<String> values = List.of( "M,foo,bar,baz", "S,fo1,ba2,ba3", "M,ba1,ba2,ba3"); // 1. @Rule public TestPipeline pipeline = TestPipeline.create(); @Test public void testSimplePipeline() throws Exception { var output = pipeline .apply(Create.of(values)) .apply(ParDo.of(new BranchFn()) .withOutputTags(BranchFn.MAIN, TupleTagList.of(List.of(BranchFn.SUB))) // 2. ); PAssert.that(output.get(BranchFn.MAIN)).containsInAnyOrder( // 3. List.of("foo", "bar", "baz"), List.of("ba1", "ba2", "ba3") ); PAssert.that(output.get(BranchFn.SUB)).containsInAnyOrder( // 4. List.of("fo1", "ba2", "ba3") ); pipeline.run().waitUntilFinish(); } } テストデータとして、各要素の先頭に分岐の条件となる M や S を配置しています。 ParDo の of メソッドを呼びだして得られた変数に対して、 withOutputTags を呼びだすことでこのパイプラインが分岐することを宣言しています。 ここでは二つに分岐していますが、三つや四つ、それよりも多くのPCollectionに分岐できます。 分岐されたパイプラインから MAIN でタグ付けされた PCollection を取り出しています。 1. では文字列の先頭が M になっているものがこれにあたります。 分岐されたパイプラインから SUB でタグ付けされた PCollection を取り出しています。 1. では文字列の先頭が S になっているものがこれにあたります。 まとめ Apache Beamを使った バッチ処理 を書く上で最も汎用性の高い ParDo を使ったスタイルをいくつか紹介しました。 今日紹介したスタイルは、それぞれ専用の API が用意されていますが、必要に応じて API を覚えなおすのはやや面倒です。 例えば、型を1:1で変換するなら、 MapElements という専用の API があります。フィルターしたいなら Filter があります。 ParDo には、この記事では紹介しきれなかった便利な機能が他にもありますので是非試してみてください。 Google Dataflowは非常に巨大なデータを バッチ処理 するための実 行基 盤として非常に安価に利用できる上にハイパフォーマンスに動作する環境です。 例えば、筆者の業務ではGCSにおいたログファイルをBigQueryへ投入する手段としてDataflowを利用しています。テラバイトクラスのログファイルが分散処理によって数十分でDBに投入されていく様子は圧巻というほかありません。 この記事を読んだ皆様がDataflowを使って、筆者が受けた感銘を共有していただけたら非常に嬉しいです。 私たちは同じチームで働いてくれる仲間を探しています。今回のエントリで紹介したような仕事に興味のある方、ご応募をお待ちしています。 社内SE(DX推進エンジニア) 執筆: @sato.taichi 、レビュー: @handa.kenta ( Shodo で執筆されました )
アバター