機器學習——果蔬分類

来源:https://www.cnblogs.com/jihua056/archive/2022/12/23/17000109.html
-Advertisement-
Play Games

上一篇文章中我們聊了Caffeine的同步、非同步的數據回源方式。本篇文章我們再一起研討下經Caffeine改良過的非同步數據驅逐處理實現,以及Caffeine支持的多種不同的數據淘汰驅逐機制和對應的實際使用。 ...


一、選題的背景

為了實現對水果和蔬菜的分類識別,收集了香蕉、蘋果、梨、葡萄、橙子、獼猴桃、西瓜、石榴、菠蘿、芒果、黃瓜、胡蘿蔔、辣椒、洋蔥、馬鈴薯、檸檬、番茄、蘿蔔、甜菜根、捲心菜、生菜、菠菜、大豆、花椰菜、甜椒、辣椒、蘿蔔、玉米、甜玉米、紅薯、辣椒粉、生薑、大蒜、豌豆、茄子共36種果蔬的圖像。該項目使用resnet18網路進行分類。

二、機器學習案例設計方案

1.本選題採用的機器學習案例(訓練集與測試集)的來源描述

數據集來自百度AI studio平臺(https://aistudio.baidu.com/aistudio/datasetdetail/119023/0),共包含36種果蔬,每一個類別包括100張訓練圖像,10張測試圖像和10張驗證圖像。

2 採用的機器學習框架描述

本次使用的網路框架,主要用到了二維捲積、激活函數、最大池化、Dropout和全連接,下麵將對搭建的網路模型進行解釋。

首先是一個二維捲積層,輸入通道數為3,輸出通道數為100,捲積核大小是3*3,填充大小是1*1。輸入通道數為3是因為這個是第一層捲積,輸入的是RGB圖像,具有三個通道,輸出通道數量可以根據實際情況自定。填充是因為希望在捲積後,不要改變圖像的尺寸。

在捲積層之後是一個RELU激活函數,如果不用激活函數,在這種情況下每一層輸出都是上層輸入的線性函數。容易驗證,無論神經網路有多少層,輸出都是輸入的線性組合,與沒有隱藏層效果相當。因此引入非線性函數作為激活函數,這樣深層神經網路就有意義了(不再是輸入的線性組合,可以逼近任意函數)。最早的想法是sigmoid函數或者tanh函數,輸出有界,很容易充當下一層輸入。

引入RELU激活函數有以下三個原因:

第一,採用sigmoid等函數,算激活函數時(指數運算),計算量大,反向傳播求誤差梯度時,求導涉及除法,計算量相對大,而採用Relu激活函數,整個過程的計算量節省很多。

第二,對於深層網路,sigmoid函數反向傳播時,很容易就會出現 梯度消失 的情況(在sigmoid接近飽和區時,變換太緩慢,導數趨於0,這種情況會造成信息丟失),從而無法完成深層網路的訓練。

第三,ReLu會使一部分神經元的輸出為0,這樣就造成了 網路的稀疏性,並且減少了參數的相互依存關係,緩解了過擬合問題的發生。

然後再跟一個二維捲積層,輸入通道數應該和上一層捲積的輸出通道數相同,所以設為100, 輸出通道數同樣根據實際情況設定,此處設為150,其他參數與第一層捲積相同。

後續每一個捲積層和全連接層後面都會跟一個RELU激活函數,所以後面不再敘述RELU激活函數層。

再之後添加一個2*2的最大池化層,該層用來縮減模型的大小,提高計算速度,同時提高所提取特征的魯棒性。

再經過三次捲積後,使用Flatten將二維Tensor拉平,變為一維Tensor,然後使用全連接層,通過多個全連接層後,使用dropout層隨機刪除一些結點,該方法可以有效的避免網路過擬合,在最後一個全連接層的輸出對應需要分類的個數。

3.涉及到的技術難點與解決思路

下載的數據集沒有劃分訓練集、測試集和驗證集,需要自己寫代碼完成劃分。在剛開始寫代碼的時候對於文件路徑沒有搞清楚,沒有弄懂os.path.join方法如何使用,導致總是讀取不到圖像,並且代碼還沒有報錯誤正常運行結束,但是查看劃分後的文件夾里沒有數據。通過debug發現文件的路徑出現問題,具體是windows下的/和\混用,導致不能正確的對路徑進行處理。在排除問題後統一使用\\,最終問題得到解決。

三、機器學習的實現步驟

(1)劃分數據集併進行縮放

 1 import os
 2 import glob
 3 import random
 4 import shutil
 5 from PIL import Image
 6 #對所有圖片進行RGB轉化,並且統一調整到一致大小,但不讓圖片發生變形或扭曲,劃分了訓練集和測試集
 7 
 8 if __name__ == '__main__':
 9     test_split_ratio = 0.05 #百分之五的比例作為測試集
10     desired_size = 128 # 圖片縮放後的統一大小
11     raw_path = './raw'
12 
13     #把多少個類別算出來,包括目錄也包括文件
14     dirs = glob.glob(os.path.join(raw_path, '*'))
15     #進行過濾,只保留目錄,一共36個類別
16     dirs = [d for d in dirs if os.path.isdir(d)]
17 
18     print(f'Totally {len(dirs)} classes: {dirs}')
19 
20     for path in dirs:
21         # 對每個類別單獨處理
22 
23         #只保留類別名稱
24         path = path.split('/')[-1]
25         print(path)
26         #創建文件夾
27         os.makedirs(f'train/{path}', exist_ok=True)
28         os.makedirs(f'test/{path}', exist_ok=True)
29 
30         #原始文件夾當前類別的圖片進行匹配
31         files = glob.glob(os.path.join( path, '*.jpg'))
32         # print(raw_path, path)
33 
34         files += glob.glob(os.path.join( path, '*.JPG'))
35         files += glob.glob(os.path.join( path, '*.png'))
36 
37         random.shuffle(files)#原地shuffle,因為要取出來驗證集
38 
39         boundary = int(len(files)*test_split_ratio) # 訓練集和測試集的邊界
40         
41         for i, file in enumerate(files):
42             img = Image.open(file).convert('RGB')
43 
44             old_size = img.size  
45 
46             ratio = float(desired_size)/max(old_size)
47 
48             new_size = tuple([int(x*ratio) for x in old_size])#等比例縮放
49 
50             im = img.resize(new_size, Image.ANTIALIAS)#後面的方法不會造成模糊
51 
52             new_im = Image.new("RGB", (desired_size, desired_size))
53 
54             #new_im在某個尺寸上更大,我們將舊圖片貼到上面
55             new_im.paste(im, ((desired_size-new_size[0])//2,
56                                 (desired_size-new_size[1])//2))
57 
58             assert new_im.mode == 'RGB'
59             
60             if i <= boundary:
61                 new_im.save(os.path.join(f'test/{path}', file.split('\\')[-1].split('.')[0]+'.jpg'))
62             else:
63                 new_im.save(os.path.join(f'train/{path}', file.split('\\')[-1].split('.')[0]+'.jpg'))
64 
65     test_files = glob.glob(os.path.join('test', '*', '*.jpg'))
66     train_files = glob.glob(os.path.join('train', '*', '*.jpg'))
67 
68     print(f'Totally {len(train_files)} files for training')
69     print(f'Totally {len(test_files)} files for test')

 

(2)圖像預處理

包括隨即旋轉、隨機翻轉、裁剪等,併進行歸一化。

 1 #圖像預處理
 2 train_dir = './train'
 3 val_dir = './test'
 4 test_dir = './test'
 5 classes0 = os.listdir(train_dir)
 6 classes=sorted(classes0)
 7 print(classes)
 8 train_transform=transforms.Compose([
 9         transforms.RandomRotation(10),      # 旋轉+/-10度
10         transforms.RandomHorizontalFlip(),  # 反轉50%的圖像
11         transforms.Resize(40),              # 調整最短邊的大小
12         transforms.CenterCrop(40),          # 作物最長邊
13         transforms.ToTensor(),
14         transforms.Normalize([0.485, 0.456, 0.406],
15                              [0.229, 0.224, 0.225])
16 ])

1 #顯示圖像
2 def show_image(img,label):
3     print('Label: ', trainset.classes[label], "("+str(label)+")")
4     plt.imshow(img.permute(1,2,0))
5     plt.show()
6 
7 show_image(*trainset[10])
8 show_image(*trainset[20])

 

 

 

(3)讀取數據

1 batch_size = 64
2 train_loader = DataLoader(train_ds, batch_size, shuffle=True, num_workers=4, pin_memory=True)
3 val_loader = DataLoader(val_ds, batch_size*2, num_workers=4, pin_memory=True)
4 test_loader = DataLoader(test_ds, batch_size*2, num_workers=4, pin_memory=True)

(4)構建CNN模型

#構建CNN模型

 1 #構建CNN模型
 2 class CnnModel(ImageClassificationBase):
 3     def __init__(self):
 4         super().__init__()
 5         #cnn提取特征
 6         self.network = nn.Sequential(
 7             nn.Conv2d(3, 100, kernel_size=3, padding=1),#Conv2D層
 8             nn.ReLU(),
 9             nn.Conv2d(100, 150, kernel_size=3, stride=1, padding=1),
10             nn.ReLU(),
11             nn.MaxPool2d(2, 2), #池化層
12 
13             nn.Conv2d(150, 200, kernel_size=3, stride=1, padding=1),
14             nn.ReLU(),
15             nn.Conv2d(200, 200, kernel_size=3, stride=1, padding=1),
16             nn.ReLU(),
17             nn.MaxPool2d(2, 2), 
18 
19             nn.Conv2d(200, 250, kernel_size=3, stride=1, padding=1),
20             nn.ReLU(),
21             nn.Conv2d(250, 250, kernel_size=3, stride=1, padding=1),
22             nn.ReLU(),
23             nn.MaxPool2d(2, 2), 
24 
25             #全連接
26             nn.Flatten(), 
27             nn.Linear(6250, 256),  
28             nn.ReLU(),            
29             nn.Linear(256, 128),  
30             nn.ReLU(),            
31             nn.Linear(128, 64),           
32             nn.ReLU(),
33             nn.Linear(64, 32),
34             nn.ReLU(),
35             nn.Dropout(0.25),
36             nn.Linear(32, len(classes)))
37         
38     def forward(self, xb):
39         return self.network(xb)

(5)訓練網路

#訓練網路

 1 #訓練網路
 2 @torch.no_grad()
 3 def evaluate(model, val_loader):
 4     model.eval()
 5     outputs = [model.validation_step(batch) for batch in val_loader]
 6     return model.validation_epoch_end(outputs)
 7 
 8 def fit(epochs, lr, model, train_loader, val_loader, opt_func=torch.optim.SGD):
 9     history = []
10     optimizer = opt_func(model.parameters(), lr)
11     for epoch in range(epochs):
12         # 訓練階段
13         model.train()
14         train_losses = []
15         for batch in tqdm(train_loader,disable=True):
16             loss = model.training_step(batch)
17             train_losses.append(loss)
18             loss.backward()
19             optimizer.step()
20             optimizer.zero_grad()
21         # 驗證階段
22         result = evaluate(model, val_loader)
23         result['train_loss'] = torch.stack(train_losses).mean().item()
24         model.epoch_end(epoch, result)
25         history.append(result)
26     return history
27 
28 model = to_device(CnnModel(), device)
29 
30 history=[evaluate(model, val_loader)]
31 
32 num_epochs = 100
33 opt_func = torch.optim.Adam
34 lr = 0.001
35 
36 history+= fit(num_epochs, lr, model, train_dl, val_dl, opt_func)

 

 

(6)繪製損失函數和準確率圖

 1 def plot_accuracies(history):
 2     accuracies = [x['val_acc'] for x in history]
 3     plt.plot(accuracies, '-x')
 4     plt.xlabel('epoch')
 5     plt.ylabel('accuracy')
 6     plt.title('Accuracy vs. No. of epochs')
 7     plt.show()
 8     
 9 def plot_losses(history):
10     train_losses = [x.get('train_loss') for x in history]
11     val_losses = [x['val_loss'] for x in history]
12     plt.plot(train_losses, '-bx')
13     plt.plot(val_losses, '-rx')
14     plt.xlabel('epoch')
15     plt.ylabel('loss')
16     plt.legend(['Training', 'Validation'])
17     plt.title('Loss vs. No. of epochs')
18     plt.show()
19 
20 plot_accuracies(history)
21 plot_losses(history)
22 
23 evaluate(model, test_loader)

(7)預測

 1 #預測分類
 2     y_true=[]
 3     y_pred=[]
 4     with torch.no_grad():
 5         for test_data in test_loader:
 6             test_images, test_labels = test_data[0].to(device), test_data[1].to(device)
 7             pred = model(test_images).argmax(dim=1)
 8             for i in range(len(pred)):
 9                 y_true.append(test_labels[i].item())
10                 y_pred.append(pred[i].item())
11 
12     from sklearn.metrics import classification_report
13     print(classification_report(y_true,y_pred,target_names=classes,digits=4))

(8)讀取圖片測試

 1 import numpy as np
 2 from PIL import Image
 3 import matplotlib.pyplot as plt
 4 import torchvision.transforms as transforms
 5 
 6 def predict(img_path):
 7     img = Image.open(img_path)
 8     plt.imshow(img)
 9     plt.show()
10     img = img.resize((32,32))
11     img = transforms.ToTensor()(img)
12     img = img.unsqueeze(0)
13     img = img.to(device)
14     pred = model(img).argmax(dim=1)
15     print('預測結果為:',classes[pred.item()])
16     return classes[pred.item()]
17 
18 predict('./raw/apple/Image_1.jpg')

四、總結

在本次課程設計中,使用深度學習的方法實現了果蔬的36分類,相對來說分類數量是比較多的,在訓練了100個epoch以後,分類的準確率可以達到74.3%。通過對果蔬的分類,我明白了當訓練集的圖像數量較少時,可以採用數據增強對原始圖像進行處理,獲得更多的數據來增強網路的泛化能力,避免網路過擬合。數據增強的方法一般有隨機翻轉、隨即旋轉、隨即裁剪、明暗變化、高斯雜訊、椒鹽雜訊等。除此之外,對整個深度學習中圖像分類的流程也有了一定的瞭解,從收集數據、對數據進行預處理、自己構建網路模型、訓練網路到最後的預測結果,加深了對圖像分類過程的理解。希望在以後的學習中,可以學習更多深度學習的方法和應用。

五、全部代碼

  1 import os
  2 import glob
  3 import random
  4 import shutil
  5 from PIL import Image
  6 #對所有圖片進行RGB轉化,並且統一調整到一致大小,但不讓圖片發生變形或扭曲,劃分了訓練集和測試集
  7 
  8 if __name__ == '__main__':
  9     test_split_ratio = 0.05 #百分之五的比例作為測試集
 10     desired_size = 128 # 圖片縮放後的統一大小
 11     raw_path = './raw'
 12 
 13     #把多少個類別算出來,包括目錄也包括文件
 14     dirs = glob.glob(os.path.join(raw_path, '*'))
 15     #進行過濾,只保留目錄,一共36個類別
 16     dirs = [d for d in dirs if os.path.isdir(d)]
 17 
 18     print(f'Totally {len(dirs)} classes: {dirs}')
 19 
 20     for path in dirs:
 21         # 對每個類別單獨處理
 22 
 23         #只保留類別名稱
 24         path = path.split('/')[-1]
 25         print(path)
 26         #創建文件夾
 27         os.makedirs(f'train/{path}', exist_ok=True)
 28         os.makedirs(f'test/{path}', exist_ok=True)
 29 
 30         #原始文件夾當前類別的圖片進行匹配
 31         files = glob.glob(os.path.join(raw_path, path, '*.jpg'))
 32         # print(raw_path, path)
 33 
 34         files += glob.glob(os.path.join(raw_path, path, '*.JPG'))
 35         files += glob.glob(os.path.join(raw_path, path, '*.png'))
 36 
 37         random.shuffle(files)#原地shuffle,因為要取出來驗證集
 38 
 39         boundary = int(len(files)*test_split_ratio) # 訓練集和測試集的邊界
 40         
 41         for i, file in enumerate(files):
 42             img = Image.open(file).convert('RGB')
 43 
 44             old_size = img.size  
 45 
 46             ratio = float(desired_size)/max(old_size)
 47 
 48             new_size = tuple([int(x*ratio) for x in old_size])#等比例縮放
 49 
 50             im = img.resize(new_size, Image.ANTIALIAS)#後面的方法不會造成模糊
 51 
 52             new_im = Image.new("RGB", (desired_size, desired_size))
 53 
 54             #new_im在某個尺寸上更大,我們將舊圖片貼到上面
 55             new_im.paste(im, ((desired_size-new_size[0])//2,
 56                                 (desired_size-new_size[1])//2))
 57 
 58             assert new_im.mode == 'RGB'
 59             
 60             if i <= boundary:
 61                 new_im.save(os.path.join(f'test/{path}', file.split('/')[-1].split('.')[0]+'.jpg'))
 62             else:
 63                 new_im.save(os.path.join(f'train/{path}', file.split('/')[-1].split('.')[0]+'.jpg'))
 64 
 65     test_files = glob.glob(os.path.join('test', '*', '*.jpg'))
 66     train_files = glob.glob(os.path.join('train', '*', '*.jpg'))
 67 
 68 
 69     print(f'Totally {len(train_files)} files for training')
 70     print(f'Totally {len(test_files)} files for test')
 71 
 72 
 73 import os
 74 import random
 75 import numpy as np
 76 import pandas as pd
 77 import torch
 78 import torch.nn as nn
 79 import torch.nn.functional as F
 80 from tqdm.notebook import tqdm
 81 
              
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 家居網購項目實現07 以下皆為部分代碼,詳見 https://github.com/liyuelian/furniture_mall.git 16.功能15-會員顯示登錄名 16.1需求分析/圖解 會員登錄成功 login_ok.jsp顯示歡迎信息 返迴首頁,顯示登錄相關菜單,如果有登錄過,顯示如上 ...
  • C++核心編程 本階段主要針對C++==面向對象==編程技術做詳細講解,探討C++中的核心和精髓。 1 記憶體分區模型 C++程式在執行時,將記憶體大方向劃分為4個區域 代碼區:存放函數體的二進位代碼,由操作系統進行管理的 全局區:存放全局變數和靜態變數以及常量 棧區:由編譯器自動分配釋放, 存放函數的 ...
  • 聲明 本文章中所有內容僅供學習交流,抓包內容、敏感網址、數據介面均已做脫敏處理,嚴禁用於商業用途和非法用途,否則由此產生的一切後果均與作者無關,若有侵權,請聯繫我立即刪除! 本文章未經許可禁止轉載,禁止任何修改後二次傳播,擅自使用本文講解的技術而導致的任何意外,作者均不負責,若有侵權,請在公眾號【K ...
  • hello,大家好呀,我是小樓。 上篇文章《一言不合就重構》 說了我最近重構的一個系統,雖然重構完了,但還在灰度,這不,在灰度過程中又發現了一個問題。 背景 這個問題簡單說一下背景,如果不明白可以看上篇文章 ,不想看也沒關係,這是個通用的解法,後面我會總結抽象下。 在上篇文章的最後提到對每個摘除的地 ...
  • 摘要:本文我們就結合案常式序來說明Java記憶體模型中的Happens-Before原則。 本文分享自華為雲社區《【高併發】一文秒懂Happens-Before原則》,作者: 冰 河。 在正式介紹Happens-Before原則之前,我們先來看一段代碼。 【示例一】 class VolatileExa ...
  • 電腦誕生以來,為適應程式不斷增長的複雜過程,程式設計方法論發生了巨大變化。例如,在電腦發展初期,程式設計是通過輸入二進位機器指令來完成的。在程式僅限於幾百條指令的情況下,這種方法是可接受的。隨著程式規模的增長,人們發明瞭彙編語言,這樣程式員就可以使用代表機器指令的符號表示法來處理大型的、複雜的程 ...
  • 1、認識SpringMVC 1、什麼是MVC MVC是一種軟體架構的思想,將軟體按照模型、視圖、控制器來劃分 M:Model,模型層,指工程中的JavaBean,作用是處理數據 JavaBean分為兩類: 一類稱為實體類Bean:專門存儲業務數據的,如 Student、User 等 一類稱為業務處理 ...
  • 前言 在學習《Python從入門到精通(第2版)》的第15章 GUI界面編程——15.2.4 將.ui文件轉換為.py文件時,按照書中步驟出錯時的問題解決,希望對同樣學習本書的同學有所幫助。 問題 問題出現 當跟著書15.2.4執行步驟(2)時PyCharm報錯 錯誤提示:pyuic5: error ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...