ジェネラティブアートとは

はじめに

 こんにちは、アンです。
 現在、聞いたことがない⼈がいないほど、世界中でNFTブームが起きています。私が注⽬しているジェネラティブアートの分野でもたくさんの作品が NFT 化しています。2021年、タイラー・ホッブス(Tyler Hobbs)のシリーズ作品《Fidenza》が販売されました。現時点で(2022年5月9日)、オープンシー(OpenSea)で販売されている合計999点の作品の総価値が45.9K ETH(約14,670,642,000円)になっています。NFTとNFTアートのブームのおかげで、私も含めてジェネラティブアートの作者が増えています。しかし、ジェネラティブアートとはいったい何なのでしょうか。今回、ジェネラティブアートの歴史を紹介します。

ジェネラティブアートとは

NoisePlanet_Echo_V4 - Samuel YAN, 2022 (https://openprocessing.org/sketch/1548117)

 簡潔に説明すると、ジェネラティブアートとは、ある程度の自律性を備えたシステムによって作成された作品、または人の介入がほとんどなくても機能できる作品を指します。言語ルール、マシン、アルゴリズムなどを使用して作者がシステムを設計します。設計したシステムに従って、作品が生成されます。これらのシステムは、デジタル、または化学、建築、詩、文学、アニメーション、視覚芸術など、さまざまな分野で実践されています。
 私の場合は、p5.js、processing、pythonなどのプログラミング言語を使って、ジェネラティブアート作品を制作しています。

ジェネラティブアートの歴史

萌芽

 19 世紀末に、ジェネラティブアートの芽が出始めました。キュビズムの原則の基礎を築いたポール・セザンヌのような芸術家たちは、彼らの作品の中で幾何学図形や、矛盾した視点などを使いはじめました。そこから、未来派構成主義がテクノロジーと機械を⽤いて 作品の魅⼒を広めます。その後、ダダイズムシュルレアリスム、抽象表現主義などの芸術運動が続きます。それぞれ、芸術を再定義しようとチャレンジしました。こうした芸術運動は、ジェネラティブアートの中心的な構成要素につながっています。

発展

 ビジネス用のコンピューターが登場した1960年代と1970年代に、ジェネラティブアーティストは、一つの部屋のような大きなサイズの計算機を使って、実験を行い、芸術とコンピューターサイエンスの関係を研究し始めました。1965 年にドイツのシュトゥットガルトで、初めての精選されたジェネラティブアートの展覧会が開催され、ゲオルク・ニース(Georg Nees)の作品が注目されました。その後、パイオニアであるニースとフリーダー・ナーケ(Frieder Nake)は、別の展覧会で彼らの作品を一緒に展示しました。その展覧会(Computer-Grafik (Nake & Nees))では、プログラムされ、コンピュータで制御されたドローイングマシンが制作した作品が特集されています(Generative Art: Origins, Artists, and Exemplary Works - Invaluable)。

Polygon of 23 Vertices (23-Ecke) — Georg Nees, 1964
13/9/65 Nr. 2 ("Hommage à Paul Klee") - Frieder Nake, 1965

 その間、⼥性の作家もジェネラティブアートの分野で活躍しました。たとえば、ジェネラティブ・アーティストで研究者のリリアン・シュワルツ(Lillian Schwartz)は、1968年から34年以上にわたってベル研究所のアーティスト・イン・レジデンスをつとめました。彼女は、MoMAが買い上げたはじめてのジェネラティブ・アートの作家としてよく名前があがります(Why Love Generative Art? — Artnome)。

Pixillation, photographic film stills - Lillian Schwartz, 1970

現状と展望

 コンピュータの普及で、アーティストたちがアルゴリズムを使用し始めました。数⼗年にわたって、芸術をコード化することができるコンピュータ・プログラムは、1990年代後半に開発されました。これらの革新があるため、コンピュータさえあれば、誰でもジェネラティブアートを作ることができます。2014年から、人工知能の飛躍的な発展により、AIが生み出すジェネラティブアート(AIアート)は開花しました。人間の脳のように考えるアルゴリズムが設計されており、ジェネラティブアートを制作することに成功しています。

ジェネラティブアートの種類

 今まで紹介したのは、最も一般的なタイプのジェネラティブアートであるビジュアルアート(視覚芸術)の分野の作品です。世界中のアーティストは、さまざまな⾃律的なシステムを⽤いて、有機的な⾃然界のものから⼈⼯的なものまで、さまざまな作品を制作してきました。コンピュータが利⽤しやすくなったので、コンピュータをベースとしたジェネラティブアートが増加しています。⽂学、⾳楽、建築、詩など、他のさまざまな分野でも実践されています。

ジェネラティブアートの魅力

 他のジャンルの芸術と⽐べ、私にとってジェネラティブアートの魅⼒がいくつかあります。これから、p5.jsを使用した自身の作品例を挙げながら紹介します。

ランダム性

 ランダム性は、ジェネラティブアートの⼀番魅⼒があるところだと思います。random関数を使って、色、位置、線の太さなどにランダム性を与えると作品に変化が生まれます。ランダムな値のおかげで、コードを実⾏するたびに異なる画像を⽣成し、さまざまな出⼒が⽣まれます。
 たとえば、次の画像では、色、線の太さ、幅の広さなどをrandom関数で設定しました。

MetalWave_3.0 - Samuel YAN, 2022 (https://openprocessing.org/sketch/1559761)

対象の可変性

 アルゴリズムやシステムを作った上で、「対象(object)」を変換すれば、生み出される結果(画像などの出⼒)が変わります。ここでの「対象」は、円、四角形、三⾓形、線などの基本的な幾何学的図形だけではなく、複数のシンプルな図形を組み合わせて作った複雑なパターンや模様も含まれます。
 たとえば、次のシリーズ作品の《urbanME》で、「対象」に異なる要素を入れることで結果が変わりました。

urbanME series - Samuel YAN, 2021~ (https://openprocessing.org/curation/73130#)

双方向性(インタラクティブ

 インタラクティブとは、相互に作⽤する、対話的な、双⽅向の、相乗効果の、などの意味を持つ英単語で、メディアコンテンツでは、 内容を利⽤者に⼀⽅的に与えるのではなく、利⽤者の働きかけにより刻々と内容が変化することをインタラクティブであるといいます(IT用語辞典e-Words)。アートの場合は、インタラクティブアートというジャンルや分野が存在します。ジェネラティブアートでも双⽅向性(インタラクティブ)がある作品が作れます。
 たとえば、次の作品では、外部の⾳源の⾳量によって、画⾯の中の点が移動しています。

Pulse Wave_1.2.2 with Music - Samuel YAN, 2022 (https://openprocessing.org/sketch/1546845)

さいごに

 ジェネラティブアートの歴史を遡って、歴史的な作家や作品を紹介しました。最後に私にとってのジェネラティブアートの魅力にも触れました。今回、たくさんの文章を読んだことで、ジェネラティブアートの分野でパイオニアたちが試行錯誤したことや考え方を知ることができました。この調査を、今後の私の研究と制作に役立てていきたいと思います。

Go言語で基本のソートアルゴリズムを実装してみた

はじめに

こんにちは,光岡です. このブログはとある大学院の研究室メンバーによるブログです! 3人体制で各々が月に1回ずつ,研究分野問わず自分が興味を持っていることについて発信していきます. そして今回は第一回目の投稿になります!

私はWebサービス開発を勉強していることもあり,今Go言語に興味があります. また,本研究室は情報系の学域ではないため(デザインよりの学域です),最近は個人的にアルゴリズムを学んでいます.アルゴリズムとデータ構造の勉強をする際はC言語Pythonが主流だと思いますが,今回は私が興味を持っている2つを掛け合わせて,Go言語のソートアルゴリズムに関して書いていきたいと思います.

Go言語とは

Go言語とは,Googleによって設計されたプログラミング言語です. Go言語の主な特徴は以下になります.

個人的にはこれまで動的型付け言語を触ってきていたので,最初の型定義が少し面倒だなと感じていましたが,今では型がないと、このメソッドの引数・返り値の型はなんだろう..とたまになってしまうので,型が欲しいなと思うようになりました.

また,最初Go言語で書かれたコードを読んだ時,:=記法を理解していなかったのですが,こちらはvarを省略した記法になります.

// 型を宣言
var n int // n = 0 (Go言語の変数は必ず初期化され,ゼロ値が入ります)

// 型推論
var n = 1

// varを省略
n := 1

実装

今回は,選択ソート,挿入ソート,シェルソートクイックソートの4種を実装してみました.

選択ソート

ソートのアルゴリズムの一つ。 配列から最小値を探し、配列の先頭要素と入れ替えていくことで並べ替える。 最悪時間計算量は O(n2) と遅いため、一般にはクイックソートなどのより高速な方法が利用される。


選択ソートは以下の手順で行う:

  1. 1 番目の要素から最後尾の要素までで最も値の小さいものを探し、それを 1 番目の要素と交換する(1番目の要素までソート済みとなる)

  2. 以降同様に、未ソート部分の最小要素を探索し、未ソート部分の先頭要素と交換する

  3. すべての要素がソート済みになったら処理を終了する

選択ソート - Wikipedia

for文で最も小さい値のインデックスをmin_idxに代入していき,最終的に格納されていたインデックスを使用して,スライスの要素を順に昇順に並び替えました.

スライスとは
他の言語では,配列を使用しますが,Go言語ではスライスを使用するほうが一般的です. 配列とスライスの違いは,配列はサイズが固定長,スライスが可変長です.

package main

import "fmt"

func SelectionSort(nums []int) []int {
    for i := range nums {
        min_idx := i

        for j := i; j < len(nums); j++ {
            if nums[min_idx] > nums[j] {
                min_idx = j
            }
        }
        nums[i], nums[min_idx] = nums[min_idx], nums[i]
    }
    return nums
}

func main() {
    nums := []int{2, 4, 3, 1, 5, 7, 6}
    fmt.Println(SelectionSort(nums)) // [1 2 3 4 5 6 7]
}

挿入ソート

数値の列を先頭から小さい順(昇順)に並べる場合を考える。まず、先頭から2つの値を比較して小さい方を先頭に、大きい方を2番目に置く。次に3番目の値を取り出し、先頭・2番目と順に比較し、適切な位置に挿入する。

4番目以降も同様にして、n番目の値を取り出して先頭からn-1番目までと順番に比較し、適切な位置に挿入する操作を繰り返す。

挿入ソート(基本挿入法 / インサーションソート)とは - IT用語辞典 e-Words

未整列の要素が逆順に近い並びの場合は,計算時間が多くかかってしまい,それを緩和したアルゴリズムシェルソート(後述)になります.

下記コードでは,tempに格納した値を,条件が当てはまる限り適切な位置まで移動させるのがポイントになります.

最初の状態[4, 3, 1, ...]において,i = 1 の時に並び替えると,[3, 4, 1, ...]となります.
次に i = 2 の時,temp = nums[2] = 1が入ります.この時 4 > temp(=1) により,nums[2] に 4 を移動させた後 j をデクリメントして,3 と temp(=1)を比較します. 3 > temp(=1) により,同様に nums[1] に 3 を移動させ,1 が nums[0] に移動し,[1, 3, 4, ...]となります.

このように,比較対象になった値は,適切な位置まで移動し切ってから次の要素を並び替えます.

package main

import "fmt"

func InsertionSort(nums []int) []int {
    for i := 1; i < len(nums); i++ {
        if nums[i-1] > nums[i] {
            j := i
            temp := nums[i]
            for 0 < j && nums[j-1] > temp {
                nums[j] = nums[j-1]
                j--
            }
            nums[j] = temp
        }
    }
    return nums
}

func main() {
    nums := []int{4, 3, 1, 5, 7, 6, 2}
    fmt.Println(InsertionSort(nums)) // [1 2 3 4 5 6 7]
}

シェルソート

挿入ソートの一般化であり、配列の中である程度間隔が離れた要素の組ごとに挿入ソートを行い、間隔を小さくしながら同様のソートを繰り返すことで高速化するアルゴリズムである。ただし、挿入ソートと異なり、安定ソートではなくなる。


主な手順は以下の通りです.

  1. 適当な間隔 h を決める

  2. 間隔 h をあけて取り出したデータ列に挿入ソートを適用する

  3. 間隔 h を狭めて、2.を適用する操作を繰り返す

  4. h = 1 になったら、最後に挿入ソートを適用して終了

シェルソート - Wikipedia

間隔の決め方については,様々な方法がありますが,今回は,まずスライスの要素数 / 2 で始め,都度2で割って間隔を狭めていく方法をとって実装してみました.

今回は数値を2で割る際に,ビット演算子の右シフトを使用してみました.

// example

num := 50
fmt.Printf("Result: %06b ( = %d )\n", num, num)          // 110010 ( = 50 )
fmt.Printf("Result: %06b ( = %d )\n", num>>1, num>>1)    // 011001 ( = 25 )
fmt.Printf("Result: %06b ( = %d )\n", num>>2, num>>2)    // 001100 ( = 12 )
package main

import "fmt"

func ShellSort(nums []int) []int {
    n := len(nums)
    h := n >> 1

    for h > 0 {
        for i := h; i < n; i++ {
            for j := i; j >= h; j -= h {
                if nums[j-h] > nums[j] {
                    nums[j], nums[j-h] = nums[j-h], nums[j]
                }
            }
        }
        h >>= 1
    }
    return nums
}

func main() {
    nums := []int{4, 3, 1, 5, 7, 6, 2}
    fmt.Println(ShellSort(nums)) // [1 2 3 4 5 6 7]
}

クイックソート

クイックソートとは分割統治法の1つです. 主な手順は以下の通りです.

  1. ピボットの選択:適当な値(ピボット(英語版)という)を境界値として選択する
  2. 配列の分割:ピボット未満の要素を配列の先頭側に集め、ピボット未満の要素のみを含む区間とそれ以外に分割する
  3. 再帰:分割された区間に対し、再びピボットの選択と分割を行う
  4. ソート終了:分割区間が整列済みなら再帰を打ち切る

クイックソート - Wikipedia

文章のみの説明だとイメージしづらいと思います.個人的には クイックソートとは | 分かりやすく図解で解説の図解がとてもわかりやすかったので,是非確認してみてください!

他のソートアルゴリズムに比べ,高速だと言われていますが,手順1のピボットの位置やデータの並びによっては計算量が多くなることもあります.

今回のピボットは,スライスの最後の要素を使用して実装してみました. また,partitionメソッドの返り値i + 1は pivot に指定していた値の最終的な位置を返しており,その pivot よりインデックスが小さいスライスと大きいスライスの2つに分け,再帰的に同じ処理を行い並び替えを行っています.

package main

import (
    "fmt"
)

func partition(nums []int, low int, high int) int {
    i := low - 1
    pivot := nums[high]
    for j := low; j < high; j++ {
        if nums[j] <= pivot {
            i++
            nums[i], nums[j] = nums[j], nums[i]
        }
    }
    nums[i+1], nums[high] = nums[high], nums[i+1]
    return i + 1
}

func QuickSort(nums []int, low int, high int) []int {
    if low < high {
        partition_idx := partition(nums, low, high)
        QuickSort(nums, low, partition_idx-1)
        QuickSort(nums, partition_idx+1, high)
    }
    return nums
}

func main() {
    nums := []int{2, 4, 3, 1, 5, 7, 6}
    low := 0
    high := len(nums) - 1
    fmt.Println(QuickSort(nums, low, high)) // [1 2 3 4 5 6 7]
}

さいごに

今回は基本のソートアルゴリズム4種をGo言語で実装してみました.これまでアルゴリズムをしっかり学んだことがなかったのですが,楽しみながら学ぶことができました.まだ次回内容は決めていませんが,他のアルゴリズムも取り上げてみたいと思います.

参考記事