<>前言

代码可在Github上下载:代码下载 <https://github.com/FlameCharmander/MachineLearning>

常常会遇到需要多分类的问题,比如手写体识别,你需要识别手写的数字是几(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⁡(wk⋅x)∑k=1Kexp⁡(wk⋅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)} }}P(Y=k∣x)=k=1∑K​exp(wk​⋅x)exp(wk​⋅x)​
对应代码,这段代码会返回一个10X1的一个数组(因为我们这次的手写体识别只有10类,从0~9):
def softmax(self, X): #softmax函数 return np.exp(X) / np.sum(np.exp(X))
类似逻辑斯谛回归,其目标函数是负对数似然估计:
E(w1,...,wK)=−ln⁡p(k∣w1,..,wK)=−∑n=1K∑k=1Ktnkln⁡ynkE({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}}} }E(w1​,...,wK​)=−lnp(k∣w1​,..,wK​)
=−n=1∑K​k=1∑K​tnk​lnynk​
随后,可以使用批量梯度下降算法来进行更新参数,梯度公式:
∇wjE(w1,...,wK)=−∑n=1N(ynj−tnj)X{\nabla _{{w_j}}}E\left( {{w_1},...,{w_K}}
\right) = - \sum\limits_{n = 1}^N {\left( {{y_{nj}} - {t_{nj}}} \right)} X∇wj​​E
(w1​,...,wK​)=−n=1∑N​(ynj​−tnj​)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))

友情链接
ioDraw流程图
API参考文档
OK工具箱
云服务器优惠
阿里云优惠券
腾讯云优惠券
华为云优惠券
站点信息
问题反馈
邮箱:ixiaoyang8@qq.com
QQ群:637538335
关注微信