ขั้นตอนวิธีทางพันธุกรรม: การค้นหาและการเพิ่มประสิทธิภาพโดยการคัดเลือกโดยธรรมชาติ
เผยแพร่แล้ว: 2022-03-11อัลกอริทึมทางพันธุกรรมคืออะไร?
ในช่วงไม่กี่ปีที่ผ่านมา ปัญญาประดิษฐ์ (AI) ได้รับความนิยมอย่างล้นหลาม บริษัทใหญ่ๆ เช่น Google, Apple และ Microsoft กำลังทำงานในหัวข้อนี้อย่างแข็งขัน อันที่จริง AI เป็นร่มที่ครอบคลุมเป้าหมาย แนวทาง เครื่องมือ และแอปพลิเคชันมากมาย Genetic Algorithms (GA) เป็นเพียงหนึ่งในเครื่องมือสำหรับการค้นหาอย่างชาญฉลาดผ่านโซลูชันที่เป็นไปได้มากมาย
GA เป็นเทคนิคการค้นหาเชิงอภิปรัชญาและการปรับให้เหมาะสมตามหลักการที่มีอยู่ในวิวัฒนาการตามธรรมชาติ มันเป็นของอัลกอริธึมวิวัฒนาการระดับใหญ่
GA รักษา จำนวนโครโมโซม ซึ่งเป็นชุดแนวทางแก้ไขปัญหาที่เป็นไปได้ แนวคิดก็คือ "วิวัฒนาการ" จะพบวิธีแก้ปัญหาที่เหมาะสมที่สุดหลังจากรุ่นต่อๆ มาหลายรุ่น ซึ่งคล้ายกับการคัดเลือกโดยธรรมชาติ
GA เลียนแบบกระบวนการวิวัฒนาการสามกระบวนการ: การคัดเลือก ยีนครอสโอเวอร์ และการกลายพันธุ์
แนวคิดหลักของการคัดเลือก GA คล้ายกับการคัดเลือกโดยธรรมชาติ คือ ความเหมาะสม โครโมโซมที่พอดีตัวมากกว่ามีโอกาสรอดมากกว่า ฟิตเนสเป็นฟังก์ชันที่วัดคุณภาพของสารละลายที่แสดงโดยโครโมโซม โดยพื้นฐานแล้ว แต่ละโครโมโซมในประชากรแสดงถึงพารามิเตอร์อินพุต ตัวอย่างเช่น หากปัญหาของคุณมีพารามิเตอร์อินพุตสองตัว เช่น ราคาและปริมาณในการซื้อขาย โครโมโซมแต่ละตัวจะประกอบด้วยสององค์ประกอบตามหลักเหตุผล วิธีการเข้ารหัสองค์ประกอบภายในโครโมโซมเป็นหัวข้อที่แตกต่างกัน
ระหว่างการคัดเลือก โครโมโซมจะจับคู่ พ่อแม่ พันธุ์เพื่อ ผสมพันธุ์ เด็ก แต่ละคนมีลักษณะเฉพาะจากพ่อแม่ โดยพื้นฐานแล้ว เด็กเป็นตัวแทนของการรวมตัวกันของคุณลักษณะจากพ่อแม่: คุณลักษณะบางอย่างถูกพรากไปจากพ่อแม่คนหนึ่งและบางส่วนจากอีกคนหนึ่ง นอกเหนือจากการรวมตัวใหม่ คุณลักษณะบางอย่างสามารถ กลายพันธุ์ ได้
เนื่องจากโครโมโซมที่ฟิตขึ้นทำให้มีลูกมากขึ้น คนรุ่นต่อๆ มาแต่ละคนจะมีสมรรถภาพร่างกายที่ดีขึ้น เมื่อถึงจุดหนึ่ง คนรุ่นหนึ่งจะมีโครโมโซมที่จะเป็นวิธีแก้ปัญหาที่ดีพอสำหรับปัญหาของเรา
GA มีประสิทธิภาพและใช้ได้กับปัญหาที่ซับซ้อนในวงกว้าง มีปัญหาการเพิ่มประสิทธิภาพจำนวนมากซึ่งค่อนข้างยากที่จะแก้ไขด้วยเทคนิคการเพิ่มประสิทธิภาพแบบเดิม อัลกอริธึมทางพันธุกรรมเป็นอัลกอริธึมที่มีประสิทธิภาพซึ่งวิธีแก้ปัญหานั้นเหมาะสมที่สุด แอปพลิเคชันที่รู้จักกันดี ได้แก่ การจัดตารางเวลา การขนส่ง การกำหนดเส้นทาง เทคโนโลยีกลุ่ม การออกแบบเลย์เอาต์ การฝึกอบรมโครงข่ายประสาทเทียม และอื่นๆ อีกมากมาย
นำสิ่งต่าง ๆ ไปสู่การปฏิบัติ
ตัวอย่างที่เราจะพิจารณาถือได้ว่าเป็น "Hello World" ของ GA ตัวอย่างนี้เริ่มต้นโดย J. Freeman ใน การจำลองโครงข่ายประสาทเทียมด้วย Mathematica ฉันนำมาจาก อัลกอริทึมทางพันธุกรรมและการออกแบบทางวิศวกรรม โดย Mitsuo Gen และ Runwei Cheng
ปัญหาการจับคู่คำพยายามที่จะพัฒนานิพจน์ด้วยอัลกอริธึมทางพันธุกรรม ในขั้นต้น อัลกอริทึมควรจะ "เดา" วลี "เป็นหรือไม่เป็น" จากรายการตัวอักษรที่สร้างแบบสุ่ม
เนื่องจากมีตัวอักษรที่เป็นไปได้ 26 ตัวสำหรับแต่ละตำแหน่งจาก 13 ตำแหน่ง [ไม่รวมช่องว่าง] ในรายการ ความน่าจะเป็นที่เราจะได้วลีที่ถูกต้องโดยการสุ่มทั้งหมดคือ (1/26)^13=4.03038×10-19 ซึ่งประมาณ โอกาสสองครั้งจาก [quintillion] (Gen & Chong, 1997)
เราจะอธิบายปัญหาให้กว้างขึ้นเล็กน้อยที่นี่ ทำให้การแก้ปัญหายากขึ้นอีก สมมติว่าเราไม่ได้จำกัดเฉพาะภาษาอังกฤษหรือวลีเฉพาะ เราสามารถลงเอยด้วยตัวอักษรใดๆ หรือแม้แต่ชุดของสัญลักษณ์ใดๆ เราไม่มีความรู้ด้านภาษา เราไม่รู้ด้วยซ้ำว่ามีภาษาใดบ้าง
สมมติว่าคู่ต่อสู้ของเรานึกถึงวลีโดยพลการ ซึ่งรวมถึงช่องว่างด้วย เราทราบความยาวของวลีและการนับสัญลักษณ์ในตัวอักษร นั่นคือความรู้เดียวที่เรามี หลังจากการเดาแต่ละครั้ง คู่ต่อสู้จะบอกเราว่ามีจดหมายกี่ฉบับ
โครโมโซมแต่ละตัวเป็นลำดับดัชนีของสัญลักษณ์ในตัวอักษร หากเรากำลังพูดถึงตัวอักษรภาษาอังกฤษ 'a' จะถูกแทนด้วย 0, 'b' คูณ 1, 'c' คูณ 2 เป็นต้น ตัวอย่างเช่น คำว่า "เป็น" จะแสดงเป็น [4, 1]
เราจะสาธิตขั้นตอนทั้งหมดผ่านข้อมูลโค้ด Java แต่ไม่จำเป็นต้องมีความรู้เกี่ยวกับ Java เพื่อทำความเข้าใจแต่ละขั้นตอน
แกนอัลกอริทึมทางพันธุกรรม
เราสามารถเริ่มต้นด้วยการนำอัลกอริธึมทางพันธุกรรมไปใช้โดยทั่วไป:
public void find() { // Initialization List<T> population = Stream.generate(supplier) .limit(populationSize) .collect(toList()); // Iteration while (!termination.test(population)) { // Selection population = selection(population); // Crossover crossover(population); // Mutation mutation(population); } }
นี่เป็นชุดขั้นตอนง่ายๆ ที่ GA ทุกแห่งประกอบด้วยไม่มากก็น้อย ในขั้นตอนการเริ่มต้น เราสร้างกลุ่มวลีเริ่มต้น ขนาดของประชากรกำหนดโดย populationSize
Size วิธีสร้างวลีนั้นขึ้นอยู่กับการใช้งานของ supplier
ภายในขั้นตอนการวนซ้ำ เราพัฒนาประชากรจนกว่าจะตรงตามเงื่อนไขการสิ้นสุดภายในการทดสอบของ while
loop เงื่อนไขการเลิกจ้างอาจรวมทั้งจำนวนรุ่นและการจับคู่แบบตรงทั้งหมดของวลีในกลุ่มประชากร การ termination
สรุปการใช้งานที่แน่นอน
ในการทำซ้ำแต่ละครั้ง เราดำเนินการตามขั้นตอน GA ทั่วไป:
- ทำการเลือกประชากรตามความเหมาะสมของโครโมโซม
- สร้าง “เจเนอเรชั่นใหม่” ผ่านการทำงานแบบครอสโอเวอร์
- ทำการรวมตัวอักษรบางตัวในวลีบางวลี
แก่นของอัลกอริทึมนั้นง่ายมากและไม่เชื่อเรื่องพระเจ้าโดเมน มันจะเหมือนกันสำหรับปัญหาทั้งหมด สิ่งที่คุณจะต้องปรับแต่งคือการใช้งานตัวดำเนินการทางพันธุกรรม ต่อไป เราจะพิจารณาตัวดำเนินการ GA แต่ละรายที่กล่าวถึงก่อนหน้านี้อย่างใกล้ชิด
การคัดเลือก
ดังที่เราทราบแล้ว การคัดเลือกเป็นกระบวนการในการค้นหาผู้สืบทอดโครโมโซมในปัจจุบัน ซึ่งเป็นโครโมโซมที่เหมาะกับปัญหาของเรามากกว่า ในระหว่างการคัดเลือก เราต้องตรวจสอบให้แน่ใจว่าโครโมโซมที่มีความฟิตดีขึ้นมีโอกาสรอดชีวิตมากขึ้น
private List<T> selection(List<T> population) { final double[] fitnesses = population.stream() .mapToDouble(fitness) .toArray(); final double totalFitness = DoubleStream.of(fitnesses).sum(); double sum = 0; final double[] probabilities = new double[fitnesses.length]; for (int i = 0; i < fitnesses.length; i++) { sum += fitnesses[i] / totalFitness; probabilities[i] = sum; } probabilities[probabilities.length - 1] = 1; return range(0, probabilities.length).mapToObj(i -> { int index = binarySearch(probabilities, random()); if (index < 0) { index = -(index + 1); } return population.get(index); }).collect(toList()); }
แนวคิดเบื้องหลังการดำเนินการนี้มีดังต่อไปนี้: ประชากรจะแสดงเป็นช่วงที่ตามมาบนแกนตัวเลข ประชากรทั้งหมดอยู่ระหว่าง 0 ถึง 1
ส่วนต่าง ๆ ของช่วงที่โครโมโซมใช้นั้นแปรผันตามความเหมาะสมของโครโมโซม ส่งผลให้โครโมโซมที่ฟิตสมบูรณ์ได้รับก้อนที่ใหญ่ขึ้น จากนั้นเราจะสุ่มดูตัวเลขระหว่าง 0 ถึง 1 และหาช่วงที่มีตัวเลขนั้นอยู่ เห็นได้ชัดว่าช่วงที่ใหญ่กว่านั้นมีโอกาสสูงที่จะเลือก ดังนั้นโครโมโซมที่ฟิตขึ้นจึงมีโอกาสรอดที่ดีกว่า
เนื่องจากเราไม่ทราบรายละเอียดเกี่ยวกับฟังก์ชันฟิตเนส เราจึงต้องปรับค่าฟิตเนสให้เป็นมาตรฐาน ฟังก์ชันฟิตเนสแสดงโดย fitness
ซึ่งแปลงโครโมโซมเป็นสองเท่าโดยพลการซึ่งแสดงถึงความสมบูรณ์ของโครโมโซม
ในโค้ดนี้ เราพบอัตราความสมบูรณ์ของโครโมโซมทั้งหมดในประชากร และเราพบความสมบูรณ์ของโครโมโซมทั้งหมดด้วย ภายในวง for
เราทำผลรวมสะสมมากกว่าความน่าจะเป็นที่ลดขนาดตามความเหมาะสมทั้งหมด ในทางคณิตศาสตร์ สิ่งนี้ควรส่งผลให้ตัวแปรสุดท้ายมีค่าเท่ากับ 1 เนื่องจากความไม่แม่นยำของจุดลอยตัว เราจึงไม่สามารถรับประกันได้ เราจึงตั้งค่าให้เป็น 1 เพื่อให้แน่ใจ
สุดท้าย สำหรับจำนวนครั้งที่เท่ากับจำนวนโครโมโซมอินพุต เราสร้างตัวเลขสุ่ม ค้นหาช่วงที่มีตัวเลข แล้วเลือกโครโมโซมที่สอดคล้องกัน ดังที่คุณอาจสังเกตเห็น โครโมโซมเดียวกันอาจถูกเลือกหลายครั้ง

ครอสโอเวอร์
ตอนนี้เราต้องการโครโมโซมเพื่อ "ผสมพันธุ์"
private void crossover(List<T> population) { final int[] indexes = range(0, population.size()) .filter(i-> random() < crossoverProbability) .toArray(); shuffle(Arrays.asList(indexes)); for (int i = 0; i < indexes.length / 2; i++) { final int index1 = indexes[2 * i]; final int index2 = indexes[2 * i + 1]; final T value1 = population.get(index1); final T value2 = population.get(index2); population.set(index1, crossover.apply(value1, value2)); population.set(index2, crossover.apply(value2, value1)); } }
ด้วยความน่าจะเป็นที่ crossoverProbability
ความน่าจะเป็นที่กำหนดไว้ล่วงหน้า เราจึงเลือกพ่อแม่เพื่อผสมพันธุ์ ผู้ปกครองที่เลือกจะถูกสับเปลี่ยน เพื่อให้สามารถผสมกันได้ เราใช้ผู้ปกครองเป็นคู่และใช้ตัวดำเนินการ crossover
เราใช้ตัวดำเนินการสองครั้งสำหรับแต่ละคู่ เนื่องจากเราจำเป็นต้องรักษาขนาดของประชากรให้เท่ากัน เด็กแทนที่พ่อแม่ของพวกเขาในประชากร
การกลายพันธุ์
สุดท้าย เราทำการรวมคุณสมบัติใหม่เข้าด้วยกัน
private void mutation(List<T> population) { for (int i = 0; i < population.size(); i++) { if (random() < mutationProbability) { population.set(i, mutation.apply(population.get(i))); } } }
ด้วยความน่าจะเป็นที่กำหนดไว้ล่วงหน้า ความน่าจะเป็น เราทำการ "กลาย mutationProbability
" บนโครโมโซม การกลายพันธุ์นั้นถูกกำหนดโดย mutation
การกำหนดค่าอัลกอริทึมเฉพาะปัญหา
ตอนนี้ มาดูกันว่าพารามิเตอร์เฉพาะปัญหาประเภทใดที่เราต้องมีเพื่อการใช้งานทั่วไปของเรา
private BiFunction<T, T, T> crossover; private double crossoverProbability; private ToDoubleFunction<T> fitness; private Function<T, T> mutation; private double mutationProbability; private int populationSize = 100; private Supplier<T> supplier; private Predicate<Collection<T>> termination;
พารามิเตอร์ตามลำดับคือ:
- ตัวดำเนินการครอสโอเวอร์
- ความน่าจะเป็นแบบครอสโอเวอร์
- ฟังก์ชั่นฟิตเนส
- โอเปอเรเตอร์การกลายพันธุ์
- ความน่าจะเป็นของการกลายพันธุ์
- ขนาดของประชากร
- ซัพพลายเออร์โครโมโซมสำหรับประชากรเริ่มต้น
- ฟังก์ชันการสิ้นสุด
นี่คือการกำหนดค่าสำหรับปัญหาของเรา:
new GeneticAlgorithm<char[]>() .setCrossover(this::crossover) .setCrossoverProbability(0.25) .setFitness(this::fitness) .setMutation(this::mutation) .setMutationProbability(0.05) .setPopulationSize(100) .setSupplier(() -> supplier(expected.length)) .setTermination(this::termination) .find()
ครอสโอเวอร์โอเปอเรเตอร์และความน่าจะเป็น
private char[] crossover(char[] value1, char[] value2) { final int i = (int) round(random() * value1.length); final char[] result = new char(value1.length); System.arraycopy(value1, 0, result, 0, i); System.arraycopy(value2, i, result, i, value2.length - i); return result; }
ความน่าจะเป็นของครอสโอเวอร์คือ 0.25 เราจึงคาดว่าโดยเฉลี่ย 25 เปอร์เซ็นต์ของโครโมโซมจะถูกเลือกเป็นครอสโอเวอร์ เราทำขั้นตอนง่าย ๆ สำหรับการครอสโอเวอร์ของโครโมโซมคู่หนึ่ง เราสร้างตัวเลขสุ่ม n
จากช่วง [0..length]
โดยที่ length
คือความยาวของโครโมโซม ตอนนี้เราจับคู่คู่ที่เลือกโดยนำอักขระ n
ตัวแรกจากโครโมโซมหนึ่งและอักขระที่เหลือหลังจากนั้นจากอันที่สอง
ฟังก์ชั่นฟิตเนส
private double fitness(char[] value) { return range(0, value.length) .filter(i -> value[i] == expected[i]) .count(); }
ฟังก์ชันฟิตเนสจะนับจำนวนการจับคู่ระหว่างวลีเป้าหมายกับโครโมโซมที่กำหนด
ตัวดำเนินการกลายพันธุ์และความน่าจะเป็น
private char[] mutation(char[] value) { final char[] result = Arrays.copyOf(value, value.length); for (int i = 0; i < 2; i++) { int letter = (int) round(random() * (ALPHABET.length - 1)); int location = (int) round(random() * (value.length - 1)); result[location] = ALPHABET[letter]; } return result; }
การดำเนินการกลายพันธุ์จะดำเนินการอย่างอิสระในแต่ละโครโมโซม ความน่าจะเป็นของการกลายพันธุ์คือ 0.05 เราจึงคาดว่า โดยเฉลี่ย ห้าเปอร์เซ็นต์ของประชากรจะถูกกลายพันธุ์ เราเปลี่ยนแปลงโดยเลือกตำแหน่งตัวอักษรสุ่มและแทนที่ค่าด้วยตัวอักษรสุ่มจากตัวอักษร เราทำสองครั้งสำหรับโครโมโซมที่กลายพันธุ์แต่ละตัว
ผู้ผลิต
private char[] supplier(int length) { final char[] result = new char(length); for (int i = 0; i < length; i++) { int letter = (int) round(random() * (ALPHABET.length - 1)); result[i] = ALPHABET[letter]; } return result; }
ซัพพลายเออร์สร้างวลีแบบสุ่มโดยใช้ตัวอักษรสุ่มจากตัวอักษร แต่ละวลีมีความยาวที่กำหนดไว้ล่วงหน้าคงที่
ฟังก์ชันการสิ้นสุด
private boolean termination(Collection<char[]> chars) { count++; final Optional<char[]> result = chars.stream() .filter(value -> round(fitness(value)) == expected.length) .findAny(); if (result.isPresent()) { System.out.println("Count: " + count); System.out.println(result.get()); return true; } final boolean terminated = count == 3000; if (terminated) { chars.forEach(System.out::println); } return terminated; }
ฟังก์ชันการสิ้นสุดจะนับจำนวนการโทรและคืนค่า true
หากมีการตรงกันทุกประการ หรือหากจำนวนการสร้างถึง 3,000
การดำเนินการ
ตอนนี้เราพร้อมที่จะทดสอบอัลกอริทึมของเราแล้ว หากคุณเรียกใช้หลายครั้ง คุณจะสังเกตเห็นว่าการวิ่งไม่สำเร็จทั้งหมด แต่ละครั้ง จำนวนการทำซ้ำจะแตกต่างกัน นั่นเป็นเพราะลักษณะความน่าจะเป็นของอัลกอริทึม อัลกอริทึมมีหลายจุดที่สามารถปรับปรุงได้ คุณสามารถเล่นกับครอสโอเวอร์และความน่าจะเป็นของการกลายพันธุ์
การลดจำนวนจะนำไปสู่วิธีแก้ปัญหาที่เสถียรแต่ช้า โครโมโซมจำนวนน้อยจะได้รับผลกระทบจากตัวดำเนินการทางพันธุกรรม ดังนั้นจึงจำเป็นต้องมีการทำซ้ำมากขึ้นสำหรับการแก้ปัญหา
การเพิ่มตัวเลขจะทำให้อัลกอริทึมเร็วขึ้น แต่จะทำให้โซลูชันไม่เสถียรด้วย Fit chromosomes ไม่เพียงแต่ถูกรักษาไว้แต่ยังจะได้รับผลกระทบจากยีนโอเปอเรเตอร์ ดังนั้นพวกเขาจะสูญเสียยีน "ดี" ของพวกเขาไป
สิ่งสำคัญคือต้องหาสมดุลที่ดี การเพิ่มจำนวนการวนซ้ำจะทำให้อัลกอริทึมมีโอกาสมากขึ้นในการค้นหาวิธีแก้ปัญหา แต่ในทางกลับกัน จะใช้เวลามากขึ้น คุณยังสามารถใช้วิธีการแบบครอสโอเวอร์และการกลายพันธุ์แบบต่างๆ ได้ ตัวเลือกที่ดีของผู้ปฏิบัติงานเหล่านี้จะปรับปรุงคุณภาพของโซลูชันอย่างมาก
อะไรต่อไป?
เราได้ครอบคลุมเพียงส่วนปลายของภูเขาน้ำแข็งที่นี่ เราได้ยกตัวอย่างที่มีเพียงหนึ่งอินพุต และสามารถนำเสนออินพุตเป็นโครโมโซมได้อย่างง่ายดาย ตัวดำเนินการทางพันธุกรรมนั้นธรรมดาและเรียบง่าย
เป็นเรื่องที่น่าสนใจมากที่จะนำปัญหาในโลกแห่งความเป็นจริงมาปรับใช้กับอัลกอริธึมทางพันธุกรรม คุณจะค้นพบวิธีการต่างๆ ในการเข้ารหัสข้อมูลอินพุตจริง ตลอดจนการใช้งานครอสโอเวอร์และการกลายพันธุ์ที่แตกต่างกัน
หากสามารถแสดงปัญหาผ่านชุดพารามิเตอร์ที่เราต้องเดาเพื่อปรับเมตริกให้เหมาะสม เราสามารถตั้งค่า GA ที่เราใช้แก้ปัญหาได้อย่างรวดเร็ว
ปัญหาที่น่าสนใจที่สุดคือการสอนโครงข่ายประสาทเทียม เราสามารถตั้งค่าพารามิเตอร์ที่ปรับให้เหมาะสมได้เป็นจุดแข็งของไซแนปส์ และเมตริกฟิตเนสให้เป็นเปอร์เซ็นต์ของอินพุตที่โครงข่ายประสาทเทียมของเราให้คำตอบที่ถูกต้อง หลังจากนั้น เราสามารถนั่งพักและปล่อยให้โครงข่ายประสาทเทียมของเราพัฒนาไปสู่โซลูชันในอุดมคติที่เราต้องการ หรืออย่างน้อยก็จนกว่าเราจะได้สิ่งที่ดีพอ เพราะวิวัฒนาการต้องใช้เวลา