├── Deep Learning ├── BP │ ├── BP.py │ ├── MNIST_data │ │ ├── t10k-images-idx3-ubyte.gz │ │ ├── t10k-labels-idx1-ubyte.gz │ │ ├── train-images-idx3-ubyte.gz │ │ └── train-labels-idx1-ubyte.gz │ ├── README.md │ └── input_data.py └── CNN │ ├── CNN.py │ ├── MNIST_data │ ├── t10k-images-idx3-ubyte.gz │ ├── t10k-labels-idx1-ubyte.gz │ ├── train-images-idx3-ubyte.gz │ └── train-labels-idx1-ubyte.gz │ ├── README.md │ └── input_data.py ├── Intelligent Optimization Algorithm ├── ACO │ ├── ACO.py │ ├── README.md │ └── test.txt ├── GA │ ├── GA.py │ ├── README.md │ └── test.txt └── PSO │ ├── PSO.py │ ├── README.md │ └── test.txt ├── README.md ├── Search Algorithms ├── Astar │ ├── Astar.py │ ├── Astar_GUI │ │ ├── .DS_Store │ │ ├── Astar.py │ │ ├── GUI.py │ │ ├── grid.py │ │ └── util.py │ └── README.md ├── BFS │ ├── BFS.py │ └── README.md └── DFS │ ├── DFS.py │ └── README.md └── img ├── ACO-ppt.png ├── ACO-repeat.png ├── ACO-result.png ├── ACO-visual.png ├── Astar-process.png ├── Astar-result.png ├── Astar-tree.png ├── BFS-process.png ├── BFS-result.png ├── BFS-tree.png ├── BFS.png ├── BP-chart.png ├── BP-dfunc.png ├── BP-func.png ├── BP-lossfunc.png ├── BP-result.png ├── CNN-p1.png ├── CNN-p2.png ├── CNN-p3.png ├── CNN-result.png ├── DFS-case.png ├── DFS-process.png ├── DFS-result.png ├── DFS-tree.png ├── DFS.png ├── GA-repeat.png ├── GA-result.png ├── GA-visual.png ├── PSO-format.png ├── PSO-repeat.png ├── PSO-result.png ├── PSO-visual.png ├── hard-case.png └── tree.png /Deep Learning/BP/BP.py: -------------------------------------------------------------------------------- 1 | #下载、导入数据用到的函数包 2 | import input_data 3 | #tensorflow 2.x没有placeholder所以要用1.x的API 4 | import tensorflow.compat.v1 as tf 5 | tf.disable_v2_behavior() 6 | #读取数据 7 | mnist = input_data.read_data_sets("MNIST_data", one_hot=True) 8 | #不是一个特定的值,而是一个占位符placeholder 9 | # 我们在TensorFlow运行计算时输入这个值。我们希望能够输入任意数量的MNIST图像,每一张图展平成784维的向量。 10 | x = tf.placeholder("float", [None, 784]) 11 | #W的维度是[784,10],因为我们想要用784维的图片向量乘以它以得到一个10维的证据值向量 12 | W = tf.Variable(tf.zeros([784,10])) 13 | #b的形状是[10],所以我们可以直接把它加到输出上面 14 | b = tf.Variable(tf.zeros([10])) 15 | #softmax模型 16 | y = tf.nn.softmax(tf.matmul(x,W) + b) 17 | #为了计算交叉熵,我们首先需要添加一个新的占位符用于输入正确值 18 | y_ = tf.placeholder("float", [None,10]) 19 | #计算交叉熵 20 | cross_entropy = -tf.reduce_sum(y_*tf.log(y)) 21 | #使用反向传播算法(backpropagation algorithm)进行训练 22 | train_step = tf.train.GradientDescentOptimizer(0.01).minimize(cross_entropy) 23 | #初始化tensorflow 24 | init = tf.initialize_all_variables() 25 | sess = tf.Session() 26 | sess.run(init) 27 | #让模型循环训练1000次 28 | for i in range(1000): 29 | batch_xs, batch_ys = mnist.train.next_batch(100) 30 | sess.run(train_step, feed_dict={x: batch_xs, y_: batch_ys}) 31 | correct_prediction = tf.equal(tf.argmax(y,1), tf.argmax(y_,1)) 32 | accuracy = tf.reduce_mean(tf.cast(correct_prediction, "float")) 33 | print(sess.run(accuracy, feed_dict={x: mnist.test.images, y_: mnist.test.labels})) 34 | -------------------------------------------------------------------------------- /Deep Learning/BP/MNIST_data/t10k-images-idx3-ubyte.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roadwide/AI-Homework/ba2b3e011630aa812c6489bf27cfef719cc75dcd/Deep Learning/BP/MNIST_data/t10k-images-idx3-ubyte.gz -------------------------------------------------------------------------------- /Deep Learning/BP/MNIST_data/t10k-labels-idx1-ubyte.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roadwide/AI-Homework/ba2b3e011630aa812c6489bf27cfef719cc75dcd/Deep Learning/BP/MNIST_data/t10k-labels-idx1-ubyte.gz -------------------------------------------------------------------------------- /Deep Learning/BP/MNIST_data/train-images-idx3-ubyte.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roadwide/AI-Homework/ba2b3e011630aa812c6489bf27cfef719cc75dcd/Deep Learning/BP/MNIST_data/train-images-idx3-ubyte.gz -------------------------------------------------------------------------------- /Deep Learning/BP/MNIST_data/train-labels-idx1-ubyte.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roadwide/AI-Homework/ba2b3e011630aa812c6489bf27cfef719cc75dcd/Deep Learning/BP/MNIST_data/train-labels-idx1-ubyte.gz -------------------------------------------------------------------------------- /Deep Learning/BP/README.md: -------------------------------------------------------------------------------- 1 | # 1 BP神经网络 2 | - [1.1算法介绍](#11算法介绍) 3 | - [1.2实验代码](#12实验代码) 4 | - [1.3实验结果](#13实验结果) 5 | - [1.4实验总结](#14实验总结) 6 | ## 1.1算法介绍 7 | 8 | 反向传播(英语:Backpropagation,缩写为BP)是“误差反向传播”的简称,是一种与最优化方法(如梯度下降法)结合使用的,用来训练人工神经网络的常见方法。该方法对网络中所有权重计算损失函数的梯度。这个梯度会反馈给最优化方法,用来更新权值以最小化损失函数。反向传播要求有对每个输入值想得到的已知输出,来计算损失函数梯度。因此,它通常被认为是一种监督式学习方法。反向传播要求人工神经元(或“节点”)的激励函数可微。 9 | 10 | 下面举一个简单的例子来介绍什么是梯度下降法以及如何训练神经网络。 11 | 12 | 假设有这样一个函数(ε是噪声) 13 | 14 | ![BP-func](../../img/BP-func.png) 15 | 16 | 但我们不知道参数的值,也就是w和b的值,但是我们直到很多(x,y)。那么我们可以通过这些值来预测原函数。 17 | 18 | 构造如下的损失函数 19 | 20 | ![BP-lossfunc](../../img/BP-lossfunc.png) 21 | 22 | 其中的x和y都是真实值,而w和b是我们要预测的值。我们预测的w和b应该使得损失函数越小越好,这点不难看出,损失函数越小证明我们预测出来的函数越接近真实情况。 23 | 24 | ![BP-chart](../../img/BP-chart.png) 25 | 26 | 假设我们得到的loss函数图像如上图所示,那么我们得目的就是找到一组w和b使得loss函数值处于一个极小值。 27 | 28 | ![BP-dfunc](../../img/BP-dfunc.png) 29 | 30 | 约定w和b按照上面得式子更新自己的值。其中w和b是原来的值,w'和b'是新值,lr是learning rate的缩写,直观理解就是横坐标移动的程度。根据高等数学的相关知识,我们知道函数梯度的方向指向极大值,所以上面式子用减号,也就达到了梯度下降的目的。梯度下降可以使得loss函数处于极小值。当然处于极小值不一定是处于最小值,可能会造成局部最优解,但这些问题超出了本篇实验报告的讨论范围,所以不作考虑。 31 | 32 | 经过若干次的更新w和b,我们就会找到一个较小的loss值,也就是说我们找到的w和b就很接近真实值了。 33 | 34 | 梯度下降是一种常用的优化loss函数的方法,还有其他的方法也可以对loss函数进行优化,例如随机梯度下降、Adagrad、Adam等。 35 | 36 | 下面介绍应用BP算法完成手写体识别问题。MNIST数据集的每一张图片是一个28x28的矩阵,每个元素是其位置的灰度信息。 37 | 38 | 我们首先将二维的矩阵打平成一维的,也就是说变成一个784x1的矩阵。上面介绍的简单的例子是单个数字,这里成了矩阵,所以我们的参数也应该是矩阵。 39 | 40 | 假设我们先将784x1的矩阵乘以一个512x784的矩阵参数,将得到一个512x1的矩阵 41 | 42 | 再将512x1的矩阵乘以一个256x512的矩阵参数,将得到一个256x1的矩阵 43 | 44 | 再将256x1的矩阵乘以一个10x256的矩阵参数,将得到一个10x1的矩阵 45 | 46 | 经过上述操作,我们将一个784x1的矩阵转换成了一个10x1的矩阵。为什么最后要转换成10个元素的矩阵?因为我们的手写体识别问题中有10个数字,最后我们得到的矩阵的每个元素代表的是可能是相应数字的概率。根据得到的概率和实际情况来构造损失函数,再利用梯度下降的方法来更新参数。 47 | 48 | 上述的好几步的矩阵转换操作实际上就是神经网络中的层,最后一步的10个元素属于输出层,第一次的784x1是输入层,中间的则是隐藏层。 49 | 50 | ## 1.2实验代码 51 | 52 | ```python 53 | #下载、导入数据用到的函数包 54 | import input_data 55 | #tensorflow 2.x没有placeholder所以要用1.x的API 56 | import tensorflow.compat.v1 as tf 57 | tf.disable_v2_behavior() 58 | #读取数据 59 | mnist = input_data.read_data_sets("MNIST_data", one_hot=True) 60 | #不是一个特定的值,而是一个占位符placeholder 61 | # 我们在TensorFlow运行计算时输入这个值。我们希望能够输入任意数量的MNIST图像,每一张图展平成784维的向量。 62 | x = tf.placeholder("float", [None, 784]) 63 | #W的维度是[784,10],因为我们想要用784维的图片向量乘以它以得到一个10维的证据值向量 64 | W = tf.Variable(tf.zeros([784,10])) 65 | #b的形状是[10],所以我们可以直接把它加到输出上面 66 | b = tf.Variable(tf.zeros([10])) 67 | #softmax模型 68 | y = tf.nn.softmax(tf.matmul(x,W) + b) 69 | #为了计算交叉熵,我们首先需要添加一个新的占位符用于输入正确值 70 | y_ = tf.placeholder("float", [None,10]) 71 | #计算交叉熵 72 | cross_entropy = -tf.reduce_sum(y_*tf.log(y)) 73 | #使用反向传播算法(backpropagation algorithm)进行训练 74 | train_step = tf.train.GradientDescentOptimizer(0.01).minimize(cross_entropy) 75 | #初始化tensorflow 76 | init = tf.initialize_all_variables() 77 | sess = tf.Session() 78 | sess.run(init) 79 | #让模型循环训练1000次 80 | for i in range(1000): 81 | batch_xs, batch_ys = mnist.train.next_batch(100) 82 | sess.run(train_step, feed_dict={x: batch_xs, y_: batch_ys}) 83 | correct_prediction = tf.equal(tf.argmax(y,1), tf.argmax(y_,1)) 84 | accuracy = tf.reduce_mean(tf.cast(correct_prediction, "float")) 85 | print(sess.run(accuracy, feed_dict={x: mnist.test.images, y_: mnist.test.labels})) 86 | ``` 87 | 88 | ## 1.3实验结果 89 | 90 | 可以看出经过1000次的训练,模型识别手写体的准确度达到了0.9166 91 | 92 | ![BP-result](../../img/BP-result.png) 93 | 94 | ## 1.4实验总结 95 | 96 | BP神经网络的原理并不难,可以自行手动实现,但使用TensorFlow不仅方便,而且有更高的效率。 97 | 98 | Python的数值计算效率并不高,要实现高效的数值运算一般会使用NumPy这样的库,将运算放到外部其他语言封装好的程序中,以此来完成高效运算。但是从外部计算切换回Python的每一个操作,仍然是一个很大的开销。 99 | 100 | TensorFlow也把复杂的计算放在Python之外完成,但是为了避免前面说的那些开销,它做了进一步完善。Tensorflow不单独地运行单一的复杂计算,而是让我们可以先用图描述一系列可交互的计算操作,然后全部一起在Python之外运行。 -------------------------------------------------------------------------------- /Deep Learning/BP/input_data.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015 Google Inc. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # ============================================================================== 15 | """Functions for downloading and reading MNIST data.""" 16 | from __future__ import absolute_import 17 | from __future__ import division 18 | from __future__ import print_function 19 | import gzip 20 | import os 21 | import tensorflow.python.platform 22 | import numpy 23 | from six.moves import urllib 24 | from six.moves import xrange # pylint: disable=redefined-builtin 25 | import tensorflow as tf 26 | SOURCE_URL = 'http://yann.lecun.com/exdb/mnist/' 27 | def maybe_download(filename, work_directory): 28 | """Download the data from Yann's website, unless it's already here.""" 29 | if not os.path.exists(work_directory): 30 | os.mkdir(work_directory) 31 | filepath = os.path.join(work_directory, filename) 32 | if not os.path.exists(filepath): 33 | filepath, _ = urllib.request.urlretrieve(SOURCE_URL + filename, filepath) 34 | statinfo = os.stat(filepath) 35 | print('Successfully downloaded', filename, statinfo.st_size, 'bytes.') 36 | return filepath 37 | def _read32(bytestream): 38 | dt = numpy.dtype(numpy.uint32).newbyteorder('>') 39 | return numpy.frombuffer(bytestream.read(4), dtype=dt)[0] 40 | def extract_images(filename): 41 | """Extract the images into a 4D uint8 numpy array [index, y, x, depth].""" 42 | print('Extracting', filename) 43 | with gzip.open(filename) as bytestream: 44 | magic = _read32(bytestream) 45 | if magic != 2051: 46 | raise ValueError( 47 | 'Invalid magic number %d in MNIST image file: %s' % 48 | (magic, filename)) 49 | num_images = _read32(bytestream) 50 | rows = _read32(bytestream) 51 | cols = _read32(bytestream) 52 | buf = bytestream.read(rows * cols * num_images) 53 | data = numpy.frombuffer(buf, dtype=numpy.uint8) 54 | data = data.reshape(num_images, rows, cols, 1) 55 | return data 56 | def dense_to_one_hot(labels_dense, num_classes=10): 57 | """Convert class labels from scalars to one-hot vectors.""" 58 | num_labels = labels_dense.shape[0] 59 | index_offset = numpy.arange(num_labels) * num_classes 60 | labels_one_hot = numpy.zeros((num_labels, num_classes)) 61 | labels_one_hot.flat[index_offset + labels_dense.ravel()] = 1 62 | return labels_one_hot 63 | def extract_labels(filename, one_hot=False): 64 | """Extract the labels into a 1D uint8 numpy array [index].""" 65 | print('Extracting', filename) 66 | with gzip.open(filename) as bytestream: 67 | magic = _read32(bytestream) 68 | if magic != 2049: 69 | raise ValueError( 70 | 'Invalid magic number %d in MNIST label file: %s' % 71 | (magic, filename)) 72 | num_items = _read32(bytestream) 73 | buf = bytestream.read(num_items) 74 | labels = numpy.frombuffer(buf, dtype=numpy.uint8) 75 | if one_hot: 76 | return dense_to_one_hot(labels) 77 | return labels 78 | class DataSet(object): 79 | def __init__(self, images, labels, fake_data=False, one_hot=False, 80 | dtype=tf.float32): 81 | """Construct a DataSet. 82 | one_hot arg is used only if fake_data is true. `dtype` can be either 83 | `uint8` to leave the input as `[0, 255]`, or `float32` to rescale into 84 | `[0, 1]`. 85 | """ 86 | dtype = tf.as_dtype(dtype).base_dtype 87 | if dtype not in (tf.uint8, tf.float32): 88 | raise TypeError('Invalid image dtype %r, expected uint8 or float32' % 89 | dtype) 90 | if fake_data: 91 | self._num_examples = 10000 92 | self.one_hot = one_hot 93 | else: 94 | assert images.shape[0] == labels.shape[0], ( 95 | 'images.shape: %s labels.shape: %s' % (images.shape, 96 | labels.shape)) 97 | self._num_examples = images.shape[0] 98 | # Convert shape from [num examples, rows, columns, depth] 99 | # to [num examples, rows*columns] (assuming depth == 1) 100 | assert images.shape[3] == 1 101 | images = images.reshape(images.shape[0], 102 | images.shape[1] * images.shape[2]) 103 | if dtype == tf.float32: 104 | # Convert from [0, 255] -> [0.0, 1.0]. 105 | images = images.astype(numpy.float32) 106 | images = numpy.multiply(images, 1.0 / 255.0) 107 | self._images = images 108 | self._labels = labels 109 | self._epochs_completed = 0 110 | self._index_in_epoch = 0 111 | @property 112 | def images(self): 113 | return self._images 114 | @property 115 | def labels(self): 116 | return self._labels 117 | @property 118 | def num_examples(self): 119 | return self._num_examples 120 | @property 121 | def epochs_completed(self): 122 | return self._epochs_completed 123 | def next_batch(self, batch_size, fake_data=False): 124 | """Return the next `batch_size` examples from this data set.""" 125 | if fake_data: 126 | fake_image = [1] * 784 127 | if self.one_hot: 128 | fake_label = [1] + [0] * 9 129 | else: 130 | fake_label = 0 131 | return [fake_image for _ in xrange(batch_size)], [ 132 | fake_label for _ in xrange(batch_size)] 133 | start = self._index_in_epoch 134 | self._index_in_epoch += batch_size 135 | if self._index_in_epoch > self._num_examples: 136 | # Finished epoch 137 | self._epochs_completed += 1 138 | # Shuffle the data 139 | perm = numpy.arange(self._num_examples) 140 | numpy.random.shuffle(perm) 141 | self._images = self._images[perm] 142 | self._labels = self._labels[perm] 143 | # Start next epoch 144 | start = 0 145 | self._index_in_epoch = batch_size 146 | assert batch_size <= self._num_examples 147 | end = self._index_in_epoch 148 | return self._images[start:end], self._labels[start:end] 149 | def read_data_sets(train_dir, fake_data=False, one_hot=False, dtype=tf.float32): 150 | class DataSets(object): 151 | pass 152 | data_sets = DataSets() 153 | if fake_data: 154 | def fake(): 155 | return DataSet([], [], fake_data=True, one_hot=one_hot, dtype=dtype) 156 | data_sets.train = fake() 157 | data_sets.validation = fake() 158 | data_sets.test = fake() 159 | return data_sets 160 | TRAIN_IMAGES = 'train-images-idx3-ubyte.gz' 161 | TRAIN_LABELS = 'train-labels-idx1-ubyte.gz' 162 | TEST_IMAGES = 't10k-images-idx3-ubyte.gz' 163 | TEST_LABELS = 't10k-labels-idx1-ubyte.gz' 164 | VALIDATION_SIZE = 5000 165 | local_file = maybe_download(TRAIN_IMAGES, train_dir) 166 | train_images = extract_images(local_file) 167 | local_file = maybe_download(TRAIN_LABELS, train_dir) 168 | train_labels = extract_labels(local_file, one_hot=one_hot) 169 | local_file = maybe_download(TEST_IMAGES, train_dir) 170 | test_images = extract_images(local_file) 171 | local_file = maybe_download(TEST_LABELS, train_dir) 172 | test_labels = extract_labels(local_file, one_hot=one_hot) 173 | validation_images = train_images[:VALIDATION_SIZE] 174 | validation_labels = train_labels[:VALIDATION_SIZE] 175 | train_images = train_images[VALIDATION_SIZE:] 176 | train_labels = train_labels[VALIDATION_SIZE:] 177 | data_sets.train = DataSet(train_images, train_labels, dtype=dtype) 178 | data_sets.validation = DataSet(validation_images, validation_labels, 179 | dtype=dtype) 180 | data_sets.test = DataSet(test_images, test_labels, dtype=dtype) 181 | return data_sets -------------------------------------------------------------------------------- /Deep Learning/CNN/CNN.py: -------------------------------------------------------------------------------- 1 | #下载、导入数据用到的函数包 2 | import input_data 3 | #读取数据 4 | mnist = input_data.read_data_sets('MNIST_data', one_hot=True) 5 | #tensorflow 2.x没有placeholder所以要用1.x的API 6 | import tensorflow.compat.v1 as tf 7 | tf.disable_v2_behavior() 8 | 9 | sess = tf.InteractiveSession() 10 | # 我们在TensorFlow运行计算时输入这个值。我们希望能够输入任意数量的MNIST图像,每一张图展平成784维的向量。 11 | x = tf.placeholder("float", shape=[None, 784]) 12 | #为了计算交叉熵,我们首先需要添加一个新的占位符用于输入正确值 13 | y_ = tf.placeholder("float", shape=[None, 10]) 14 | #W的维度是[784,10],因为我们想要用784维的图片向量乘以它以得到一个10维的证据值向量 15 | W = tf.Variable(tf.zeros([784,10])) 16 | #b的形状是[10],所以我们可以直接把它加到输出上面 17 | b = tf.Variable(tf.zeros([10])) 18 | #初始化 19 | sess.run(tf.initialize_all_variables()) 20 | #计算每个分类的softmax概率值 21 | y = tf.nn.softmax(tf.matmul(x,W) + b) 22 | #损失函数是目标类别和预测类别之间的交叉熵。 23 | cross_entropy = -tf.reduce_sum(y_*tf.log(y)) 24 | #用最速下降法让交叉熵下降,步长为0.01 25 | train_step = tf.train.GradientDescentOptimizer(0.01).minimize(cross_entropy) 26 | 27 | #整个模型的训练可以通过反复地运行train_step来完成 28 | for i in range(1000): 29 | batch = mnist.train.next_batch(50) 30 | train_step.run(feed_dict={x: batch[0], y_: batch[1]}) 31 | 32 | #评估模型 33 | correct_prediction = tf.equal(tf.argmax(y,1), tf.argmax(y_,1)) 34 | accuracy = tf.reduce_mean(tf.cast(correct_prediction, "float")) 35 | print (accuracy.eval(feed_dict={x: mnist.test.images, y_: mnist.test.labels})) 36 | 37 | #上面的代码是应用BP神经网络 38 | #下面将构建一个多层卷积网络 39 | 40 | #权重初始化 41 | def weight_variable(shape): 42 | initial = tf.truncated_normal(shape, stddev=0.1) 43 | return tf.Variable(initial) 44 | 45 | def bias_variable(shape): 46 | initial = tf.constant(0.1, shape=shape) 47 | return tf.Variable(initial) 48 | 49 | #卷积和池化 50 | def conv2d(x, W): 51 | return tf.nn.conv2d(x, W, strides=[1, 1, 1, 1], padding='SAME') 52 | 53 | def max_pool_2x2(x): 54 | return tf.nn.max_pool(x, ksize=[1, 2, 2, 1], 55 | strides=[1, 2, 2, 1], padding='SAME') 56 | 57 | #第一层卷积 58 | W_conv1 = weight_variable([5, 5, 1, 32]) 59 | b_conv1 = bias_variable([32]) 60 | 61 | x_image = tf.reshape(x, [-1,28,28,1]) 62 | 63 | h_conv1 = tf.nn.relu(conv2d(x_image, W_conv1) + b_conv1) 64 | h_pool1 = max_pool_2x2(h_conv1) 65 | 66 | #第二层卷积 67 | W_conv2 = weight_variable([5, 5, 32, 64]) 68 | b_conv2 = bias_variable([64]) 69 | 70 | h_conv2 = tf.nn.relu(conv2d(h_pool1, W_conv2) + b_conv2) 71 | h_pool2 = max_pool_2x2(h_conv2) 72 | 73 | #密集连接层 74 | W_fc1 = weight_variable([7 * 7 * 64, 1024]) 75 | b_fc1 = bias_variable([1024]) 76 | 77 | h_pool2_flat = tf.reshape(h_pool2, [-1, 7*7*64]) 78 | h_fc1 = tf.nn.relu(tf.matmul(h_pool2_flat, W_fc1) + b_fc1) 79 | 80 | #Dropout 81 | keep_prob = tf.placeholder("float") 82 | h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob) 83 | 84 | #输出层 85 | W_fc2 = weight_variable([1024, 10]) 86 | b_fc2 = bias_variable([10]) 87 | 88 | y_conv=tf.nn.softmax(tf.matmul(h_fc1_drop, W_fc2) + b_fc2) 89 | 90 | #训练和评估模型 91 | cross_entropy = -tf.reduce_sum(y_*tf.log(y_conv)) 92 | train_step = tf.train.AdamOptimizer(1e-4).minimize(cross_entropy) 93 | correct_prediction = tf.equal(tf.argmax(y_conv,1), tf.argmax(y_,1)) 94 | accuracy = tf.reduce_mean(tf.cast(correct_prediction, "float")) 95 | sess.run(tf.initialize_all_variables()) 96 | for i in range(20000): 97 | batch = mnist.train.next_batch(50) 98 | if i%100 == 0: 99 | train_accuracy = accuracy.eval(feed_dict={ 100 | x:batch[0], y_: batch[1], keep_prob: 1.0}) 101 | print ("step %d, training accuracy %g"%(i, train_accuracy)) 102 | train_step.run(feed_dict={x: batch[0], y_: batch[1], keep_prob: 0.5}) 103 | 104 | print ("test accuracy %g"%accuracy.eval(feed_dict={ 105 | x: mnist.test.images, y_: mnist.test.labels, keep_prob: 1.0})) 106 | 107 | -------------------------------------------------------------------------------- /Deep Learning/CNN/MNIST_data/t10k-images-idx3-ubyte.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roadwide/AI-Homework/ba2b3e011630aa812c6489bf27cfef719cc75dcd/Deep Learning/CNN/MNIST_data/t10k-images-idx3-ubyte.gz -------------------------------------------------------------------------------- /Deep Learning/CNN/MNIST_data/t10k-labels-idx1-ubyte.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roadwide/AI-Homework/ba2b3e011630aa812c6489bf27cfef719cc75dcd/Deep Learning/CNN/MNIST_data/t10k-labels-idx1-ubyte.gz -------------------------------------------------------------------------------- /Deep Learning/CNN/MNIST_data/train-images-idx3-ubyte.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roadwide/AI-Homework/ba2b3e011630aa812c6489bf27cfef719cc75dcd/Deep Learning/CNN/MNIST_data/train-images-idx3-ubyte.gz -------------------------------------------------------------------------------- /Deep Learning/CNN/MNIST_data/train-labels-idx1-ubyte.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roadwide/AI-Homework/ba2b3e011630aa812c6489bf27cfef719cc75dcd/Deep Learning/CNN/MNIST_data/train-labels-idx1-ubyte.gz -------------------------------------------------------------------------------- /Deep Learning/CNN/README.md: -------------------------------------------------------------------------------- 1 | # 2 卷积神经网络 2 | - [2.1算法介绍](#21算法介绍) 3 | - [2.2实验代码](#22实验代码) 4 | - [2.3实验结果](#23实验结果) 5 | - [2.4实验总结](#24实验总结) 6 | ## 2.1算法介绍 7 | 8 | 卷积神经网络(Convolutional Neural Network, CNN)是一种前馈神经网络,它的人工神经元可以响应一部分覆盖范围内的周围单元,对于大型图像处理有出色表现。卷积神经网络由一个或多个卷积层和顶端的全连通层(对应经典的神经网络)组成,同时也包括关联权重和池化层(pooling layer)。这一结构使得卷积神经网络能够利用输入数据的二维结构。 9 | 10 | 卷积神经网络中比较重要的一个概念是卷积操作,如下图所示,根据learned weights将若干块映射到一个新的矩阵上。 11 | 12 | ![CNN-p1](../../img/CNN-p1.png) 13 | 14 | 其实卷积操作并不陌生,在日常生活中p图时就经常会用到,例如对图片的锐化、模糊等操作,就是用一个特定的矩阵(图中的learned weights矩阵)对原图像进行卷积操作。下图就是一张图片卷积后的样子。 15 | 16 | ![CNN-p2](../../img/CNN-p2.png) 17 | 18 | 不难看出卷积操作后图片会变小,经过若干次的操作(相当于隐藏层),最后输出一个和上面介绍BP神经网络时一样的具有10个元素的向量,分别代表是某个数字的概率。如下图 19 | 20 | ![CNN-p3](../../img/CNN-p3.png) 21 | 22 | 不断优化中间参数的值,利用梯度下降或其他优化方法,就可以完成模型的训练。 23 | 24 | ## 2.2实验代码 25 | 26 | ```python 27 | #下载、导入数据用到的函数包 28 | import input_data 29 | #读取数据 30 | mnist = input_data.read_data_sets('MNIST_data', one_hot=True) 31 | #tensorflow 2.x没有placeholder所以要用1.x的API 32 | import tensorflow.compat.v1 as tf 33 | tf.disable_v2_behavior() 34 | 35 | sess = tf.InteractiveSession() 36 | # 我们在TensorFlow运行计算时输入这个值。我们希望能够输入任意数量的MNIST图像,每一张图展平成784维的向量。 37 | x = tf.placeholder("float", shape=[None, 784]) 38 | #为了计算交叉熵,我们首先需要添加一个新的占位符用于输入正确值 39 | y_ = tf.placeholder("float", shape=[None, 10]) 40 | #W的维度是[784,10],因为我们想要用784维的图片向量乘以它以得到一个10维的证据值向量 41 | W = tf.Variable(tf.zeros([784,10])) 42 | #b的形状是[10],所以我们可以直接把它加到输出上面 43 | b = tf.Variable(tf.zeros([10])) 44 | #初始化 45 | sess.run(tf.initialize_all_variables()) 46 | #计算每个分类的softmax概率值 47 | y = tf.nn.softmax(tf.matmul(x,W) + b) 48 | #损失函数是目标类别和预测类别之间的交叉熵。 49 | cross_entropy = -tf.reduce_sum(y_*tf.log(y)) 50 | #用最速下降法让交叉熵下降,步长为0.01 51 | train_step = tf.train.GradientDescentOptimizer(0.01).minimize(cross_entropy) 52 | 53 | #整个模型的训练可以通过反复地运行train_step来完成 54 | for i in range(1000): 55 | batch = mnist.train.next_batch(50) 56 | train_step.run(feed_dict={x: batch[0], y_: batch[1]}) 57 | 58 | #评估模型 59 | correct_prediction = tf.equal(tf.argmax(y,1), tf.argmax(y_,1)) 60 | accuracy = tf.reduce_mean(tf.cast(correct_prediction, "float")) 61 | print (accuracy.eval(feed_dict={x: mnist.test.images, y_: mnist.test.labels})) 62 | 63 | #上面的代码是应用BP神经网络 64 | #下面将构建一个多层卷积网络 65 | 66 | #权重初始化 67 | def weight_variable(shape): 68 | initial = tf.truncated_normal(shape, stddev=0.1) 69 | return tf.Variable(initial) 70 | 71 | def bias_variable(shape): 72 | initial = tf.constant(0.1, shape=shape) 73 | return tf.Variable(initial) 74 | 75 | #卷积和池化 76 | def conv2d(x, W): 77 | return tf.nn.conv2d(x, W, strides=[1, 1, 1, 1], padding='SAME') 78 | 79 | def max_pool_2x2(x): 80 | return tf.nn.max_pool(x, ksize=[1, 2, 2, 1], 81 | strides=[1, 2, 2, 1], padding='SAME') 82 | 83 | #第一层卷积 84 | W_conv1 = weight_variable([5, 5, 1, 32]) 85 | b_conv1 = bias_variable([32]) 86 | 87 | x_image = tf.reshape(x, [-1,28,28,1]) 88 | 89 | h_conv1 = tf.nn.relu(conv2d(x_image, W_conv1) + b_conv1) 90 | h_pool1 = max_pool_2x2(h_conv1) 91 | 92 | #第二层卷积 93 | W_conv2 = weight_variable([5, 5, 32, 64]) 94 | b_conv2 = bias_variable([64]) 95 | 96 | h_conv2 = tf.nn.relu(conv2d(h_pool1, W_conv2) + b_conv2) 97 | h_pool2 = max_pool_2x2(h_conv2) 98 | 99 | #密集连接层 100 | W_fc1 = weight_variable([7 * 7 * 64, 1024]) 101 | b_fc1 = bias_variable([1024]) 102 | 103 | h_pool2_flat = tf.reshape(h_pool2, [-1, 7*7*64]) 104 | h_fc1 = tf.nn.relu(tf.matmul(h_pool2_flat, W_fc1) + b_fc1) 105 | 106 | #Dropout 107 | keep_prob = tf.placeholder("float") 108 | h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob) 109 | 110 | #输出层 111 | W_fc2 = weight_variable([1024, 10]) 112 | b_fc2 = bias_variable([10]) 113 | 114 | y_conv=tf.nn.softmax(tf.matmul(h_fc1_drop, W_fc2) + b_fc2) 115 | 116 | #训练和评估模型 117 | cross_entropy = -tf.reduce_sum(y_*tf.log(y_conv)) 118 | train_step = tf.train.AdamOptimizer(1e-4).minimize(cross_entropy) 119 | correct_prediction = tf.equal(tf.argmax(y_conv,1), tf.argmax(y_,1)) 120 | accuracy = tf.reduce_mean(tf.cast(correct_prediction, "float")) 121 | sess.run(tf.initialize_all_variables()) 122 | for i in range(20000): 123 | batch = mnist.train.next_batch(50) 124 | if i%100 == 0: 125 | train_accuracy = accuracy.eval(feed_dict={ 126 | x:batch[0], y_: batch[1], keep_prob: 1.0}) 127 | print ("step %d, training accuracy %g"%(i, train_accuracy)) 128 | train_step.run(feed_dict={x: batch[0], y_: batch[1], keep_prob: 0.5}) 129 | 130 | print ("test accuracy %g"%accuracy.eval(feed_dict={ 131 | x: mnist.test.images, y_: mnist.test.labels, keep_prob: 1.0})) 132 | ``` 133 | 134 | ## 2.3实验结果 135 | 136 | 可以看出使用卷积神经网络训练的准确率要比BP神经网络高。 137 | 138 | ![CNN-result](../../img/CNN-result.png) 139 | 140 | ## 2.4实验总结 141 | 142 | 1、我们看到一个的东西时,其实眼睛并没有注意到整个物体,而是把注意力集中在一小部分。比如说我们看到一个人时,首先注意到的是他的脸。卷积操作就相当于提取图片的特征信息,相当于将计算机的注意力放在图片的某一部分。我们将中间的隐藏层作为图片输出时,看到的东西会很奇怪,虽然我们看不懂,但这些相当于计算机对这张图片的一种抽象认识。 143 | 144 | 2、完成本篇实验报告后对卷积神经网络有了初步的认识。但对其中的激励层、池化层等概念还没有深入彻底的理解,只是模糊的知道大概是干什么的,对深度学习中的数学原理也没有很透彻的理解,还需要今后不断地学习。 -------------------------------------------------------------------------------- /Deep Learning/CNN/input_data.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015 Google Inc. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # ============================================================================== 15 | """Functions for downloading and reading MNIST data.""" 16 | from __future__ import absolute_import 17 | from __future__ import division 18 | from __future__ import print_function 19 | import gzip 20 | import os 21 | import tensorflow.python.platform 22 | import numpy 23 | from six.moves import urllib 24 | from six.moves import xrange # pylint: disable=redefined-builtin 25 | import tensorflow as tf 26 | SOURCE_URL = 'http://yann.lecun.com/exdb/mnist/' 27 | def maybe_download(filename, work_directory): 28 | """Download the data from Yann's website, unless it's already here.""" 29 | if not os.path.exists(work_directory): 30 | os.mkdir(work_directory) 31 | filepath = os.path.join(work_directory, filename) 32 | if not os.path.exists(filepath): 33 | filepath, _ = urllib.request.urlretrieve(SOURCE_URL + filename, filepath) 34 | statinfo = os.stat(filepath) 35 | print('Successfully downloaded', filename, statinfo.st_size, 'bytes.') 36 | return filepath 37 | def _read32(bytestream): 38 | dt = numpy.dtype(numpy.uint32).newbyteorder('>') 39 | return numpy.frombuffer(bytestream.read(4), dtype=dt)[0] 40 | def extract_images(filename): 41 | """Extract the images into a 4D uint8 numpy array [index, y, x, depth].""" 42 | print('Extracting', filename) 43 | with gzip.open(filename) as bytestream: 44 | magic = _read32(bytestream) 45 | if magic != 2051: 46 | raise ValueError( 47 | 'Invalid magic number %d in MNIST image file: %s' % 48 | (magic, filename)) 49 | num_images = _read32(bytestream) 50 | rows = _read32(bytestream) 51 | cols = _read32(bytestream) 52 | buf = bytestream.read(rows * cols * num_images) 53 | data = numpy.frombuffer(buf, dtype=numpy.uint8) 54 | data = data.reshape(num_images, rows, cols, 1) 55 | return data 56 | def dense_to_one_hot(labels_dense, num_classes=10): 57 | """Convert class labels from scalars to one-hot vectors.""" 58 | num_labels = labels_dense.shape[0] 59 | index_offset = numpy.arange(num_labels) * num_classes 60 | labels_one_hot = numpy.zeros((num_labels, num_classes)) 61 | labels_one_hot.flat[index_offset + labels_dense.ravel()] = 1 62 | return labels_one_hot 63 | def extract_labels(filename, one_hot=False): 64 | """Extract the labels into a 1D uint8 numpy array [index].""" 65 | print('Extracting', filename) 66 | with gzip.open(filename) as bytestream: 67 | magic = _read32(bytestream) 68 | if magic != 2049: 69 | raise ValueError( 70 | 'Invalid magic number %d in MNIST label file: %s' % 71 | (magic, filename)) 72 | num_items = _read32(bytestream) 73 | buf = bytestream.read(num_items) 74 | labels = numpy.frombuffer(buf, dtype=numpy.uint8) 75 | if one_hot: 76 | return dense_to_one_hot(labels) 77 | return labels 78 | class DataSet(object): 79 | def __init__(self, images, labels, fake_data=False, one_hot=False, 80 | dtype=tf.float32): 81 | """Construct a DataSet. 82 | one_hot arg is used only if fake_data is true. `dtype` can be either 83 | `uint8` to leave the input as `[0, 255]`, or `float32` to rescale into 84 | `[0, 1]`. 85 | """ 86 | dtype = tf.as_dtype(dtype).base_dtype 87 | if dtype not in (tf.uint8, tf.float32): 88 | raise TypeError('Invalid image dtype %r, expected uint8 or float32' % 89 | dtype) 90 | if fake_data: 91 | self._num_examples = 10000 92 | self.one_hot = one_hot 93 | else: 94 | assert images.shape[0] == labels.shape[0], ( 95 | 'images.shape: %s labels.shape: %s' % (images.shape, 96 | labels.shape)) 97 | self._num_examples = images.shape[0] 98 | # Convert shape from [num examples, rows, columns, depth] 99 | # to [num examples, rows*columns] (assuming depth == 1) 100 | assert images.shape[3] == 1 101 | images = images.reshape(images.shape[0], 102 | images.shape[1] * images.shape[2]) 103 | if dtype == tf.float32: 104 | # Convert from [0, 255] -> [0.0, 1.0]. 105 | images = images.astype(numpy.float32) 106 | images = numpy.multiply(images, 1.0 / 255.0) 107 | self._images = images 108 | self._labels = labels 109 | self._epochs_completed = 0 110 | self._index_in_epoch = 0 111 | @property 112 | def images(self): 113 | return self._images 114 | @property 115 | def labels(self): 116 | return self._labels 117 | @property 118 | def num_examples(self): 119 | return self._num_examples 120 | @property 121 | def epochs_completed(self): 122 | return self._epochs_completed 123 | def next_batch(self, batch_size, fake_data=False): 124 | """Return the next `batch_size` examples from this data set.""" 125 | if fake_data: 126 | fake_image = [1] * 784 127 | if self.one_hot: 128 | fake_label = [1] + [0] * 9 129 | else: 130 | fake_label = 0 131 | return [fake_image for _ in xrange(batch_size)], [ 132 | fake_label for _ in xrange(batch_size)] 133 | start = self._index_in_epoch 134 | self._index_in_epoch += batch_size 135 | if self._index_in_epoch > self._num_examples: 136 | # Finished epoch 137 | self._epochs_completed += 1 138 | # Shuffle the data 139 | perm = numpy.arange(self._num_examples) 140 | numpy.random.shuffle(perm) 141 | self._images = self._images[perm] 142 | self._labels = self._labels[perm] 143 | # Start next epoch 144 | start = 0 145 | self._index_in_epoch = batch_size 146 | assert batch_size <= self._num_examples 147 | end = self._index_in_epoch 148 | return self._images[start:end], self._labels[start:end] 149 | def read_data_sets(train_dir, fake_data=False, one_hot=False, dtype=tf.float32): 150 | class DataSets(object): 151 | pass 152 | data_sets = DataSets() 153 | if fake_data: 154 | def fake(): 155 | return DataSet([], [], fake_data=True, one_hot=one_hot, dtype=dtype) 156 | data_sets.train = fake() 157 | data_sets.validation = fake() 158 | data_sets.test = fake() 159 | return data_sets 160 | TRAIN_IMAGES = 'train-images-idx3-ubyte.gz' 161 | TRAIN_LABELS = 'train-labels-idx1-ubyte.gz' 162 | TEST_IMAGES = 't10k-images-idx3-ubyte.gz' 163 | TEST_LABELS = 't10k-labels-idx1-ubyte.gz' 164 | VALIDATION_SIZE = 5000 165 | local_file = maybe_download(TRAIN_IMAGES, train_dir) 166 | train_images = extract_images(local_file) 167 | local_file = maybe_download(TRAIN_LABELS, train_dir) 168 | train_labels = extract_labels(local_file, one_hot=one_hot) 169 | local_file = maybe_download(TEST_IMAGES, train_dir) 170 | test_images = extract_images(local_file) 171 | local_file = maybe_download(TEST_LABELS, train_dir) 172 | test_labels = extract_labels(local_file, one_hot=one_hot) 173 | validation_images = train_images[:VALIDATION_SIZE] 174 | validation_labels = train_labels[:VALIDATION_SIZE] 175 | train_images = train_images[VALIDATION_SIZE:] 176 | train_labels = train_labels[VALIDATION_SIZE:] 177 | data_sets.train = DataSet(train_images, train_labels, dtype=dtype) 178 | data_sets.validation = DataSet(validation_images, validation_labels, 179 | dtype=dtype) 180 | data_sets.test = DataSet(test_images, test_labels, dtype=dtype) 181 | return data_sets -------------------------------------------------------------------------------- /Intelligent Optimization Algorithm/ACO/ACO.py: -------------------------------------------------------------------------------- 1 | import math 2 | import random 3 | import matplotlib.pyplot as plt 4 | #读取数据 5 | f=open("test.txt") 6 | data=f.readlines() 7 | #将cities初始化为字典,防止下面被当成列表 8 | cities={} 9 | for line in data: 10 | #原始数据以\n换行,将其替换掉 11 | line=line.replace("\n","") 12 | #最后一行以EOF为标志,如果读到就证明读完了,退出循环 13 | if(line=="EOF"): 14 | break 15 | #空格分割城市编号和城市的坐标 16 | city=line.split(" ") 17 | map(int,city) 18 | #将城市数据添加到cities中 19 | cities[eval(city[0])]=[eval(city[1]),eval(city[2])] 20 | #计算适应度,也就是距离分之一,这里用伪欧氏距离 21 | #用于决定释放多少信息素 22 | def calcfit(addr): 23 | sum=0 24 | for i in range(-1,len(addr)-1): 25 | nowcity=addr[i] 26 | nextcity=addr[i+1] 27 | nowloc=cities[nowcity] 28 | nextloc=cities[nextcity] 29 | sum+=math.sqrt(((nowloc[0]-nextloc[0])**2+(nowloc[1]-nextloc[1])**2)/10) 30 | #最后要回到初始城市 31 | return 1/sum 32 | #计算两个城市的距离,用于启发信息计算 33 | def calc2c(c1,c2): 34 | #cities是一个字典,key是城市编号,value是一个两个元素的list,分别是x y的坐标 35 | return math.sqrt((cities[c1][0]-cities[c2][0])**2+(cities[c1][1]-cities[c2][1])**2) 36 | 37 | 38 | #方便从1开始,所以0-48共49个数字 39 | #全部初始化为1,否则后面的概率可能因为乘以0而全为0 40 | #信息素浓度表 41 | matrix=[[1 for i in range(49)] for i in range(49)] 42 | 43 | #蚂蚁的类,实现了根据信息素和启发信息完成一次遍历 44 | class Ant: 45 | def __init__(self): 46 | #tabu是已经走过的城市 47 | #规定从第一个城市开始走 48 | self.tabu=[1] 49 | self.allowed=[i for i in range(2,49)] 50 | self.nowCity=1 51 | #a,b分别表示信息素和期望启发因子的相对重要程度 52 | self.a=2 53 | self.b=7 54 | #rho表示路径上信息素的挥发系数,1-rho表示信息素的持久性系数。 55 | self.rho=0.1 56 | #本条路线的适应度,距离分之一 57 | self.fit=0 58 | #计算下一个城市去哪 59 | def next(self): 60 | sum=0 61 | #用一个数组储存下一个城市的概率 62 | p=[0 for i in range(49)] 63 | #计算分母和分子 64 | for c in self.allowed: 65 | tmp=math.pow(matrix[self.nowCity][c],self.a)*math.pow(1/calc2c(self.nowCity,c),self.b) 66 | sum+=tmp 67 | #此处p是分子 68 | p[c]=tmp 69 | #更新p为概率 70 | for c in self.allowed: 71 | p[c]=p[c]/sum 72 | #更新p为区间 73 | for i in range(1,49): 74 | p[i]+=p[i-1] 75 | r=random.random() 76 | for i in range(48): 77 | if(rp[i]): 78 | #i+1即为下一个要去的城市 79 | self.tabu.append(i+1) 80 | self.allowed.remove(i+1) 81 | self.nowCity=i+1 82 | return 83 | #将所有城市遍历 84 | def tour(self): 85 | while(self.allowed): 86 | self.next() 87 | self.fit=calcfit(self.tabu) 88 | 89 | 90 | #更新信息素矩阵 91 | def updateMatrix(self): 92 | #line储存本次经历过的城市 93 | line=[] 94 | for i in range(47): 95 | #因为矩阵是对阵的,2-1和1-2应该有相同的值,所以两个方向都要加 96 | line.append([self.tabu[i],self.tabu[i+1]]) 97 | line.append([self.tabu[i+1],self.tabu[i]]) 98 | for i in range(1,49): 99 | for j in range(1,49): 100 | if([i,j] in line): 101 | matrix[i][j]=(1-self.rho)*matrix[i][j]+self.fit 102 | else: 103 | matrix[i][j]=(1-self.rho)*matrix[i][j] 104 | #一只蚂蚁复用,每次恢复初始状态 105 | def clear(self): 106 | self.tabu=[1] 107 | self.allowed=[i for i in range(2,49)] 108 | self.nowCity=1 109 | self.fit=0 110 | 111 | 112 | #蚁群算法的类,实现了算法运行过程 113 | class ACO: 114 | def __init__(self): 115 | #初始先随机N只蚂蚁 116 | self.initN=200 117 | self.bestTour=[i for i in range(1,49)] 118 | self.bestFit=calcfit(self.bestTour) 119 | self.initAnt() 120 | 121 | def initAnt(self): 122 | i=0 123 | tmpAnt=Ant() 124 | print(self.initN,"只先锋蚂蚁正在探路") 125 | while(iself.bestFit): 134 | self.bestFit=tmpFit 135 | self.bestTour=tmpAnt.tabu 136 | tmpAnt.clear() 137 | 138 | #n为蚂蚁数量 139 | def startAnt(self,n): 140 | i=0 141 | ant=Ant() 142 | Gen=[] #迭代次数 143 | dist=[] #距离,这两个列表是为了画图 144 | while(iself.bestFit): 148 | self.bestFit=ant.fit 149 | self.bestTour=ant.tabu 150 | print(i,":",1/self.bestFit) 151 | ant.clear() 152 | Gen.append(i) 153 | dist.append(1/self.bestFit) 154 | #绘制求解过程曲线 155 | plt.plot(Gen,dist,'-r') 156 | plt.show() 157 | 158 | 159 | a=ACO() 160 | a.startAnt(1000) 161 | 162 | #下面是若干次重复实验测试的代码 163 | # res=[] 164 | # for i in range(1,11): 165 | # a=ACO() 166 | # a.startAnt(1000) 167 | # res.append(1/a.bestFit) 168 | # print("第{}次实验\t当前最优解{}".format(i,1/a.bestFit)) 169 | # sum=0 170 | # for item in res: 171 | # sum+=item 172 | # print("10次实验平均解",sum/len(res)) 173 | -------------------------------------------------------------------------------- /Intelligent Optimization Algorithm/ACO/README.md: -------------------------------------------------------------------------------- 1 | # 3 蚁群算法 2 | - [3.1算法介绍](#31算法介绍) 3 | - [3.2实验代码](#32实验代码) 4 | - [3.3实验结果](#33实验结果) 5 | - [3.4实验总结](#34实验总结) 6 | ## 3.1算法介绍 7 | 8 | 蚁群算法(Ant Colony Optimization, ACO),是一种用来在图中寻找优化路径的机率型算法,对蚂蚁行为进行模仿抽象。在求解旅行推销员问题时,蚂蚁随机从某一城市出发,根据城市间距离与残留信息素浓度按概率选择下一城市,蚂蚁走完所有的城市后,在走过的路径上留下信息素,蚂蚁走的总路程越少,留下的信息素就越多。多次循环后,最好的路径就有可能被筛选出。 9 | 10 | 其核心原理在于单位时间内通过短距离的路径的次数要多,也就会留下更浓信息素,而蚂蚁会选择信息素浓的路径,这样根据信息素的浓度就能找到最短路径,非常适合解决TSP问题。 11 | 12 | ## 3.2实验代码 13 | 14 | ```python 15 | import math 16 | import random 17 | import matplotlib.pyplot as plt 18 | #读取数据 19 | f=open("test.txt") 20 | data=f.readlines() 21 | #将cities初始化为字典,防止下面被当成列表 22 | cities={} 23 | for line in data: 24 | #原始数据以\n换行,将其替换掉 25 | line=line.replace("\n","") 26 | #最后一行以EOF为标志,如果读到就证明读完了,退出循环 27 | if(line=="EOF"): 28 | break 29 | #空格分割城市编号和城市的坐标 30 | city=line.split(" ") 31 | map(int,city) 32 | #将城市数据添加到cities中 33 | cities[eval(city[0])]=[eval(city[1]),eval(city[2])] 34 | #计算适应度,也就是距离分之一,这里用伪欧氏距离 35 | #用于决定释放多少信息素 36 | def calcfit(addr): 37 | sum=0 38 | for i in range(-1,len(addr)-1): 39 | nowcity=addr[i] 40 | nextcity=addr[i+1] 41 | nowloc=cities[nowcity] 42 | nextloc=cities[nextcity] 43 | sum+=math.sqrt(((nowloc[0]-nextloc[0])**2+(nowloc[1]-nextloc[1])**2)/10) 44 | #最后要回到初始城市 45 | return 1/sum 46 | #计算两个城市的距离,用于启发信息计算 47 | def calc2c(c1,c2): 48 | #cities是一个字典,key是城市编号,value是一个两个元素的list,分别是x y的坐标 49 | return math.sqrt((cities[c1][0]-cities[c2][0])**2+(cities[c1][1]-cities[c2][1])**2) 50 | 51 | 52 | #方便从1开始,所以0-48共49个数字 53 | #全部初始化为1,否则后面的概率可能因为乘以0而全为0 54 | #信息素浓度表 55 | matrix=[[1 for i in range(49)] for i in range(49)] 56 | 57 | #蚂蚁的类,实现了根据信息素和启发信息完成一次遍历 58 | class Ant: 59 | def __init__(self): 60 | #tabu是已经走过的城市 61 | #规定从第一个城市开始走 62 | self.tabu=[1] 63 | self.allowed=[i for i in range(2,49)] 64 | self.nowCity=1 65 | #a,b分别表示信息素和期望启发因子的相对重要程度 66 | self.a=2 67 | self.b=7 68 | #rho表示路径上信息素的挥发系数,1-rho表示信息素的持久性系数。 69 | self.rho=0.1 70 | #本条路线的适应度,距离分之一 71 | self.fit=0 72 | #计算下一个城市去哪 73 | def next(self): 74 | sum=0 75 | #用一个数组储存下一个城市的概率 76 | p=[0 for i in range(49)] 77 | #计算分母和分子 78 | for c in self.allowed: 79 | tmp=math.pow(matrix[self.nowCity][c],self.a)*math.pow(1/calc2c(self.nowCity,c),self.b) 80 | sum+=tmp 81 | #此处p是分子 82 | p[c]=tmp 83 | #更新p为概率 84 | for c in self.allowed: 85 | p[c]=p[c]/sum 86 | #更新p为区间 87 | for i in range(1,49): 88 | p[i]+=p[i-1] 89 | r=random.random() 90 | for i in range(48): 91 | if(rp[i]): 92 | #i+1即为下一个要去的城市 93 | self.tabu.append(i+1) 94 | self.allowed.remove(i+1) 95 | self.nowCity=i+1 96 | return 97 | #将所有城市遍历 98 | def tour(self): 99 | while(self.allowed): 100 | self.next() 101 | self.fit=calcfit(self.tabu) 102 | 103 | 104 | #更新信息素矩阵 105 | def updateMatrix(self): 106 | #line储存本次经历过的城市 107 | line=[] 108 | for i in range(47): 109 | #因为矩阵是对阵的,2-1和1-2应该有相同的值,所以两个方向都要加 110 | line.append([self.tabu[i],self.tabu[i+1]]) 111 | line.append([self.tabu[i+1],self.tabu[i]]) 112 | for i in range(1,49): 113 | for j in range(1,49): 114 | if([i,j] in line): 115 | matrix[i][j]=(1-self.rho)*matrix[i][j]+self.fit 116 | else: 117 | matrix[i][j]=(1-self.rho)*matrix[i][j] 118 | #一只蚂蚁复用,每次恢复初始状态 119 | def clear(self): 120 | self.tabu=[1] 121 | self.allowed=[i for i in range(2,49)] 122 | self.nowCity=1 123 | self.fit=0 124 | 125 | 126 | #蚁群算法的类,实现了算法运行过程 127 | class ACO: 128 | def __init__(self): 129 | #初始先随机N只蚂蚁 130 | self.initN=200 131 | self.bestTour=[i for i in range(1,49)] 132 | self.bestFit=calcfit(self.bestTour) 133 | self.initAnt() 134 | 135 | def initAnt(self): 136 | i=0 137 | tmpAnt=Ant() 138 | print(self.initN,"只先锋蚂蚁正在探路") 139 | while(iself.bestFit): 148 | self.bestFit=tmpFit 149 | self.bestTour=tmpAnt.tabu 150 | tmpAnt.clear() 151 | 152 | #n为蚂蚁数量 153 | def startAnt(self,n): 154 | i=0 155 | ant=Ant() 156 | Gen=[] #迭代次数 157 | dist=[] #距离,这两个列表是为了画图 158 | while(iself.bestFit): 162 | self.bestFit=ant.fit 163 | self.bestTour=ant.tabu 164 | print(i,":",1/self.bestFit) 165 | ant.clear() 166 | Gen.append(i) 167 | dist.append(1/self.bestFit) 168 | #绘制求解过程曲线 169 | plt.plot(Gen,dist,'-r') 170 | plt.show() 171 | #达到阈值后更改策略,对信息素的倾向更大 172 | # if(1/self.bestFit<36000): 173 | # 174 | # ant.a=5 175 | # ant.b=2 176 | # break 177 | # while(iself.bestFit): 181 | # self.bestFit=ant.fit 182 | # self.bestTour=ant.tabu 183 | # print(i,":",1/self.bestFit) 184 | # ant.clear() 185 | 186 | 187 | a=ACO() 188 | a.startAnt(1000) 189 | ``` 190 | 191 | ## 3.3实验结果 192 | 193 | 下面是放置1000只蚂蚁的结果,计算出最优解是10967. 194 | 195 | ![ACO-reslut](../../img/ACO-result.png) 196 | 197 | 为防止实验的偶然性,下面进行10次重复实验并求平均值 198 | 199 | ![ACO-repeat](../../img/ACO-repeat.png) 200 | 201 | ![ACO-visual](../../img/ACO-visual.png) 202 | 203 | 上图横坐标是放出的蚂蚁数,纵坐标是信息素浓度最大的路径的距离。可以看出其收敛得非常快。 204 | 205 | ## 3.4实验总结 206 | 207 | 1、群算法的不难看出其非常适合求解最短距离问题,因此在三个算法中,ACO无论是算法的稳定性,还是最优解的距离长度都是最好的。不仅如此,它的运行速度也是另外两者无法匹敌的。还有,GA和PSO都要维持一个群体,而ACO只需要一只蚂蚁就能完成任务,占用内存更小。可见,ACO在算法的时间复杂度,空间复杂度,解的质量等各个方面都完胜另外两个算法。 208 | 209 | 2、初始状态时每条路的信息素浓度相等,所以蚂蚁的选择会有随机性。随机的选择也需要在每个路口进行判断,而这种判断是无意义,因为并不能选出信息素浓度高的。所以,我在初始时将若干只蚂蚁指定随机的几条路,这样能防止无意义的判断,并在算法正式开始时各条路上已经存在具有指示性的信息素。 210 | 211 | 3、蚂蚁选择路线并不是只依靠信息素,还有一个启发信息。启发信息也就是距离下一个城市的距离,蚂蚁会趋向于选择更近的城市。 212 | 213 | ![ACO-ppt](../../img/ACO-ppt.png) 214 | 215 | 我在进行信息素和期望启发因子的重要程度参数调优时发现期望启发因子的重要程度会影响解的收敛速度。当启发信息的权重越大,收敛得就会越快,而当启发信息权重很小时,就会收敛得很慢。通过观察公式,不难发现,启发信息权重越大,算法就越趋向于贪婪算法,也就是说一部分城市序列得最优解可以用贪婪算法求。这可以给遗传算法和粒子群寻优的初始群体的确定一定的启发。也就是说贪婪算法求出的位置比随机分布更接近解,从而节省从随机位置到趋近解这一过程所消耗的时间。 216 | 217 | 同时,对于蚁群算法的优化,我提出一种猜想。初始阶段较大的启发信息权重有助于解的快速收敛,而到后面时则会影响找到最优解。所以可以在初始阶段设置较大的启发信息权重,让解快速收敛,到了后面再降低其权重,让算法变为以信息素为主要导向的,以此来加快逼近最优解的速度。也就是说在两个阶段动态调整信息素和期望启发因子的重要程度。 218 | 219 | 4、一只蚂蚁是如何实现众多蚂蚁的行为的? 220 | 221 | 真实世界的蚂蚁是在同一时间有蚁群蚂蚁经过几条路线,因为单位时间内通过的蚂蚁数量不同,而造成每条路的信息素浓度不同。而在算法实现时并没有模拟很多蚂蚁。 222 | 223 | 算法所模拟的是一只蚂蚁,在走完全程时,对其经过的所有城市之间施加相同的信息素值,这个值是该次行程的距离分之一。每次进行的只是一只蚂蚁。 224 | 225 | 仔细想想我们就会发现其实两者所达到的效果是一样的。尽管一个是同时有很多蚂蚁在行走,另一个是同一时间只有一只蚂蚁在行走。但是他们施加信息素的方式也不同,一个是经过蚂蚁的多少决定,另一个是该条路的距离决定。这两方面的不同所导致的结果是一样的,并且模拟一只蚂蚁更加简便,容易实现。 -------------------------------------------------------------------------------- /Intelligent Optimization Algorithm/ACO/test.txt: -------------------------------------------------------------------------------- 1 | 1 6734 1453 2 | 2 2233 10 3 | 3 5530 1424 4 | 4 401 841 5 | 5 3082 1644 6 | 6 7608 4458 7 | 7 7573 3716 8 | 8 7265 1268 9 | 9 6898 1885 10 | 10 1112 2049 11 | 11 5468 2606 12 | 12 5989 2873 13 | 13 4706 2674 14 | 14 4612 2035 15 | 15 6347 2683 16 | 16 6107 669 17 | 17 7611 5184 18 | 18 7462 3590 19 | 19 7732 4723 20 | 20 5900 3561 21 | 21 4483 3369 22 | 22 6101 1110 23 | 23 5199 2182 24 | 24 1633 2809 25 | 25 4307 2322 26 | 26 675 1006 27 | 27 7555 4819 28 | 28 7541 3981 29 | 29 3177 756 30 | 30 7352 4506 31 | 31 7545 2801 32 | 32 3245 3305 33 | 33 6426 3173 34 | 34 4608 1198 35 | 35 23 2216 36 | 36 7248 3779 37 | 37 7762 4595 38 | 38 7392 2244 39 | 39 3484 2829 40 | 40 6271 2135 41 | 41 4985 140 42 | 42 1916 1569 43 | 43 7280 4899 44 | 44 7509 3239 45 | 45 10 2676 46 | 46 6807 2993 47 | 47 5185 3258 48 | 48 3023 1942 49 | EOF 50 | -------------------------------------------------------------------------------- /Intelligent Optimization Algorithm/GA/GA.py: -------------------------------------------------------------------------------- 1 | import random 2 | import math 3 | import matplotlib.pyplot as plt 4 | #读取数据 5 | f=open("test.txt") 6 | data=f.readlines() 7 | #将cities初始化为字典,防止下面被当成列表 8 | cities={} 9 | for line in data: 10 | #原始数据以\n换行,将其替换掉 11 | line=line.replace("\n","") 12 | #最后一行以EOF为标志,如果读到就证明读完了,退出循环 13 | if(line=="EOF"): 14 | break 15 | #空格分割城市编号和城市的坐标 16 | city=line.split(" ") 17 | map(int,city) 18 | #将城市数据添加到cities中 19 | cities[eval(city[0])]=[eval(city[1]),eval(city[2])] 20 | 21 | #计算适应度,也就是距离分之一,这里用伪欧氏距离 22 | def calcfit(gene): 23 | sum=0 24 | #最后要回到初始城市所以从-1,也就是最后一个城市绕一圈到最后一个城市 25 | for i in range(-1,len(gene)-1): 26 | nowcity=gene[i] 27 | nextcity=gene[i+1] 28 | nowloc=cities[nowcity] 29 | nextloc=cities[nextcity] 30 | sum+=math.sqrt(((nowloc[0]-nextloc[0])**2+(nowloc[1]-nextloc[1])**2)/10) 31 | 32 | return 1/sum 33 | 34 | #每个个体的类,方便根据基因计算适应度 35 | class Person: 36 | def __init__(self,gene): 37 | self.gene=gene 38 | self.fit=calcfit(gene) 39 | class Group: 40 | def __init__(self): 41 | self.GroupSize=100 #种群规模 42 | self.GeneSize=48 #基因数量,也就是城市数量 43 | self.initGroup() 44 | self.upDate() 45 | #初始化种群,随机生成若干个体 46 | def initGroup(self): 47 | self.group=[] 48 | i=0 49 | while(ibestFit): 63 | bestFit=person.fit 64 | best=person 65 | return best 66 | #计算种群中所有个体的平均距离 67 | def getAvg(self): 68 | sum=0 69 | for p in self.group: 70 | sum+=1/p.fit 71 | return sum/len(self.group) 72 | #根据适应度,使用轮盘赌返回一个个体,用于遗传交叉 73 | def getOne(self): 74 | #section的简称,区间 75 | sec=[0] 76 | sumsec=0 77 | for person in self.group: 78 | sumsec+=person.fit 79 | sec.append(sumsec) 80 | p=random.random()*sumsec 81 | for i in range(len(sec)): 82 | if(p>sec[i] and pbestFit): 90 | bestFit=person.fit 91 | best=person 92 | return best 93 | #计算种群中所有个体的平均距离 94 | def getAvg(self): 95 | sum=0 96 | for p in self.group: 97 | sum+=1/p.fit 98 | return sum/len(self.group) 99 | #根据适应度,使用轮盘赌返回一个个体,用于遗传交叉 100 | def getOne(self): 101 | #section的简称,区间 102 | sec=[0] 103 | sumsec=0 104 | for person in self.group: 105 | sumsec+=person.fit 106 | sec.append(sumsec) 107 | p=random.random()*sumsec 108 | for i in range(len(sec)): 109 | if(p>sec[i] and pself.bestFit): 78 | self.bestFit=newfit 79 | self.bestAddr=self.addr[:] 80 | 81 | #变异操作 82 | #设置变异后避免了所有鸟都聚集到一个离食物近,但又不是最近的地方,并且就停在那里不动了 83 | def change(self): 84 | i,j=random.randrange(0,48),random.randrange(0,48) 85 | self.addr[i],self.addr[j]=self.addr[j],self.addr[i] 86 | self.upDate() 87 | #贪婪倒立变异 88 | def reverse(self): 89 | #随机选择一个城市 90 | cityx=random.randrange(1,49) 91 | noxcity=self.addr[:] 92 | noxcity.remove(cityx) 93 | maxFit=0 94 | nearCity=noxcity[0] 95 | for c in noxcity: 96 | fit=calcfit([c,cityx]) 97 | if(fit>maxFit): 98 | maxFit=fit 99 | nearCity=c 100 | index1=self.addr.index(cityx) 101 | index2=self.addr.index(nearCity) 102 | tmp=self.addr[index1+1:index2+1] 103 | tmp.reverse() 104 | self.addr[index1+1:index2+1]=tmp 105 | self.upDate() 106 | 107 | #种群的类,里面有很多鸟 108 | class Group: 109 | def __init__(self): 110 | self.groupSize=500 #鸟的个数、粒子个数 111 | self.addrSize=48 #位置的维度,也就是TSP城市数量 112 | self.w=0.25 #w为惯性系数,也就是保留上次速度的程度 113 | self.pChange=0.1 #变异系数pChange 114 | self.pReverse=0.1 #贪婪倒立变异概率 115 | self.initBirds() 116 | self.best=self.getBest() 117 | self.Gen=0 118 | #初始化鸟群 119 | def initBirds(self): 120 | self.group=[] 121 | for i in range(self.groupSize): 122 | addr=[i+1 for i in range(self.addrSize)] 123 | random.shuffle(addr) 124 | bird=Bird(addr) 125 | self.group.append(bird) 126 | #获取当前离食物最近的鸟 127 | def getBest(self): 128 | bestFit=0 129 | bestBird=None 130 | #遍历群体里的所有鸟,找到路径最短的 131 | for bird in self.group: 132 | nowfit=calcfit(bird.addr) 133 | if(nowfit>bestFit): 134 | bestFit=nowfit 135 | bestBird=bird 136 | return bestBird 137 | #返回所有鸟的距离平均值 138 | def getAvg(self): 139 | sum=0 140 | for p in self.group: 141 | sum+=1/p.fit 142 | return sum/len(self.group) 143 | #打印最优位置的鸟的相关信息 144 | def showBest(self): 145 | print(self.Gen,":",1/self.best.fit) 146 | #更新每一只鸟的速度和位置 147 | def upDateBird(self): 148 | self.Gen+=1 149 | for bird in self.group: 150 | #g代表group,m代表me,分别代表自己和群组最优、自己最优的差 151 | deltag=switchB2A(self.best.addr,bird.addr) 152 | deltam=switchB2A(bird.bestAddr,bird.addr) 153 | newv=multiply(self.w,bird.v)[:]+multiply(random.random(),deltag)[:]+multiply(random.random(),deltam) 154 | bird.switch(newv) 155 | bird.v=newv 156 | if(random.random()self.best.fit): 162 | self.best=bird 163 | 164 | Gen=[] #代数 165 | dist=[] #距离 166 | avgDist=[] #平均距离 167 | #上面三个列表是为了画图 168 | group=Group() 169 | i=0 170 | #进行若干次迭代 171 | while(i<500): 172 | i+=1 173 | group.upDateBird() 174 | group.showBest() 175 | Gen.append(i) 176 | dist.append(1/group.getBest().fit) 177 | avgDist.append(group.getAvg()) 178 | #将过程可视化 179 | plt.plot(Gen,dist,'-r') 180 | plt.plot(Gen,avgDist,'-b') 181 | plt.show() 182 | 183 | 184 | #下面是进行若干次重复实验的代码 185 | # res=[] 186 | # for p in range(1,11): 187 | # group=Group() 188 | # i=0 189 | # while(i<500): 190 | # i+=1 191 | # group.upDateBird() 192 | # #group.showBest() 193 | # res.append(1/group.getBest().fit) 194 | # print("第{}次实验\t当前最优{}".format(p,1/group.getBest().fit)) 195 | # sum=0 196 | # for item in res: 197 | # sum+=item 198 | # print("10次实验平均值",sum/len(res)) 199 | -------------------------------------------------------------------------------- /Intelligent Optimization Algorithm/PSO/README.md: -------------------------------------------------------------------------------- 1 | # 2 粒子群寻优 2 | - [2.1算法介绍](#21算法介绍) 3 | - [2.2实验代码](#22实验代码) 4 | - [2.3实验结果](#23实验结果) 5 | - [2.4实验总结](#24实验总结) 6 | ## 2.1算法介绍 7 | 8 | 粒子群算法(particle swarm optimization,PSO)的思想源于对鸟/鱼群捕食行为的研究,模拟鸟集群飞行觅食的行为,鸟之间通过集体的协作使群体达到最优目的。 9 | 10 | 粒子群寻优算法作以下假设: 11 | 12 | 1. 每个寻优的问题解都被想像成一只鸟,称为“粒子”。所有粒子都在一个D维空间进行搜索。 13 | 2. 所有的粒子都由一个fitness function 确定适应值以判断目前的位置好坏。 14 | 3. 每一个粒子必须赋予记忆功能,能记住所搜寻到的最佳位置。 15 | 4. 每一个粒子还有一个速度以决定飞行的距离和方向。这个速度根据它本身的飞行经验以及同伴的飞行经验进行动态调整。 16 | 17 | 传统的粒子群寻优算法的位置更新公式如下 18 | 19 | ![PSO-format](../../img/PSO-format.png) 20 | 21 | 每一只鸟会根据自身速度惯性、自身最佳位置和群体最佳位置来决定下一时刻的速度(包括大小和方向),并根据速度来更新位置。 22 | 23 | 当应用PSO来解决TSP问题时需要进行一些改进,将多维的城市列表信息转换为一种坐标信息,并在此基础上定义相应的速度、加速度等。这些研究在卞锋的《粒子群优化算法在TSP中的研究及应用》和《求解TSP的改进QPSO算法》两篇文章中有详细介绍。其中最重要的一个概念是交换序,相当于传统PSO中的速度。在进行了这些改进之后就可以将PSO算法应用于TSP问题中了。 24 | 25 | ## 2.2实验代码 26 | 27 | ```python 28 | import random 29 | import math 30 | import matplotlib.pyplot as plt 31 | #读取数据 32 | f=open("test.txt") 33 | data=f.readlines() 34 | #将cities初始化为字典,防止下面被当成列表 35 | cities={} 36 | for line in data: 37 | #原始数据以\n换行,将其替换掉 38 | line=line.replace("\n","") 39 | #最后一行以EOF为标志,如果读到就证明读完了,退出循环 40 | if(line=="EOF"): 41 | break 42 | #空格分割城市编号和城市的坐标 43 | city=line.split(" ") 44 | map(int,city) 45 | #将城市数据添加到cities中 46 | cities[eval(city[0])]=[eval(city[1]),eval(city[2])] 47 | #计算适应度,也就是距离分之一,这里用伪欧氏距离 48 | def calcfit(addr): 49 | sum=0 50 | for i in range(-1,len(addr)-1): 51 | nowcity=addr[i] 52 | nextcity=addr[i+1] 53 | nowloc=cities[nowcity] 54 | nextloc=cities[nextcity] 55 | sum+=math.sqrt(((nowloc[0]-nextloc[0])**2+(nowloc[1]-nextloc[1])**2)/10) 56 | #最后要回到初始城市 57 | return 1/sum 58 | 59 | #生成交换序的函数,交换后数组b变为a,也就是a-b的结果 60 | def switchB2A(a,b): 61 | #防止传进来的b被更改 62 | tmpb=b[:] 63 | q=[] 64 | for i in range(len(a)): 65 | if(a[i]!=tmpb[i]): 66 | j=b.index(a[i]) 67 | q.append([i,j]) 68 | #刚学的简洁的交换list的方法 69 | tmpb[j],tmpb[i]=tmpb[i],tmpb[j] 70 | return q 71 | 72 | #w*v,w是一个数,v是一个数组。w乘v的数组长度,然后对结果取整,取数组的前这么多个元素 73 | def multiply(w,v): 74 | l=int(w*len(v)) 75 | res=v[0:l] 76 | return res 77 | 78 | #鸟个体的类,实现鸟位置的移动 79 | class Bird: 80 | def __init__(self,addr): 81 | self.addr=addr 82 | self.v=0 83 | #初始化时自己曾遇到得最优位置就是初始化的位置 84 | self.bestAddr=addr 85 | #初始状态没有速度 86 | self.v=[] 87 | self.fit=calcfit(self.addr) 88 | self.bestFit=self.fit 89 | 90 | #根据交换序移动位置 91 | def switch(self,switchq): 92 | for pair in switchq: 93 | i,j=pair[0],pair[1] 94 | self.addr[i],self.addr[j]=self.addr[j],self.addr[i] 95 | #交换后自动更行自己的成员变量 96 | self.upDate() 97 | 98 | #更新鸟自身相关信息 99 | def upDate(self): 100 | newfit=calcfit(self.addr) 101 | self.fit=newfit 102 | if(newfit>self.bestFit): 103 | self.bestFit=newfit 104 | self.bestAddr=self.addr 105 | 106 | #变异操作 107 | #设置变异后避免了所有鸟都聚集到一个离食物近,但又不是最近的地方,并且就停在那里不动了 108 | def change(self): 109 | i,j=random.randrange(0,48),random.randrange(0,48) 110 | self.addr[i],self.addr[j]=self.addr[j],self.addr[i] 111 | self.upDate() 112 | #贪婪倒立变异 113 | def reverse(self): 114 | #随机选择一个城市 115 | cityx=random.randrange(1,49) 116 | noxcity=self.addr[:] 117 | noxcity.remove(cityx) 118 | maxFit=0 119 | nearCity=noxcity[0] 120 | for c in noxcity: 121 | fit=calcfit([c,cityx]) 122 | if(fit>maxFit): 123 | maxFit=fit 124 | nearCity=c 125 | index1=self.addr.index(cityx) 126 | index2=self.addr.index(nearCity) 127 | tmp=self.addr[index1+1:index2+1] 128 | tmp.reverse() 129 | self.addr[index1+1:index2+1]=tmp 130 | self.upDate() 131 | 132 | #种群的类,里面有很多鸟 133 | class Group: 134 | def __init__(self): 135 | self.groupSize=500 #鸟的个数、粒子个数 136 | self.addrSize=48 #位置的维度,也就是TSP城市数量 137 | self.w=0.25 #w为惯性系数,也就是保留上次速度的程度 138 | self.pChange=0.1 #变异系数pChange 139 | self.pReverse=0.1 #贪婪倒立变异概率 140 | self.initBirds() 141 | self.best=self.getBest() 142 | self.Gen=0 143 | #初始化鸟群 144 | def initBirds(self): 145 | self.group=[] 146 | for i in range(self.groupSize): 147 | addr=[i+1 for i in range(self.addrSize)] 148 | random.shuffle(addr) 149 | bird=Bird(addr) 150 | self.group.append(bird) 151 | #获取当前离食物最近的鸟 152 | def getBest(self): 153 | bestFit=0 154 | bestBird=None 155 | #遍历群体里的所有鸟,找到路径最短的 156 | for bird in self.group: 157 | nowfit=calcfit(bird.addr) 158 | if(nowfit>bestFit): 159 | bestFit=nowfit 160 | bestBird=bird 161 | return bestBird 162 | #返回所有鸟的距离平均值 163 | def getAvg(self): 164 | sum=0 165 | for p in self.group: 166 | sum+=1/p.fit 167 | return sum/len(self.group) 168 | #打印最优位置的鸟的相关信息 169 | def showBest(self): 170 | print(self.Gen,":",1/self.best.fit) 171 | #更新每一只鸟的速度和位置 172 | def upDateBird(self): 173 | self.Gen+=1 174 | for bird in self.group: 175 | #g代表group,m代表me,分别代表自己和群组最优、自己最优的差 176 | deltag=switchB2A(self.best.addr,bird.addr) 177 | deltam=switchB2A(bird.bestAddr,bird.addr) 178 | newv=multiply(self.w,bird.v)[:]+multiply(random.random(),deltag)[:]+multiply(random.random(),deltam) 179 | bird.switch(newv) 180 | bird.v=newv 181 | if(random.random()self.best.fit): 187 | self.best=bird 188 | 189 | Gen=[] #代数 190 | dist=[] #距离 191 | avgDist=[] #平均距离 192 | #上面三个列表是为了画图 193 | group=Group() 194 | i=0 195 | #进行若干次迭代 196 | while(i<500): 197 | i+=1 198 | group.upDateBird() 199 | group.showBest() 200 | Gen.append(i) 201 | dist.append(1/group.getBest().fit) 202 | avgDist.append(group.getAvg()) 203 | #将过程可视化 204 | plt.plot(Gen,dist,'-r') 205 | plt.plot(Gen,avgDist,'-b') 206 | plt.show() 207 | ``` 208 | 209 | ## 2.3实验结果 210 | 211 | 下面是进行500次迭代后的结果,求出的最优解是11198 212 | 213 | ![PSO-result](../../img/PSO-result.png) 214 | 215 | 为避免计算过程的偶然性,下面进行10次重复实验并求平均值。 216 | 217 | ![PSO-repeat](../../img/PSO-repeat.png) 218 | 219 | ![PSO-visual](../../img/PSO-visual.png) 220 | 221 | 上图横坐标是迭代次数,纵坐标是距离,红色曲线是每次迭代最接近食物的鸟(也就是本次迭代的最优解,食物的位置也就是最优解城市序列所构成的坐标),蓝色曲线是每次迭代所有鸟的平均距离。可以看出不同于遗传算法,最初的最优解具有波动性,并不是一直下降的(遗传算法之所以一直下降是因为每次都保留的最优个体直接传到下一代)出现这种情况的原因是,最开始阶段所有鸟都是随机分散的,大家离食物的距离都差不多,就算是距离食物最近的鸟其能提供的信息的参考价值也不大。所以在开始的一段时间内最优位置的鸟在波动,而到后期,当食物位置更加确定之后,其波动性就消失了。 222 | 223 | 从趋势来看,无论是每次迭代的最优距离还是大家的平均距离,整体都是呈现下降趋势的,也就是说整个群体都是在朝着食物的位置移动。 224 | 225 | ## 2.4实验总结 226 | 227 | 1、在阅读完卞锋的两篇文章,并用他的方法将PSO应用于解决TSP问题后,让我认识到原来一种算法并不是拘泥于解决特定的一类问题,将算法与实际情况相结合,然后进行抽象和类比就能应用于新的问题的解决。这种抽象和类比的思维非常让我惊讶,我要好好学习。 228 | 229 | 2、引入遗传算法的变异操作 230 | 231 | 起初完成PSO时,测试发现很容易陷入离最优解较远的局部最优。在参考卞锋的《求解TSP的改进QPSO算法》后引入相关变异操作,从而解决了这个问题。 232 | 233 | 他提出的贪婪倒立变异很有意思。贪婪倒立变异是指找到一个城市,再找到离他最近的城市,然后将城市序列中两个城市之间的序列进行倒序排列。这样能够在实现优化了所选两个城市的距离的同时,保证其他城市顺序尽量不变化。 234 | 235 | 不知道作者是怎么想到这种变异方法的。我在看完文章后首先想到的是染色体变异中的倒位,也就是染色体中的一段旋转180度放回原来位置。染色体的倒位变异和作者提到的贪婪倒立变异非常相似。可能作者也是借鉴自然界的染色体变异吧,再次感叹自然界的智慧无穷。 236 | 237 | 传统的PSO没有变异操作,这种引入变异的操作是借鉴遗传算法的。可见吸收借鉴其他算法的精华,能够提升自身算法的效率。 -------------------------------------------------------------------------------- /Intelligent Optimization Algorithm/PSO/test.txt: -------------------------------------------------------------------------------- 1 | 1 6734 1453 2 | 2 2233 10 3 | 3 5530 1424 4 | 4 401 841 5 | 5 3082 1644 6 | 6 7608 4458 7 | 7 7573 3716 8 | 8 7265 1268 9 | 9 6898 1885 10 | 10 1112 2049 11 | 11 5468 2606 12 | 12 5989 2873 13 | 13 4706 2674 14 | 14 4612 2035 15 | 15 6347 2683 16 | 16 6107 669 17 | 17 7611 5184 18 | 18 7462 3590 19 | 19 7732 4723 20 | 20 5900 3561 21 | 21 4483 3369 22 | 22 6101 1110 23 | 23 5199 2182 24 | 24 1633 2809 25 | 25 4307 2322 26 | 26 675 1006 27 | 27 7555 4819 28 | 28 7541 3981 29 | 29 3177 756 30 | 30 7352 4506 31 | 31 7545 2801 32 | 32 3245 3305 33 | 33 6426 3173 34 | 34 4608 1198 35 | 35 23 2216 36 | 36 7248 3779 37 | 37 7762 4595 38 | 38 7392 2244 39 | 39 3484 2829 40 | 40 6271 2135 41 | 41 4985 140 42 | 42 1916 1569 43 | 43 7280 4899 44 | 44 7509 3239 45 | 45 10 2676 46 | 46 6807 2993 47 | 47 5185 3258 48 | 48 3023 1942 49 | EOF 50 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AI-Homework 2 | 人工智能结课作业(A星八数码/广度优先/深度优先/粒子群寻优算法/遗传算法/蚁群算法/BP神经网络/卷积神经网络) 3 | 4 | # 简介 5 | 6 | 本项目包含我当时人工智能与专家系统的结课作业。总共分为三大部分,每部分由几个相关算法组成,如下 7 | 8 | ### 搜索算法 9 | 10 | 1. [深度优先](https://github.com/roadwide/AI-Homework/tree/master/Search%20Algorithms/DFS) 11 | 2. [广度优先](https://github.com/roadwide/AI-Homework/tree/master/Search%20Algorithms/BFS) 12 | 3. [A星八数码](https://github.com/roadwide/AI-Homework/tree/master/Search%20Algorithms/Astar) 13 | 14 | 博客园地址:[https://www.cnblogs.com/roadwide/p/12890295.html](https://www.cnblogs.com/roadwide/p/12890295.html) 15 | 16 | Tips:三种算法都用于解决八数码问题。在Astar算法中比较了三者的性能,显然Astar要比另外两个强 17 | 18 | ### 智能优化算法 19 | 20 | 1. [遗传算法](https://github.com/roadwide/AI-Homework/tree/master/Intelligent%20Optimization%20Algorithm/GA) 21 | 2. [粒子群寻优算法](https://github.com/roadwide/AI-Homework/tree/master/Intelligent%20Optimization%20Algorithm/PSO) 22 | 3. [蚁群算法](https://github.com/roadwide/AI-Homework/tree/master/Intelligent%20Optimization%20Algorithm/ACO) 23 | 24 | 博客园地址:[https://www.cnblogs.com/roadwide/p/12890309.html](https://www.cnblogs.com/roadwide/p/12890309.html) 25 | 26 | Tips:三种算法都用于解决TSP问题,其中粒子群寻优算法不适合解决TSP问题,但经过改造后仍然可以用于解决TSP。数据集是att48,其最优解是10628/33523,这两个数分别是伪欧氏距离和欧氏距离 27 | 28 | ### 深度学习 29 | 30 | 1. [BP神经网络](https://github.com/roadwide/AI-Homework/tree/master/Deep%20Learning/BP/) 31 | 2. [卷积神经网络](https://github.com/roadwide/AI-Homework/tree/master/Deep%20Learning/CNN) 32 | 33 | 博客园地址:[https://www.cnblogs.com/roadwide/p/12890316.html](https://www.cnblogs.com/roadwide/p/12890316.html) 34 | 35 | Tips:两种算法都用于解决手写体识别。由于使用的是TensorFlow,已经很好的实现了深度学习的功能。所以主要是学习了深度学习的原理,并能够使用TensorFlow。(其实这个例子在官方教程就有) 36 | 37 | # 关于实验报告 38 | 39 | - 全部使用Python实现 40 | - 根据作业要求,每个算法都有相应的算法介绍、实验代码、实验结果、实验总结。 41 | - 当时不管是代码还是实验报告都写了挺长时间的,自我感觉写得挺好的。希望能对你有帮助(如果你的作业和这个差不多,希望不要纯抄,真的很有意思的) -------------------------------------------------------------------------------- /Search Algorithms/Astar/Astar.py: -------------------------------------------------------------------------------- 1 | import copy 2 | #棋盘的类,实现移动和扩展状态 3 | class grid: 4 | def __init__(self,stat): 5 | self.pre=None 6 | #stat是一个二维列表 7 | self.stat=stat 8 | self.find0() 9 | self.update() 10 | 11 | #更新启发函数的相关信息 12 | def update(self): 13 | self.fH() 14 | self.fG() 15 | self.fF() 16 | 17 | #G是深度,也就是走的步数 18 | def fG(self): 19 | if(self.pre!=None): 20 | self.G=self.pre.G+1 21 | else: 22 | self.G=0 23 | 24 | #H是和目标状态距离之和 25 | def fH(self): 26 | target=[[1,2,3],[8,0,4],[7,6,5]] 27 | self.H=0 28 | for i in range(3): 29 | for j in range(3): 30 | targetX=target[i][j] 31 | nowP=self.findx(targetX) 32 | #曼哈顿距离之和 33 | self.H+=abs(nowP[0]-i)+abs(nowP[1]-j) 34 | 35 | #F是启发函数,F=G+H 36 | def fF(self): 37 | self.F=self.G+self.H 38 | 39 | #以三行三列的形式输出当前状态 40 | def see(self): 41 | for i in range(3): 42 | print(self.stat[i]) 43 | print("F=",self.F,"G=",self.G,"H=",self.H) 44 | print("-"*10) 45 | 46 | #查看找到的解是如何从头移动的 47 | def seeAns(self): 48 | ans=[] 49 | ans.append(self) 50 | p=self.pre 51 | while(p): 52 | ans.append(p) 53 | p=p.pre 54 | ans.reverse() 55 | for i in ans: 56 | i.see() 57 | 58 | #找到数字x的位置 59 | def findx(self,x): 60 | for i in range(3): 61 | if(x in self.stat[i]): 62 | j=self.stat[i].index(x) 63 | return [i,j] 64 | 65 | #找到0,也就是空白格的位置 66 | def find0(self): 67 | self.zero=self.findx(0) 68 | 69 | #扩展当前状态,也就是上下左右移动。返回的是一个状态列表,也就是包含stat的列表 70 | def expand(self): 71 | i=self.zero[0] 72 | j=self.zero[1] 73 | gridList=[] 74 | if(j==2 or j==1): 75 | gridList.append(self.left()) 76 | if(i==2 or i==1): 77 | gridList.append(self.up()) 78 | if(i==0 or i==1): 79 | gridList.append(self.down()) 80 | if(j==0 or j==1): 81 | gridList.append(self.right()) 82 | return gridList 83 | 84 | 85 | #deepcopy多维列表的复制,防止指针赋值将原列表改变 86 | #move只能移动行或列,即row和col必有一个为0 87 | #向某个方向移动 88 | def move(self,row,col): 89 | newStat=copy.deepcopy(self.stat) 90 | tmp=self.stat[self.zero[0]+row][self.zero[1]+col] 91 | newStat[self.zero[0]][self.zero[1]]=tmp 92 | newStat[self.zero[0]+row][self.zero[1]+col]=0 93 | return newStat 94 | 95 | def up(self): 96 | return self.move(-1,0) 97 | 98 | def down(self): 99 | return self.move(1,0) 100 | 101 | def left(self): 102 | return self.move(0,-1) 103 | 104 | def right(self): 105 | return self.move(0,1) 106 | 107 | #判断状态g是否在状态集合中,g是对象,gList是对象列表 108 | #返回的结果是一个列表,第一个值是真假,如果是真则第二个值是g在gList中的位置索引 109 | def isin(g,gList): 110 | gstat=g.stat 111 | statList=[] 112 | for i in gList: 113 | statList.append(i.stat) 114 | if(gstat in statList): 115 | res=[True,statList.index(gstat)] 116 | else: 117 | res=[False,0] 118 | return res 119 | 120 | #Astar算法的函数 121 | def Astar(startStat): 122 | #open和closed存的是grid对象 123 | open=[] 124 | closed=[] 125 | #初始化状态 126 | g=grid(startStat) 127 | open.append(g) 128 | #time变量用于记录遍历次数 129 | time=0 130 | #当open表非空时进行遍历 131 | while(open): 132 | #根据启发函数值对open进行排序,默认升序 133 | open.sort(key=lambda G:G.F) 134 | #找出启发函数值最小的进行扩展 135 | minFStat=open[0] 136 | #检查是否找到解,如果找到则从头输出移动步骤 137 | if(minFStat.H==0): 138 | print("found and times:",time,"moves:",minFStat.G) 139 | minFStat.seeAns() 140 | break 141 | 142 | #走到这里证明还没有找到解,对启发函数值最小的进行扩展 143 | open.pop(0) 144 | closed.append(minFStat) 145 | expandStats=minFStat.expand() 146 | #遍历扩展出来的状态 147 | for stat in expandStats: 148 | #将扩展出来的状态(二维列表)实例化为grid对象 149 | tmpG=grid(stat) 150 | #指针指向父节点 151 | tmpG.pre=minFStat 152 | #初始化时没有pre,所以G初始化时都是0 153 | #在设置pre之后应该更新G和F 154 | tmpG.update() 155 | #查看扩展出的状态是否已经存在与open或closed中 156 | findstat=isin(tmpG,open) 157 | findstat2=isin(tmpG,closed) 158 | #在closed中,判断是否更新 159 | if(findstat2[0]==True and tmpG.Fnums[i]): 129 | N+=1 130 | return N 131 | 132 | #根据逆序数之和判断所给八数码是否可解 133 | def judge(src,target): 134 | N1=N(src) 135 | N2=N(target) 136 | if(N1%2==N2%2): 137 | return True 138 | else: 139 | return False 140 | 141 | 142 | #Astar算法的函数 143 | def Astar(startStat): 144 | #检查是否有解 145 | target=[[1,2,3],[4,5,6],[7,8,0]] 146 | if(judge(startStat, target)!=True): 147 | print("所给八数码无解,请检查输入") 148 | exit(1) 149 | 150 | #open和closed存的是grid对象 151 | open=[] 152 | closed=[] 153 | #初始化状态 154 | g=grid(startStat) 155 | open.append(g) 156 | #time变量用于记录遍历次数 157 | time=0 158 | #当open表非空时进行遍历 159 | while(open): 160 | #根据启发函数值对open进行排序,默认升序 161 | open.sort(key=lambda G:G.F) 162 | #找出启发函数值最小的进行扩展 163 | minFStat=open[0] 164 | #检查是否找到解,如果找到则从头输出移动步骤 165 | if(minFStat.H==0): 166 | print("found and times:",time,"moves:",minFStat.G) 167 | minFStat.seeAns() 168 | break 169 | 170 | #走到这里证明还没有找到解,对启发函数值最小的进行扩展 171 | open.pop(0) 172 | closed.append(minFStat) 173 | expandStats=minFStat.expand() 174 | #遍历扩展出来的状态 175 | for stat in expandStats: 176 | #将扩展出来的状态(二维列表)实例化为grid对象 177 | tmpG=grid(stat) 178 | #指针指向父节点 179 | tmpG.pre=minFStat 180 | #初始化时没有pre,所以G初始化时都是0 181 | #在设置pre之后应该更新G和F 182 | tmpG.update() 183 | #查看扩展出的状态是否已经存在与open或closed中 184 | findstat=isin(tmpG,open) 185 | findstat2=isin(tmpG,closed) 186 | #在closed中,判断是否更新 187 | if(findstat2[0]==True and tmpG.F>', self.listbox_click) 42 | self.heuristic_function_list.select_set(0) # 默认选择第一项 43 | 44 | self.init_button = tk.Button(self, text="随机初始化", command=self.set_init_puzzle) 45 | self.init_button.grid(column=0, row=3) 46 | self.one_step_button = tk.Button(self, text="单步执行", command=self.next_step) 47 | self.one_step_button.grid(column=1, row=3) 48 | self.continus_step_button = tk.Button(self, text="连续执行", command=self.all_step) 49 | self.continus_step_button.grid(column=2, row=3) 50 | # self.draw_search_tree_button = tk.Button(self, text="绘制搜索树") 51 | # self.draw_search_tree_button.grid(column=0, row=4) 52 | self.expand_num_button = tk.Button(self, text="统计扩展节点数", command=self.show_expand_num) 53 | self.expand_num_button.grid(column=1, row=4) 54 | self.show_exec_time_button = tk.Button(self, text="显示执行时间", command=self.show_exec_time) 55 | self.show_exec_time_button.grid(column=2, row=4) 56 | 57 | self.open_show_tip = tk.Label(self, text="open表实时显示") 58 | self.open_show_tip.grid(column=0, row=5, pady=10) 59 | self.open_list_text = tk.Text(self, width=50, height=10, state=tk.DISABLED) 60 | self.open_list_text.grid(column=0, row=6, columnspan=3) 61 | 62 | self.closed_show_tip = tk.Label(self, text="closed表实时显示") 63 | self.closed_show_tip.grid(column=0, row=7, pady=10) 64 | self.closed_list_text = tk.Text(self, width=50, height=10, state=tk.DISABLED) 65 | self.closed_list_text.grid(column=0, row=8, columnspan=3) 66 | 67 | self.tree_show_tip = tk.Label(self, text="搜索树实时显示") 68 | self.tree_show_tip.grid(column=4, row=0) 69 | self.tree_text = tk.Text(self, width=55, height=40, state=tk.DISABLED, wrap="none") 70 | self.tree_text.grid(column=4, row=1, rowspan=8, padx=20) 71 | 72 | 73 | def set_init_stat(self, text): 74 | self.init_stat["text"] = text 75 | 76 | 77 | def set_curr_min_stat(self, text): 78 | self.curr_min_stat["text"] = text 79 | 80 | 81 | def set_curr_expand_stat(self, text): 82 | self.curr_expand_stat["text"] = text 83 | 84 | 85 | def show_expand_num(self): 86 | tkinter.messagebox.showinfo(title="结果", message="扩展节点数: {}".format(len(self.open)+len(self.closed))) 87 | 88 | 89 | def show_exec_time(self): 90 | tkinter.messagebox.showinfo(title="结果", message="算法执行时间: {}".format(self.step_time)) 91 | 92 | 93 | def set_open_text(self, text): 94 | self.open_list_text.config(state=tk.NORMAL) 95 | self.open_list_text.delete(1.0, tk.END) 96 | self.open_list_text.insert(tk.END, text) 97 | self.open_list_text.config(state=tk.DISABLED) 98 | 99 | 100 | def set_closed_text(self, text): 101 | self.closed_list_text.config(state=tk.NORMAL) 102 | self.closed_list_text.delete(1.0, tk.END) 103 | self.closed_list_text.insert(tk.END, text) 104 | self.closed_list_text.config(state=tk.DISABLED) 105 | 106 | def set_tree_text(self, text): 107 | self.tree_text.config(state=tk.NORMAL) 108 | self.tree_text.delete(1.0, tk.END) 109 | self.tree_text.insert(tk.END, text) 110 | self.tree_text.config(state=tk.DISABLED) 111 | 112 | def set_init_puzzle(self, puzzle=None): 113 | self.node = [] 114 | self.tree = Tree() 115 | self.puzzle_matrix = puzzle if puzzle else getPuzzle() 116 | g = grid(self.puzzle_matrix, self.heuristic_method) 117 | self.insert_node(g) 118 | self.open = [g] # 防止非空 119 | self.closed = [] 120 | puzzle_text = matrix2text(self.puzzle_matrix) 121 | self.init_stat["text"] = puzzle_text 122 | self.curr_min_stat["text"] = "" 123 | self.curr_expand_stat["text"] = "" 124 | self.step_time = 0 125 | self.update_open_text() 126 | self.update_closed_text() 127 | 128 | def insert_node(self, node): 129 | # node 是一个grid实例 130 | if (node not in self.node): 131 | self.node.append(node) 132 | if (node.pre == None): # 根节点 133 | self.tree.create_node(stat2TreeNode(node), self.node.index(node)) 134 | else: 135 | self.tree.create_node(stat2TreeNode(node), self.node.index(node), self.node.index(node.pre)) 136 | self.set_tree_text(self.tree.show(stdout=False)) 137 | 138 | 139 | 140 | def update_open_text(self): 141 | openList = [] 142 | for g in self.open: 143 | openList.append(g.stat) 144 | text = matrixList2text(openList) 145 | self.set_open_text(text) 146 | 147 | def update_closed_text(self): 148 | closedList = [] 149 | for g in self.closed: 150 | closedList.append(g.stat) 151 | text = matrixList2text(closedList) 152 | self.set_closed_text(text) 153 | 154 | 155 | # 启发函数选择列表被点击时触发事件 156 | def listbox_click(self, event): # 要传event,具体原因不详 157 | try: 158 | # 更换启发式函数 159 | self.heuristic_method=self.heuristic_function_list.curselection()[0] 160 | self.set_init_puzzle(self.puzzle_matrix) # 更换启发式函数后重置,但初始矩阵不变 161 | except: 162 | pass # 失去焦点 163 | 164 | 165 | def next_step(self): 166 | self.step_time += 1 167 | if (len(self.expandStats) == 0): 168 | #根据启发函数值对open进行排序,默认升序 169 | self.open.sort(key=lambda G:G.F) 170 | #找出启发函数值最小的进行扩展 171 | self.minFStat=self.open[0] 172 | self.curr_min_stat["text"] = matrix2text(self.minFStat.stat) 173 | #检查是否找到解,如果找到则从头输出移动步骤 174 | if(self.minFStat.H==0): 175 | tkinter.messagebox.showinfo(title="结果", message="扩展节点数: {}\n算法执行时间: {}".format(len(self.open)+len(self.closed), self.step_time)) 176 | self.minFStat.seeAns() 177 | return 178 | #走到这里证明还没有找到解,对启发函数值最小的进行扩展 179 | self.open.pop(0) 180 | self.update_open_text() 181 | self.closed.append(self.minFStat) 182 | self.update_closed_text() 183 | self.expandStats=self.minFStat.expand() 184 | else: 185 | statMatrix = self.expandStats.pop() 186 | self.curr_expand_stat["text"] = matrix2text(statMatrix) 187 | #将扩展出来的状态(二维列表)实例化为grid对象 188 | tmpG=grid(statMatrix, self.heuristic_method) 189 | #指针指向父节点 190 | tmpG.pre=self.minFStat 191 | #初始化时没有pre,所以G初始化时都是0 192 | #在设置pre之后应该更新G和F 193 | tmpG.update() 194 | self.insert_node(tmpG) 195 | #查看扩展出的状态是否已经存在与open或closed中 196 | findstat=isin(tmpG,self.open) 197 | findstat2=isin(tmpG,self.closed) 198 | #在closed中,判断是否更新 199 | if(findstat2[0]==True and tmpG.Fnums[i]): 55 | N+=1 56 | return N 57 | 58 | #根据逆序数之和判断所给八数码是否可解 59 | def judge(src,target): 60 | N1=N(src) 61 | N2=N(target) 62 | if(N1%2==N2%2): 63 | return True 64 | else: 65 | return False 66 | 67 | 68 | # 返回一个可解的八数码矩阵 69 | def getPuzzle(): 70 | src = [1, 2, 3, 4, 5, 6, 7, 8, 0] 71 | target = [1, 2, 3, 4, 5, 6, 7, 8, 0] 72 | random.shuffle(src) 73 | while(not judge(src, target)): 74 | random.shuffle(src) 75 | matrix = [] 76 | for i in range(3): 77 | matrix.append([]) 78 | for j in range(3): 79 | matrix[-1].append(src[i*3+j]) 80 | return matrix 81 | 82 | 83 | #判断状态g是否在状态集合中,g是对象,gList是对象列表 84 | #返回的结果是一个列表,第一个值是真假,如果是真则第二个值是g在gList中的位置索引 85 | def isin(g,gList): 86 | gstat=g.stat 87 | statList=[] 88 | for i in gList: 89 | statList.append(i.stat) 90 | if(gstat in statList): 91 | res=[True,statList.index(gstat)] 92 | else: 93 | res=[False,0] 94 | return res -------------------------------------------------------------------------------- /Search Algorithms/Astar/README.md: -------------------------------------------------------------------------------- 1 | # 3 A*算法实现8数码问题 2 | - [3.1算法介绍](#31算法介绍) 3 | - [3.2实验代码](#32实验代码) 4 | - [3.3实验结果](#33实验结果) 5 | - [3.4实验总结](#34实验总结) 6 | ## 3.1算法介绍 7 | 8 | Astar算法是一种求解最短路径最有效的直接搜索方法,也是许多其他问题的常用启发式算法。它的启发函数为f(n)=g(n)+h(n),其中,f(n) 是从初始状态经由状态n到目标状态的代价估计,g(n) 是在状态空间中从初始状态到状态n的实际代价,h(n) 是从状态n到目标状态的最佳路径的估计代价。 9 | 10 | h(n)是启发函数中很重要的一项,它是对当前状态到目标状态的最小代价h\*(n)的一种估计,且需要满足 11 | 12 | > **h(n)<=h\*(n)** 13 | 14 | 也就是说h(n)是h*(n)的下界,这一要求保证了Astar算法能够找到最优解。这一点很容易想清楚,因为满足了这一条件后,启发函数的值总是小于等于最优解的代价值,也就是说寻找过程是在朝着一个可能是最优解的方向或者是比最优解更小的方向移动,如果启发函数值恰好等于实际最优解代价值,那么搜索算法在一直尝试逼近最优解的过程中会找到最优解;如果启发函数值比最优解的代价要低,虽然无法达到,但是因为方向一致,会在搜索过程中发现最优解。 15 | 16 | h是由我们自己设计的,h函数设计的好坏决定了Astar算法的效率。h值越大,算法运行越快。但是在设计评估函数时,需要注意一个很重要的性质:评估函数的值一定要小于等于实际当前状态到目标状态的代价。否则虽然程序运行速度加快,但是可能在搜索过程中漏掉了最优解。相对的,只要评估函数的值小于等于实际当前状态到目标状态的代价,就一定能找到最优解。所以,在这个问题中我们可以将评估函数设定为1-8八数字当前位置到目标位置的曼哈顿距离之和。 17 | 18 | Astar算法与BFS算法的不同之处在于每次会根据启发函数的值来进行排序,每次先出队的是启发函数值最小的状态。 19 | 20 | Astar算法可以被认为是Dijkstra算法的扩展。Dijkstra算法在搜索最短距离时是已知了各个节点之间的距离,而对于Astar而言,这个已知的距离被启发函数值替换。 21 | 22 | ## 3.2实验代码 23 | 24 | ```python 25 | import copy 26 | #棋盘的类,实现移动和扩展状态 27 | class grid: 28 | def __init__(self,stat): 29 | self.pre=None 30 | #目标状态 31 | self.target=[[1,2,3],[8,0,4],[7,6,5]] 32 | #stat是一个二维列表 33 | self.stat=stat 34 | self.find0() 35 | self.update() 36 | #更新启发函数的相关信息 37 | def update(self): 38 | self.fH() 39 | self.fG() 40 | self.fF() 41 | 42 | #G是深度,也就是走的步数 43 | def fG(self): 44 | if(self.pre!=None): 45 | self.G=self.pre.G+1 46 | else: 47 | self.G=0 48 | 49 | #H是和目标状态距离之和 50 | def fH(self): 51 | self.H=0 52 | for i in range(3): 53 | for j in range(3): 54 | targetX=self.target[i][j] 55 | nowP=self.findx(targetX) 56 | #曼哈顿距离之和 57 | self.H+=abs(nowP[0]-i)+abs(nowP[1]-j) 58 | 59 | #F是启发函数,F=G+H 60 | def fF(self): 61 | self.F=self.G+self.H 62 | 63 | #以三行三列的形式输出当前状态 64 | def see(self): 65 | for i in range(3): 66 | print(self.stat[i]) 67 | print("F=",self.F,"G=",self.G,"H=",self.H) 68 | print("-"*10) 69 | 70 | #查看找到的解是如何从头移动的 71 | def seeAns(self): 72 | ans=[] 73 | ans.append(self) 74 | p=self.pre 75 | while(p): 76 | ans.append(p) 77 | p=p.pre 78 | ans.reverse() 79 | for i in ans: 80 | i.see() 81 | 82 | #找到数字x的位置 83 | def findx(self,x): 84 | for i in range(3): 85 | if(x in self.stat[i]): 86 | j=self.stat[i].index(x) 87 | return [i,j] 88 | 89 | #找到0,也就是空白格的位置 90 | def find0(self): 91 | self.zero=self.findx(0) 92 | 93 | #扩展当前状态,也就是上下左右移动。返回的是一个状态列表,也就是包含stat的列表 94 | def expand(self): 95 | i=self.zero[0] 96 | j=self.zero[1] 97 | gridList=[] 98 | if(j==2 or j==1): 99 | gridList.append(self.left()) 100 | if(i==2 or i==1): 101 | gridList.append(self.up()) 102 | if(i==0 or i==1): 103 | gridList.append(self.down()) 104 | if(j==0 or j==1): 105 | gridList.append(self.right()) 106 | return gridList 107 | 108 | 109 | #deepcopy多维列表的复制,防止指针赋值将原列表改变 110 | #move只能移动行或列,即row和col必有一个为0 111 | #向某个方向移动 112 | def move(self,row,col): 113 | newStat=copy.deepcopy(self.stat) 114 | tmp=self.stat[self.zero[0]+row][self.zero[1]+col] 115 | newStat[self.zero[0]][self.zero[1]]=tmp 116 | newStat[self.zero[0]+row][self.zero[1]+col]=0 117 | return newStat 118 | 119 | def up(self): 120 | return self.move(-1,0) 121 | 122 | def down(self): 123 | return self.move(1,0) 124 | 125 | def left(self): 126 | return self.move(0,-1) 127 | 128 | def right(self): 129 | return self.move(0,1) 130 | 131 | #判断状态g是否在状态集合中,g是对象,gList是对象列表 132 | #返回的结果是一个列表,第一个值是真假,如果是真则第二个值是g在gList中的位置索引 133 | def isin(g,gList): 134 | gstat=g.stat 135 | statList=[] 136 | for i in gList: 137 | statList.append(i.stat) 138 | if(gstat in statList): 139 | res=[True,statList.index(gstat)] 140 | else: 141 | res=[False,0] 142 | return res 143 | 144 | #计算逆序数之和 145 | def N(grid): 146 | nums = [i for item in grid for i in item] 147 | N=0 148 | for i in range(len(nums)): 149 | if(nums[i]!=0): 150 | for j in range(i): 151 | if(nums[j]>nums[i]): 152 | N+=1 153 | return N 154 | 155 | #根据逆序数之和判断所给八数码是否可解 156 | def judge(src,target): 157 | N1=N(src) 158 | N2=N(target) 159 | if(N1%2==N2%2): 160 | return True 161 | else: 162 | return False 163 | 164 | #Astar算法的函数 165 | def Astar(startStat): 166 | #open和closed存的是grid对象 167 | open=[] 168 | closed=[] 169 | #初始化状态 170 | g=grid(startStat) 171 | #检查是否有解 172 | if(judge(startStat,g.target)!=True): 173 | print("所给八数码无解,请检查输入") 174 | exit(1) 175 | 176 | open.append(g) 177 | #time变量用于记录遍历次数 178 | time=0 179 | #当open表非空时进行遍历 180 | while(open): 181 | #根据启发函数值对open进行排序,默认升序 182 | open.sort(key=lambda G:G.F) 183 | #找出启发函数值最小的进行扩展 184 | minFStat=open[0] 185 | #检查是否找到解,如果找到则从头输出移动步骤 186 | if(minFStat.H==0): 187 | print("found and times:",time,"moves:",minFStat.G) 188 | minFStat.seeAns() 189 | break 190 | 191 | #走到这里证明还没有找到解,对启发函数值最小的进行扩展 192 | open.pop(0) 193 | closed.append(minFStat) 194 | expandStats=minFStat.expand() 195 | #遍历扩展出来的状态 196 | for stat in expandStats: 197 | #将扩展出来的状态(二维列表)实例化为grid对象 198 | tmpG=grid(stat) 199 | #指针指向父节点 200 | tmpG.pre=minFStat 201 | #初始化时没有pre,所以G初始化时都是0 202 | #在设置pre之后应该更新G和F 203 | tmpG.update() 204 | #查看扩展出的状态是否已经存在与open或closed中 205 | findstat=isin(tmpG,open) 206 | findstat2=isin(tmpG,closed) 207 | #在closed中,判断是否更新 208 | if(findstat2[0]==True and tmpG.Fnums[i]): 102 | N+=1 103 | return N 104 | 105 | #根据逆序数之和判断所给八数码是否可解 106 | def judge(src,target): 107 | N1=N(src) 108 | N2=N(target) 109 | if(N1%2==N2%2): 110 | return True 111 | else: 112 | return False 113 | 114 | #初始化状态 115 | startStat=[[2,8,3],[1,0,4],[7,6,5]] 116 | g=grid(startStat) 117 | if(judge(startStat,g.target)!=True): 118 | print("所给八数码无解,请检查输入") 119 | exit(1) 120 | 121 | visited=[] 122 | queue=[g] 123 | time=0 124 | while(queue): 125 | time+=1 126 | v=queue.pop(0) 127 | #判断是否找到解 128 | if(v.H==0): 129 | print("found and times:",time,"moves:",v.G) 130 | #查看找到的解是如何从头移动的 131 | v.seeAns() 132 | break 133 | else: 134 | #对当前状态进行扩展 135 | visited.append(v.stat) 136 | expandStats=v.expand() 137 | for stat in expandStats: 138 | tmpG=grid(stat) 139 | tmpG.pre=v 140 | tmpG.update() 141 | if(stat not in visited): 142 | queue.append(tmpG) 143 | -------------------------------------------------------------------------------- /Search Algorithms/BFS/README.md: -------------------------------------------------------------------------------- 1 | # 2 广度优先遍历搜索(BFS) 2 | - [2.1算法介绍](#21算法介绍) 3 | - [2.2实验代码](#22实验代码) 4 | - [2.3实验结果](#23实验结果) 5 | - [2.4实验总结](#24实验总结) 6 | ## 2.1算法介绍 7 | 8 | 广度优先搜索算法(英语:Breadth-First-Search,缩写为BFS),是一种图形搜索算法。简单的说,BFS是从根节点开始,沿着树的宽度遍历树的节点。如果所有节点均被访问,则算法中止。BFS是一种盲目搜索法,目的是系统地展开并检查图中的所有节点,以找寻结果。 9 | 10 | BFS会先访问根节点的所有邻居节点,然后再依次访问邻居节点的邻居节点,直到所有节点都访问完毕。在具体的实现中,使用open和closed两个表,open是一个队列,每次对open进行一次出队操作(并放入closed中),并将其邻居节点进行入队操作。直到队列为空时即完成了所有节点的遍历。closed表在遍历树时其实没有用,因为子节点只能从父节点到达。但在进行图的遍历时,一个节点可能会由多个节点到达,所以此时为了防止重复遍历应该每次都检查下一个节点是否已经在closed中了。 11 | 12 | ![节点搜索的顺序](../../img/tree.png) 13 | 14 | 依然使用上面的这个例子,如果使用BFS进行遍历,那么节点的访问顺序是“1-2-7-8-3-6-9-12-4-5-10-11”。可以看出来BFS进行遍历时是一层一层的搜索的。 15 | 16 | ![BFS](../../img/BFS.png) 17 | 18 | 在应用BFS算法进行八数码问题搜索时需要open和closed两个表。首先将初始状态加入open队列,然后进行出队操作并放入closed中,对出队的状态进行扩展(所谓扩展也就是找出其上下左右移动后的状态),将扩展出的状态加入队列,然后继续循环出队-扩展-入队的操作,直到找到解为止。 19 | 20 | 上图这个例子中,红圈里的数字是遍历顺序。当找到解时一直往前找父节点即可找出求解的移动路线。 21 | 22 | ## 2.2实验代码 23 | 24 | ```python 25 | import copy 26 | #棋盘的类,实现移动和扩展状态 27 | class grid: 28 | def __init__(self,stat): 29 | self.pre=None 30 | self.target=[[1,2,3],[8,0,4],[7,6,5]] 31 | self.stat=stat 32 | self.find0() 33 | self.update() 34 | #更新深度和距离和 35 | def update(self): 36 | self.fH() 37 | self.fG() 38 | #G是深度,也就是走的步数 39 | def fG(self): 40 | if(self.pre!=None): 41 | self.G=self.pre.G+1 42 | else: 43 | self.G=0 44 | #H是和目标状态距离之和,可以用来判断是否达到最优解 45 | def fH(self): 46 | self.H=0 47 | for i in range(3): 48 | for j in range(3): 49 | targetX=self.target[i][j] 50 | nowP=self.findx(targetX) 51 | self.H+=abs(nowP[0]-i)+abs(nowP[1]-j) 52 | #查看当前状态 53 | def see(self): 54 | print("depth:",self.G) 55 | for i in range(3): 56 | print(self.stat[i]) 57 | print("-"*10) 58 | #查看找到的解是如何从头移动的 59 | def seeAns(self): 60 | ans=[] 61 | ans.append(self) 62 | p=self.pre 63 | while(p): 64 | ans.append(p) 65 | p=p.pre 66 | ans.reverse() 67 | for i in ans: 68 | i.see() 69 | #找到数字x的位置,返回其坐标 70 | def findx(self,x): 71 | for i in range(3): 72 | if(x in self.stat[i]): 73 | j=self.stat[i].index(x) 74 | return [i,j] 75 | #找到0,也就是空白格的位置 76 | def find0(self): 77 | self.zero=self.findx(0) 78 | #对当前状态进行所有可能的扩展,返回一个扩展状态的列表 79 | def expand(self): 80 | i=self.zero[0] 81 | j=self.zero[1] 82 | gridList=[] 83 | 84 | if(j==2 or j==1): 85 | gridList.append(self.left()) 86 | if(i==2 or i==1): 87 | gridList.append(self.up()) 88 | if(i==0 or i==1): 89 | gridList.append(self.down()) 90 | if(j==0 or j==1): 91 | gridList.append(self.right()) 92 | 93 | return gridList 94 | 95 | 96 | #deepcopy多维列表的复制,防止指针赋值将原列表改变 97 | #move只能移动行或列,即row和col必有一个为0 98 | #对当前状态进行移动 99 | def move(self,row,col): 100 | newStat=copy.deepcopy(self.stat) 101 | tmp=self.stat[self.zero[0]+row][self.zero[1]+col] 102 | newStat[self.zero[0]][self.zero[1]]=tmp 103 | newStat[self.zero[0]+row][self.zero[1]+col]=0 104 | return newStat 105 | 106 | def up(self): 107 | return self.move(-1,0) 108 | 109 | def down(self): 110 | return self.move(1,0) 111 | 112 | def left(self): 113 | return self.move(0,-1) 114 | 115 | def right(self): 116 | return self.move(0,1) 117 | 118 | 119 | #计算逆序数之和 120 | def N(nums): 121 | N=0 122 | for i in range(len(nums)): 123 | if(nums[i]!=0): 124 | for j in range(i): 125 | if(nums[j]>nums[i]): 126 | N+=1 127 | return N 128 | 129 | #根据逆序数之和判断所给八数码是否可解 130 | def judge(src,target): 131 | N1=N(src) 132 | N2=N(target) 133 | if(N1%2==N2%2): 134 | return True 135 | else: 136 | return False 137 | 138 | #初始化状态 139 | startStat=[[2,8,3],[1,0,4],[7,6,5]] 140 | g=grid(startStat) 141 | if(judge(startStat,g.target)!=True): 142 | print("所给八数码无解,请检查输入") 143 | exit(1) 144 | 145 | visited=[] 146 | queue=[g] 147 | time=0 148 | while(queue): 149 | time+=1 150 | v=queue.pop(0) 151 | #判断是否找到解 152 | if(v.H==0): 153 | print("found and times:",time,"moves:",v.G) 154 | #查看找到的解是如何从头移动的 155 | v.seeAns() 156 | break 157 | else: 158 | #对当前状态进行扩展 159 | visited.append(v.stat) 160 | expandStats=v.expand() 161 | for stat in expandStats: 162 | tmpG=grid(stat) 163 | tmpG.pre=v 164 | tmpG.update() 165 | if(stat not in visited): 166 | queue.append(tmpG) 167 | 168 | ``` 169 | 170 | ## 2.3实验结果 171 | 172 | 仍然用相同的例子,用BFS进行搜索。 173 | 174 | ![BFS-case](../../img/DFS-case.png) 175 | 176 | 将找出的解从初始状态一步一步输出到解状态。 177 | 178 | ![BFS-process](../../img/BFS-process.png) 179 | 180 | 从结果中可以看出总共进行了27次遍历,并在第4层时找到了解状态。 181 | 182 | 下面我们来看一看BFS的所有27次遍历,以此来更深入的理解BFS的原理。稍微对代码进行改动,使其输出遍历次数和当前层数。由于结果太长,为了方便展示,下面将以树的形式展示。 183 | 184 | ![BFS-tree](../../img/BFS-tree.png) 185 | 186 | 上面输出的解就是按照红色路线标注找到的,从遍历次数可以看出是一层一层的找。 187 | 188 | ## 2.4实验总结 189 | 190 | 由于BFS是一层一层找的,所以一定能找到解,并且是最优解。虽然能找到最优解,但它的盲目性依然是一个很大的缺点。从上面的遍历树状图中,每一层都比上一层元素更多,且是近似于指数型的增长。也就是说,深度每增加一,这一层的搜索速度就要增加很多。 -------------------------------------------------------------------------------- /Search Algorithms/DFS/DFS.py: -------------------------------------------------------------------------------- 1 | import copy 2 | #棋盘的类,实现移动和扩展状态 3 | class grid: 4 | def __init__(self, stat): 5 | self.pre = None 6 | self.target = [[1, 2, 3], [8, 0, 4], [7, 6, 5]] 7 | self.stat = stat 8 | self.find0() 9 | self.update() 10 | #更新深度和距离和 11 | def update(self): 12 | self.fH() 13 | self.fG() 14 | 15 | # G是深度,也就是走的步数 16 | def fG(self): 17 | if (self.pre != None): 18 | self.G = self.pre.G + 1 19 | else: 20 | self.G = 0 21 | 22 | # H是和目标状态距离之和,可以用来判断是否找到解 23 | def fH(self): 24 | 25 | self.H = 0 26 | for i in range(3): 27 | for j in range(3): 28 | targetX = self.target[i][j] 29 | nowP = self.findx(targetX) 30 | self.H += abs(nowP[0] - i) + abs(nowP[1] - j) 31 | 32 | #以三行三列的形式输出当前状态 33 | def see(self): 34 | print("depth:", self.G) 35 | for i in range(3): 36 | print(self.stat[i]) 37 | print("-" * 10) 38 | 39 | # 查看找到的解是如何从头移动的 40 | def seeAns(self): 41 | ans = [] 42 | ans.append(self) 43 | p = self.pre 44 | while (p): 45 | ans.append(p) 46 | p = p.pre 47 | ans.reverse() 48 | for i in ans: 49 | i.see() 50 | #找到数字x的位置 51 | def findx(self, x): 52 | for i in range(3): 53 | if (x in self.stat[i]): 54 | j = self.stat[i].index(x) 55 | return [i, j] 56 | #找到0的位置,也就是空白格的位置 57 | def find0(self): 58 | self.zero = self.findx(0) 59 | 60 | #对当前状态进行扩展,也就是上下左右移动,返回的列表中是状态的二维列表,不是对象 61 | def expand(self): 62 | i = self.zero[0] 63 | j = self.zero[1] 64 | gridList = [] 65 | 66 | if (j == 2 or j == 1): 67 | gridList.append(self.left()) 68 | if (i == 2 or i == 1): 69 | gridList.append(self.up()) 70 | if (i == 0 or i == 1): 71 | gridList.append(self.down()) 72 | if (j == 0 or j == 1): 73 | gridList.append(self.right()) 74 | 75 | return gridList 76 | 77 | # deepcopy多维列表的复制,防止指针赋值将原列表改变 78 | # move只能移动行或列,即row和col必有一个为0 79 | #对当前状态进行移动的函数 80 | def move(self, row, col): 81 | newStat = copy.deepcopy(self.stat) 82 | tmp = self.stat[self.zero[0] + row][self.zero[1] + col] 83 | newStat[self.zero[0]][self.zero[1]] = tmp 84 | newStat[self.zero[0] + row][self.zero[1] + col] = 0 85 | return newStat 86 | 87 | def up(self): 88 | return self.move(-1, 0) 89 | 90 | def down(self): 91 | return self.move(1, 0) 92 | 93 | def left(self): 94 | return self.move(0, -1) 95 | 96 | def right(self): 97 | return self.move(0, 1) 98 | 99 | 100 | 101 | # 判断状态g是否在状态集合中,g是对象,gList是对象列表 102 | #返回的结果是一个列表,第一个值是真假,如果是真则第二个值是g在gList中的位置索引 103 | def isin(g, gList): 104 | gstat = g.stat 105 | statList = [] 106 | for i in gList: 107 | statList.append(i.stat) 108 | if (gstat in statList): 109 | res = [True, statList.index(gstat)] 110 | else: 111 | res = [False, 0] 112 | return res 113 | 114 | #计算逆序数之和 115 | def N(nums): 116 | N=0 117 | for i in range(len(nums)): 118 | if(nums[i]!=0): 119 | for j in range(i): 120 | if(nums[j]>nums[i]): 121 | N+=1 122 | return N 123 | 124 | #根据逆序数之和判断所给八数码是否可解 125 | def judge(src,target): 126 | N1=N(src) 127 | N2=N(target) 128 | if(N1%2==N2%2): 129 | return True 130 | else: 131 | return False 132 | 133 | #初始状态 134 | startStat = [[2, 8, 3], [1, 0, 4], [7, 6, 5]] 135 | g = grid(startStat) 136 | #判断所给的八数码受否有解 137 | if(judge(startStat,g.target)!=True): 138 | print("所给八数码无解,请检查输入") 139 | exit(1) 140 | #visited储存的是已经扩展过的节点 141 | visited = [] 142 | time = 0 143 | #用递归的方式进行DFS遍历 144 | def DFSUtil(v, visited): 145 | global time 146 | #判断是否达到深度界限 147 | if (v.G > 4): 148 | return 149 | time+=1 150 | #判断是否已经找到解 151 | if (v.H == 0): 152 | print("found and times", time, "moves:", v.G) 153 | v.seeAns() 154 | exit(1) 155 | 156 | #对当前节点进行扩展 157 | visited.append(v.stat) 158 | expandStats = v.expand() 159 | w = [] 160 | for stat in expandStats: 161 | tmpG = grid(stat) 162 | tmpG.pre = v 163 | tmpG.update() 164 | if (stat not in visited): 165 | w.append(tmpG) 166 | for vadj in w: 167 | DFSUtil(vadj, visited) 168 | #visited查重只对一条路,不是全局的,每条路开始时都为空 169 | #因为如果全局查重,会导致例如某条路在第100层找到的状态,在另一条路是第2层找到也会被当做重复 170 | #进而导致明明可能会找到解的路被放弃 171 | visited.pop() 172 | 173 | 174 | DFSUtil(g, visited) 175 | #如果找到解程序会在中途退出,走到下面这一步证明没有找到解 176 | print("在当前深度下没有找到解,请尝试增加搜索深度") 177 | 178 | -------------------------------------------------------------------------------- /Search Algorithms/DFS/README.md: -------------------------------------------------------------------------------- 1 | # 1 深度优先遍历搜索(DFS) 2 | - [1.1算法介绍](#11算法介绍) 3 | - [1.2实验代码](#12实验代码) 4 | - [1.3实验结果](#13实验结果) 5 | - [1.4实验总结](#14实验总结) 6 | ## 1.1算法介绍 7 | 8 | 深度优先搜索算法(Depth-First-Search,DFS)是一种用于遍历或搜索树或图的算法。沿着树的深度遍历树的节点,尽可能深的搜索树的分支。当节点v的所在边都己被探寻过,搜索将回溯到发现节点v的那条边的起始节点。这一过程一直进行到已发现从源节点可达的所有节点为止。如果还存在未被发现的节点,则选择其中一个作为源节点并重复以上过程,整个进程反复进行直到所有节点都被访问为止。属于盲目搜索。 9 | 10 | ![节点搜索的顺序](../../img/tree.png) 11 | 12 | 以上图为例,简述DFS的过程。首先从根节点“1”出发,按一定的顺序遍历其子节点,这里我们假设优先遍历左边的。所以,在遍历“1”之后,我们到了节点“2”,此时“2”仍有子节点,所以应继续向下遍历,下一个节点是“3”,然后是“4”。到了“4”之后,没有子节点了,说明我们已经将这一条路遍历完了,接着我们应该回溯,应该回到“4”的父节点,也就是“3”。因为“3”还有一个子节点“5”没有遍历,所以下一个我们应该遍历的是“5”。遍历完“5”之后又发现一条路到头了,再次回溯依然回溯到其父节点“3”,此时“3”的所有子节点都已经遍历完了,因该接着回溯到“3”的父节点“2”,然后检查“2”是否有没有遍历完的子节点。按照这样的规则,完成所有节点的遍历。最终得到的遍历顺序是“1-2-3-4-5-6-7-8-9-10-11-12” 13 | 14 | 15 | 16 | 在介绍了DFS在遍历树的应用后,我们将其应用于八数码问题的解决。八数码问题也称为九宫问题。在3×3的棋盘,摆有八个棋子,每个棋子上标有1至8的某一数字,不同棋子上标的数字不相同。棋盘上还有一个空格,与空格相邻的棋子可以移到空格中。要求解决的问题是:给出一个初始状态和一个目标状态,找出一种从初始转变成目标状态的移动棋子步数最少的移动步骤。所谓问题的一个状态就是棋子在棋盘上的一种摆法。棋子移动后,状态就会发生改变。解八数码问题实际上就是找出从初始状态到达目标状态所经过的一系列中间过渡状态。 17 | 18 | 上面说的DFS遍历的树是已经存在的,我们只需要按照规定的遍历方法就能完成遍历,而对于八数码问题,没有已经存在的路径供我们遍历,需要我们从初始状态向下延伸(也就是上下左右移动)才能构造出类似的树。 19 | 20 | ![DFS](../../img/DFS.png) 21 | 22 | 以上图为例。在使用DFS进行搜索时,每个状态都会按照一定的顺序进行上下左右移动(在上图中是下、左、右、上的顺序),一次移动后会产生一个新的状态,然后以新状态为起点继续按约定的顺序(例如先向下)移动。终止的条件是找到解或者达到深度界限。那么如果按照图中下、左、右、上的顺序搜索后的结果将会是最左边的一条路一直是优先向下移动,如果不能向下则依次会是左、右、上的一种。 23 | 24 | ## 1.2实验代码 25 | 26 | ```python 27 | import copy 28 | #棋盘的类,实现移动和扩展状态 29 | class grid: 30 | def __init__(self, stat): 31 | self.pre = None 32 | self.target = [[1, 2, 3], [8, 0, 4], [7, 6, 5]] 33 | self.stat = stat 34 | self.find0() 35 | self.update() 36 | #更新深度和距离和 37 | def update(self): 38 | self.fH() 39 | self.fG() 40 | 41 | # G是深度,也就是走的步数 42 | def fG(self): 43 | if (self.pre != None): 44 | self.G = self.pre.G + 1 45 | else: 46 | self.G = 0 47 | 48 | # H是和目标状态距离之和,可以用来判断是否找到解 49 | def fH(self): 50 | 51 | self.H = 0 52 | for i in range(3): 53 | for j in range(3): 54 | targetX = self.target[i][j] 55 | nowP = self.findx(targetX) 56 | self.H += abs(nowP[0] - i) + abs(nowP[1] - j) 57 | 58 | #以三行三列的形式输出当前状态 59 | def see(self): 60 | print("depth:", self.G) 61 | for i in range(3): 62 | print(self.stat[i]) 63 | print("-" * 10) 64 | 65 | # 查看找到的解是如何从头移动的 66 | def seeAns(self): 67 | ans = [] 68 | ans.append(self) 69 | p = self.pre 70 | while (p): 71 | ans.append(p) 72 | p = p.pre 73 | ans.reverse() 74 | for i in ans: 75 | i.see() 76 | #找到数字x的位置 77 | def findx(self, x): 78 | for i in range(3): 79 | if (x in self.stat[i]): 80 | j = self.stat[i].index(x) 81 | return [i, j] 82 | #找到0的位置,也就是空白格的位置 83 | def find0(self): 84 | self.zero = self.findx(0) 85 | 86 | #对当前状态进行扩展,也就是上下左右移动,返回的列表中是状态的二维列表,不是对象 87 | def expand(self): 88 | i = self.zero[0] 89 | j = self.zero[1] 90 | gridList = [] 91 | if (j == 2 or j == 1): 92 | gridList.append(self.left()) 93 | if (i == 2 or i == 1): 94 | gridList.append(self.up()) 95 | if (i == 0 or i == 1): 96 | gridList.append(self.down()) 97 | if (j == 0 or j == 1): 98 | gridList.append(self.right()) 99 | return gridList 100 | 101 | # deepcopy多维列表的复制,防止指针赋值将原列表改变 102 | # move只能移动行或列,即row和col必有一个为0 103 | #对当前状态进行移动的函数 104 | def move(self, row, col): 105 | newStat = copy.deepcopy(self.stat) 106 | tmp = self.stat[self.zero[0] + row][self.zero[1] + col] 107 | newStat[self.zero[0]][self.zero[1]] = tmp 108 | newStat[self.zero[0] + row][self.zero[1] + col] = 0 109 | return newStat 110 | 111 | def up(self): 112 | return self.move(-1, 0) 113 | 114 | def down(self): 115 | return self.move(1, 0) 116 | 117 | def left(self): 118 | return self.move(0, -1) 119 | 120 | def right(self): 121 | return self.move(0, 1) 122 | 123 | # 判断状态g是否在状态集合中,g是对象,gList是对象列表 124 | #返回的结果是一个列表,第一个值是真假,如果是真则第二个值是g在gList中的位置索引 125 | def isin(g, gList): 126 | gstat = g.stat 127 | statList = [] 128 | for i in gList: 129 | statList.append(i.stat) 130 | if (gstat in statList): 131 | res = [True, statList.index(gstat)] 132 | else: 133 | res = [False, 0] 134 | return res 135 | 136 | #计算逆序数之和 137 | def N(nums): 138 | N=0 139 | for i in range(len(nums)): 140 | if(nums[i]!=0): 141 | for j in range(i): 142 | if(nums[j]>nums[i]): 143 | N+=1 144 | return N 145 | 146 | #根据逆序数之和判断所给八数码是否可解 147 | def judge(src,target): 148 | N1=N(src) 149 | N2=N(target) 150 | if(N1%2==N2%2): 151 | return True 152 | else: 153 | return False 154 | 155 | #初始状态 156 | startStat = [[2, 8, 3], [1, 0, 4], [7, 6, 5]] 157 | g = grid(startStat) 158 | #判断所给的八数码受否有解 159 | if(judge(startStat,g.target)!=True): 160 | print("所给八数码无解,请检查输入") 161 | exit(1) 162 | #visited储存的是已经扩展过的节点 163 | visited = [] 164 | time = 0 165 | #用递归的方式进行DFS遍历 166 | def DFSUtil(v, visited): 167 | global time 168 | #判断是否达到深度界限 169 | if (v.G > 4): 170 | return 171 | time+=1 172 | #判断是否已经找到解 173 | if (v.H == 0): 174 | print("found and times", time, "moves:", v.G) 175 | v.seeAns() 176 | exit(1) 177 | 178 | #对当前节点进行扩展 179 | visited.append(v.stat) 180 | expandStats = v.expand() 181 | w = [] 182 | for stat in expandStats: 183 | tmpG = grid(stat) 184 | tmpG.pre = v 185 | tmpG.update() 186 | if (stat not in visited): 187 | w.append(tmpG) 188 | for vadj in w: 189 | DFSUtil(vadj, visited) 190 | #visited查重只对一条路,不是全局的,每条路开始时都为空 191 | #因为如果全局查重,会导致例如某条路在第100层找到的状态,在另一条路是第2层找到也会被当做重复 192 | #进而导致明明可能会找到解的路被放弃 193 | visited.pop() 194 | 195 | DFSUtil(g, visited) 196 | #如果找到解程序会在中途退出,走到下面这一步证明没有找到解 197 | print("在当前深度下没有找到解,请尝试增加搜索深度") 198 | ``` 199 | 200 | ## 1.3实验结果 201 | 202 | 以下面这个八数码为例,用DFS进行搜索。 203 | 204 | ![DFS-case](../../img/DFS-case.png) 205 | 206 | 将找出的解从初始状态一步一步输出到解状态。 207 | 208 | ![DFS-process](../../img/DFS-process.png) 209 | 210 | 可以看出总共进行了15次遍历,在某一条路的第4层找到了解。 211 | 212 | 下面我们来看一看DFS的所有15次遍历,以此来更深入的理解DFS的原理。稍微对代码进行改动,使其输出遍历次数和当前层数。由于结果太长,为了方便展示,下面将以树的形式展示。 213 | 214 | ![DFS-tree](../../img/DFS-tree.png) 215 | 216 | 上面输出的解就是按照红色路线标注找到的,从遍历次数可以看出DFS是一条道走到黑的找法,因为设置的深度界限是4,所以每一条路最多找到第4层。 217 | 218 | ## 1.4实验总结 219 | 220 | 1、为什么要设置深度界限? 221 | 222 | 因为理论上我们只需要一条路就可以找到解,只要不停地向下扩展就可以了。而这样做的缺点是会绕远路,也许第一条路找到第100层才找到解,但第二条路找两层就能找到解。从DFS的原理出发,我们不难看出这一点。还有一个问题是其状态数太多了,在不设置深度界限的情况下经常出现即使程序的栈满了依然没有找到解的情况。所以理论只是理论,在坚持“一条道走到黑”时,很可能因为程序“爆栈”而走到了黑还是没有找到解。 223 | 224 | 2、如何进行回溯? 225 | 226 | 在八数码问题中,我们回溯的条件只有一个,就是达到深度界限了。因为在找到解时会退出,找不到时会继续向下扩展。回溯的过程是先回溯到父节点,检查父节点是否还能扩展其他节点,如果能,就扩展新的节点并继续向下搜索,如果不能则递归地继续向上回溯。 227 | 228 | 3、出现重复状态怎么解决? 229 | 230 | 不难想出假如按照下、左、右、上这样地顺序进行搜索时,在第三层时就会出现和初始状态相同的情况。因为第二层向一个方向移动,第三层会有一个向反方向移动的状态也就是回到初始状态了。这样不仅增加了运算量,而且没有意义,会出现很多冗余步骤。所以我们应该设置一个查重的表,将已经遍历过的状态存入这个表中,当再次遇到这种情况时我们就跳过。 231 | 232 | 那么这个查重的表是该对于全局而言呢,还是每条路的查重表是独立的呢?在经过很多测试之后,我发现这个查重表对每条路独立是更好的。因为在一条路上出现的状态所需要的步数和另一条需要的步数不一定相同,也就是说我在第一条路上的第100层找到了某个状态,放入了查重表中,但是这个状态可能在另一条路上第2层就能找到,或许再下面几层就能找到解了,可是由于被放进了全局查重表中而放弃了这个条路的扩展,也损失了更快找到解的机会。所以一条路一个查重表是好的。 233 | 234 | 4、由于需要设置深度界限,每条路都会在深度界限处截至, 而如果所给的八数码的最优解大于深度界限,就会出现遍历完所有情况都找不解。而在事先不知道最优解的深度的情况下这个深度界限很难确定,设置大了会增大搜索时间,设置小了会找不到解。这也是DFS的一个缺点。 235 | 236 | 5、DFS不一定能找到最优解。因为深度界限的原因,找到的解可能在最优解和深度界限之间。 -------------------------------------------------------------------------------- /img/ACO-ppt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roadwide/AI-Homework/ba2b3e011630aa812c6489bf27cfef719cc75dcd/img/ACO-ppt.png -------------------------------------------------------------------------------- /img/ACO-repeat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roadwide/AI-Homework/ba2b3e011630aa812c6489bf27cfef719cc75dcd/img/ACO-repeat.png -------------------------------------------------------------------------------- /img/ACO-result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roadwide/AI-Homework/ba2b3e011630aa812c6489bf27cfef719cc75dcd/img/ACO-result.png -------------------------------------------------------------------------------- /img/ACO-visual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roadwide/AI-Homework/ba2b3e011630aa812c6489bf27cfef719cc75dcd/img/ACO-visual.png -------------------------------------------------------------------------------- /img/Astar-process.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roadwide/AI-Homework/ba2b3e011630aa812c6489bf27cfef719cc75dcd/img/Astar-process.png -------------------------------------------------------------------------------- /img/Astar-result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roadwide/AI-Homework/ba2b3e011630aa812c6489bf27cfef719cc75dcd/img/Astar-result.png -------------------------------------------------------------------------------- /img/Astar-tree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roadwide/AI-Homework/ba2b3e011630aa812c6489bf27cfef719cc75dcd/img/Astar-tree.png -------------------------------------------------------------------------------- /img/BFS-process.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roadwide/AI-Homework/ba2b3e011630aa812c6489bf27cfef719cc75dcd/img/BFS-process.png -------------------------------------------------------------------------------- /img/BFS-result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roadwide/AI-Homework/ba2b3e011630aa812c6489bf27cfef719cc75dcd/img/BFS-result.png -------------------------------------------------------------------------------- /img/BFS-tree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roadwide/AI-Homework/ba2b3e011630aa812c6489bf27cfef719cc75dcd/img/BFS-tree.png -------------------------------------------------------------------------------- /img/BFS.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roadwide/AI-Homework/ba2b3e011630aa812c6489bf27cfef719cc75dcd/img/BFS.png -------------------------------------------------------------------------------- /img/BP-chart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roadwide/AI-Homework/ba2b3e011630aa812c6489bf27cfef719cc75dcd/img/BP-chart.png -------------------------------------------------------------------------------- /img/BP-dfunc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roadwide/AI-Homework/ba2b3e011630aa812c6489bf27cfef719cc75dcd/img/BP-dfunc.png -------------------------------------------------------------------------------- /img/BP-func.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roadwide/AI-Homework/ba2b3e011630aa812c6489bf27cfef719cc75dcd/img/BP-func.png -------------------------------------------------------------------------------- /img/BP-lossfunc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roadwide/AI-Homework/ba2b3e011630aa812c6489bf27cfef719cc75dcd/img/BP-lossfunc.png -------------------------------------------------------------------------------- /img/BP-result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roadwide/AI-Homework/ba2b3e011630aa812c6489bf27cfef719cc75dcd/img/BP-result.png -------------------------------------------------------------------------------- /img/CNN-p1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roadwide/AI-Homework/ba2b3e011630aa812c6489bf27cfef719cc75dcd/img/CNN-p1.png -------------------------------------------------------------------------------- /img/CNN-p2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roadwide/AI-Homework/ba2b3e011630aa812c6489bf27cfef719cc75dcd/img/CNN-p2.png -------------------------------------------------------------------------------- /img/CNN-p3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roadwide/AI-Homework/ba2b3e011630aa812c6489bf27cfef719cc75dcd/img/CNN-p3.png -------------------------------------------------------------------------------- /img/CNN-result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roadwide/AI-Homework/ba2b3e011630aa812c6489bf27cfef719cc75dcd/img/CNN-result.png -------------------------------------------------------------------------------- /img/DFS-case.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roadwide/AI-Homework/ba2b3e011630aa812c6489bf27cfef719cc75dcd/img/DFS-case.png -------------------------------------------------------------------------------- /img/DFS-process.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roadwide/AI-Homework/ba2b3e011630aa812c6489bf27cfef719cc75dcd/img/DFS-process.png -------------------------------------------------------------------------------- /img/DFS-result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roadwide/AI-Homework/ba2b3e011630aa812c6489bf27cfef719cc75dcd/img/DFS-result.png -------------------------------------------------------------------------------- /img/DFS-tree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roadwide/AI-Homework/ba2b3e011630aa812c6489bf27cfef719cc75dcd/img/DFS-tree.png -------------------------------------------------------------------------------- /img/DFS.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roadwide/AI-Homework/ba2b3e011630aa812c6489bf27cfef719cc75dcd/img/DFS.png -------------------------------------------------------------------------------- /img/GA-repeat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roadwide/AI-Homework/ba2b3e011630aa812c6489bf27cfef719cc75dcd/img/GA-repeat.png -------------------------------------------------------------------------------- /img/GA-result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roadwide/AI-Homework/ba2b3e011630aa812c6489bf27cfef719cc75dcd/img/GA-result.png -------------------------------------------------------------------------------- /img/GA-visual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roadwide/AI-Homework/ba2b3e011630aa812c6489bf27cfef719cc75dcd/img/GA-visual.png -------------------------------------------------------------------------------- /img/PSO-format.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roadwide/AI-Homework/ba2b3e011630aa812c6489bf27cfef719cc75dcd/img/PSO-format.png -------------------------------------------------------------------------------- /img/PSO-repeat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roadwide/AI-Homework/ba2b3e011630aa812c6489bf27cfef719cc75dcd/img/PSO-repeat.png -------------------------------------------------------------------------------- /img/PSO-result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roadwide/AI-Homework/ba2b3e011630aa812c6489bf27cfef719cc75dcd/img/PSO-result.png -------------------------------------------------------------------------------- /img/PSO-visual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roadwide/AI-Homework/ba2b3e011630aa812c6489bf27cfef719cc75dcd/img/PSO-visual.png -------------------------------------------------------------------------------- /img/hard-case.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roadwide/AI-Homework/ba2b3e011630aa812c6489bf27cfef719cc75dcd/img/hard-case.png -------------------------------------------------------------------------------- /img/tree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roadwide/AI-Homework/ba2b3e011630aa812c6489bf27cfef719cc75dcd/img/tree.png --------------------------------------------------------------------------------