学人工智能一次竞赛都不打?闹呢?
各位人工智能愛好者,大家好!
由TinyMind發起的?#第一屆漢字書法識別挑戰賽#?正在火熱進行中,比賽才開始2周,已有數只黑馬沖進榜單。目前TOP23全部為90分以上了,可謂競爭激烈,高手如林。不是比賽太簡單,是大佬們太厲害了啊!
No.1微胖君(microfat_htu)目前以99.01高分領銜榜首,還未報名的同學歡迎點擊"原文鏈接"參賽,向這些同學發起挑戰~~
4.24 榜單
本次比賽主要是以學習交流為目的,吸引了不少萌新們報名參賽~雖是入門級別的賽題,對于沒動手實戰過的同學,還是有些不知所措。為此TinyMind特邀戰場中奮勇拼搏的三名前鋒,為大家整理了一些經驗心得,用不同的解題思路,以啟發新手們如何開動,參與到本次書法識別比賽中。
以下為參賽ID:真的學不會 的經驗分享
漢字書法識別入門
前段時間參加了一次TinyMind舉辦的漢字書法識別挑戰賽,說是挑戰賽其實就是一場練習賽。為一些剛剛入門的同學和沒有比賽經驗的同學提供了一個探索圖像識別領域的平臺。我目前是暫列榜首(沒想到轉眼就被超越了-。-),所以把自己的思路和想法稍微做一個分享,給有需要的人提供一個base line。
先來看數據集~~
100個漢字的訓練集
10000張書法圖片的測試集
上面的訓練集總共有100個漢字,每一個漢字都有400張不同字體的圖片,數據量上來看算是一個比較小的數據集。
等等,看到的確定是漢字嗎,第一眼望過去我是真的emmmmm.....甲骨文,篆體各種字體都冒出來了。先喝口水冷靜一下,仔細看一看發現圖片都是gray的。想了一想突然覺得這個和mnist并沒有太大的區別只是字體更加復雜一些,可能要用稍微深一點的網絡來訓練。
圖片看完了,那么開始擼代碼了。分析終究是分析,還是實踐才能說明一切。
數據集劃分
競賽中只給了train和test,所以需要自己手動劃分一個val來做模型訓練的驗證測試。在這里簡單說明一下經常用的兩種劃分數據集的方法。
本地劃分
內存劃分
本地劃分:圖片是按照文件夾分類的,所以只需要從每個文件夾中按ratio抽取部分圖片到val中即可,當然不要忘記了shuffle。
內存劃分:把所有圖片和標簽讀進內存中,存為list或者array然后shuffle后按長度劃分。前提是把數據讀進去內存不會爆炸掉。內存劃分只適合小型數據集,不然會Boom!!!
注:劃分數據集的時候一定要打亂數據,shuffle很重要!!!
1def move_ratio(data_list, original_str, replace_str):
2 ? ?for x in data_list:
3 ? ? ? ?fromImage = Image.open(x)
4 ? ? ? ?x = x.replace(original_str, replace_str)
5 ? ? ? ?fromImage.save(x)
注:這里只給出部分代碼,文章最下面github有完整鏈接。
1for d in $(ls datadir); do ? ? ? ? ? ? ? ? ? ? ? ?
2 ? ?for f in $(ls datadir/$d | shuf | head -n 100 ); do
3 ? ? ? ?mkdir -p valid_dir/$d/
4 ? ? ? ?mv datadir/$d/$f valid_dir/$d/;
5 ? ?done;
6done
注:這里引用dwSun的linux shell腳本,如果想用簡單腳本實現也可以采用他的代碼~
模型建立與數據預處理
對于CNN網絡來說,大的數據集對應大的訓練樣本,如果小的數據集想要用深層次的網絡來訓練的話,那么必不可少的一步就是數據增強。
數據增強的大部分方法,所有深度學習框架都已經封裝好了。這里我采用的是keras自帶的數據增強方法。
1from keras.preprocessing.image import ImageDataGenerator
2datagen = ImageDataGenerator(
3 ? ?# horizontal_flip=True,
4 ? ?width_shift_range=0.15,
5 ? ?height_shift_range=0.15,
6 ? ?rescale=1 / 255
7)
由于漢字是具有筆畫順序的,所以做了翻轉以后訓練的效果不是很好。這里就做了一個寬度和高度的偏移,由于給的數據集圖片長寬不是固定的而且字體的內容也是有長有短。所以用這兩種增強方式可以提高模型的準確率,結果測試這兩種方式還是有效的。
數據處理完了,那么下面就是我們可愛的CNN網絡模型了
cnn一把梭
嗯,就是干。
1# bn + prelu
2def bn_prelu(x):
3 ? ?x = BatchNormalization()(x)
4 ? ?x = PReLU()(x)
5 ? ?return x
6# build baseline model
7def build_model(out_dims, input_shape=(128, 128, 1)):
8 ? ?inputs_dim = Input(input_shape)
9 ? ?x = Conv2D(32, (3, 3), strides=(2, 2), padding='valid')(inputs_dim)
10 ? ?x = bn_prelu(x)
11 ? ?x = Conv2D(32, (3, 3), strides=(1, 1), padding='valid')(x)
12 ? ?x = bn_prelu(x)
13 ? ?x = MaxPool2D(pool_size=(2, 2))(x)
14 ? ?x = Conv2D(64, (3, 3), strides=(1, 1), padding='valid')(x)
15 ? ?x = bn_prelu(x)
16 ? ?x = Conv2D(64, (3, 3), strides=(1, 1), padding='valid')(x)
17 ? ?x = bn_prelu(x)
18 ? ?x = MaxPool2D(pool_size=(2, 2))(x)
19 ? ?x = Conv2D(128, (3, 3), strides=(1, 1), padding='valid')(x)
20 ? ?x = bn_prelu(x)
21 ? ?x = MaxPool2D(pool_size=(2, 2))(x)
22 ? ?x = Conv2D(128, (3, 3), strides=(1, 1), padding='valid')(x)
23 ? ?x = bn_prelu(x)
24 ? ?x = AveragePooling2D(pool_size=(2, 2))(x)
25 ? ?x_flat = Flatten()(x)
26 ? ?fc1 = Dense(512)(x_flat)
27 ? ?fc1 = bn_prelu(fc1)
28 ? ?dp_1 = Dropout(0.3)(fc1)
29 ? ?fc2 = Dense(out_dims)(dp_1)
30 ? ?fc2 = Activation('softmax')(fc2)
31 ? ?model = Model(inputs=inputs_dim, outputs=fc2)
32 ? ?return model
這里用了6個簡單的卷積層,和PRelu+bn層。
下面是一個比較大的模型ResNet50,模型是已經merge在了keras的applications中,可以直接用。不過需要調整分類層。
1def resnet50_100(feat_dims, out_dims):
2 ? ?# resnett50 only have a input_shape=(128, 128, 3), if use resnet we must change
3 ? ?# shape at least shape=(197, 197, 1)
4 ? ?resnet_base_model = ResNet50(include_top=False, weights=None, input_shape=(128, 128, 1))
5 ? ?# get output of original resnet50
6 ? ?x = resnet_base_model.get_layer('avg_pool').output
7 ? ?x = Flatten()(x)
8 ? ?fc = Dense(feat_dims)(x)
9 ? ?x = bn_prelu(fc)
10 ? ?x = Dropout(0.5)(x)
11 ? ?x = Dense(out_dims)(x)
12 ? ?x = Activation("softmax")(x)
13 ? ?# buid myself model
14 ? ?input_shape = resnet_base_model.input
15 ? ?output_shape = x
16 ? ?resnet50_100_model = Model(inputs=input_shape, outputs=output_shape)
17 ? ?return resnet50_100_model
好了,煉丹爐有了接下來就是你懂的。
訓練模型
訓練模型和調參真的是一個技術活,這里我跑了共40個epoch。思路只有一個那就是先把train的數據跑到loss下降并且先過擬合再說。只要過擬合了后面的一切都好調整了,如果訓練數據都不能到過擬合或者99以上那么要仔細想想數據量夠不夠和模型的選擇了。
loss
acc
可以很清楚的看出來,訓練數據集已經過擬合了。我用的優化器是sgd,學習率設置的是lr=0.01。val_acc可以跑到了0.94左右,這是一個比較正常的訓練水平。還可以進一步的提高。
提高方法
數據增強:采取其他的數據增強方法進一步擴大訓練數據,這里可以采用GAN來生成近似于真實圖片的數據。
衰減學習率:當到達一定epoch的時候,loss不再下降了這個時候可以通過減小學習率的方法進一步訓練。
模型融合:模型融合的方法在大部分數據集上都會有所提高,這個是最常用的一種競賽方式。
以上就是我自己做的流程和思路,提交結果和評測的代碼寫在我的github上面了,有興趣參加比賽練手的同學可以參考一下。
github地址:https://github.com/FlyEgle/chinese_font_recognition/
好多小伙伴是從開發或者是其他工程上轉到AI的,所以下面我給有需要的同學列舉出一些必要的基礎知識點.
基礎知識
數學:線性代數和概率論是必須要會的,而且基本的概念和計算都要懂。可以把高數,線性代數和概率論看一遍,這里推薦李航的統計學習方法。
圖像處理:如果是做圖像方面的小伙伴,那么需要把岡薩雷斯的圖像處理那本巨作看一遍,需要懂基本的圖像概念和基本方法。
機器學習:周志華的西瓜書
深度學習:
如果能把這幾本書完全吃透那也很厲害了,當然學習知識點的途徑還有很多。
知乎
微信公眾號
Google
TinyMind
加油!!
以下為參賽ID:Link 的經驗分享
深度學習入門指南:從零開始TinyMind漢字書法識別
環境搭建
數據導入
啟動網絡
環境搭建:
對入門來說,最容易的還是在windows下進行開發。而且現在各種深度學習架構大都支持windows,因此如果只是入門深度學習,最好還是從windows開始。不過因為github上提交的代碼全都運行在linux環境下,因此希望大家最終能轉向linux下,話不多說,現在開始。
我們選用的深度學習架構是pytorch, 相比于tensorflow,pytorch更加簡單易用,而且符合python的編程習慣,官網的支持也足夠完善。
環境搭建步驟
安裝Anaconda, 裝python3.6版本的,至于為啥用python3這都2018年了,就別用上古版本了
安裝pycharm,將pycharm的解釋器改為anaconda安裝目錄下的python。當然用別的IDE也可以,但是我習慣用pycahrm了,如果大家用別的IDE這步另當別論
安裝深度學習架構pytorch, 到了最重要的步驟了,如果沒有英偉達顯卡,或者顯卡不支持請忽略1-3步
安裝英偉達顯卡驅動
安裝CUDA
安裝Cuddn
安裝pytorch gpu版 (沒有顯卡的裝cpu版)具體方法參見知乎這篇文章 https://zhuanlan.zhihu.com/p/26871672 選擇自己對應的版本、系統、cuda版本,按照命令直接裝就可以
數據導入
數據下載在TinyMind的比賽網站 http://www.tinymind.cn/competitions/41 下載解壓后是兩部分,分別是train和test1,其中train是訓練集,test1是用來提交評分的測試集 為了導入圖片數據,需要調用opencv,沒裝opencv的話就先裝opencv
1conda install -c https://conda.binstar.org/menpo opencv
1import os
2import numpy as np
3import torch
4import torch.utils.data as data
5import cv2
6from PIL import Image
7from tqdm import tqdm
8trainpath = 'E:\\Code\\TMD1st\\train\\' #這是我的儲存路徑,windows下的路徑是需要用\\隔開的,linux是反斜杠/
9testpath = 'E:\\Code\\TMD1st\\test1\\'
10words = os.listdir(trainpath) ? # 按時間排序 從早到晚
11category_number = len(words) # 一共有多少個字
12img_size = (256, 256) #將圖片大小統一設定為這個值
13def loadOneWord(order):
14 ? ?path = trainpath + words[order] + '\\'
15 ? ?files = os.listdir(path)
16 ? ?datas = []
17 ? ?for file in files:
18 ? ? ? ?file = path + file
19 ? ? ? ?img = np.asarray(Image.open(file))
20 ? ? ? ?img = cv2.resize(img, img_size)
21 ? ? ? ?datas.append(img)
22 ? ?datas = np.array(datas)
23 ? ?labels = np.zeros([len(datas), len(words)], dtype=np.uint8)
24 ? ?labels[:, order] = 1
25 ? ?return datas, labels
26def transData(): ? ?#將所有數據轉存,以后就不用每次都從原始數據讀取了
27 ? ?num = len(words)
28 ? ?datas = np.array([], dtype=np.uint8)
29 ? ?datas.shape = -1, 256, 256
30 ? ?labels = np.array([], dtype=np.uint8)
31 ? ?labels.shape = -1, 100
32 ? ?for k in range(num):
33 ? ? ? ?data, label = loadOneWord(k)
34 ? ? ? ?datas = np.append(datas, data, axis=0)
35 ? ? ? ?labels = np.append(labels, label, axis=0)
36 ? ? ? ?print('loading', k)
37 ? ?np.save('data.npy', datas) #將數據和標簽分別存為data和label
38 ? ?np.save('label.npy', labels)
將轉存完的結果讀出來看一下
1if __name__ == '__main__':
2 ? ?datas = np.load('data.npy')
3 ? ?labels = np.load('label.npy')
4 ? ?index = np.arange(0, len(datas), 1, dtype=np.int)
5 ? ?print(datas.shape, labels.shape)
(40000, 256, 256) (40000, 100)
我是將40000個圖像的label按照one-hot編碼存的,這么干其實浪費空間,但是反正也沒幾兆,就懶得改了,index那一行就是專為將ong-hot轉label
這才將數據轉存,為了訓練時給pytorch使用,最方便的方法是使用pytorch做好的loader工具,為此需要實現自己的data.Dataset。只需繼承data.Dataset,并且重寫__getitem__和__len__兩個方法就可以。
1class TrainSet(data.Dataset):
2 ? ?def __init__(self, eval=False):
3 ? ? ? ?datas = np.load('data.npy') #裝載
4 ? ? ? ?labels = np.load('label.npy')
5 ? ? ? ?index = np.arange(0, len(datas), 1, dtype=np.int) #換one-hot為label
6 ? ? ? ?np.random.seed(123)
7 ? ? ? ?np.random.shuffle(index)
8 ? ? ? ?if eval: ? ?#如果eval為真,就取10%作為驗證集,設定隨機數種子是為了每次取出來的都是固定的10%,以免將驗證集用于訓練
9 ? ? ? ? ? ?index = index[:int(len(datas) * 0.1)]
10 ? ? ? ?else:
11 ? ? ? ? ? ?index = index[int(len(datas) * 0.1):]
12 ? ? ? ?self.data = datas[index]
13 ? ? ? ?self.label = labels[index]
14 ? ? ? ?np.random.seed()
15 ? ?def __getitem__(self, index):
16 ? ? ? ?return torch.from_numpy(self.data[index]), \
17 ? ? ? ? ? ? ? torch.from_numpy(self.label[index])
18 ? ?def __len__(self):
19 ? ? ? ?return len(self.data)
完成dataset后只要使用torch.utils.data.DataLoader就可以自動劃分batch。
啟動網絡
無論網絡結構如何,用網絡進行訓練的整個過程是相同的
1import torch
2import torch.optim as optim
3from torch.autograd import Variable
4import torch.nn as nn
5import data
6import torch.nn.functional as F
7n_epoch, batch_size = 25, 8 # 設置遍歷次數及每個batch的大小
8trainset = data.TrainSet(eval=False) #實例化上面定義的數據集對象
9trainloader = torch.utils.data.DataLoader(trainset, batch_size=batch_size, shuffle=True) #用trainset實例化loader
10evalset = data.TrainSet(eval=True) ?#驗證集
11evalloader = torch.utils.data.DataLoader(evalset, batch_size=batch_size, shuffle=True)
12net = Net() # 實例化模型
13if torch.cuda.is_available(): ? # 將模型移到GPU上
14 ? ?net.cuda()
15criterion = nn.CrossEntropyLoss() ? #損失函數使用交叉熵
16optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=1e-1, weight_decay=1e-4) #優化器使用SGD 學習率1e-3
17def train(epoch):
18 ? ?net.train() # 經模型切換到訓練模式
19 ? ?correct = 0
20 ? ?sum = 0
21 ? ?for batch_index, (datas, labels) in enumerate(trainloader, 0): ?#從loader裝載數據
22 ? ? ? ?labels = labels.max(1)[1]
23 ? ? ? ?datas = Variable(datas).float()
24 ? ? ? ?datas = datas.view(-1, 1, 256, 256)
25 ? ? ? ?labels = Variable(labels).long()
26 ? ? ? ?if torch.cuda.is_available(): ? #數據轉移到GPU
27 ? ? ? ? ? ?datas = datas.cuda()
28 ? ? ? ? ? ?labels = labels.cuda()
29 ? ? ? ?optimizer.zero_grad() ? # 每次前項計算之前,將優化器梯度清零
30 ? ? ? ?outputs = net(datas) ? ?# 前項計算
31 ? ? ? ?loss = criterion(outputs, labels) # 根據結果和label計算損失函數
32 ? ? ? ?loss.backward() # 做反向傳播
33 ? ? ? ?optimizer.step() # 用優化器進行一次更新
34 ? ? ? ?pred_choice = outputs.data.max(1)[1] ? ?# 前向輸出計算最大的一個作為最可能的輸出
35 ? ? ? ?correct += pred_choice.eq(labels.data).cpu().sum() # 統計正確個數
36 ? ? ? ?sum += len(labels) ?# 總數
37 ? ? ? ?# 輸出每次計算的信息
38 ? ? ? ?print('batch_index: [%d/%d]' % (batch_index, len(trainloader)),
39 ? ? ? ? ? ? ?'Train epoch: [%d]' % (epoch),
40 ? ? ? ? ? ? ?# 'acc:%.4f p:%.4f r:%.4f F1:%.4f' % (acc, p, r, F1),
41 ? ? ? ? ? ? ?'correct/sum:%d/%d, %.4f' % (correct, sum, correct / sum))
42def eval(epoch): ? ?# 用驗證集做類似過程,只是不計算梯度、不更新參數
43 ? ?net.eval()
44 ? ?correct = 0
45 ? ?sum = 0
46 ? ?for batch_index, (datas, labels) in enumerate(evalloader, 0):
47 ? ? ? ?labels = labels.max(1)[1]
48 ? ? ? ?datas = Variable(datas).cuda().float()
49 ? ? ? ?datas = datas.view(-1, 1, 256, 256)
50 ? ? ? ?labels = Variable(labels).cuda().long()
51 ? ? ? ?# optimizer.zero_grad()
52 ? ? ? ?outputs = net(datas)
53 ? ? ? ?# loss = criterion(outputs, labels)
54 ? ? ? ?# loss.backward()
55 ? ? ? ?# optimizer.step()
56 ? ? ? ?pred_choice = outputs.data.max(1)[1]
57 ? ? ? ?correct += pred_choice.eq(labels.data).cpu().sum()
58 ? ? ? ?sum += len(labels)
59 ? ? ? ?print('batch_index: [%d/%d]' % (batch_index, len(evalloader)),
60 ? ? ? ? ? ? ?'Eval epoch: [%d]' % (epoch),
61 ? ? ? ? ? ? ?# 'acc:%.4f p:%.4f r:%.4f F1:%.4f' % (acc, p, r, F1),
62 ? ? ? ? ? ? ?'correct/sum:%d/%d, %.4f' % (correct, sum, correct / sum))
63if __name__ == '__main__':
64 ? ?for epoch in range(n_epoch):
65 ? ? ? ?train(epoch)
66 ? ? ? ?eval(epoch)
如此,我們就完成了從原始數據制作dataset送入loader并且啟動網絡的所有代碼。 等等,我們忘記了最重要的部分,我們沒有定義網絡的結構。 Net這里,這是一個繼承自nn.Moudule的類,只要在這個類中定義網絡的前向計算即可,反向計算會由pytorch自動實現。
為了簡單起見,我們只舉一個簡單的例子,這個網絡是隨便寫的,完全沒有任何合理性的考慮,但至少能開始訓練了。
1class net(nn.Module):
2 ? ?def __init__(self):
3 ? ? ? ?super(net, self).__init__()
4 ? ? ? ?self.pool = nn.MaxPool2d(2)
5 ? ? ? ?self.drop = nn.Dropout(p=0.5)
6 ? ? ? ?self.conv1 = nn.Conv2d(1, 32, 7, stride=2, padding=3)
7 ? ? ? ?self.norm1 = nn.BatchNorm2d(32)
8 ? ? ? ?self.conv2 = nn.Conv2d(32, 32, 3, stride=1, padding=1)
9 ? ? ? ?self.norm2 = nn.BatchNorm2d(32)
10 ? ? ? ?self.conv3 = nn.Conv2d(32, 64, 3, stride=1, padding=1)
11 ? ? ? ?self.norm3 = nn.BatchNorm2d(64)
12 ? ? ? ?# Sequential 是連續操作的寫法
13 ? ? ? ?self.convs = nn.Sequential(nn.Conv2d(64, 128, 3, stride=1, padding=1),
14 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? nn.BatchNorm2d(128),
15 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? nn.ReLU(),
16 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? nn.Conv2d(128, 128, 3, stride=1, padding=1),
17 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? nn.BatchNorm2d(128),
18 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? nn.ReLU(),
19 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? )
20 ? ? ? ?self.out_layers = nn.Sequential(nn.Linear(128 * 8 * 8, 1024),
21 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?nn.BatchNorm1d(1024),
22 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?nn.ReLU(),
23 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?nn.Linear(1024, 256),
24 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?nn.BatchNorm1d(256),
25 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?nn.ReLU(),
26 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?nn.Linear(256, 100),
27 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?nn.BatchNorm1d(100),
28 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?nn.ReLU(),
29 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?)
30 ? ?def forward(self, x):
31 ? ? ? ?x = F.relu(self.norm1(self.conv1(x))) ? # 卷積 BN ReLU
32 ? ? ? ?x = self.pool(x) ? ? ? ? ? ? ? ? ? ? ? ?# 池化
33 ? ? ? ?x = F.relu(self.norm2(self.conv2(x))) ?# 卷積 BN ReLU
34 ? ? ? ?x = F.relu(self.norm3(self.conv3(x))) ?# 卷積 BN ReLU
35 ? ? ? ?x = self.pool(x)
36 ? ? ? ?x = self.convs(x) ? ? ? ? ? ? ? ? ? ? ?# 連續操作,里面是 conv -> BN -> ReLU -> conv -> BN -> ReLU
37 ? ? ? ?x = self.pool(x)
38 ? ? ? ?x = x.view(-1, 128 * 8 * 8) ? ? ? ? ? ? # 將圖像拉直為向量
39 ? ? ? ?x = self.drop(x)
40 ? ? ? ?x = self.out_layers(x)
41 ? ? ? ?return x
這樣,代碼就完整了,運行開始以后我么你就恩能夠看到訓練正確率從0慢慢的向上爬。當然,這個網絡是隨意寫的,性能肯定極其的差,但至少舉了一個栗子。
我們都知道,深度學習也叫煉丹。所以接下來的活便是研究拜讀各個大牛級煉丹師的的煉丹秘籍(論文),學習人家先進的煉丹手法(trick),把我們的栗子給煉成金丹。
五年煉丹,三年悟道,煉丹一道,非大毅力大智慧者不可成。吾等當晝夜苦修,方有機緣窺得一絲丹道真諦,與諸君共勉。
源代碼
鏈接地址:https://github.com/Link2Link/TinyMind-start-with-0
以下為參賽ID:microfat_htu 的經驗分享
前一段時間在用遷移學習實現圖像分類Github,正好趕上這次書法識別比賽,就想嘗試用遷移學習的方法實現書法圖像分類。本來沒有抱太大希望,因為根據遷移學習理論,訓練源域模型的數據應與訓練目標域的數據有相似的特征分布,然而,ILSVRC數據集中并沒有漢字符號類別,所以,期望的最好結果是能夠收斂。但,事實出乎意料,不僅收斂,而且得到99.01%的測試準確率(看來我是低估深度模型的特征提取能力了)。
總結
以上是生活随笔為你收集整理的学人工智能一次竞赛都不打?闹呢?的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: (MATLAB/C/Python)快速中
- 下一篇: 单片机微型计算机原理及接口技术陈,单片机