1. 程式人生 > >機器學習(一)邏輯迴歸與softmax迴歸及程式碼示例

機器學習(一)邏輯迴歸與softmax迴歸及程式碼示例

本文適合已經對機器學習、人工智慧有過一定了解,但是還沒有自己寫過程式碼,或者一直在使用現有框架的同學。不用框架自己寫一次程式碼的過程還是很有必要的,能讓你真正地理解原理與機器學習中各個步驟的實現過程,而不是停留在“好像懂了”、只會調庫的階段。

目錄

一、logistics迴歸簡介(僅理論)

線性迴歸輸出的值可以很大,很難確定一個閾值作為分類線。因此使用logistics迴歸,將模型的輸出變數對映到0和1之間,就能以 0.5 作為分類的閾值。

邏輯迴歸模型的假設是: 其中: ​X 代表特徵向量 , g 代表邏輯函式. 一個常用的邏輯函式為S

形函式(Sigmoid function),公式為:

simoid函式的影象為:

其中 (也可以用更復雜的組合)

程式碼實現

import numpy as np
  ​
  def sigmoid(z):
      return 1/(1+np.exp(-z))

構造一個代價函式 J(θ),衡量模型的輸出值與真實值的差別大小。

有以下兩種情況

  • 當類標籤 y 為1時, h(x)越接近1, J(θ)越小; h(x)越接近0, J(θ)越大

  • 當類標籤 y 為1時, h(x)越接近1, J(θ)越小; h(x)越接近0, J(θ)越大

這其實就是個數學建模中的 0-1 型整數規劃問題

可根據這種特性構造代價函式:

二、softmax迴歸(softmax_model.py檔案)

softmax迴歸模型是logistics迴歸模型在多分類問題上的推廣 ,類標籤 可以取兩個以上的值

2.1 代價函式

由於 y 有k個類別,故 變成一個 維的向量,來表示這 個類別估計的概率值, 並歸一化(概率和為1)

def softmax(z):
      [m,k]= z.shape
      p = np.zeros([m,k])
      for i in range(m):
          p[i,:]=np.exp(z[i,:])/np.sum(np.exp(z[i,:]))
      return p

logistics迴歸的 0-1 整數規劃思想(二維),拓展為 k維 , 引入一個指示函式 1{·}

其取值規則為: 1{表示式為真} = 1

利用指示函式,構造出了代價函式

def cost(theta, x,y):  #x(m行n列),y(m行k列),theta(k行n列)
      [k,m]=y.shape
      theta = np.matrix(theta)
      sum = 0
      p = softmax(np.dot(x,theta.T))  ## [m,k]     P(i)為 [1,k]
      p = p.T.reshape([k*m,1])
      y = y.reshape([k*m,1])
      temp_p=np.mat(np.log(p))
      #punish = np.sum(np.power(theta,2))   #懲罰項,在梯度下降時求導,這裡不必加上去
      cost = -1/m*np.dot(y.T,temp_p) #+ punish
      return cost         #輸出m行k列,代表m個樣本,k個類別各自概率

2.2 批量梯度下降(反向傳播)

利用批量梯度下降(BGD)的思想更新 θ:

其中,代價函式的梯度 經過公式推導為:

  def gradientDescent(x,y,theta,iters,alpha,regulization_rate):
      #theta:權重係數    iters:迭代次數    alpha:學習率
      COST = np.zeros((iters,1))    #存放每次迭代後,cost值的變化
      thetaNums = int(theta.shape[0])  #維數,即j的取值個數
      print(thetaNums)
      for i in range(iters):
          #bb = x*theta.T
          p = softmax(np.dot(x,theta.T));
          grad = (-1/m*  np.dot(x.T ,(y.T-p)) ).T   #[3,784]
          #更新theta
          theta = theta - alpha*grad #- regulization_rate * theta
          COST[i] = cost(theta,x,y)
          #每訓練一次,輸出當前訓練步數與損失值
          print("訓練次數: ",i)
          print(COST[i])
          print("\n")
      #返回迭代後的theta值,和每次迭代的代價函式值
      return theta,COST

2.3 正則化(權重衰減)

在代價函式中新增一個權重衰減項 ,使代價函式變成凸函式,以期能夠收斂到最優解

現在代價函式變為:

對應程式碼更新:

  punish = np.sum(np.power(theta,2))   
  cost = -1/m*np.dot(y.T,temp_p) + punish

對應地,梯度公式變為:

也就是說,更新 θ 時,需要再減去一個 λθ ,公式變為:

對應程式碼更新:

  theta = theta - alpha*grad - regulization_rate * theta

2.4 mnist資料集的提取

讀取影象資料方法:

  def loadImageSet(filename):
      print("load image set", filename)
      binfile = open(filename, 'rb')
      buffers = binfile.read()
  ​
      head = struct.unpack_from('>IIII', buffers, 0)
      print("head,", head)
  ​
      offset = struct.calcsize('>IIII')
      imgNum = head[1]
      width = head[2]
      height = head[3]
      # [60000]*28*28
      bits = imgNum * width * height
      bitsString = '>' + str(bits) + 'B'  # like '>47040000B'
  ​
      imgs = struct.unpack_from(bitsString, buffers, offset)
  ​
      binfile.close()
      imgs = np.reshape(imgs, [imgNum, 1, width * height])
      print("load imgs finished")
      return imgs

讀取標籤方法:

  def loadLabelSet(filename):
      print("load label set", filename)
      binfile = open(filename, 'rb')
      buffers = binfile.read()
  ​
      head = struct.unpack_from('>II', buffers, 0)
      print("head,", head)
      imgNum = head[1]
  ​
      offset = struct.calcsize('>II')
      numString = '>' + str(imgNum) + "B"
      labels = struct.unpack_from(numString, buffers, offset)
      binfile.close()
      labels = np.reshape(labels, [imgNum, 1])
  ​
      print('load label finished')
      return labels

載入mnist資料集:

  def data_Load():
      imgs_train = loadImageSet("train-images.idx3-ubyte")
      imgs_train = imgs_train.reshape(-1, 784)
      labels_train = loadLabelSet("train-labels.idx1-ubyte")
      imgs_test = loadImageSet("t10k-images.idx3-ubyte")
      imgs_test = imgs_test.reshape(-1, 784)
      labels_test = loadLabelSet("t10k-labels.idx1-ubyte")
      return imgs_train,labels_train,imgs_test,labels_test

並在主函式中呼叫

[imgs_train, labels_train, imgs_test, labels_test] = data_Load()  # 讀取訓練集與測試集
x = imgs_train[0:num_train, :]   #num_train為讀取的樣本的數量
#將灰度圖轉化為二值圖
x[x<=40]=0;
x[x>40]=1;
y = np.zeros((num_train,10))    
#mnist每個樣本的標籤是一個數字,以獨熱碼的形式將其擴充為10維的向量
for t in np.arange(0,num_train):
    y[t,labels_train[t]]=1;
y=y.T

2.5模型訓練與儲存

訓練模型

首先進行引數的設定(沒來得及寫引數調優視覺化的程式碼了,多次嘗試後設定成如下數值):

iters = 1000;       #訓練次數
alpha = 0.5;        #學習率
regulization_rate = 0.1;    #懲罰係數
k = 10;             #標籤類別數
num_train = 2000    #樣本數量

呼叫2.2寫好的​gradientDescent() 方法完成訓練,並得到訓練後的 θ 與損失值 cost

[finalTheta,finalCost] = gradientDescent(x,y,theta,iters,alpha,regulization_rate)

儲存模型

import pickle
  ​
  f = open('saved_model/model.pickle', 'wb') #儲存到資料夾saved_model中
      pickle.dump(finalTheta, f)
      f.close()

2.6 準確度評估

y_predict = np.argmax(np.dot(imgs_test, finalTheta.T), axis=1)
      for i in range(len(labels_test)):
          if (y_predict[i]==labels_test[i]):
              num = num+1
      print("準確率為 :",num/len(labels_test))

執行結果:

準確率為 0.8751 (對於mnist自帶的測試集)

訓練耗時 151秒

三、模型運用(Application.py檔案)

3.1模型的載入

def model_load(filepath):
      f = open(filepath,'rb')
      w = pickle.load(f)
      f.close()
      return w
  ##主函式中
  theta = model_load('saved_model/model.pickle')

filepath 為之前儲存模型的路徑'saved_model/model.pickle'

3.2 滑鼠寫數字並儲存為影象

滑鼠繪圖方法

drawing = False  # true if mouse is pressed
  ix, iy = -1, -1
  # 滑鼠手寫數字
  def draw_circle(event, x, y, s,a):
      global ix, iy, drawing, mode
      if event == cv2.EVENT_LBUTTONDOWN:
          drawing = True
          ix, iy = x, y
      elif event == cv2.EVENT_MOUSEMOVE:
          if drawing == True:
              #圓半徑(筆畫粗細)設定為25是為了和mnist資料集中的數字儘可能粗細相似
              cv2.circle(image, (x, y), 25, (255, 255, 255), -1)    #畫筆顏色為白色
      elif event == cv2.EVENT_LBUTTONUP:
          drawing = False
          cv2.circle(image, (x, y), 25, (255, 255, 255), -1)
  image = np.zeros((512, 512, 3), np.uint8)       #初始化畫布,黑色
  cv2.namedWindow('image')
  cv2.setMouseCallback('image', draw_circle)

3.3 模型的使用

思路

  1.展現一張512x512大小的黑色畫布

  2.利用opencv的滑鼠回撥方法,滑鼠在畫布上按下時繪製一個小圓,隨著滑鼠的拖動,完成數字的書寫

  3.當書寫完成,按下鍵盤'p'鍵,程式將當前畫布上的影象儲存到本地

  4.將儲存的影象進行預處理,轉化成mnist資料集的格式

  5.把處理好的影象矩陣交給模型進行識別

圖片預處理

def pre_picture(picName):
      img = cv2.resize(picName,(28,28))    #將影象等比例縮小至28x28大小
      im_arr = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)   #轉化為灰度圖
      x_input = im_arr.reshape([1,784])               #把影象矩陣轉化為一維向量
      x_input[x_input < 40] = 0                       #設定灰度閾值,將向量二值化
      x_input[x_input>=40]=1                          ##
      return x_input                                  #返回影象處理後的生成的特徵向量

滑鼠書寫數字並識別

while(1):
          cv2.imshow('image', image)
          k = cv2.waitKey(1) & 0xFF
          #按下 p 鍵,識別當前畫布上的數字
          if k == ord('p'):
              #img_new = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
              cv2.imwrite("REC.jpg", image)
              x = pre_picture(image)
              labels_pro=np.dot(x,theta.T)
              y = np.argmax(np.dot(x,theta.T),axis=1)
              #輸出模型識別的結果
              print("手寫數字識別結果 : ",y)
              print("各標籤概率  = ",labels_pro)
          #按下 c 鍵,清空當前影象,還原成黑色畫布
          elif k == ord('c'):
              image = np.zeros((512, 512, 3), np.uint8)#清空
          #按下 “ESC” 鍵,退出程式
          elif k == 27:
              break

執行截圖

四、模型缺陷

一個很大的問題是mnist中數字的字型風格是西方式的,和中國人常用手寫體有很大的區別。因此當使用Application.py檔案進行手寫數字識別時,必須要刻意去模仿mnist的字型風格,才能提高識別準確率。

嘛,直接用全畫素點訓練的後果就是這樣啦。有兩個解決方向:(1)選用表現力好的特徵 (2)使用更復雜的模型

如果用CNN等神經網路做的話顯然效果會好很多。以後有時間我會更新。