日本語LLM

こんにちは、未来環境ラボの中口です。みなさんもうChatGPTは使っていますか?ChatGPTと会話するだけでも楽しいですが、Webページの要約や論文の検索、Pythonコードの実行など、次々と新しい機能がリリースされてどんどん進化しています。今回はそんなChatGPTに近い性能を持ち、かつ無償で利用できるLLM(Large Language Model)を紹介します。

続きを読む

comments

2020年度 活動まとめ

2020年度はコロナ禍の中,活動方法を模索する1年でした。年度当初より京都情報大学院大学,京都コンピュータ学院含むKCGグループは一斉にオンライン講義に移行し,ラボの活動も全てオンライン化しました。その影響で機材を使ったプロトタイピング などができなくなりましたが,金曜夕方に実施していたゼミの参加者は増加しました。

できごと

  • 9/18 オンラインアイデアソン :「オンライン授業を効率的に行う仕組みを考えよう」
  • 11/6  KCGI創立17周年記念式典,技術交流会

対外発表など

  • 堀口尚史, 中口孝雄, 佐藤元昭, “フォームサービスを用いた口腔ケア実施報告システム ,” 電子情報通信学会サービスコンピューティング研究会, 2020.8.
  • 中口孝雄,”リアルタイムコラボレーションツールのためのオブジェクト共有サービス,” 電子情報通信学会サービスコンピューティング研究会, 2020.11.
  • 堀口尚史, 中口孝雄, 佐藤元昭, “フォームサービスとスプレッドシートサービスおよびテキスト解析サービスを組み合わせた口腔ケア実施報告分析システム,” NAIS Journal, 2021.3.
  • 中口孝雄, “機械学習サービスを登録・提供するサービス基盤の構築に向けて,” NAIS Journal, 2021.3.

ゼミは主に授業期間中,隔週で金曜日の夕方に開催しています。大学で実施されているようなゼミと同様で,KCG生,KCGI生,他大学の学生,OBや現役エンジニアも参加する,研究開発活動の発表や議論の場です。毎回数件の発表ののち,オンライン飲み会に以降し,交流を深めるとともに熱い議論を交わしています。

ゼミのテーマ例

コラボレーションツール,仮想空間+ビデオ会議,ソーラー発電+仮想通貨マイニング,DeepL,口腔ケアシステム,消防団員支援,なでしこ,M1Macなど

前年度→ 2019年度 活動まとめ

comments

2019年度 活動まとめ

2019年度の活動をまとめてみました。

できごと

ゼミ

  • 主に授業期間中、隔週で金曜日の夕方にゼミを開催しました。
    大学で実施されているようなゼミと同様で、発表や議論の場です。
    メンバーは固定ではなく、学外の社会人なども参加しています。

各種リンク

前年度→ 2018年度 活動まとめ

comments

11月祭で「利きコード選手権」を開催しました (その3)

前回に引き続いて、今回は 4~5 問目の解説です。だいぶ難易度が上がってきました。
前回までは Java 8 と JavaScript で解答を作成していましたが、今回は筆者が普段使っている C# で作成しています。

(4) ABC 128 B – Guidebook (難易度 198) 解答例 (C#)

市名の辞書順、点数が高い順にレストランの番号を出力してください。

これはもう実務そのまま、典型的なデータ加工のスキルが問われています。
次の手順に分解するとよいでしょう。

1. 入力データを整形

  • レストランの番号をデータとして追加
  • 扱いやすいように、メモリ上でデータベースを構築するイメージ

2. ソート

  • 市名の昇順、点数の降順
  • SQL で考えると select Id from Restaurants order by City, Point desc
  • 実装はプラットフォームごとのライブラリに左右される

ソートは次の方法が考えられます。

  • ソート条件を一度に指定
    • 市名、点数の順
  • 2回のソート
    • 点数、市名の順
    • 安定ソート (マージソートなど) であれば可能、不安定ソート (クイックソートなど) では不可能

ソートを実装するには、各言語プラットフォームで用意されているライブラリを使うことになると思います。ソート条件を引数で指定する方法もライブラリごとに異なるためそれに従ってください。

  • キーを指定
    • r => r.Point
  • 「負, 0, 正」の3つの状態を返す
    • (r1, r2) => r1.Point - r2.Point

C# での実装は次のようになります。
まるで業務のように手堅く書くなら、エンティティ型を用意するとよいでしょう。
また、ここでは C# のクエリ式を使っています。普段はクエリ式をそれほど使わないのですが、where や orderby を使う程度で済む場合は見やすいコードになると思います。

using System;
using System.Linq;

class B
{
	static void Main()
	{
		var n = int.Parse(Console.ReadLine());
		var restaurants = Enumerable.Range(0, n)
			.Select(i => Console.ReadLine().Split())
			.Select((x, i) => new Restaurant { Id = i + 1, City = x[0], Point = int.Parse(x[1]) });

		var query =
			from r in restaurants
			orderby r.City, r.Point descending
			select r.Id;
		Console.WriteLine(string.Join("\n", query));
	}
}

class Restaurant
{
	public int Id { get; set; }
	public string City { get; set; }
	public int Point { get; set; }
}

この問題の場合はとくにエンティティ型を作成しなくても、匿名型で間に合います。
次の解答例では通常の LINQ で実装しています。慣れればメソッドチェーンでつなげて書けます。

using System;
using System.Linq;

class B
{
	static void Main()
	{
		var n = int.Parse(Console.ReadLine());
		var query = Enumerable.Range(0, n)
			.Select(i => Console.ReadLine().Split())
			.Select((x, i) => new { Id = i + 1, City = x[0], Point = int.Parse(x[1]) })
			.OrderBy(r => r.City)
			.ThenByDescending(r => r.Point)
			.Select(r => r.Id);
		Console.WriteLine(string.Join("\n", query));
	}
}

他にトリッキーな解法も存在しますが、データ加工は手堅い方法で実装するのがよいでしょう。

(5) ABC 016 C – 友達の友達 (難易度 1008 とあるけど今だともっと低いはず) 解答例 (C#)

各ユーザの「友達の友達」の人数を求めてください。
ただし、自分自身や友達は、「友達の友達」に含みません。

前問と同様、まずは入力データをいい感じの形式にしておきます。

1. 入力データを整形

  • 各ユーザの友達のリストを作る
    • 隣接者へのマップ (グラフ化)

2. 集計

  • マップを辿って友達の友達のリストを作る
    • さらに重複を排除
  • そこから友達と自分を排除

集計するための具体的な実装方法は、次の通りいくつか考えられます。

  • 関数型パラダイムのライブラリを使う (簡単)
    • コレクションのコレクション (入れ子構造) を平坦化
      • 孫データを一列にする
  • フラグの配列
    • 重複を排除して管理できる
    • ハッシュセットのようなコンテナーも同様
  • 幅優先探索
    • グラフ理論っぽい解法
    • 最短距離が2となるユーザの数

C# で関数型っぽい実装をすると次のようになります。
ToLookup メソッドでグルーピングができます。 また、SelectMany メソッドで入れ子構造を平坦化し、Except メソッドで集合の差を求めています。

Func<int[]> read = () => Console.ReadLine().Split().Select(int.Parse).ToArray();
var h = read();
var paths = new int[h[1]].Select(_ => read()).ToArray();
var map = paths.Concat(paths.Select(x => new[] { x[1], x[0] })).ToLookup(x => x[0], x => x[1]);

for (int i = 1; i <= h[0]; i++)
{
	var f2 = map[i].SelectMany(j => map[j]).Distinct();
	var f1 = map[i].Concat(new[] { i });
	Console.WriteLine(f2.Except(f1).Count());
}

フラグの配列で実装すると次のようになります。

Func<int[]> read = () => Console.ReadLine().Split().Select(int.Parse).ToArray();
var h = read();
var paths = new int[h[1]].Select(_ => read()).ToArray();
var map = paths.Concat(paths.Select(x => new[] { x[1], x[0] })).ToLookup(x => x[0], x => x[1]);

for (int i = 1; i <= h[0]; i++)
{
	var f = new bool[h[0] + 1];
	foreach (var j in map[i])
		foreach (var k in map[j])
			f[k] = true;
	foreach (var j in map[i])
		f[j] = false;
	f[i] = false;

	Console.WriteLine(f.Count(x => x));
}

以上、利きコード選手権で出題した5問の解法について説明しました。
全体的に解答を関数型プログラミングのパラダイムで作成してきましたが、実務でもデータの加工・集計に関して簡潔に書ける場面が多いと思います。

comments

11月祭で「利きコード選手権」を開催しました (その2)

前回に引き続いて、今回は 2~3 問目の解説です。

(2) ABC 143 C – Slimes (難易度 34) 解答例 (Java 8, JavaScript)

同じ色を持つ隣接するスライムが融合すると、最終的に存在するスライムは何匹となるか。

同じ文字が連続した区間の数だけをカウントします。SQL でいうところの distinct に近いのですが、文字列全体に対しての distinct ではなく、連続した部分だけ distinct する必要があります。

方針としては、1文字ずつ読み込んで、直前と異なる文字ならカウントすればよいです。これを実現するために以下の方法が考えられます。

  • 直前の文字を変数にキャッシュして比較
  • 各 i に対して s[i] と s[i – 1] を比較 (範囲に注意)

前者は少し長くなるので後者だけコードを示します。
for 文を使う人が多いとは思いますが、今回もやはり Java で Stream を使います。( import java.util.stream.*; )
Java で String から char を取り出すには、charAt メソッドまたは toCharArray メソッドを使います。

char[] s = sc.next().toCharArray();
System.out.println(IntStream.range(1, s.length).filter(i -> s[i - 1] != s[i]).count() + 1);

sc.next() に文字列が渡されます。

他の考え方として、

  • “xxx…xxx” を “x” に置き換える

という方針でもできます。
Ruby では、このように連続した部分だけ distinct できる squeeze メソッドがあるためとても簡単に書けます。実は Java でも、正規表現のパターンに一致した文字列を置換する replaceAll メソッドを使えば同様のことができます。

System.out.println(sc.next().replaceAll("(.)\\1*", "$1").length());

(3) CF 2017 A – AKIBA (難易度 42) 解答例 (Java 8, JavaScript)

与えられた文字列の好きな位置に好きなだけ文字 "A" を挿入して "AKIHABARA" に変えることはできるか。

この問題では多くの解法が考えられます。
適合するケースが 24 = 16 通りしかないことがわかれば効率的に全探索することも可能です。

  • 1文字ずつ比較してなんやかんや
    • わからん読めん
  • 16通りを全列挙
    • “KIHBR”, “KIHBRA” などを直書き
  • 16通りを全探索
    • 4重ループ
  • 正規表現
    • 処理速度は落ちるが圧倒的な保守性

1文字ずつ比較する方針の人が多かったです。しかしこの解法では人によってコードの違いが大きすぎて、本人以外はコードの意味を理解できないという状態に陥りました。
業務でこのタイプの要件が現れたら、他に単純化する方法がない限りは正規表現を利用することをお薦めします。

System.out.println(sc.next().matches("^A?KIHA?BA?RA?$") ? "YES" : "NO");

他の考え方として、

  • “A” があるはずのところに “A” を挿入してみて、最終的に “AKIHABARA” となるか

という方針でもできます。つまり「1文字ずつ比較しながら “A” を挿入」の代わりに「”A” を挿入してみて、最後に全体を比較」と考えても同じです。

StringBuilder sb = new StringBuilder(sc.next());
int[] ai = { 0, 4, 6, 8 };
for (int i : ai) {
    if (i < sb.length() && sb.charAt(i) != 'A' || i == sb.length()) {
        sb.insert(i, "A");
    }
}
System.out.println(sb.toString().equals("AKIHABARA") ? "YES" : "NO");

次回は 4~5 問目の解説です。

comments

11月祭で「利きコード選手権」を開催しました (その1)

11月8日 (金)、KCG 11月祭の1日目に「利きコード選手権」というクレイジーなイベントを開催しました。

企画意図

匿名のソースコードを見て指摘し合ってみると盛り上がりそう、という単純な動機。
それと同時にコーディングについて学習しよう、他の人のコードを読む習慣を付けよう、という教育的効果 (後付け)。

概要

  • 準備段階であらかじめ問題を設定して、コードを学生と教員を含めて10人くらいに書いてもらう。
  • 当日の参加者は (コードを書いていなくてもかまわない)、匿名のコードを読み比べて学生のコードか教員のコードかを2択で当てる (Google フォームで解答)。
    • その際、各問題に対して20分ほどの議論時間を取り、参加者は匿名のコードに対して自由にコメントする。
  • 最も当てることができた人が利きコーダーとして表彰される。
  • 最後に各問題のいろいろな解法について解説。

Kikicode-1
壇上ではコードを書いた人同士でコメント合戦が繰り広げられるの図 ↑

結果

コンピュータの専門学校らしい企画ができました。
十人十色のコードが集まり、とても盛り上がったので各所で流行ってもいいんじゃないかと思います。
ただし、学習の面で有意義にするためにも、難易度設定、問題選択、解説を準備する必要があります (今回はかなり準備した)。

Kikicode-2

以下では詳細を書いていきます。

問題セット

問題は競技プログラミングサービスの AtCoder の過去問から出題し、プログラミング言語としては学校の授業でも使われることの多い Java 8 または JavaScript に限定しました。
AtCoder 上での実行および提出は必須とはしていません。また入出力の部分もあまり本質ではないため、引数 (args) を使うなどの別の方法でもよいこととしました。

\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/
こちらの3問は必須です。
(1) 81 (難易度 5)
(2) Slimes (難易度 34)
(3) AKIBA (難易度 42)

以下は任意です。(余裕のある人向け)
(4) Guidebook (難易度 198)
(5) 友達の友達 (難易度 1008 とあるけど今だともっと低いはず)

※ 難易度: AtCoder Problems における推定値で、そのレート (戦闘力) の人が正答率 50% であることを表します。例えば、レート 5 のキャラが1問目を正解できるかどうかは半々です。
/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\

問題文がシンプルで、実務に近くて、別解のパターンが多くて、、とかいろいろ検討して厳選しました。もとは3問目に「Guidebook」だったのですが、初回だし大変かなと思って「AKIBA」にして、難易度を100未満としました。

解説

とりあえず今回の記事では1問目の解法のみ書いておきます。
私は普段は C# を使っていますが、今回のために Java 8 と JavaScript で解答例を作成しました。

(1) ABC 144 B – 81 (難易度 5) 解答例 (Java 8, JavaScript)

与えられた整数 N を 1 以上 9 以下の 2 つの整数の積として表すことができるか。

ループと条件分岐が必要となる典型的な問題です。

アルゴリズム:

  • 81通りを全探索
    • 2重ループ
    • O(N2)
    • i ≦ j に制限してもオーダーは変わらず
  • 割るほうだけでループ
    • 商は自動的に決定
    • O(N)

コーディング スタイル:

  • ループの実現方法
    • for ループ
    • 関数型パラダイム、ラムダ式
  • 別メソッド化
    • for の内部で return

タイトルが「81」であることから反射的に2重ループを使いたくなります。今回の制約では全探索でも問題ありませんが、N が大きくなって「1000000以下の2つの整数の積」とかになると数秒では完了しません。
また、繰り返し処理については for 文を使う人が多いとは思いますが、Java の Stream を使う ( import java.util.stream.*; ) と、N が与えられればあとは1行で書けます。JavaScript でも Array で同様のことができます。

System.out.println(IntStream.range(1, 10).anyMatch(i -> n % i == 0 && n / i <= 9) ? "Yes" : "No");

あとはネタ枠として、キモい for 文を挙げておきます。

boolean b = false;
for (int i = 1; i <= 9 && !(b = n % i == 0 && n / i <= 9); i++) ;
System.out.println(b ? "Yes" : "No");

与太話

10年以上前のイメージでは、コードレビューといえばクラス設計やコーディングスタイルに関するものがほとんどでしたが、近年では大量のデータを扱う開発現場も増えてくるなど、実現したいことの要求が上がるにつれてアルゴリズムが相対的に重視されてきていると感じます。
例えば大量のデータ処理を現実的な制限時間以内に完了させるにも、「どのような関数がどれだけ呼び出されるか」というような精確な見積りが必要となります。リソース制約の厳しいゲーム系の開発などでは以前からそうなのだろうと想像します。

逆にコーディングスタイルに関しては、スマートデバイス、電子工作、機械学習などを扱うことも増えてプログラミング言語が多様化したこともあり、最低限の規約を守っていれば可で、各エンジニアのスタイルが尊重される風潮にあると思います。
他の言語文化にいるエンジニアにローカルの規約を守ってもらうのはなかなか酷です。

むしろ昔はアルゴリズムに触れなさすぎたという印象で、今くらいのバランスが良いのではないかと思います。コードレビューで時間を割くべきポイントが遷移してきているのですね。

Code-Review

で、それぞれの開発現場の状況に合わせて最適な実装を提供できるようになるには、

  • 複数の解法を経験しましょう
  • 他の人のコードを読みましょう

と考えています。

Consequence

さて、次回は残りの問題の解説です。

参照

comments

学内で Git・GitHub 勉強会を開催しました

2019年10月18日 (金) 17:00-18:30 に、京都コンピュータ学院・京都情報大学院大学の学生向けに「Git・GitHub 勉強会」を開催しました。
資料はこちらです。GitHub 上で作成してあります。

Git 関連の情報を検索しても、Git の各機能単体や Git コマンドの説明が多く見られますが、この勉強会では GitHub とローカルの開発環境 (Git ツールとして SourceTree) を連携させ、開発プロジェクトで実践的に使えるようになることを目標として、一連の手順をデモ中心で説明しました。
実際の開発プロジェクトでは、例えば GitHub のブランチやリリースといった機能を使うことになります。資料にはそのための手順を記載しています。

また試験的に、そのときの画面を YouTube Live で配信してアーカイブしました (ただし限定公開)。勉強会の講演を手軽に残せるのはとてもよいです。
配信はこちらのサイトの手順の通りにやればできます。配信ソフトとして OBS Studio を使いました。

参照

comments

AtCoder を始めようかと思っている学生たちへ

現在、全国的に情報系の学生たちの間では空前の AtCoder ブームとなっていて、
「AtCoder を始めた知り合いが増えてきて、就職活動にも活用できそうだし、この機会に自分も始めてみたいけどどうしようかな」と考えている学生も増えてきているのではないでしょうか?
結論を先に言うと「なるべく早いうちから参加しましょう」となりますが、これまでに何回かコンテストに出場してみた経験からいろいろ書いていこうと思います。

AtCoder は、競技プログラミングのコンテストを Web で実施しているサービスです。
以前は競技プログラミングといえば難解でマイナーなイメージでしたが、AtCoder では難度の低い問題から段階的に用意されており、ページも見やすくカジュアルに参加できるようになっています。
初心者向けである AtCoder Beginner Contest はほぼ毎週、土曜日または日曜日の夜にオンラインで実施されています。

AtCoder の参加者のうち7割を学生が占めています。
とくにここ1年くらいの参加者の伸びが大きく、最近の AtCoder Beginner Contest では約5000人が参加しています。
しかしこのブームに乗って就職活動に役立てようと思いつつも、「実力を上げてから参加したい」と考えている人もいるのではないでしょうか?

これに対しては「なるべく早いうちから参加しましょう」が答えです。
なぜなら、コンテストへの参加回数が少ないと、実力がレートに反映されないからです。
最低でも14回以上は参加しておかないと実力よりも低いレートになる仕組みで (リセットマラソンの効果が低い)、実力証明が欲しいと思ったときに急に参加しても実力が認定されるわけではありません。

また、AtCoder におけるレートやランク (色) のレベル感を認識しておく必要があります。こちらの記事に説明が載っています。

AtCoder には日本全国・世界の強者が集まっており、水色 (1200) 以上あれば実務上カンストの上級者だろうと考えています。実社会では緑色の人に遭遇するのもなかなか難しいでしょう。
通常の学生であれば、茶色が認定されれば十分実力があると言えます。

初級者はまず AtCoder Beginner Contest に参加して、問題 A・B を解くことを目標にしましょう。
問題 C までは分岐、ループ、データ集計など、一般的な開発者が実務で書く程度のコードでクリアできます。
問題 D 以降は処理高速化のためのアルゴリズムの知識・経験が必要になってきます。
もし仕事でプログラミングをしているのであれば、問題 C までは正解しておきたいところです。

本番のコンテストに参加する前に、過去問を練習しておきましょう。
問題文や解答の体裁に慣れていないと本番で無駄な時間を使ってしまいます (標準入出力など)。
コンテスト中もコンテスト外のときもサイトの使い方はまったく同じなので、過去問で本番同様の練習ができます。
過去のコンテストの問題は全て公開されています。他の参加者のコードを読んで参考にするのもよいでしょう。
AtCoder Problems という有志のサイトもおすすめです。

プログラミング言語は好きなものを選んでください。
標準入出力などのよく使うコードを含めたテンプレートを用意しておくとよいでしょう。例えば C# ではこんな感じです。
https://gist.github.com/sakapon/a88697da54fc78f838baeadcc19b54c4

各問題にはテストケースが用意されていて、それらをすべてパスすれば正解 (Accepted) となります。
こんな感じで、エディターからコピー&ペーストで提出できます。

AtCoder-Sub

というわけでまずは2~3か月、毎週参加し続けるのです(沼

追記 (11/26):出場11回目で水色に到達しました。
ABC146-Rating

comments

習作のすすめ

「習作」というのは、主に芸術の分野で使われている言葉で、練習のために小作品を作ることを指します。
プログラミングにおいても習作はいいぞ、という話をします。

学生の皆さんは普段、授業などで身に付けた技術を応用して何かを制作しているでしょうか?
あるいは逆に、何かを実現するために技術を応用しているでしょうか?
創作の経験の少ない人が何か壮大なものを作りたいと思い浮かべたとしても、すぐに実現するのは難しいことだと思います。
技術の習得とその応用には乖離があるのです。技術力と創造性のマッチングが必要で、いずれかが不足していても達成できません。

そこで、普段からそのギャップを埋めるための練習をしておくことが求められます。
ブログのネタを例に考えればわかりやすいと思います。
最近習得した技術についてブログの記事を何か書くことになったとして、他の人が作ったチュートリアルを手順通りに実施してそれについて書く・・・というのでは物足りないですよね。
例えば、自分なりの視点でどういう場合に役に立ちそうかを考えながら技術検証し、自分の興味の延長にあるサンプルを作るという方法が考えられます。これが習作です。
いろいろな人のブログ記事を見れば、このタイプがかなりあることに気づくでしょう。ブログのネタとしてなんとなく「ちょうどええ」範囲があると思います。

習作の結果として前例に重複することもあるとは思いますが、それはかまいません。
自分で考え出したものを自分で解決して作ってみることが成長につながります。
初めて使う技術の検証と軽い創作を同時に実施するという点で一石二鳥であると考えています。

一見それほどでもないアイデアのものでも、実装してみると知らなかった機能に出会えたり、あるいは、考えていたことが実現不可能であることがわかったりします。
その結果から技術を深掘りしていくこともあれば、アイデアを別の作品に昇華させていくこともできるでしょう。
習作では、技術力と創造性の向上の両面があることがポイントです。
技術検証や創作の練習を通して、今後の引き出しを増やし、何ができるかわかるようになります。

英語の表現を新しく覚えたら自分が日常生活で使いそうな文にアレンジしてみたり、
微積分を覚えたら乗り物の速度・加速度や目的地までの距離を概算してみたり、
身に付けたことを普段から社会に応用しているか、そのための視点を持っているかが大切になるのです。

参照

comments

NT京都2019に出展しました

2019年3月24日 (日) に西院春日神社・春日幼稚園で開催された NT京都2019 に、
PC 用 TAS 開発環境 Speedrunner を出展しました。

Speedrunner-2.0.4-VS

「NT」はニコニコ技術部のことで、ネタもの系ものづくりの展示会です。
2010年にニコニコ動画で 【TASさんの休日】mixiアプリ 四則演算ゲーム 92問正解 などの TAS 動画を投稿しており、そのときに開発したツールを実演しました。

なお、TAS とは Tool-Assisted Speedrun の略で、ツールの補助によりゲームを高速でプレイすることを指します。一般には各ゲーム機のエミュレーターを利用することが多いですが、これは PC (Windows) 上での TAS となっています。
最近では RPA (Robotic Process Automation) というキーワードもあり、原理的にはこれも同じです。

また、ツールをバージョンアップして、Visual Studio の GUI でワークフローを組めるようにしました。

機能:

  • PC のマウス・キーボード操作を記録
  • Visual Studio の GUI でワークフローを作成
  • ワークフローを実行
  • いちおうプログラミングもできる

次の動画は円周率を求める例です (2倍速)。最後に計算結果が表示されています。

NT京都は、今までに見た展示会の中で最もカオスでした。
発想の源が狂っているものが多いです。既存の技術なのに「そんな使い方するのか!?」という驚きがあって素晴らしいです。

IMG_20190323_175834

IMG_20190324_100804 IMG_20190324_132946

IMG_20190324_110617 IMG_20190324_101859

IMG_20190323_164516

comments