進化的アルゴリズム簡易版を試してみる
AIによるアート制作が注目を集める中、「進化的アルゴリズム」を使った創作の可能性にも注目が集まっています。
今回はJavaScriptとp5.jsを使って、進化的アート制作の初歩を体験してみた事例をご紹介します。
特別なGPUや大規模なAIモデルを使わなくても、ブラウザさえあれば簡単に始められる進化的アートです。
今回使用したのは、HTML + JavaScript + p5.jsによる進化的コードです。
描画対象は「抽象的な線のパターン」で、これらの形状は「遺伝子」として扱われ、自動的に進化(改善)されていきます。
進化的アートの基本構造
このシステムでは以下の要素が組み合わさって動作します。
Genotype(遺伝子):線の本数、色相、線の太さ、ノイズ、複雑さ、回転速度といったビジュアルのパラメータ。
Phenotype(表現型):p5.jsを用いて実際にキャンバス上に描画されるアート。
進化の仕組み:選択、交叉(クロスオーバー)、突然変異(ミューテーション)を自動で繰り返し、次世代のパターンを生成。
進化の1サイクルでは、6つのパターンが画面上に表示され、毎世代ランダムに2体の親が選ばれて子を生み出します。
自動モードでは「30世代ずつ進化」ボタンを押すことで進化を継続でき、結果としてより興味深く洗練されたパターンが現れていきます。
コードの特徴
p5.jsというJavaScriptライブラリを使うことで、グラフィック描画の制御が簡単にできます。
※ pythonプラスHugging faceのコンビも作ったのですが、Google colabからHugging faceへのアクセスがうまくいかず、一旦保留としました。
(以前よりHugging faceへのアクセスが失敗することが多いです。Huggingさんお手柔らかに)
自動進化モード:ボタンをクリックするだけで、世代をまたいで自動でパターンが変化。
ミューテーション(突然変異):色味や形の微妙な変化が自然発生。
インタラクティブ選択:マウスでパターンをクリックすると、その個体の情報と小型プレビューが表示される仕組み。
ブラウザ上で動作するため、誰でもすぐに試せるのも大きな利点です。
表示された図形はどれも抽象的なものが多く、生成過程そのものが一種の進化体験となります。



⚫︎コードを下記に記します。
ご自由に改変して使ってください。
ちなみに初期のコードですので、改変の余地ありです。
・最初にいびつな形の図形が世代を重ねていくと、正円に変化していきます。
このコードが進化的アルゴリズムを使っているかと言えば、そうでないという方もいるでしょう。
ただ「世代を重ねていくと進化していく」というのはビジュアル的に実感できるかと。
千里の道も〇〇から。まずはここからです。
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.9.0/p5.js"></script>
<style>
body { font-family: 'Arial', sans-serif; margin: 20px; background-color: #f4f4f4; color: #333; display: flex; flex-direction: column; align-items: center; }<br /> #controls { margin-bottom: 20px; padding: 15px; background-color: #fff; border-radius: 8px; box-shadow: 0 2px 5px rgba(0,0,0,0.1); text-align: center; width: 90%; max-width: 750px; }<br /> #controls button, #controls select { padding: 10px 15px; margin: 5px; background-color: #007bff; color: white; border: none; border-radius: 5px; cursor: pointer; font-size: 1em; }<br /> #controls select { background-color: #555; color: white; }<br /> #controls button:hover:not(:disabled) { background-color: #0056b3; }<br /> #controls button:disabled { background-color: #ccc; cursor: not-allowed; }<br /> #generation-counter { font-weight: bold; margin-top: 10px; font-size: 1.1em; }<br /> #auto-evolution-status { min-height: 1.2em; margin-top: 5px; color: #555; }<br /> #genotype-display { display: flex; flex-wrap: wrap; justify-content: space-around; width: 100%; margin-top: 15px; margin-bottom: 10px; padding: 0px; font-size: 0.85em; text-align: left; box-sizing: border-box; }<br /> #genotype-display > div { width: calc(50% - 20px); min-width: 280px; margin: 10px; padding: 10px; border: 1px solid #ccc; background-color: #fdfdfd; min-height: 150px; box-sizing: border-box; border-radius: 4px; }<br /> #genotype-display h4 { margin-top: 0; margin-bottom: 8px; font-size: 1.1em; color: #333; border-bottom: 1px solid #eee; padding-bottom: 5px; }<br /> #genotype-display p { margin: 4px 0; line-height: 1.4; }<br /> #current-best-preview img { border:1px solid #999; display:none; margin-top: 5px;}<br /> #canvas-container { border: 1px solid #ddd; box-shadow: 0 2px 5px rgba(0,0,0,0.1); margin-top: 10px; }<br /> </style>
<div id="controls">
<div><label for="target-shape-selector">Target Shape: </label>
<select id="target-shape-selector">
<option selected="selected" value="circle">Circle</option>
<option value="square">Square (Approx.)</option>
<option value="triangle">Triangle (Approx.)</option>
<option value="random">Random (No Target)</option>
</select></div>
<button id="start-auto-evolution-button">Start Auto Evolution (30 Gens)</button>
<button id="continue-evolution-button" style="display: none;">Continue Evolution (+30 Gens)</button>
<button id="reset-button">Reset Population</button>
<p id="generation-counter">Generation: 0</p>
<div id="genotype-display">
<div id="current-best-info">
<h4>Best of Generation (Gen 0)</h4>
Evolution not started.
</div>
<div id="current-best-preview">
<h4>Preview</h4>
<img id="current-best-img" alt="Current Best Preview" width="120" height="120" />
</div>
</div>
</div>
<div id="canvas-container"></div>
<script>
// --- Genetic Algorithm Parameters ---
const POP_SIZE_DISPLAY = 6; // For main grid display
const INTERNAL_POP_SIZE = 20;
const MUTATION_RATE = 0.1; // Adjusted
const MUTATION_STRENGTH = 0.1; // Adjusted
const TOURNAMENT_SIZE = 2; // Adjusted
// --- Global Variables ---
let population = [];
let generationCount = 0;
let autoEvolving = false;
let currentAutoEvolutionStep = 0;
let currentTargetShape = 'circle';
let canvasWidth = 600;
let canvasHeight = 400;
let cols = 3;
let rows = 2;
let cellWidth, cellHeight;
let currentBestInfoDiv, currentBestImg, generationCounterP, autoEvolutionStatusP;
let startAutoEvolutionButton, continueEvolutionButton, targetShapeSelector;
let pgBest;
// --- Genotype Class ---
class Genotype {
constructor(genes) {
if (genes) {
this.numLines = genes.numLines;
this.baseHue = genes.baseHue;
this.strokeW = genes.strokeW;
this.noiseScale = genes.noiseScale;
this.shapeComplexity = genes.shapeComplexity;
this.rotationSpeed = genes.rotationSpeed;
} else {
this.numLines = floor(random(20, 150));
this.baseHue = random(0, 360);
this.strokeW = random(1, 3);
this.noiseScale = random(0.01, 0.4); // Initial noise can be higher
this.shapeComplexity = random(2.5, 12.5);
this.rotationSpeed = random(-0.005, 0.005);
}
}
clone() { return new Genotype({ ...this }); }
}
// --- p5.js Setup Function ---
function setup() {
let canvas = createCanvas(canvasWidth, canvasHeight);
canvas.parent('canvas-container');
colorMode(HSB, 360, 100, 100, 100);
angleMode(RADIANS);
cellWidth = canvasWidth / cols;
cellHeight = canvasHeight / rows;
pgBest = createGraphics(120, 120);
pgBest.colorMode(HSB, 360, 100, 100, 100);
pgBest.angleMode(RADIANS);
currentBestInfoDiv = document.getElementById('current-best-info');
currentBestImg = document.getElementById('current-best-img');
generationCounterP = document.getElementById('generation-counter');
autoEvolutionStatusP = document.getElementById('auto-evolution-status');
startAutoEvolutionButton = document.getElementById('start-auto-evolution-button');
continueEvolutionButton = document.getElementById('continue-evolution-button');
targetShapeSelector = document.getElementById('target-shape-selector');
currentTargetShape = targetShapeSelector.value;
initializePopulation();
startAutoEvolutionButton.addEventListener('click', () => startEvolutionCycle(30));
continueEvolutionButton.addEventListener('click', () => startEvolutionCycle(30));
document.getElementById('reset-button').addEventListener('click', resetEvolution);
targetShapeSelector.addEventListener('change', (event) => {
currentTargetShape = event.target.value;
console.log("Target shape changed to:", currentTargetShape);
resetEvolution();
});
noLoop();
}
function draw() { /* Paused by noLoop() */ }
// --- Initialization ---
function initializePopulation() {
population = [];
for (let i = 0; i < INTERNAL_POP_SIZE; i++) { population.push(new Genotype()); } generationCount = 0; updateGenerationCounter(); if (population.length > 0) {
let initialFitnessScores = population.map(geno => calculateFitness(geno, currentTargetShape));
let bestInitialFitness = -Infinity; // Start with -Infinity
let bestInitialIndividual = population[0];
for(let i=0; i<population.length; i++){ if(initialFitnessScores[i] > bestInitialFitness){
bestInitialFitness = initialFitnessScores[i];
bestInitialIndividual = population[i];
}
}
updateBestIndividualDisplay(bestInitialIndividual, generationCount, false, bestInitialFitness);
} else {
updateBestIndividualDisplay(null, generationCount);
}
displayPopulationSubset();
autoEvolving = false;
startAutoEvolutionButton.disabled = false;
targetShapeSelector.disabled = false;
continueEvolutionButton.style.display = 'none';
autoEvolutionStatusP.innerText = "";
currentAutoEvolutionStep = 0;
}
function resetEvolution() {
autoEvolving = false;
initializePopulation();
}
// --- Drawing Functions ---
function displayPopulationSubset() {
background(250);
for (let r = 0; r < rows; r++) {
for (let c = 0; c < cols; c++) {
let index = c + r * cols;
if (index < POP_SIZE_DISPLAY && index < population.length) { // Display from actual population
let xPos = c * cellWidth;
let yPos = r * cellHeight;
push();
translate(xPos, yPos);
stroke(200); strokeWeight(1); noFill();
rect(0, 0, cellWidth, cellHeight);
drawPhenotype(population[index], cellWidth / 2, cellHeight / 2, cellWidth, cellHeight);
pop();
} else {
push();
translate(c * cellWidth, r * cellHeight);
stroke(220); strokeWeight(1); noFill();
rect(0, 0, cellWidth, cellHeight);
pop();
}
}
}
}
function drawPhenotype(geno, centerX, centerY, w, h, pg = null) {
const target = pg || window;
target.push();
if (pg) {
pg.background(240);
pg.strokeWeight(1); pg.stroke(200); pg.noFill();
pg.rect(0,0,pg.width-1, pg.height-1);
}
target.translate(centerX, centerY);
target.stroke(geno.baseHue, 80, 90, 80);
target.strokeWeight(geno.strokeW);
target.noFill();
let time = geno.baseHue / 360 + generationCount * 0.005;
target.rotate(time * geno.rotationSpeed);
target.beginShape();
for (let i = 0; i < floor(geno.numLines); i++) { // Ensure numLines is int
let angle = map(i, 0, floor(geno.numLines), 0, TWO_PI);
let baseRadius = min(w, h) * 0.35;
let xOff = map(cos(angle) * geno.shapeComplexity, -geno.shapeComplexity, geno.shapeComplexity, 0, 5);
let yOff = map(sin(angle) * geno.shapeComplexity, -geno.shapeComplexity, geno.shapeComplexity, 0, 5);
let noiseVal = noise(xOff + geno.baseHue * 0.01, yOff + geno.strokeW * 0.1, time * 0.2 + geno.strokeW * 0.05);
let r_dev = map(noiseVal, 0, 1, -min(w,h) * 0.1 * geno.noiseScale * 10, min(w,h) * 0.1 * geno.noiseScale * 10);
let r = baseRadius + r_dev;
let x_coord = r * cos(angle);
let y_coord = r * sin(angle);
target.vertex(x_coord, y_coord);
}
target.endShape(CLOSE);
target.pop();
}
// --- Fitness Calculation ---
function getPhenotypeVertices(geno, simW = 100, simH = 100) {
let vertices = [];
let time = geno.baseHue / 360 + generationCount * 0.005;
let rotation = time * geno.rotationSpeed;
for (let i = 0; i < floor(geno.numLines); i++) {
let angle = map(i, 0, floor(geno.numLines), 0, TWO_PI);
let baseRadius = min(simW, simH) * 0.35;
let xOff = map(cos(angle) * geno.shapeComplexity, -geno.shapeComplexity, geno.shapeComplexity, 0, 5);
let yOff = map(sin(angle) * geno.shapeComplexity, -geno.shapeComplexity, geno.shapeComplexity, 0, 5);
let noiseVal = noise(xOff + geno.baseHue * 0.01, yOff + geno.strokeW * 0.1, time * 0.2 + geno.strokeW * 0.05);
let r_dev = map(noiseVal, 0, 1, -min(simW,simH) * 0.1 * geno.noiseScale * 10, min(simW,simH) * 0.1 * geno.noiseScale * 10);
let r = baseRadius + r_dev;
let x_unrotated = r * cos(angle);
let y_unrotated = r * sin(angle);
let x_rotated = x_unrotated * cos(rotation) - y_unrotated * sin(rotation);
let y_rotated = x_unrotated * sin(rotation) + y_unrotated * cos(rotation);
vertices.push({ x: x_rotated, y: y_rotated });
}
return vertices;
}
function calculateFitness(genotype, targetShape) {
if (targetShape === 'random') return random(0.5, 1.5); // Give some variance for random
let points = getPhenotypeVertices(genotype);
if (points.length < 3) return 0.001; // Minimal fitness for invalid shapes let fitness = 0; if (targetShape === 'circle') { let cx = 0, cy = 0; points.forEach(p => { cx += p.x; cy += p.y; });
cx /= points.length; cy /= points.length;
let distances = points.map(p => dist(cx, cy, p.x, p.y));
let meanDistance = distances.reduce((sum, d) => sum + d, 0) / distances.length;
if (meanDistance < 1) return 0.001; let variance = distances.map(d => Math.pow(d - meanDistance, 2)).reduce((sum, sq) => sum + sq, 0) / distances.length;
let stdDev = Math.sqrt(variance);
let relativeStdDev = stdDev / meanDistance;
// Adjusted fitness scaling for slower evolution
if (relativeStdDev < 0.005) { fitness = 20.0; } // Very good circle
else if (relativeStdDev < 0.02) { fitness = 5.0 + (0.02 - relativeStdDev) * (15.0 / 0.015); }
else if (relativeStdDev < 0.08) { fitness = 1.0 + (0.08 - relativeStdDev) * (4.0 / 0.06); }
else if (relativeStdDev < 0.3) { fitness = 0.2 + (0.3 - relativeStdDev) * (0.8 / 0.22); } else { fitness = 0.05 + Math.max(0, 0.15 - relativeStdDev); } // Low base fitness *= (1 + Math.min(0.5, genotype.numLines / 300)); // numLines bonus capped and scaled down fitness *= (1 / (1 + genotype.noiseScale * 10)); // Stronger penalty for high noise } else if (targetShape === 'square') { fitness = (1 / (1 + Math.abs(genotype.shapeComplexity - 4) * 2)) * 1.0; // Gentler complexity penalty fitness *= (1 / (1 + genotype.noiseScale * 15)); // Stronger noise penalty if(genotype.numLines > 15 && genotype.numLines < 70) fitness *=1.05; // Smaller bonus } else if (targetShape === 'triangle') { fitness = (1 / (1 + Math.abs(genotype.shapeComplexity - 3) * 2)) * 1.0; fitness *= (1 / (1 + genotype.noiseScale * 15)); if(genotype.numLines > 10 && genotype.numLines < 50) fitness *=1.05;
}
return Math.max(0.001, fitness);
}
// --- Automatic Evolution Control ---
async function startEvolutionCycle(numGenerations) {
if (autoEvolving) return;
autoEvolving = true;
startAutoEvolutionButton.disabled = true;
targetShapeSelector.disabled = true;
continueEvolutionButton.style.display = 'none';
for (let i = 0; i < numGenerations; i++) { if (!autoEvolving) { autoEvolutionStatusP.innerText = "Evolution stopped by reset."; return; } await evolveOneGenerationAutomatically(); currentAutoEvolutionStep++; autoEvolutionStatusP.innerText = <code>Evolving... Cycle: ${currentAutoEvolutionStep}/${numGenerations} (Total Gen: ${generationCount})</code>; await new Promise(resolve => setTimeout(resolve, 60)); // Slightly longer delay for visibility
}
autoEvolving = false;
if (population.length > 0) {
continueEvolutionButton.style.display = 'inline-block';
continueEvolutionButton.disabled = false;
}
targetShapeSelector.disabled = false;
autoEvolutionStatusP.innerText = <code>Cycle finished. Total Gens: ${generationCount}. Continue or Reset.</code>;
currentAutoEvolutionStep = 0;
}
async function evolveOneGenerationAutomatically() {
let fitnessScores = population.map(geno => calculateFitness(geno, currentTargetShape));
let parentsPool = selectParentsByFitness(population, fitnessScores, INTERNAL_POP_SIZE);
let newPopulation = [];
for(let i=0; i < INTERNAL_POP_SIZE; i++) { let p1 = random(parentsPool); let p2 = random(parentsPool); if(!p1 && population.length > 0) p1 = population[0]; else if (!p1) p1 = new Genotype(); // Robust fallback
if(!p2 && population.length > 0) p2 = population[0]; else if (!p2) p2 = new Genotype(); // Robust fallback
let childGenes = crossover(p1, p2);
let mutatedChildGenes = mutate(childGenes);
newPopulation.push(mutatedChildGenes);
}
population = newPopulation;
generationCount++;
updateGenerationCounter();
let currentGenFitnessScores = population.map(geno => calculateFitness(geno, currentTargetShape));
let bestFitness = -Infinity;
let bestIndividual = null;
if (population.length > 0) {
for (let i = 0; i < population.length; i++) { if (currentGenFitnessScores[i] > bestFitness) {
bestFitness = currentGenFitnessScores[i];
bestIndividual = population[i];
}
}
updateBestIndividualDisplay(bestIndividual, generationCount, false, bestFitness);
}
displayPopulationSubset();
}
function selectParentsByFitness(currentPopulation, fitnessScores, numToSelect) {
let selectedParents = [];
if(currentPopulation.length === 0) return selectedParents;
for (let n = 0; n < numToSelect; n++) {
let bestContestant = null;
let bestFitnessInTournament = -Infinity; // Renamed for clarity
for (let i = 0; i < TOURNAMENT_SIZE; i++) { let randomIndex = floor(random(currentPopulation.length)); if (fitnessScores[randomIndex] > bestFitnessInTournament) {
bestFitnessInTournament = fitnessScores[randomIndex];
bestContestant = currentPopulation[randomIndex];
}
}
if (bestContestant) {
selectedParents.push(bestContestant);
} else {
selectedParents.push(random(currentPopulation)); // Fallback
}
}
return selectedParents;
}
function crossover(geno1, geno2) {
let childData = {}; let g1 = geno1; let g2 = geno2;
childData.numLines = random() < 0.5 ? g1.numLines : g2.numLines;
childData.baseHue = random() < 0.5 ? g1.baseHue : g2.baseHue;
childData.strokeW = random() < 0.5 ? g1.strokeW : g2.strokeW;
childData.noiseScale = random() < 0.5 ? g1.noiseScale : g2.noiseScale;
childData.shapeComplexity = random() < 0.5 ? g1.shapeComplexity : g2.shapeComplexity;
childData.rotationSpeed = random() < 0.5 ? g1.rotationSpeed : g2.rotationSpeed;
return new Genotype(childData);
}
function mutate(geno) {
let mutatedData = { ...geno };
const strength = MUTATION_STRENGTH; // Use the global constant
if (random() < MUTATION_RATE) { mutatedData.numLines = floor(max(10, mutatedData.numLines + randomGaussian(0, 10 * strength) * 5)); }
if (random() < MUTATION_RATE) { mutatedData.baseHue = (mutatedData.baseHue + randomGaussian(0, 30 * strength) * 1.5 + 360) % 360; }
if (random() < MUTATION_RATE) { mutatedData.strokeW = max(0.5, mutatedData.strokeW + randomGaussian(0, 0.3 * strength) * 1.5); }
if (random() < MUTATION_RATE) {
let noiseChange = randomGaussian(0, 0.05 * strength) * 2;
mutatedData.noiseScale = max(0.0001, mutatedData.noiseScale + noiseChange);
}
if (random() < MUTATION_RATE) { mutatedData.shapeComplexity = max(1, mutatedData.shapeComplexity + randomGaussian(0, 1 * strength) * 1.5); }
if (random() < MUTATION_RATE) { mutatedData.rotationSpeed = mutatedData.rotationSpeed + randomGaussian(0, 0.003 * strength) * 1.5; } return new Genotype(mutatedData); } // --- User Interaction (for inspecting individuals when not auto-evolving) --- function mousePressed() { if (!autoEvolving && mouseX > 0 && mouseX < width && mouseY > 0 && mouseY < height) {
let c = floor(mouseX / cellWidth);
let r = floor(mouseY / cellHeight);
let gridIndex = c + r * cols;
if (gridIndex < POP_SIZE_DISPLAY && gridIndex < population.length) {
let actualIndividual = population[gridIndex];
let itsFitness = calculateFitness(actualIndividual, currentTargetShape);
updateBestIndividualDisplay(actualIndividual, generationCount, true, itsFitness);
}
}
}
function updateGenerationCounter() {
generationCounterP.innerText = <code>Generation: ${generationCount}</code>;
}
function formatGeneValue(value, precision = 2) {
if (typeof value === 'number') { return value.toFixed(precision); }
return String(value);
}
function updateBestIndividualDisplay(individual, gen, fromClick = false, fitness = -1) {
if (!currentBestInfoDiv || !currentBestImg) return;
if (!individual) {
currentBestInfoDiv.innerHTML = <code></p>
<p>
</p>
<h4>Best of Gen (Gen ${gen})</h4>
<p>
</p>
<p>No individual data.</p>
<p>
</p>
<p></code>;
currentBestImg.style.display = 'none';
return;
}
let titlePrefix = fromClick ? "Clicked Individual" : "Best of Generation";
let fitnessText = fitness > -Infinity ? <code></p>
<p>
</p>
<p>Fitness: ${formatGeneValue(fitness, 3)}</p>
<p>
</p>
<p></code> : ""; // Show fitness if available
currentBestInfoDiv.innerHTML = <code></p>
<p>
</p>
<h4>${titlePrefix} (Gen ${gen})</h4>
<p>
</p>
<p>
${fitnessText}
</p>
<p>
</p>
<p>Lines: ${formatGeneValue(individual.numLines, 0)}</p>
<p>
</p>
<p>
</p>
<p>
</p>
<p>Hue: ${formatGeneValue(individual.baseHue, 1)}</p>
<p>
</p>
<p>
</p>
<p>
</p>
<p>StrokeW: ${formatGeneValue(individual.strokeW, 2)}</p>
<p>
</p>
<p>
</p>
<p>
</p>
<p>NoiseSc: ${formatGeneValue(individual.noiseScale, 3)}</p>
<p>
</p>
<p>
</p>
<p>
</p>
<p>Complexity: ${formatGeneValue(individual.shapeComplexity, 1)}</p>
<p>
</p>
<p>
</p>
<p>
</p>
<p>RotSpeed: ${formatGeneValue(individual.rotationSpeed, 3)}</p>
<p>
</p>
<p></code>;
drawPhenotype(individual, pgBest.width / 2, pgBest.height / 2, pgBest.width * 0.9, pgBest.height * 0.9, pgBest);
currentBestImg.src = pgBest.canvas.toDataURL();
currentBestImg.style.display = 'block';
}
</script>
進化的アート簡易版を実際にやってみて感じたこと
この進化的アート簡易版の特徴は、「世代を重ねるごとに新しいものが生まれる面白さ」です。
最初はランダムな線の集合だったものが、何世代か進むうちに、幾何学的に整ったパターンへと進化していきます。
また、パラメータ(遺伝子)を微調整することで全体の雰囲気ががらっと変わるため、「進化」を観察する楽しみがあります。
進化とは、より良いものを生み出すだけでなく、意外性のある楽しみもあるなと実感しました。
進化的アート × 生成AIの未来
このように、簡単な進化的アルゴリズムで、アートの基礎を生み出すことができます。
Stable Diffusionなどの大規模AIモデルがなくても、自分のパソコンとブラウザさえあれば、進化的アートの世界に入ることができます。
一見するととっつきにくい文言ですが、生成AIなどを使って質問回答を繰り返していけば、誰でも制作は可能です。
今後は、こうした技術が教育現場などでも活用されるでしょう。
進化的アルゴリズム簡易版でアート制作の基礎を試す 終わりに
進化的アルゴリズムは「特別なAIの技術」ではなく、誰もが気軽に扱える創造のツールです。
今回のように簡単なコードを使えば、誰でも「進化するアートの基礎」を作ることができます。
難しいAIの理論やモデル構築に頼らずとも、AIを使えば、新しい表現が生まれてくる。
みなさんも新しいアートを創造してみませんか。
****************
X 旧ツイッターもやってます。
https://x.com/ison1232
インスタグラムはこちら
https://www.instagram.com/nanahati555/
****************