【机器学习】Softmax回归 Python实现

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/tudaodiaozhale/article/details/80432552

前言

代码可在Github上下载:代码下载
常常会遇到需要多分类的问题,比如手写体识别,你需要识别手写的数字是几(0~9),比如文本生成,你需要知道生成的是哪个字,都需要进行多分类。那么我们最常用的多分类函数就是softmax了。接下来本文将会实现一个softmax来进行手写体识别。

数据集

本次的数据集分为训练集:文件名为"trainingDigits"和测试集:文件名为"testDigits",每个文件夹里面有txt文件若干,比如’0_83.txt’文件名前部分是标签,后部分是编号,这个可以不用管。用记事本打开文件可以看到,里面是一幅由32X32的0、1数字组成的矩阵。
图片
可以依稀看出是0的手写体,其它类似。

    def loadData(self, dir):    #给出文件目录,读取数据
        digits = list() #数据集(数据)
        labels = list() #标签
        if os.path.exists(dir): #判断目录是否存在
            files = os.listdir(dir) #获取目录下的所有文件名
            for file in files:  #遍历所有文件
                labels.append(file.split('_')[0])   #按照文件名规则,文件名第一位是标签
                with open(dir + '\\' + file) as f:  #通过“目录+文件名”,获取文件内容
                    digit = list()
                    for line in f:  #遍历文件每一行
                        digit.extend(map(int, list(line.replace('\n', ''))))    #遍历每行时,把数字通过extend的方法扩展
                    digits.append(digit)    #将数据扩展进去
        digits = np.array(digits)   #数据集
        labels = list(map(int, labels)) #标签
        labels = np.array(labels).reshape((-1, 1))  #将标签重构成(N, 1)的大小
        return digits, labels

这里由loadData函数实现了对文件夹的读取,并返回数据集和标签。
都有注释了,不再赘述。

算法实现

之前的机器学习中,总是将类别分成两类,比如逻辑斯谛回归中,将>=0.5的概率分成正类,<0.5的概率分成负类。如果是要分成多类,那可以将逻辑斯谛回归进行一个推广,比如要分成10类,可以用一个数组表示,就是[0.11, 0.13, 0.06, 0.07, 0.03, 0.15, 0.3, 0.08, 0.03, 0.04]来表示每一个类别的概率,其中0.3概率最大,则有很大可能是这个类别。逻辑斯谛回归用的sigmoid函数,而多分类用的是softmax函数,可以表示成如下形式。
P ( Y = k x ) = exp ( w k x ) k = 1 K exp ( w k x ) P\left( {Y = k|x} \right) = \frac{{\exp \left( {{w_k} \cdot x} \right)}}{{\sum\limits_{k = 1}^K {\exp \left( {{w_k} \cdot x} \right)} }}
对应代码,这段代码会返回一个10X1的一个数组(因为我们这次的手写体识别只有10类,从0~9):

def softmax(self, X):   #softmax函数
        return np.exp(X) / np.sum(np.exp(X))

类似逻辑斯谛回归,其目标函数是负对数似然估计:
E ( w 1 , . . . , w K ) = ln p ( k w 1 , . . , w K ) = n = 1 K k = 1 K t n k ln y n k E({w_1},...,{w_K}{\rm{ }}) = - \ln {\rm{ }}p(k|{w_1},..,{w_K}{\rm{ }}) = {\rm{ }} - \sum\limits_{n = 1}^K {\sum\limits_{k = 1}^K {{t_{nk}}\ln {y_{nk}}} }
随后,可以使用批量梯度下降算法来进行更新参数,梯度公式:
w j E ( w 1 , . . . , w K ) = n = 1 N ( y n j t n j ) X {\nabla _{{w_j}}}E\left( {{w_1},...,{w_K}} \right) = - \sum\limits_{n = 1}^N {\left( {{y_{nj}} - {t_{nj}}} \right)} X
对应代码:

self.weights -= alpha * (np.dot((y_ - y), x.T))

全部代码

import numpy as np
import os

class Softmax:
    def loadData(self, dir):    #给出文件目录,读取数据
        digits = list() #数据集(数据)
        labels = list() #标签
        if os.path.exists(dir): #判断目录是否存在
            files = os.listdir(dir) #获取目录下的所有文件名
            for file in files:  #遍历所有文件
                labels.append(file.split('_')[0])   #按照文件名规则,文件名第一位是标签
                with open(dir + '\\' + file) as f:  #通过“目录+文件名”,获取文件内容
                    digit = list()
                    for line in f:  #遍历文件每一行
                        digit.extend(map(int, list(line.replace('\n', ''))))    #遍历每行时,把数字通过extend的方法扩展
                    digits.append(digit)    #将数据扩展进去
        digits = np.array(digits)   #数据集
        labels = list(map(int, labels)) #标签
        labels = np.array(labels).reshape((-1, 1))  #将标签重构成(N, 1)的大小
        return digits, labels

    def softmax(self, X):   #softmax函数
        return np.exp(X) / np.sum(np.exp(X))

    def train(self, digits, labels, maxIter = 100, alpha = 0.1):
        self.weights = np.random.uniform(0, 1, (10, 1024))
        for iter in range(maxIter):
            for i in range(len(digits)):
                x = digits[i].reshape(-1, 1)
                y = np.zeros((10, 1))
                y[labels[i]] = 1
                y_ = self.softmax(np.dot(self.weights, x))
                self.weights -= alpha * (np.dot((y_ - y), x.T))
        return self.weights

    def predict(self, digit):   #预测函数
        return np.argmax(np.dot(self.weights, digit))   #返回softmax中概率最大的值

if __name__ == '__main__':
    softmax = Softmax()
    trainDigits, trainLabels = softmax.loadData('files/softmax/trainingDigits')
    testDigits, testLabels = softmax.loadData('files/softmax/testDigits')
    softmax.train(trainDigits, trainLabels, maxIter=100) #训练
    accuracy = 0
    N = len(testDigits) #总共多少测试样本
    for i in range(N):
        digit = testDigits[i]   #每个测试样本
        label = testLabels[i][0]    #每个测试标签
        predict = softmax.predict(digit)  #测试结果
        if (predict == label):
            accuracy += 1
        print("predict:%d, actual:%d"% (predict, label))
    print("accuracy:%.1f%%" %(accuracy / N * 100))

猜你喜欢

转载自blog.csdn.net/tudaodiaozhale/article/details/80432552