TensorFlow實現inception-resnet

TensorFlow實現inception-resnet
1 Star2 Stars3 Stars4 Stars5 Stars 給文章打分!
Loading...

論文地址

inception-v1inception-v2inception-v3inception-v4

論文解析網上已經有很多內容了,這裡就不過多贅述,只簡單過一遍

inception-v1:

inception-v1的重點有兩個,一個根據赫布理論,論文認為整個神經網路應該是被稀疏表達的,也就是相關性高的連線應該被聚集啟用。反應到影象上就是畫素特徵,我們簡單理解一下,假設一副人臉的影象,那麼人的眼睛周圍的畫素相關性就會很高,因為它們都屬於眼睛,那麼最後在神經網路中應該有一塊範圍的權重是用於表達這個特徵集合的,也就是說在經過n輪特徵採集後(比如卷積操作)最後的權重應該是稀疏的,例如眼睛聚集在一塊,鼻子聚集在一塊,當然這是很淺顯的解釋。另一個重點便是開始大量應用1×1的卷積操作,我們可以想到,1×1的卷積其實就是集中提取一個單位上的特徵,可能有人會問,一個單位怎麼提取,但是我們的資料其實不是單通道的,即使是輸入影象也是三通道的,結合前面我們很容易想到,一個畫素的rgb三個通道上的資料相關性實際上是非常高的,然而1×1卷積的計算量是卷積中最小的,就如論文中所說,這是價效比極高的一次特徵提取,只是視野比較小。1×1另一個重要的作用便是控制通道數,1×1卷積操作可以在不改變長寬的情況下壓縮或者增加通道,正是因為這個才產生了inception模組。

inception-v2:

inception-v2的重點便是提出了batch-normalization,也就是資料批次歸一化,歸一化操作是常見的減去均值,除以標準差,使資料分佈服從均值為0,方差為1。我也沒有深究為什麼有作用,有興趣的可以查詢相關資訊,一般認為是其規範了資料分佈使資料值波動不那麼大,使其對超引數不那麼敏感,所以優化演算法就更加有效果了。值得一提的是,這裡的batch-normalization一般是作用在非線性變換之前,也就是啟用函式之前做歸一化。

inception-v3

inception-v3的重點是使用小卷積替代大卷積,這裡放一張論文中的圖很好說明

我們很清楚看到,圖中的inception模組使用的都是1xn或者nx1的卷積了,以前是nxn的卷積,直觀上這樣做帶來的好處便是減少了計算量,卷積的計算量可以認為是  (資料大小-卷積核長或寬 2填充大小)/步長x卷積核大小x通道數,nxn的計算量很明顯比1xn和nx1的計算和要大。inception一貫的思想就是以最小的計算量換取最好的效果,當然這些很多都是來自他人的靈感,所以inception看起來像是吸收各家所長。然後就是說小卷積組合能更加細緻的提取特徵,這些原理我也不深究了,論文中細節有興趣者可以自行查閱。

inception-v4

inception-v4也就是目前的終極版本,也是今天的重點要實現的內容,重點有兩部分,一部分是對inception-v3進行架構的細化與改進,inception模組中嵌入inception模組,但是總的來看模型是一致的。另一部分便是結合了微軟的resnet,也就是殘差網路,使用了新的inception模組,也就是inception-resnet模組,相較於傳統的inception-v4模型inception-resnet-v2計算量更小,能達到相差不多的效果。至於resnet的重點,也就是殘差塊,這裡簡單提一下,就是在普通網路的基礎上,將隱藏層的輸入直接通過一個額外的分支加到最後輸出上面,這裡放一張論文的圖

事前準備

我這邊使用的是Windows平臺TensorFlow1.4 gpu版本,當然如果是cpu版本也玩不了,太慢了,這個網路可是用來跑ImageNet的。另外你可以下載前面的inception-v4論文,因為接下來要對照論文進行實現。當然這裡只是嘗試模擬,可能會有錯誤,歡迎指正。最後就是資料集的準備了,這裡使用的是ImageNet dog的一部分,來自網友分享

連結: https://pan.baidu.com/s/1pMS6Xon 密碼: dnha

裡面有訓練集的資料和標籤,測試集只有資料沒有標籤,當然,如果你問Windows怎麼裝TensorFlow啊,推薦:

Windows下安裝TensorFlow

準備好了就準備進入正題吧

inception-resnet網路主體

如果你已經閱讀了inception-v4的論文,你可能會發現裡面提到了兩個inception-resnet,一個是v1,一個是v2,v1是對應於inception-v3進行resnet改造的,v2就自然是對應於inception-v4,這裡我們只管v2,其實他倆結構是一樣的。另外讀者可以下載TensorFlow的開源模組實現,在research/slim/net中有其他網路的實現,這裡也是參考了一部分。

TensorFlow model

現在我們可以開啟inception-v4論文了,我們找到17號圖

 Schema for Inception-ResNet-v1 and Inception-ResNet-v2 networks. This schema applies to both networks but the underlying components differ. Inception-ResNet-v1 uses the blocks as described in Figures 14, 10, 7, 11, 12 and 13. Inception-ResNet-v2 uses the blocks as described in Figures 3, 16, 7,17, 18 and 19. The output sizes in the diagram refer to the activation vector tensor shapes of Inception-ResNet-v1.

它說這個架構是resnet v1和v2共用的,但是裡面的元件細節可能有不同,v1使用元件為圖14,10巴拉巴拉,v2為巴拉巴拉,輸出大小兩者都適用。

知道了這些,我們就清楚瞭如何來看這篇論文了,其實這篇論文敘述文字並不多,主要是以圖來講解結構,因為使用的理論前面都有了。所以我們直接按順序來看那些用於組成inception-v2的圖,當然,論文中提到了一些細節,後面會說。回過頭來看這張圖,首先它的輸入是299x299x3,這是ImageNet當年的規定。然後進入一個叫stem的模組,出來後變為35x35x256,這是第一步提取特徵。

Stem

我們找到stem模組,圖號為3

我們可以新建一個python檔案mystruct.py,裡面用於存放這裡模組,本著簡單說事的原則,這裡就不詳細展開其中的卷積操作了,希望讀者已經熟悉卷積操作,對圖中的輸入輸出能夠自己推算。所以如果不是特殊情況,這裡就直接上程式碼了。先說明一下,圖中矩形,從上到下從左到右依次為

  1. 卷積核大小
  2. 操作,卷積或者池化
  3. 通道數
  4. 標出stride代表步長,一般為1時不標出
  5. V代表valid,不填充,不標出則表示SAME填充,讀者從輸入輸出大小變化也可以看出
  6. filter concat表示張量連線,圖中可以看見連線後通道數為前者的和
import tensorflow as tf
import tensorflow.contrib.slim as slim
def Stem(inputs):
output = slim.conv2d(inputs, 32, [3, 3], stride=2, padding='VALID')
output = slim.conv2d(output, 32, [3, 3], padding='VALID')
output = slim.conv2d(output, 64, [3, 3])
output_left = slim.max_pool2d(output, [3, 3])
output_right = slim.conv2d(output, 96, [3, 3], stride=2, padding='VALID')
output = tf.concat([output_left, output_right], 3)
output_left = slim.conv2d(output, 64, [1, 1])
output_left = slim.conv2d(output_left, 96, [3, 3], padding='VALID')
output_right = slim.conv2d(output, 64, [1, 1])
output_right = slim.conv2d(output_right, 64, [7, 1])
output_right = slim.conv2d(output_right, 64, [1, 7])
output_right = slim.conv2d(output_right, 96, [3, 3], padding='VALID')
output = tf.concat([output_left, output_right], 3)
output_left = slim.conv2d(output, 192, [3, 3], stride=2, padding='VALID')
output_right = slim.max_pool2d(output, [3, 3])
output = tf.concat([output_left, output_right], 3)
return tf.nn.relu(output)

要提的一點是,這裡為了簡化網路構建,使用TensorFlow中的slim元件,可以將啟用函式,批次歸一化這些操作統一到一個操作中了,所以你現在看到conv2d函式並沒有接啟用函式。程式碼完全根據圖上的結構寫的,conv2d函式預設stride為1,padding為SAME,max_pool2d函式預設stride為2,padding為VALID。concat函式便是張量連線函式,第二個引數用於指定連線的維度,因為TensorFlow中的維度順序是[樣本數,寬,高,通道數],所以我們要連線通道數,從0開始第3個。

inception-resnet-A

找到inception-resnet-A模組,圖號為16

這是第一個inception-resnet模組,有兩個地方需要提一下,看到論文3.2. Residual Inception Blocks章節開頭:

For the residual versions of the Inception networks, we use cheaper Inception blocks than the original Inception. Each Inception block is followed by filter-expansion layer (1 × 1 convolution without activation) which is used for scaling up the dimensionality of the filter bank before the addition to match the depth of the input. This is needed to

compensate for the dimensionality reduction induced by the Inception block.

意思是,在這個殘差版本的inception模組,使用更簡單的inception模型(可以看到才三個簡單分支,比起inception-v4來說,確實是簡單許多)。並且每個inception分支後面都跟著一個1×1線性卷積層(就是不帶啟用函式的卷積層),這是為了匹配輸入時的維度,因為殘差分支是直接將輸入送給輸出,但是inception這邊很明顯進行通道壓縮了,所以要通過1×1卷積重新擴增到輸入時的維度,用原文話說就是,補償inception模組帶來的降維。

另外一個地方是論文3.3. Scaling of the Residuals:

Also we found that if the number of filters exceeded 1000, the residual variants started to exhibit instabilities and the network has just “died” early in the training, meaning that the last layer before the average pooling started to pro-
duce only zeros after a few tens of thousands of iterations.This could not be prevented, neither by lowering the learn-

ing rate, nor by adding an extra batch-normalization to this layer.

它說如果通道數量超過1000時,這個inception-resnet就會失去穩定,並且在訓練的早期死亡,死亡意指幾千次迭代後在平均池化前的最後一層會開始完全零化,就是隻產生很小的數。並且這種現象無法被避免,無論是調低學習率還是增加批次歸一化在這一層。

We found that scaling down the residuals before adding them to the previous layer activation seemed to stabilize the

training. In general we picked some scaling factors between 0.1 and 0.3 to scale the residuals before their being added to the accumulated layer activations (cf. Figure 20).

但是他們發現如果在將其新增到前一層啟用函式之前進行縮小似乎就能穩定訓練,一般他們會挑選一些0.1到0.3之間的縮放係數在進行累加層啟用之前來壓縮inception-resnet模組。如圖20

我們找到圖20

我們可以看見圖中表達的意思是在inception模組與殘差分支相加前進行縮放,前面論文意思和這個似乎有點對不上,不過可能是我水平不夠,沒get到,不過這裡還是以圖為主,並且取論文中的0.1作為係數。

def Inception_ResNet_A(inputs, activation_fn=tf.nn.relu):
output_res = tf.identity(inputs)
output_inception_a = slim.conv2d(inputs, 32, [1, 1])
output_inception_a = slim.conv2d(output_inception_a, 384, [1, 1], activation_fn=None)
output_inception_b = slim.conv2d(inputs, 32, [1, 1])
output_inception_b = slim.conv2d(output_inception_b, 32, [3, 3])
output_inception_b = slim.conv2d(output_inception_b, 384, [1, 1], activation_fn=None)
output_inception_c = slim.conv2d(inputs , 32, [1, 1])
output_inception_c = slim.conv2d(output_inception_c, 48, [3, 3])
output_inception_c = slim.conv2d(output_inception_c, 64, [3, 3])
output_inception_c = slim.conv2d(output_inception_c, 384, [1, 1], activation_fn=None)
output_inception = tf.add_n([output_inception_a, output_inception_b, output_inception_c])
output_inception = tf.multiply(output_inception, 0.1)
return activation_fn(tf.add_n([output_res, output_inception]))

tf.identity是恆等對映,就是輸入等於輸出,可能會有人問為什麼要多此一舉,這個我也無法給出完全合理的解釋,不過這樣可以額外增加一個節點,使其在計算圖中可以被顯示,在使用tensorboard時可能需要。我們可以看到在最後的1×1卷積操作中我額外顯示指定了啟用函式為空,因為這是線性的。tf.add_n函式為張量相加函式,需要列表中張量必須形狀完全一致,這裡都是提前計算好的形狀,正常情況都應該是一致的。可以看到在inception輸出與res輸出相加之前進行了0.1的數乘操作,縮小10倍。

Reduction-A

我們找到reduction-a模組,圖號為7

細心的同學應該發現,這張圖上通道數是變數。是的,這個模組是三個模型共用的,所以不同模型有不同係數,我們可以找到3.3節的最後部分,表1

def Reduction_A(inputs):
output_a = slim.max_pool2d(inputs, [3, 3])
output_b = slim.conv2d(inputs, 384, [3, 3], stride=2, padding='VALID')
output_c = slim.conv2d(inputs, 256, [1, 1])
output_c = slim.conv2d(output_c, 256, [3, 3])
output_c = slim.conv2d(output_c, 384, [3, 3], stride=2, padding='VALID')
return tf.nn.relu(tf.concat([output_a, output_b, output_c], 3))

如其字面意思,reduction就是減少的意思,這個模組負責減半資料空間尺寸,並且加深通道,不知道從某種程度上我們是否可以將其理解為聚集特徵。要提的一點是,我這裡在最後都是加了一個relu啟用函式的,讀者可以試試不同情況。

inception-resnet-B

圖號為17

def Inception_ResNet_B(inputs, acfivation_fn=tf.nn.relu):
output_res = tf.identity(inputs)
output_a = slim.conv2d(inputs, 192, [1, 1])
output_a = slim.conv2d(output_a, 1152, [1, 1], activation_fn=None)
output_b = slim.conv2d(inputs, 128, [1, 1])
output_b = slim.conv2d(output_b, 160, [1, 7])
output_b = slim.conv2d(output_b, 192, [7, 1])
output_b = slim.conv2d(output_b, 1152, [1, 1], activation_fn=None)
output = tf.add_n([output_a, output_b])
output = tf.multiply(output, 0.1)
return acfivation_fn(tf.add_n([output_res, output]))

細心的同學應該發現了程式碼中的通道數與圖中不服,我們可以計算一下,按照前面的流程,reduction-a輸出的應該是1152通道,應該是384×3,但是圖中為1154,我也不去管這是為什麼了,不過我們要知道的是,inception-resnet模組的輸入與輸出應該是一致的。其實到這裡我們發現inception模組都是1×1卷積然後接不同大小卷積,這其實有一種換不同的視野進行相關性特徵提取的感覺,然後到reduction模組就是將提取的特徵進行聚合,當然這只是邏輯上這麼想。

Reduction-B

圖號為18

def Reduction_B(inputs):
output_a = slim.max_pool2d(inputs, [3, 3])
output_b = slim.conv2d(inputs, 256, [1, 1])
output_b = slim.conv2d(output_b, 384, [3, 3], stride=2, padding='VALID')
output_c = slim.conv2d(inputs, 256, [1, 1])
output_c = slim.conv2d(output_c, 256, [1, 1])
output_c = slim.conv2d(output_c, 288, [3, 3], stride=2, padding='VALID')
output_d = slim.conv2d(inputs, 256, [1, 1])
output_d = slim.conv2d(output_d, 288, [3, 3])
output_d = slim.conv2d(output_d, 320, [3, 3], stride=2, padding='VALID')
return tf.nn.relu(tf.concat([output_a, output_b, output_c, output_d], 3))
inception-resnet-C

圖19

def Inception_ResNet_C(inputs, activation_fn=tf.nn.relu):
output_res = tf.identity(inputs)
output_a = slim.conv2d(inputs, 192, [1, 1])
output_a = slim.conv2d(output_a, 2144, [1, 1], activation_fn=None)
output_b = slim.conv2d(inputs, 192, [1, 1])
output_b = slim.conv2d(output_b, 224, [1, 3])
output_b = slim.conv2d(output_b, 256, [3, 1])
output_b = slim.conv2d(output_b, 2144, [1, 1], activation_fn=None)
output = tf.add_n([output_a, output_b])
output = tf.multiply(output, 0.1)
return activation_fn(tf.add_n([output_res, output]))

還是前面的問題,這裡依然以reduction-B輸出為準,將通道數修改為2144

average pooling與dropout
def Average_Pooling(inputs):
output = slim.avg_pool2d(inputs, [8, 8])
return output
def Dropout(inputs, keep=0.8):
output = slim.dropout(inputs, keep_prob=keep)
return output

這裡平均池化是全域性的,也就是將空間8×8變為1×1,dropout,這裡0.8是指以0.8的概率保留,0.2的概率置零,目的是為了增強模型泛化能力,減少過擬合,原理解析想必網上也挺多的,自行查閱吧

前向傳播

前向傳播我們根據前面那個結構圖來設計,這裡新增一個inference.py來存放前向傳播過程

from mystruct import *
def Forward(inputs, num_classes):
with slim.arg_scope([slim.conv2d],
weights_initializer=tf.truncated_normal_initializer(stddev=0.1),
activation_fn = tf.nn.relu,
normalizer_fn = slim.batch_norm):
with tf.name_scope('Stem'):
output = Stem(inputs)
with tf.name_scope('5xInception-ResNet-A'):
for i in range(5):
output = Inception_ResNet_A(output)
with tf.name_scope('Reduction-A'):
output = Reduction_A(output)
with tf.name_scope('10xInception-ResNet-B'):
for i in range(10):
output = Inception_ResNet_B(output)
with tf.name_scope('Reduction-B'):
output = Reduction_B(output)
with tf.name_scope('5xInception-ResNet-C'):
for i in range(5):
output = Inception_ResNet_C(output)
with tf.name_scope('AveragePooling'):
output = Average_Pooling(output)
with tf.name_scope('Dropout0.8'):
output = Dropout(output)
output = slim.flatten(output)
with tf.name_scope('fc'):
output = slim.fully_connected(output,num_classes)
return output

這裡提幾點,一點是函式引數num_classes,用於指定最後的全連線層輸出大小,用於最後的softmax分類,另一點是在dropout後進行了張量平整化,也就是[batch,1,1,2144]變為[batch,2144]。最後就是slim引數空間,我們可以看到slim.arg_scope函式指定了卷積操作的預設啟用函式,batch_normalization以及權重生成方式,這裡是截斷正態分佈。

訓練

其實這個挺麻煩的,因為訓練集標籤是字典形式,所以無法直接張量讀取,所以我們先將其轉換為TFRecord資料,這個就蛋疼了,整個訓練集直接轉換很佔記憶體,高峰時能佔近10G,但是後面穩定後保持在5G左右,可能前面讀取快後面就慢下來了,而且越來越慢。所以這裡我建議使用分批轉換,我們解壓後可以看到三個東西,一個訓練集一個測試集一個訓練標籤,標籤是csv格式。我們可以建立一個train.py用於存放訓練過程

首先讀取標籤,將其轉換為序號格式,因為softmax需要這種格式。

def read_labels(dir, file):
with open(os.path.join(dir, file), 'r') as f:
lines = f.readlines()[1:]
label_list = [l.rstrip().split(',') for l in lines]
label_dict = dict(((idx, label) for idx, label in label_list))
labels = list(set(label_dict.values()))
dict_idx = {}
for i in range(len(labels)):
dict_idx[labels[i]] = i
return label_dict, dict_idx

這裡讀取標籤,跳過第一行標題,最後返回兩個字典,一個是uid到label的對映,一個是label到index的對映。

def save_by_tfrecord(train_dir, label_dict, idx_dict):
files = tf.train.match_filenames_once(train_dir)
files = files[9198:10220]
filename_queue = tf.train.string_input_producer(files)
reader = tf.WholeFileReader()
filename, value = reader.read(filename_queue)
init_op = tf.local_variables_initializer()
with tf.Session() as sess:
sess.run(init_op)
coord = tf.train.Coordinator()
threads = tf.train.start_queue_runners(sess=sess, coord=coord)
file_num = sess.run(files).shape[0]
batch_size = file_num
print('準備寫入檔案個數為', file_num)
# for n in range(10):
outpath = ('E:\dog_tf_record\dog299x299.tfrecords-%.5d' % 9)
writer = tf.python_io.TFRecordWriter(outpath)
for i in range(batch_size):
name, image = sess.run([filename, value])
name = ''.join(list(str(name))[-37:-5])
label = idx_dict[label_dict[name]]
image = tf.image.decode_jpeg(image)
image = tf.image.resize_images(image, [299, 299], method=0,)
image = tf.image.convert_image_dtype(image, dtype=tf.uint8)
image = sess.run(image)
image = image.tobytes()
example = tf.train.Example(features=tf.train.Features(feature={
'label':tf.train.Feature(int64_list=tf.train.Int64List(value=[label])),
'image':tf.train.Feature(bytes_list=tf.train.BytesList(value=[image]))
}))
writer.write(example.SerializeToString())
if i % 100 == 0:
print('寫入%d個檔案,label為%d' % (i, label))
writer.close()
coord.request_stop()
coord.join(threads)

這個函式用於將訓練集資料轉換為TFRecord資料,函式接收前面讀取的兩個字典,首先將訓練集目錄中的檔案生成一個檔名序列,這裡我是手動指定範圍,我是分成10批,一共10222個檔案,那麼每個批次就應該是1022個,輸出由outpath指定。這裡使用WholeReader物件進行讀取,其返還兩個值,一個是檔案路徑,一個是位元組流,我們擷取檔名對字典取值作為label,將位元組流解碼為畫素值矩陣,形狀為[3,w,h],要注意的是,這裡再調整大小為299後要重新進行資料格式的調整,為什麼可以看

TensorFlow resize_images函式導致TFRecord產生形狀不匹配

最後大概是這樣

然後就是讀取tf資料

def read_tf_record(filenames):
filename_queue = tf.train.string_input_producer(filenames)
image_reader = tf.TFRecordReader()
filename, image = image_reader.read(filename_queue)
features = tf.parse_single_example(image, features={
'label':tf.FixedLenFeature([], tf.int64),
'image':tf.FixedLenFeature([], tf.string)
})
img = tf.decode_raw(features['image'], tf.uint8)
img = tf.reshape(img, [3, 299, 299])
img = tf.transpose(img, [1, 2, 0])
label = tf.cast(features['label'], tf.int32)
return img, label

這裡filenames與上面的files一樣都是檔名列表,然後產生佇列用於讀取,最後將矩陣形狀[3,299,299]轉換為[299,299,3],這是因為TensorFlow需要形狀為[寬,高,通道],此外,這裡操作的資料都是張量,可以被用於多執行緒佇列操作。所以後面我們可以使用batch函式自動獲取批次資料。

寫一個簡單訓練函式

def train():
batch_size = 16
num_train = 10000
epoch = 3
with tf.Graph().as_default():
with tf.variable_scope('input'):
xs = tf.placeholder(tf.float32, [batch_size, 299, 299, 3])
ys = tf.placeholder(tf.int32, [batch_size])
with tf.name_scope('forward'):
y_ = Forward(xs, 120)
with tf.name_scope('loss'):
entropy_cross = tf.nn.sparse_softmax_cross_entropy_with_logits(logits=y_, labels=ys)
loss = tf.reduce_mean(entropy_cross)
with tf.name_scope('optimizer'):
optimizer = tf.train.RMSPropOptimizer(learning_rate= 0.045, epsilon=1.0)
global_step = tf.Variable(initial_value=0, trainable=False, name='global_step', dtype=tf.int64)
train_op = optimizer.minimize(loss, global_step)
with tf.name_scope('eval'):
eval = tf.nn.in_top_k(y_, ys, k=1)
with tf.Session() as sess:
files = tf.train.match_filenames_once('E:\dog_tf_record\dog*')
img, label = read_tf_record(files)
batch_data, batch_label = tf.train.shuffle_batch([img, label], batch_size,
num_threads=16,
min_after_dequeue=4000,
capacity=4000   3 * batch_size)
init_op = tf.local_variables_initializer()
sess.run(init_op)
sess.run(tf.global_variables_initializer())
coord = tf.train.Coordinator()
threads = tf.train.start_queue_runners(sess=sess, coord=coord)
for i in range(epoch):
for j in range(num_train//batch_size):
img, label = sess.run([batch_data, batch_label])
_, curr_loss, accuracy = sess.run([train_op, loss, eval], feed_dict={xs:img, ys:label})
curr_step = sess.run(global_step)
curr_accuracy = np.sum(accuracy) / batch_size
print('第%d輪,第%d批次,損失%f, 準確率%f' % (i, curr_step, curr_loss, curr_accuracy))
coord.request_stop()
coord.join(threads)

訓練方式參考論文第4章,使用RMS優化,學習率0.045,ε為1.0,它還指定了學習率衰減,不過這裡訓練不了那麼久就不用了。視訊記憶體限制導致只能使用16大小的batch,有條件的可以使用更大的batch。這裡使用的是隨機批次shuffle_batch,所以多次訓練可能會有不同情況。這裡放上700批次的情況供參考

第1輪,第690批次,損失4.787498, 準確率0.937500
第1輪,第691批次,損失4.787681, 準確率0.875000
第1輪,第692批次,損失4.787492, 準確率1.000000
第1輪,第693批次,損失4.787560, 準確率0.812500
第1輪,第694批次,損失4.787492, 準確率1.000000
第1輪,第695批次,損失4.787570, 準確率0.937500
第1輪,第696批次,損失4.787492, 準確率1.000000
第1輪,第697批次,損失4.787492, 準確率1.000000
第1輪,第698批次,損失4.787496, 準確率0.937500
第1輪,第699批次,損失4.787708, 準確率0.937500
第1輪,第700批次,損失4.787492, 準確率1.000000

相關文章

程式語言 最新文章