建立一個植物毒性分類器:數據準備和清理

作者|Kenichi Nakanishi 編譯|VK 來源|Towards Data Science

我有一個愛買植物的未婚妻,還有一隻愛啃植物的貓——我想,有什麼比把一個能告訴我植物是否安全的分類器更好呢!

需要注意的一點是,這裏所做的所有工作都是在google colabs上完成的,使用的notebook可以在我的Github上找到:https://github.com/kenichinakanishi/houseplant_classifier


步驟1-獲取數據

不幸的是,我找不到一個適合我在Kaggle上或使用Google的數據集搜索的預先製作的圖像數據集。所以,我準備建立我自己的!

我決定使用ASPCA的《貓和狗的植物毒性清單》,我已經用了好幾次了。這給了我們一個很好的核心工作。爲了從網站上獲取這些文本數據,我們可以求助於BeautifulSoup,這是一個Python庫,用於從HTML和XML文件中提取數據。

from urllib.request import Request, urlopen
from bs4 import BeautifulSoup

def getHTMLContent(link):
    html = urlopen(link)
    soup = BeautifulSoup(html, 'html.parser')
    return soup

然而,當查看他們的網站時,該表並不是一個易於訪問的html表,而是將數據存儲爲面板中的行。幸運的是,beauthulsoup爲我們提供了一種簡單的方法來搜索解析樹,以找到我們想要的數據。例如:

req = Request('https://www.aspca.org/pet-care/animal-poison-control/cats-plant-list', headers={'User-Agent': 'Mozilla/5.0'})
webpage = urlopen(req).read()

# 爬取數據
soup = BeautifulSoup(webpage, 'lxml')    

# 搜索解析樹以從表中獲得所有內容  
content_list = soup.find_all('span')[7:-4]       

# 將其放入一個dataframe中進行進一步處理
df_cats = pd.DataFrame(content_list)  

在收集完原始數據後,我們需要將其分爲多個列,並進行一些拆分:

# 清理字符串
df_cats[0] = df_cats[0].apply(lambda x: str(x).split('>')[1][:-3])
df_cats[4] = df_cats[4].apply(lambda x: str(x).split('>')[1][:-3])
df_cats[1] = df_cats[1].apply(lambda x: str(x).split('(')[1][0:-4])

# 刪除無用的列並重命名列
df_cats = df_cats.drop(columns=[2,3,5,6]).rename(columns = {0:'Name',1:'Alternative Names',4:'Scientific Name',7:'Family'})

# 將有毒和無毒植物分開
df_cats['Toxic to Cats'] = True
first_nontoxic_cats = [index for index in df_cats[df_cats['Name'].str.startswith('A')].index if index>100][0]
df_cats.loc[first_nontoxic_cats:,'Toxic to Cats'] = False

然後,我們可以對特定於狗的列表重複此過程,然後合併數據幀並清理nan:

# 合併數據框架到一個,用於保留只存在於一邊的值
df_catsdogs = df_dogs.merge(df_cats, how='outer', on=['Name','Alternative Names','Scientific Name','Family'])
df_catsdogs = df_catsdogs.fillna('Unknown')
aspca_df = df_catsdogs.copy()

# 假設對貓和狗有相同的毒性
aspca_df['Toxic to Cats'] = aspca_df.apply(lambda x: x['Toxic to Dogs'] if (x['Toxic to Cats'] == 'Unknown') else x['Toxic to Cats'], axis=1)
aspca_df['Toxic to Dogs'] = aspca_df.apply(lambda x: x['Toxic to Cats'] if (x['Toxic to Dogs'] == 'Unknown') else x['Toxic to Dogs'], axis=1)

步驟2-淺度清理

接下來,我們可以開始進行淺度清理,包括查看數據集,決定要使用哪些關鍵特徵,並標準化它們的格式。

我們目前有名字,替代名稱,學名,家族以及毒性列,所有這些都是從用BeautifulSoup在ASPCA網站上爬來的。

由於我們將使用谷歌圖像搜索收集圖像,因此我們決定根據每種植物的確切學名進行搜索,以獲得儘可能具體的圖像。像“珍珠點”、“大象耳朵”、“蓬鬆褶邊”和“粉紅珍珠”這樣的名字會很快返回我們所尋找的植物之外的結果。

我們編寫了幾個快速函數來應用於該系列,以嘗試將數據標準化以便進一步清理。

# 確保每個學名的標點符號正確
def normalize_capitalization(x):
  first_word, rest = x.split()[0], x.split()[1:]
  first_word = [first_word.capitalize()]
  rest = [word.lower() for word in rest]
  return ' '.join(first_word+rest)

# 清理那些名字不同的重複物種
def species_normalizer(word):
  if word.split()[-1] in ['sp','species','spp','sp.','spp.']:
    word = ''.join(word.split()[:-1])
  return word

# 從名稱中刪除cv,因爲這是一種過時的表示品種的方式
def cv_remover(word):
  if 'cv' in word:
    word = word.replace(' cv ',' ')
  return word

# 從名稱中刪除var
def var_remover(word):
  if 'var' in word:
    word = word.replace(' var. ',' ')
  return word

# 應用每個函數
aspca_df['Scientific Name'] = aspca_df['Scientific Name'].apply(normalize_capitalization)
aspca_df['Scientific Name'] = aspca_df['Scientific Name'].apply(species_normalizer)
aspca_df['Scientific Name'] = aspca_df['Scientific Name'].apply(cv_remover)
aspca_df['Scientific Name'] = aspca_df['Scientific Name'].apply(var_remover)

# 刪除特殊字符
aspca_df['Scientific Name'] = aspca_df['Scientific Name'].apply(lambda x: ''.join([character for character in x if character.isalnum() or character.isspace()]))

# 進一步處理重置數據
aspca_df = aspca_df.sort_values('Scientific Name').drop_duplicates('Scientific Name')
aspca_df = aspca_df.reset_index(drop=True).sort_index()

步驟3-通過交叉引用進行深度清理

仔細研究一下我們的數據裏的學名(Scientific Name),我們發現很多名稱是物種的過時同義詞,或者拼寫錯誤。這將在圖像採集和以後的訓練模型識別具有不同標籤的相同圖像時引起問題。

一個谷歌之後,我們發現了世界植物在線數據庫,一個開放存取的,基於網絡的世界植物物種簡編(http://www.worldfloraonline.org/)。它們列出了同義詞和公認的物種名稱,並由“分類學專家網絡”定期更新。非常適合交叉引用我們不可靠的學名。這個數據庫以一個.txt文件提供了它們的數據,我們可以讀入該文件並與從ASPCA植物毒性數據庫中獲取的數據庫進行比較。

# 讀取WFO數據,只保留有用的列
use_cols = ['scientificName','taxonRank','family','genus','taxonomicStatus','taxonID', 'acceptedNameUsageID']
wfo_df = pd.read_csv('/content/drive/My Drive/Houseplant Classifier/classification.txt', sep='\t', lineterminator='\n', usecols=use_cols)
wfo_df = wfo_df.sort_values('taxonomicStatus')

作爲第一步,我們將對來自ASPCA的數據進行左合併,保留我們的所有類,並添加與我們當前擁有的確切學名匹配的任何數據。我們的目標是將數據庫中的所有植物更新爲最新的可接受的學名。

# 不需要這個列,我們更信任WFO數據庫
aspca_df.drop('Family', axis=1, inplace=True)

# 合併數據文件以獲得可信信息
aspca_df = aspca_df.merge(wfo_df, how = 'left', left_on = ['Scientific Name'], right_on = ['scientificName'])

# 按taxonomicStatus進行排序,並刪除重複項,保持優先級爲被接受的名稱
aspca_df = aspca_df.sort_values('taxonomicStatus').drop_duplicates('Scientific Name', keep='first').reset_index(drop=True)

# 用Unknown來填滿NaN
aspca_df = aspca_df.fillna('Unknown')

步驟3.1-用字符串匹配修復印刷錯誤

許多學名指的是同一物種,但由於在ASPCA數據庫中的打字錯誤,有幾個字母被刪掉了。讓我們使用difflib中的SequenceMatcher來量化字符串距離,通過比較WFO數據庫中不匹配的條目來發現這些錯誤。

我們可以對數據幀進行排序,只與以同一字母開頭的學名進行比較,以節省時間。如果名稱足夠相似,我們將保留它並最終返回最接近的匹配項。這裏我們將閾值設置爲0.9,以避免任何不正確的匹配。

def get_closest_name(unknown_name, name_df = wfo_df, name_col = 'scientificName', threshold=0.9, verbose=False):
  """ 將'unknown_name'與'name_df'中接受的名稱進行匹配。將返回超過接近的“threshold”的名字. 

  Parameters
  ----------
  unknown_name: str
    我們希望與該名稱進行匹配. 
  name_df: DataFrame
    包含名稱的數據框.
  name_col: str, name of name_df column 
    包含可接受名稱的列
  threshold: int
    unknown_name需要在多大程度上與接受的名稱匹配
    如果超過這個閾值,名稱將被添加到可能的名稱字典中
  verbose: bool
    函數是否打印整個列表

  Returns:
  ----------
  str
    與‘unknown_name’最接近的、高於給定‘閾值’的名稱。
  """
  import operator
  from difflib import SequenceMatcher
  def similar(a, b):
      return SequenceMatcher(None, a, b).ratio()
  poss_names = {}
    
  # 爲了節省時間,只看第一個字母相同的條目
  for true_sciname in name_df[name_df[name_col].str.startswith(unknown_name[0])][name_col].values:
    similar_score = similar(unknown_name, true_sciname)
    if similar_score>threshold:
      poss_names[true_sciname]=similar_score
    
  # 如果dict爲空
  if verbose == True:
    print(poss_names)
  if not bool(poss_names):
    print(f'No names close enough to {unknown_name}.')
    return ''
  else:
    print(f'{unknown_name} is closest to {max(poss_names.items(), key=operator.itemgetter(1))[0]}, with a score of {max(poss_names.items(), key=operator.itemgetter(1))[1]:.2f}')
    return max(poss_names.items(), key=operator.itemgetter(1))[0]

我們還定義了一個函數來修復數據中的問題條目,它將把它們的學名、科、屬和分類狀態更新爲WFO數據庫中的(正確的)相應條目。

def fix_name(unknown_name, true_name):
  """ 根據已接受的wfo_df條目修復aspca_df條目.

  Parameters
  ----------
  unknown_name: str
    我們想要修復的名字. 
  true_name: DataFrame
    修復的名稱.
  """

  #得到我們想要改變的列
  unknown_data = aspca_df[aspca_df['Scientific Name'] == unknown_name]

  # 根據ID查找從wfo數據庫中獲取已接受的數據
  true_data = wfo_df[wfo_df['scientificName'] == true_name]
  true_sciname = true_data.loc[:,'scientificName'].values[0]
  true_family = true_data.loc[:,'family'].values[0]
  true_genus = true_data.loc[:,'genus'].values[0]
  true_taxonomicStatus = true_data.loc[:,'taxonomicStatus'].values[0]

  # 更改學名、科、屬和分類學地位爲可接受的版本
  aspca_df.iloc[unknown_data.index,2] = true_sciname
  aspca_df.iloc[unknown_data.index,8] = true_family
  aspca_df.iloc[unknown_data.index,9] = true_genus
  aspca_df.iloc[unknown_data.index,10] = true_taxonomicStatus

現在,我們可以遍歷我們的數據,搜索匹配的名稱並當場更正它們對應的數據幀條目。

unknown_idx = aspca_df[aspca_df.taxonomicStatus == 'Unknown'].index
print(f'{len(unknown_idx)} plants currently cannot be matched.')
from tqdm.notebook import tqdm
for i in tqdm(unknown_idx):
  unknown_name = aspca_df.iloc[i,2]
  closest_name = get_closest_name(unknown_name)
  if closest_name == '':
    continue
  fix_name(unknown_name,closest_name)

此過程有助於我們發現錯誤,否則需要進行深入的檢查

步驟3.2-人工清理不明物種

不幸的是,許多未被確認的物種在數據庫中沒有一個足夠接近的條目。因此,我們對剩餘的未知項進行一些手動修復。謝天謝地,上面的代碼將需要手動關注的樣本數量減少到了50個左右,我們可以重新使用之前的fix_name函數,根據我們在Google上找到的正確條目來修復這些條目。

步驟3.3-匹配同義學名

既然學名已經全部更正,我們仍然需要對它們進行標準化,因爲隨着研究的更新,學名可能會隨着時間的推移而改變(導致在“分類狀態”列中出現同義詞標籤)。如果一個學名是一個公認的名字的同義詞,我們希望在將來的谷歌圖像搜索中使用這個被接受的名字。

# 更新剩下的已接受的學名的同義詞學名
aspca_df = aspca_df.sort_values('taxonomicStatus').drop_duplicates('Scientific Name', keep='first').reset_index(drop=True)
synonym_idx = aspca_df[aspca_df['taxonomicStatus'].values == 'Synonym'].index
for i in synonym_idx:
    
  # 得到我們想要改變的列
  synonym_data = aspca_df.iloc[i,:]
  synonym_name = synonym_data.loc['Scientific Name']

  # 根據ID查找從wfo數據庫中獲取已接受的數據
  true_data = wfo_df[wfo_df['taxonID'] == synonym_data.loc['acceptedNameUsageID']]
  true_sciname = true_data.iloc[:,1].values[0]
  fix_name(synonym_name,true_sciname)

幸運的是,WFO數據庫包含一個acceptedNameUsageID字段,該字段包含給定同義學名的可接受名稱,我們可以利用該字段查找接受的學名並將其傳遞到fix_name函數中。

步驟3.4-結束

現在,我們已經糾正了拼寫錯誤(自動和手動),並將發回的同義詞與最新的已接受名稱進行了匹配。剩下的就是清理圖像下載的數據幀。

# 再次排序並刪除
aspca_df = aspca_df.sort_values('taxonomicStatus').drop_duplicates('Scientific Name', keep='first')
aspca_df = aspca_df.sort_values('Scientific Name').reset_index(drop=True).sort_index()

# 設置一個單詞名稱的屬作爲名稱,而不是NaN
aspca_df.loc[aspca_df.fillna('Unknown')['genus']=='Unknown', 'genus'] = aspca_df.loc[aspca_df.fillna('Unknown')['genus']=='Unknown', 'Scientific Name']

# 刪除我們不再需要的行
aspca_df = aspca_df.drop(['taxonID', 'scientificName', 'taxonomicStatus', 'acceptedNameUsageID', 'taxonRank'], axis=1)

# 標準化列名
aspca_df.rename(columns = {'genus':'Genus', 'family':'Family'}, inplace=True)

# 重新排序
cols = ['Name', 'Scientific Name', 'Genus', 'Family', 'Alternative Names', 'Toxic to Dogs', 'Toxic to Cats']
aspca_df = aspca_df[cols]

這個過程需要多次迭代才能使方法正確。然而,在我們建立圖像數據庫之前,確保我們有乾淨的數據可以工作,這在花費時間訓練模型之前是至關重要的。

從最終的寵物植物毒性數據框架中得出一些有趣的結論:

  • 110個植物家族中有33個並非完全有毒或無毒。

  • 350個植物屬中有7個並非完全有毒或無毒。

  • 只有兩種植物表現出物種特異性毒性,莉莉花對貓和核桃對狗!

步驟4-下載圖像

下載圖像的第一步是獲取我們想要獲取的每個圖像的url。爲此,我們根據fabianbosler的一篇文章,採用了一種基於Selenium的方法。

Selenium是一個用於測試web應用程序的可移植框架。Selenium webdriver充當我們的虛擬瀏覽器,可以通過python命令進行控制。

這裏使用一個腳本來搜索Google圖片,我們給它一個查詢,只查找和下載縮略圖的網址,因爲我們要抓取很多圖片。一個問題是,谷歌的許多圖像縮略圖存儲爲base64編碼的圖像。我們還想抓取這些圖片,這樣我們就不會錯過任何具有高度相關性的圖片,因爲我們在搜索結果中走的越遠,這些圖片就越不適合用於訓練目的。

# 如果運行在Colab
!pip install selenium -q
!apt-get update # to update ubuntu to correctly run apt install
!apt install chromium-chromedriver -q
!cp /usr/lib/chromium-browser/chromedriver /usr/bin
import sys
sys.path.insert(0,'/usr/lib/chromium-browser/chromedriver')

# 導入並設置Selenium webdriver
from selenium import webdriver
chrome_options = webdriver.ChromeOptions()
chrome_options.add_argument('--headless')
chrome_options.add_argument('--no-sandbox')
chrome_options.add_argument('--disable-dev-shm-usage')
wd = webdriver.Chrome('chromedriver',chrome_options=chrome_options)
import requests
import time

def fetch_thumbnail_urls(query:str, max_links_to_fetch:int, wd:webdriver, sleep_between_interactions:int=1, non_commercial=False, shuffle=False):
    """ 使用Selenium webdriver (wd)根據查詢從谷歌圖像中收集url
    可以將sleep_between_interactions更改爲適應較慢的計算機。
    如果shuffle爲真,則返回的url列表將被打亂爲隨機順序

    Parameters
    ----------
    query: str
      傳遞給谷歌圖像。
    max_links_to_fetch: int
      要獲取的url數目。
    wd: Selenium webdriver
      要使用的webdriver實例。
    sleep_between_interactions: int
       在webdriver交互之間等待的時間(秒)。
    non_commercial: bool
      標記僅爲非商業用途。 
    shuffle: bool
      返回的url順序是否打亂。

    Returns:
    ----------
    List
      url的列表。
    """
    def scroll_to_end(wd):
        wd.execute_script("window.scrollTo(0, document.body.scrollHeight);")
        time.sleep(sleep_between_interactions)    
    
    # 構建谷歌查詢
    if non_commercial == True:
      search_url = 'https://www.google.com/search?as_st=y&source=hp&safe=off&tbm=isch&as_epq={q}&gs_l=img&tbs=sur%3Af'
    else:
      search_url = "https://www.google.com/search?as_st=y&source=hp&safe=off&tbm=isch&as_epq={q}&gs_l=img"
    
    # 加載頁面
    wd.get(search_url.format(q=query))

    image_urls = []
    image_count = 0
    results_start = 0
    while image_count < max_links_to_fetch:
        scroll_to_end(wd)

        # 獲得所有圖像縮略圖結果
        thumbnail_results = wd.find_elements_by_css_selector("img.Q4LuWd")
        number_results = len(thumbnail_results)
        
        for img in thumbnail_results:
            # 提取圖像url,如果它們是可用的地址
            if img.get_attribute('src') and 'http' in img.get_attribute('src'):
                image_urls.append(img.get_attribute('src'))
                
            # 還獲取了谷歌使用的編碼圖像
            elif img.get_attribute('src') and 'data' in img.get_attribute('src'):
                image_urls.append(img.get_attribute('src'))

            image_count = len(image_urls)

            # 如果我們達到指定的配額就中斷
            if len(image_urls) >= max_links_to_fetch:
                break
        # 如果我們需要更多的圖片,點擊加載更多圖片按鈕     
        else:
            time.sleep(30)
            load_more_button = wd.find_element_by_css_selector(".mye4qd")
            if load_more_button:
                wd.execute_script("document.querySelector('.mye4qd').click();")

        # 移動指針
        results_start = len(thumbnail_results)

    if shuffle==True:
      random.shuffle(image_urls)

    return image_urls

太好了!現在我們有了一種從谷歌圖片中獲取圖片的方法!爲了下載我們的圖片,我們將利用fast.ai v2。然而,我們將深入研究源代碼並對其進行一點升級,以便在圖像進入時對其進行哈希處理,並忽略/刪除任何重複項,以便最終得到一致的唯一圖像集。我們還將允許它解碼和下載編碼的.jpg和.png圖像,這是谷歌圖像用來存儲縮略圖的格式。

# 每個會話運行一次
!pip install fastai==2.0.14 -q
from fastai.vision.all import *
import io
from PIL import Image
import base64
import hashlib
def download_images(dest, url_file=None, urls=None, max_pics=150, n_workers=1, timeout=4):
    """
      下載文本文件' url_file '中列出的圖片到路徑' dest ',最多下載' max_pics '個
      下載圖像後,在保存之前將哈希與其他圖像哈希進行比較。
	  如果哈希已經存在,則嘗試下一個url。

    Parameters
    ----------
    dest: Path or str
      下載目標文件夾。
    url_file: 
      URL文件,\n作爲分隔符
    urls:
     url的列表。
    max_pics: int
       要下載的圖像數量。
    n_workers: int
      要並行使用的內核數量。

    Returns:
    ----------
    從給定的url下載圖像到dest目錄。
    """
    hash_keys = dict()
    
    # 設置哈希以防止複製圖像下載
    if urls is None: urls = url_file.read().strip().split("\n")
    dest = Path(dest)
    dest.mkdir(exist_ok=True)
    
    # n_workers必須是1,因爲我們在下載過程中檢查唯一的圖像
    parallel(partial(_download_image_inner, dest, timeout=timeout, max_pics=max_pics), list(enumerate(urls)), n_workers=1)

def _download_image_inner(dest, inp, timeout=4, max_pics=150):
    # 輸入是一個枚舉對象
    i,url = inp
    suffix = re.findall(r'\.\w+?(?=(?:\?|$))', url)
    suffix = suffix[0] if len(suffix)>0  else '.jpg'
    
    # 如果我們有足夠的圖片,什麼都不用做,直到url用完
    if len(dest.ls()) >= max_pics:
      return

    # 函數處理base64編碼的圖像
    # 如果抓取的url是已編碼的jpg格式,將其解碼並與其他格式一起保存
    try:
      if url[:15] == 'data:image/jpeg':
        encoded_image = url[url.find('/9'):]
        im = Image.open(io.BytesIO(base64.b64decode(encoded_image)))
        filehash = hashlib.md5(im.tobytes()).hexdigest()
        if filehash not in hash_keys: 
          hash_keys[filehash] = i
          im.save(dest/f"{i:08d}{suffix}")
        else:
          pass
    except:
      pass

    # 函數處理base64編碼的圖像
    # 如果抓取的url是已編碼的png,將其解碼並將其與其餘內容一起內聯保存
    try:
      if url[:14] == 'data:image/png':
        encoded_image = url[url.find('iVBOR'):]
        im = Image.open(io.BytesIO(base64.standard_b64decode(encoded_image))).convert('RGB')
        filehash = hashlib.md5(im.tobytes()).hexdigest()
        if filehash not in hash_keys: 
          hash_keys[filehash] = i
          im.save(dest/f"{i:08d}{suffix}")
        else:
          pass
    except:
      pass

    # 如果抓取的url是一個http站點,下載它,並檢查我們還沒有得到相同的圖像。
    try: 
      download_url(url, dest/f"{i:08d}{suffix}", overwrite=True, show_progress=True, timeout=timeout)
      im = Image.open(dest/f"{i:08d}{suffix}")
      filehash = hashlib.md5(im.tobytes()).hexdigest()
      if filehash not in hash_keys: 
        hash_keys[filehash] = i
      else:
        (dest/f"{i:08d}{suffix}").unlink()
    except Exception as e: f"Couldn't download {url}."

現在,我們可以遍歷我們的每一個科學植物名稱,收集它們的網址,然後下載這些圖片,同時驗證這些圖片是否是唯一的。每一組圖像都下載到Colabs上我的鏈接驅動器中自己的文件夾中。需要注意的一點是,由於google images上存在大量重複的圖片,要抓取的url數量必須遠遠大於你最終想要的圖片數量。

# 實例化webdriver
wd = webdriver.Chrome('chromedriver',options=options)
from tqdm.notebook import tqdm
import itertools
scientific_names = aspca_df['Scientific Name']

# 循環所有室內植物的名字,抓取url並下載到我的谷歌驅動器
for name in tqdm(scientific_names):
  try:
    path = Path('/content/drive/My Drive/Houseplant Classifier/plant_images_deepest');
    folder = name
    dest = path/folder
    dest.mkdir(parents=True, exist_ok=True)
    if len(dest.ls())<150:
      print(f'{name} has {len(dest.ls())} images.')
      url_science = fetch_thumbnail_urls(f'{name}', max_links_to_fetch = 600, wd=wd, non_commercial = False, shuffle = False)
      dest = path/folder
    
      # 強制刷新hash_key—在函數中作爲全局變量存儲,這裏清空
      hash_keys = dict()
      download_images(path/folder, urls = url_science, max_pics=150) 
      print(f'Finished downloading images of {name} : {len(dest.ls())} images downloaded.') 
    else:
      print(f'{name} already has sufficient images.')
  except Exception as e:
    print(f'Error with {name}. {e}')

下載後,我們將採取步驟確保每個文件夾包含正確數量的唯一圖像。

因此,在這個階段,這些圖片被整齊地分到各自的文件夾中,並直接放在我們的谷歌硬盤上。需要注意的是,如果你想用這些圖片來訓練CNN,如果你在使用它們之前把這些圖片帶到本地的Colab環境中,但這將在下一篇文章中進一步討論。

最後

從零開始構建數據庫圖像分類項目對於簡單的玩具示例來說很簡單,參見fast.ai v2一個棕色/黑色/泰迪熊分類器的好例子(https://github.com/fastai/fastbook/blob/master/02_production.ipynb)。對於這個項目,我想擴展相同的方法,但將其應用到更大的類集合中。這個過程實際上可以分爲幾個步驟:

  1. 獲取類列表

由於beauthulsoup,從web頁面中獲取表格或文本數據非常簡單,通常只需要通過正則表達式或內置python方法進行更多處理。

清理和驗證下載數據的準確性是這一步中最大的挑戰。當我們有10個類和領域知識時,在繼續之前很容易發現錯誤並修復它們。當我們有500個類,事情就變得更難了。一個獨立的數據源是至關重要的,我們可以根據它來驗證我們的數據。在這種情況下,我們信任ASPCA數據中的毒性信息,但不信任它們提供的學名,因此必須使用WFO數據庫對其進行更正,後者提供了最新的分類信息。

  1. 獲取每個類的圖像url列表

我們可以執行搜索,找到縮略圖並下載,甚至可以下載得到更大分辨率的圖像。

  1. 將每個圖像下載到帶標籤的文件夾中

用於下載圖像的fastai函數運行良好,但是一個主要的絆腳石是下載重複的圖像。如果你想要更多的圖片(10-15張),並且你下載了谷歌圖片搜索的所有結果,你很快就會得到大量的圖片副本。此外,該函數無法處理base64編碼的圖像。值得慶幸的是,fastai提供了它們的源代碼,可以對其進行修改,以解釋編碼的圖像以及下載http鏈接,下載後對它們進行哈希處理,並且只保留唯一的圖像。

原文鏈接:https://towardsdatascience.com/creating-a-plant-pet-toxicity-classifier-a29587f3f04c

歡迎關注磐創AI博客站: http://panchuang.net/

sklearn機器學習中文官方文檔: http://sklearn123.com/

歡迎關注磐創博客資源彙總站: http://docs.panchuang.net/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章