TensorFlow 實戰(zhàn)之實現(xiàn)卷積神經(jīng)網(wǎng)絡(luò)的實例講解
本文根據(jù)最近學(xué)習(xí)TensorFlow書籍網(wǎng)絡(luò)文章的情況,特將一些學(xué)習(xí)心得做了總結(jié),詳情如下.如有不當(dāng)之處,請各位大拿多多指點,在此謝過。
一、相關(guān)性概念
1、卷積神經(jīng)網(wǎng)絡(luò)(ConvolutionNeural Network,CNN)
19世紀(jì)60年代科學(xué)家最早提出感受野(ReceptiveField)。當(dāng)時通過對貓視覺皮層細(xì)胞研究,科學(xué)家發(fā)現(xiàn)每一個視覺神經(jīng)元只會處理一小塊區(qū)域的視覺圖像,即感受野。20世紀(jì)80年代,日本科學(xué)家提出神經(jīng)認(rèn)知機(Neocognitron)的概念,被視為卷積神經(jīng)網(wǎng)絡(luò)最初的實現(xiàn)原型。神經(jīng)認(rèn)知機中包含兩類神經(jīng)元:S-cells和C-cells。S-cells用來抽取特征,對應(yīng)我們現(xiàn)在主流卷積神經(jīng)網(wǎng)絡(luò)中的卷積核濾波操作;C-cells用來抗形變,對應(yīng)現(xiàn)在的激活函數(shù)、最大池化(Max-Pooling)等操作。
一般情況下,卷積神經(jīng)網(wǎng)絡(luò)由多個卷積層構(gòu)成,每個卷積層通常會進(jìn)行如下操作:
(1) 圖像通過多個不同的卷積核的濾波,并加偏置(bias),提取出局部特征,每一個卷積核會映射出一個新的2D圖像。
(2) 將前面卷積核的濾波處理結(jié)果,進(jìn)行非線性的激活函數(shù)處理。目前最常見的是使用ReLU函數(shù),之前Sigmoid函數(shù)應(yīng)用較多。
(3)多激活函數(shù)處理的結(jié)果再進(jìn)行池化操作(即降采樣,例如:將4*4的圖片降為1*1的圖片),一般會使用最大池化,保留最顯著特征,并提升模型畸變?nèi)萑棠芰Α?/p>
這幾個步驟就構(gòu)成了最常見的卷積層,也可以再加上一個LRN(LocalResponse Normalization,局部響應(yīng)歸一化層)層,現(xiàn)在非常流行的Trick還有BatchNormalization等。
2、池化層
3、卷積核尺寸
4、神經(jīng)網(wǎng)絡(luò)算法相關(guān)特性
4.1、優(yōu)點
(1)可以高效提取特征。
當(dāng)我們面對一個分類任務(wù)時,傳統(tǒng)的機器學(xué)習(xí)算法,一般要首先明確feature和label,然后拿數(shù)據(jù)取“喂”訓(xùn)練模型并保存,最后測試模型的準(zhǔn)確性。這就需要我們確定好特征,當(dāng)特征數(shù)目很少就無法精確進(jìn)行分類而引起欠擬合;當(dāng)特征數(shù)目很多,又會在分類過程中太過于看重某個特征引起分類錯誤,產(chǎn)生過擬合。而神經(jīng)網(wǎng)絡(luò)則不需要做大量的特征工程,可以直接把數(shù)據(jù)“灌”進(jìn)去而讓其自身訓(xùn)練,自我“修正”,即可達(dá)到預(yù)期效果。 ?。?)數(shù)據(jù)格式更加簡易
利用傳統(tǒng)的機器學(xué)習(xí)解決分類問題時,數(shù)據(jù)不能直接“灌”進(jìn)去的,需要對數(shù)據(jù)進(jìn)行一些處理,譬如量綱的歸一化,格式的轉(zhuǎn)化等等,然而在神經(jīng)網(wǎng)絡(luò)里卻不需要額外對數(shù)據(jù)做過多的處理。
(3) 參數(shù)數(shù)目的少量性
同樣在面對一個分類問題時,利用傳統(tǒng)機器學(xué)習(xí)SVM來做的話,需要涉及核函數(shù),懲罰因子,松弛變量等等參數(shù),而這些不同的參數(shù)組合會對模型效果產(chǎn)生不一樣的影響,想要迅速而又準(zhǔn)確的得到最適合模型的參數(shù),需要對相關(guān)理論知識有深入研究,但對于一個基本的三層神經(jīng)網(wǎng)絡(luò)來說(輸入-隱含-輸出),只需要初始化時給每一個神經(jīng)元上隨機的賦予一個權(quán)重w和偏置項b,在訓(xùn)練過程中,這兩個參數(shù)就會不斷的修正,調(diào)整到最優(yōu)質(zhì),使模型的誤差最小。所以從這個角度來看,我們的工作效率會更佳。 4.2、缺點
如果我們加深我網(wǎng)絡(luò)層,每一個網(wǎng)絡(luò)層都增加神經(jīng)元數(shù)量,則參數(shù)的個數(shù)將是M*N(m為網(wǎng)絡(luò)層數(shù),N為每層神經(jīng)元個數(shù)),這樣一來參數(shù)很多,引起模型復(fù)雜化,就更加不好調(diào)參,進(jìn)而會更加容易導(dǎo)致過擬合。另外,從神經(jīng)網(wǎng)絡(luò)的反向傳播的過程來看,梯度在反向傳播時,不斷的迭代會導(dǎo)致梯度越來越小,引起梯度趨近于0(梯度消失),梯度消失就使得權(quán)值無法更新,這個神經(jīng)元的存在就毫無意義,很難導(dǎo)致收斂。尤其是在圖像領(lǐng)域,直接使用最基本的神經(jīng)網(wǎng)絡(luò),是不合理的。
二、卷積神經(jīng)網(wǎng)絡(luò)基本原理
1、基本闡述
現(xiàn)在有一圖像,其尺寸大小是1000像素*1000像素且設(shè)定為黑白圖像,也就是只有一個顏色通道,則一張圖片就要100萬個像素點,輸入數(shù)據(jù)維度也是100萬維。如果連接的現(xiàn)在隱含層大小也是同樣大?。?00萬個隱含節(jié)點),最后將產(chǎn)生100萬*100萬即一億萬個連接。僅僅一個全連接(FullConnected Layer),就有一萬億連接的權(quán)重需要去訓(xùn)練,目前看,顯然是不劃算不現(xiàn)實。
通過局部連接(LocalConnect)方法優(yōu)化解決:由于每一個感受野只接受一小塊區(qū)域的信號,且這一小塊區(qū)域內(nèi)的像素是互相關(guān)聯(lián)的,每一個神經(jīng)元不需要接收全部像素點的信息,只需要接收局部的像素點作為輸入,而后將所有這些神經(jīng)元收到的局部信息綜合起來,就可以得到全局信息。假設(shè)局部感受野大小是10*10,即每個隱含節(jié)點只與10*10個像素點相連,現(xiàn)在只需要10*100萬即1億個連接。
現(xiàn)在隱含層每一個節(jié)點都與10*10的像素相連,即每一個隱含層節(jié)點都擁有100個參數(shù)。假設(shè)我們的局部連接方式是卷積操作,即默認(rèn)每一個隱含節(jié)點的參數(shù)都完全一樣,這樣參數(shù)從1億降為100。不管圖像大小是多大,一律都是這個10*10=100個參數(shù),即卷積核尺寸,顯然卷積核對縮小參數(shù)數(shù)量貢獻(xiàn)非常大、意義非凡。因此,此時,我們不需要再擔(dān)心有多少隱含節(jié)點或者圖片多大,參數(shù)量只跟卷積核的大小有關(guān),即所謂的權(quán)值共享。
總結(jié):卷積神經(jīng)網(wǎng)絡(luò)要素是局部連接(LocalConnection)、權(quán)值共享(WeightSharing)和池化層(Pooling)中的降采樣(Down-Sampling)。其中,局部連接和權(quán)值共享降低了參數(shù)量,訓(xùn)練復(fù)雜度被大大下降、過擬合被減輕。同時,權(quán)值共享還賦予了卷積網(wǎng)絡(luò)對平移的容忍性,而池化層降采樣則進(jìn)一步降低了輸出參數(shù)量,并賦予模型對輕度形變的容忍性,提供了模型的泛化能力。
2、LeNet5
1994年,大名鼎鼎的LeNet5誕生,作為最早的深層卷積神經(jīng)網(wǎng)絡(luò)之一,推動了深度學(xué)習(xí)的發(fā)展。自1998年開始,在多次成功迭代之后,由Yann LeCun完成的開拓性成果被命名為LeNet5。LeCun認(rèn)為,可訓(xùn)練參數(shù)的卷積層是一種利用少量參數(shù)在圖像的多個位置上提取相似特征的有效方式,這和直接把每個像素作為多層神經(jīng)網(wǎng)絡(luò)的輸入不一樣。像素不應(yīng)該被使用在輸入層,因為圖像具有很強的空間相關(guān)性,而使用圖像中獨立的像素直接作為輸入則利用不到這些相關(guān)性。筆者認(rèn)為,這些內(nèi)容比較重要。
在當(dāng)時,LeNet5的特性如下:
(1)每個卷積層包含三個部分:卷積、池化和非線性激活函數(shù);
(2)使用卷積提取空間特征;
(3)降采樣(Subsample)的平均池化層(AveragePooling);
(4)雙曲正切(Tanh)或S型(Sigmoid)的激活函數(shù);
(5)MLP作為最后的分類器;
(6)層與層之間的稀疏性連接減少計算復(fù)雜度。
三、TensorFlow 實現(xiàn)簡單的卷積網(wǎng)絡(luò)
1、簡要說明
這里使用的數(shù)據(jù)集依然是MNIST,使用兩個卷積層加一個全連接層構(gòu)建一個簡單但非常有代表性的卷積神經(jīng)網(wǎng)絡(luò),預(yù)計準(zhǔn)確率約為99.2%左右。
2、實現(xiàn)過程
#載入MNIST數(shù)據(jù)集,創(chuàng)建默認(rèn)的Interactive Session。 from tensorflow.examples.tutorials.mnist import input_data import tensorflow as tf mnist = input_data.read_data_sets("MNIST_data/", one_hot=True) sess = tf.InteractiveSession() #定義初始化函數(shù),以便重復(fù)使用創(chuàng)建權(quán)重、偏置、卷積層、池化層。 def weight_variable(shape): initial = tf.truncated_normal(shape, stddev=0.1) return tf.Variable(initial) def bias_variable(shape): initial = tf.constant(0.1, shape=shape) return tf.Variable(initial) def conv2d(x, W): return tf.nn.conv2d(x, W, strides=[1, 1, 1, 1], padding='SAME') def max_pool_2x2(x): return tf.nn.max_pool(x, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME') #在設(shè)計卷積神經(jīng)網(wǎng)絡(luò)結(jié)構(gòu)之前,定義輸入的placeholder,x是特征,y_是真實Label。 #由于卷積神經(jīng)網(wǎng)絡(luò)會使用到空間結(jié)構(gòu)信息,所以,需要將1D的輸入向量轉(zhuǎn)為2D圖片結(jié)構(gòu),即從1*784的形式轉(zhuǎn)換為原始的28*28結(jié)構(gòu)。 #因為只有一個顏色通道,所以最終尺寸為[-1,28,28,1],其中‘-1'代表樣本數(shù)量不固定,'1'代表顏色通道數(shù)量。 x = tf.placeholder(tf.float32, [None, 784]) y_ = tf.placeholder(tf.float32, [None, 10]) x_image = tf.reshape(x, [-1,28,28,1]) #定義第一個卷積層。 #先使用前面函數(shù)進(jìn)行初始化,包括weights和bias。其中[5,5,1,32]代表卷積核尺寸為5**5,1個顏色通道,32個不同的卷積核。 W_conv1 = weight_variable([5, 5, 1, 32]) b_conv1 = bias_variable([32]) h_conv1 = tf.nn.relu(conv2d(x_image, W_conv1) + b_conv1) h_pool1 = max_pool_2x2(h_conv1) #定義第二個卷積層。 #基本與第一個卷積層一樣,只是其中的卷積核數(shù)量變成64. W_conv2 = weight_variable([5, 5, 32, 64]) b_conv2 = bias_variable([64]) h_conv2 = tf.nn.relu(conv2d(h_pool1, W_conv2) + b_conv2) h_pool2 = max_pool_2x2(h_conv2) W_fc1 = weight_variable([7 * 7 * 64, 1024]) b_fc1 = bias_variable([1024]) h_pool2_flat = tf.reshape(h_pool2, [-1, 7*7*64]) h_fc1 = tf.nn.relu(tf.matmul(h_pool2_flat, W_fc1) + b_fc1) #為了減輕過擬合,使用一個Dropout層,其用法是通過一個placeholder傳入keep_prob比率來控制。 keep_prob = tf.placeholder(tf.float32) h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob) W_fc2 = weight_variable([1024, 10]) b_fc2 = bias_variable([10]) y_conv=tf.nn.softmax(tf.matmul(h_fc1_drop, W_fc2) + b_fc2) #定義損失函數(shù)cross_entropy,這里選擇Adam優(yōu)化器。 cross_entropy = tf.reduce_mean(-tf.reduce_sum(y_ * tf.log(y_conv), reduction_indices=[1])) train_step = tf.train.AdamOptimizer(1e-4).minimize(cross_entropy) #繼續(xù)定義評測準(zhǔn)確率操作。 correct_prediction = tf.equal(tf.argmax(y_conv,1), tf.argmax(y_,1)) accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32)) #開始訓(xùn)練過程。 tf.global_variables_initializer().run() for i in range(20000): batch = mnist.train.next_batch(50) if i%100 == 0: train_accuracy = accuracy.eval(feed_dict={ x:batch[0], y_: batch[1], keep_prob: 1.0}) print("step %d, training accuracy %g"%(i, train_accuracy)) train_step.run(feed_dict={x: batch[0], y_: batch[1], keep_prob: 0.5}) #全部訓(xùn)練完畢,在最終的測試集上進(jìn)行全面測試,得到整體的分類準(zhǔn)確率。 print("test accuracy %g"%accuracy.eval(feed_dict={ x: mnist.test.images, y_: mnist.test.labels, keep_prob: 1.0}))
3、執(zhí)行結(jié)果
Extracting MNIST_data/train-images-idx3-ubyte.gz Extracting MNIST_data/train-labels-idx1-ubyte.gz Extracting MNIST_data/t10k-images-idx3-ubyte.gz Extracting MNIST_data/t10k-labels-idx1-ubyte.gz step 0, training accuracy 0.1 step 100, training accuracy 0.82 step 200, training accuracy 0.9 step 300, training accuracy 0.9 step 400, training accuracy 0.96 step 500, training accuracy 0.9 step 600, training accuracy 0.94 step 700, training accuracy 0.96 step 800, training accuracy 0.94 step 900, training accuracy 0.98 . . . . . step 19600, training accuracy 1 step 19700, training accuracy 1 step 19800, training accuracy 1 step 19900, training accuracy 1 test accuracy 0.9929
4、模型分析
CNN模型的最終準(zhǔn)確率約為99.2%,基本上可以滿足對手寫數(shù)字識別準(zhǔn)確率的初步要求。相比于之前的MLP2%的錯誤率,CNN下降了60%左右。這里的性能提升主要在于更佳的網(wǎng)絡(luò)設(shè)計,也就是卷積網(wǎng)絡(luò)對圖像特征的提取和抽象能力。依靠卷積核的權(quán)值共享,CNN的參數(shù)數(shù)量沒有爆炸,降低計算量的同時也減去了過擬合,所以,整個模型的性能會有較大的提升。
四、TensorFlow實現(xiàn)進(jìn)階的卷積網(wǎng)絡(luò)
1、基本介紹
這里使用CIFAR-10數(shù)據(jù)集,包含60,000張32*32的彩色圖像,其中訓(xùn)練集50,000張,測試10,000張,一共標(biāo)注為10類,分別為airplane、automobile、bird、cat、deer、dog、frog、horse、shhip、truck,,每一類圖片6000張,其中沒有任何重疊情況發(fā)生,例如:automobile 只包括小型汽車,truck只包括卡車,不會出現(xiàn)一張圖片展現(xiàn)兩類物體的現(xiàn)象。
2、實現(xiàn)過程
#載入數(shù)據(jù) import cifar10,cifar10_input import tensorflow as tf import numpy as np import time max_steps = 3000 #訓(xùn)練輪數(shù) batch_size = 128 data_dir = '/tmp/cifar10_data/cifar-10-batches-bin'#下載數(shù)據(jù)默認(rèn)路徑。 def variable_with_weight_loss(shape, stddev, wl): var = tf.Variable(tf.truncated_normal(shape, stddev=stddev)) if wl is not None: weight_loss = tf.multiply(tf.nn.l2_loss(var), wl, name='weight_loss') tf.add_to_collection('losses', weight_loss) return var #計算CNN的損失。 def loss(logits, labels): labels = tf.cast(labels, tf.int64) cross_entropy = tf.nn.sparse_softmax_cross_entropy_with_logits( logits=logits, labels=labels, name='cross_entropy_per_example') cross_entropy_mean = tf.reduce_mean(cross_entropy, name='cross_entropy') tf.add_to_collection('losses', cross_entropy_mean) return tf.add_n(tf.get_collection('losses'), name='total_loss') cifar10.maybe_download_and_extract() images_train, labels_train = cifar10_input.distorted_inputs(data_dir=data_dir, batch_size=batch_size) images_test, labels_test = cifar10_input.inputs(eval_data=True, data_dir=data_dir, batch_size=batch_size) image_holder = tf.placeholder(tf.float32, [batch_size, 24, 24, 3]) label_holder = tf.placeholder(tf.int32, [batch_size]) #創(chuàng)建第一個卷積層。 weight1 = variable_with_weight_loss(shape=[5, 5, 3, 64], stddev=5e-2, wl=0.0) kernel1 = tf.nn.conv2d(image_holder, weight1, [1, 1, 1, 1], padding='SAME') bias1 = tf.Variable(tf.constant(0.0, shape=[64])) conv1 = tf.nn.relu(tf.nn.bias_add(kernel1, bias1)) pool1 = tf.nn.max_pool(conv1, ksize=[1, 3, 3, 1], strides=[1, 2, 2, 1], padding='SAME') norm1 = tf.nn.lrn(pool1, 4, bias=1.0, alpha=0.001 / 9.0, beta=0.75) #創(chuàng)建第二個卷積層。 weight2 = variable_with_weight_loss(shape=[5, 5, 64, 64], stddev=5e-2, wl=0.0) kernel2 = tf.nn.conv2d(norm1, weight2, [1, 1, 1, 1], padding='SAME') bias2 = tf.Variable(tf.constant(0.1, shape=[64])) conv2 = tf.nn.relu(tf.nn.bias_add(kernel2, bias2)) norm2 = tf.nn.lrn(conv2, 4, bias=1.0, alpha=0.001 / 9.0, beta=0.75) pool2 = tf.nn.max_pool(norm2, ksize=[1, 3, 3, 1], strides=[1, 2, 2, 1], padding='SAME') #使用一個全連接層,先把第二個卷積層的輸出結(jié)果flatten,將每個樣本都變成一維向量。 reshape = tf.reshape(pool2, [batch_size, -1]) dim = reshape.get_shape()[1].value weight3 = variable_with_weight_loss(shape=[dim, 384], stddev=0.04, wl=0.004) bias3 = tf.Variable(tf.constant(0.1, shape=[384])) local3 = tf.nn.relu(tf.matmul(reshape, weight3) + bias3) #下面這個使用的全連接層,其隱含節(jié)點數(shù)下降了一半。 weight4 = variable_with_weight_loss(shape=[384, 192], stddev=0.04, wl=0.004) bias4 = tf.Variable(tf.constant(0.1, shape=[192])) local4 = tf.nn.relu(tf.matmul(local3, weight4) + bias4) weight5 = variable_with_weight_loss(shape=[192, 10], stddev=1/192.0, wl=0.0) bias5 = tf.Variable(tf.constant(0.0, shape=[10])) logits = tf.add(tf.matmul(local4, weight5), bias5) loss = loss(logits, label_holder) train_op = tf.train.AdamOptimizer(1e-3).minimize(loss) #0.72 top_k_op = tf.nn.in_top_k(logits, label_holder, 1) sess = tf.InteractiveSession() tf.global_variables_initializer().run() tf.train.start_queue_runners() #正式開始訓(xùn)練。 for step in range(max_steps): start_time = time.time() image_batch,label_batch = sess.run([images_train,labels_train]) _, loss_value = sess.run([train_op, loss],feed_dict={image_holder: image_batch, label_holder:label_batch}) duration = time.time() - start_time if step % 10 == 0: examples_per_sec = batch_size / duration sec_per_batch = float(duration) format_str = ('step %d, loss = %.2f (%.1f examples/sec; %.3f sec/batch)') print(format_str % (step, loss_value, examples_per_sec, sec_per_batch)) #評測模型在測試集上的準(zhǔn)確率。 num_examples = 10000 import math num_iter = int(math.ceil(num_examples / batch_size)) true_count = 0 total_sample_count = num_iter * batch_size step = 0 while step < num_iter: image_batch,label_batch = sess.run([images_test,labels_test]) predictions = sess.run([top_k_op],feed_dict={image_holder: image_batch, label_holder:label_batch}) true_count += np.sum(predictions) step += 1 #最后,將準(zhǔn)確率的評測結(jié)果計算并輸出。 precision = true_count / total_sample_count print('precision @ 1 = %.3f' % precision)
3、執(zhí)行結(jié)果
由于筆者試了幾次CIFAR-10模塊,最后一步失敗,所以沒能正確顯示,后續(xù)找機會再試試,但以筆者的初步判斷,在CIFAR-10安裝成功的情況下,執(zhí)行結(jié)果應(yīng)該是沒問題的。感興趣的朋友可以再看看,這里就不貼出相關(guān)結(jié)果了,望各位網(wǎng)友理解。
4、模型分析
在CIFAR-10數(shù)據(jù)集上,通過一個短時間小迭代次數(shù)的訓(xùn)練,可以達(dá)到約73%的準(zhǔn)確率,后續(xù)若增加max_steps,期望準(zhǔn)確率會逐漸增加。若max_steps比較大的化,建議使用學(xué)習(xí)速率衰減(decay)的SGD來進(jìn)行訓(xùn)練,其訓(xùn)練過程中準(zhǔn)確率的峰值會較高,約86%,因為這其中的L2正則和LRN層的使用均提升了模型準(zhǔn)確率和模型的泛化性能。
五、小結(jié)
卷積網(wǎng)絡(luò)最后的幾個全連接層的作用是輸出分類結(jié)果,前面的卷積層主要做特征提取工作,直到最后的全連接層才開始對特征進(jìn)行組合分配,并進(jìn)行分類。
卷積層一般需要和一個池化層連接,二者組合是做圖像識別時的一個標(biāo)準(zhǔn)組件。卷積層的訓(xùn)練相對于全連接層更復(fù)雜,訓(xùn)練全連接層基本是進(jìn)行一個些矩陣乘法運算,而目前卷積層的訓(xùn)練基本上依賴于cuDNN實現(xiàn),其中的算法也相對復(fù)雜,甚至?xí)婕暗礁道锶~變換。
參考資料 主要參考資料《TensorFlow實戰(zhàn)》(黃文堅 唐源 著)(電子工業(yè)出版社)。
以上這篇TensorFlow 實戰(zhàn)之實現(xiàn)卷積神經(jīng)網(wǎng)絡(luò)的實例講解就是小編分享給大家的全部內(nèi)容了,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
Python使用matplotlib的pie函數(shù)繪制餅狀圖功能示例
這篇文章主要介紹了Python使用matplotlib的pie函數(shù)繪制餅狀圖功能,結(jié)合實例形式分析了Python使用matplotlib的pie函數(shù)進(jìn)行餅狀圖繪制的具體操作技巧,注釋中對pie函數(shù)的用法進(jìn)行了詳細(xì)的說明,便于理解,需要的朋友可以參考下2018-01-01關(guān)于探究python中sys.argv時遇到的問題詳解
這篇文章主要給大家介紹了python里sys.argv時遇到問題的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-02-02python?管理系統(tǒng)實現(xiàn)mysql交互的示例代碼
這篇文章主要介紹了python?管理系統(tǒng)實現(xiàn)mysql交互,本文通過實例代碼圖文相結(jié)合給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-12-12python基礎(chǔ)之面對對象基礎(chǔ)類和對象的概念
這篇文章主要介紹了python面對對象基礎(chǔ)類和對象的概念,實例分析了Python中返回一個返回值與多個返回值的方法,需要的朋友可以參考下2021-10-10