版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
前言
代码可在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函数,可以表示成如下形式。
对应代码,这段代码会返回一个10X1的一个数组(因为我们这次的手写体识别只有10类,从0~9):
def softmax(self, X): #softmax函数
return np.exp(X) / np.sum(np.exp(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))