OC實現(xiàn)Softmax識別手寫數(shù)字

簡介


Softmax回歸模型是logistic回歸模型在多分類問題上的推廣扫责,在多分類問題中逃呼,類標(biāo)簽 y 可以取兩個以上的值抡笼。Softmax模型運用廣泛,很多復(fù)雜精細(xì)的訓(xùn)練模型最后一步都會用softmax來分配概率推姻。

公式


Softmax回歸最主要的代價函數(shù)如下:

代價函數(shù)

其中,1{-} 是示性函數(shù)增炭,其取值規(guī)則為:1{值為真的表達(dá)式}= 1。
根據(jù)代價函數(shù)灾前,分別對k個分類進(jìn)行求導(dǎo)孟辑,就可以得到:


k分類偏導(dǎo)函數(shù)

其中饲嗽,Xi為類別j的概率:

Xi為類別j的概率

有了上面的偏導(dǎo)數(shù)公式以后,我們就可以用梯度下降算法更新theta值吞加,每一次迭代需要進(jìn)行如下更新:


j = 1,...,k

以上公式并沒有涉及到bias優(yōu)化衔憨,加上bias的公式形如:


softmax-regression-vectorequation.png

其實bias的迭代更新和theta一樣袄膏,只需要找到bias的偏導(dǎo)數(shù)就行沉馆,我的代碼中包含了對bias求導(dǎo)優(yōu)化。

數(shù)據(jù)


訓(xùn)練所用數(shù)據(jù)集是MNIST揖盘,MNIST數(shù)據(jù)集的官網(wǎng)是Yann LeCun's website锌奴。這個數(shù)據(jù)集包含60000行的訓(xùn)練數(shù)據(jù)集和10000行的測試數(shù)據(jù)集。

文件 內(nèi)容
train-images-idx3-ubyte.gz 訓(xùn)練集圖片 - 60000 張 訓(xùn)練圖片
train-labels-idx1-ubyte.gz 訓(xùn)練集圖片對應(yīng)的數(shù)字標(biāo)簽
t10k-images-idx3-ubyte.gz 測試集圖片 - 10000 張 圖片
t10k-labels-idx1-ubyte.gz 測試集圖片對應(yīng)的數(shù)字標(biāo)簽

其中每一張圖片都是28*28的椭符,矩陣表示如下:


MNIST-Matrix.png

在數(shù)據(jù)集中销钝,每張圖片都是一個展開的向量琐簇,長度是 28x28 = 784座享。因此渣叛,在MNIST訓(xùn)練數(shù)據(jù)集中盯捌,train-images-idx3-ubyte解析后是一個形狀為 [60000, 784]的張量。


mnist-train-xs.png

而train-labels-idx1-ubyte則是一個長度60000的向量箫攀,每一個值對應(yīng)train-images-idx3-ubyte中代表的數(shù)字范圍0~9靴跛。
測試數(shù)據(jù)與訓(xùn)練數(shù)據(jù)結(jié)構(gòu)一樣渡嚣,只是數(shù)量只有10000張。

下載過后讀取與解析到數(shù)據(jù)代碼:

//
//  MLLoadMNIST.m
//  MNIST
//
//  Created by Jiao Liu on 9/23/16.
//  Copyright ? 2016 ChangHong. All rights reserved.
//

#import "MLLoadMNIST.h"

@implementation MLLoadMNIST

int reverseInt(int input)
{
    unsigned char ch1, ch2, ch3, ch4;
    ch1=input&255;
    ch2=(input>>8)&255;
    ch3=(input>>16)&255;
    ch4=(input>>24)&255;
    return((int)ch1<<24)+((int)ch2<<16)+((int)ch3<<8)+ch4;
}

double **readImageData(const char *filePath)
{
    FILE *file = fopen(filePath, "rb");
    double **output = NULL;
    if (file) {
        int magic_number=0;
        int number_of_images=0;
        int n_rows=0;
        int n_cols=0;
        fread((char*)&magic_number, sizeof(magic_number), 1, file);
        magic_number= reverseInt(magic_number);
        fread((char*)&number_of_images, sizeof(number_of_images), 1, file);
        number_of_images= reverseInt(number_of_images);
        fread((char*)&n_rows, sizeof(n_rows), 1, file);
        n_rows= reverseInt(n_rows);
        fread((char*)&n_cols, sizeof(n_cols), 1, file);
        n_cols= reverseInt(n_cols);
        output = (double **)malloc(sizeof(double) * number_of_images);
        for(int i=0;i<number_of_images;++i)
        {
            output[i] = (double *)malloc(sizeof(double) * n_rows * n_cols);
            for(int r=0;r<n_rows;++r)
            {
                for(int c=0;c<n_cols;++c)
                {
                    unsigned char temp=0;
                    fread((char*)&temp, sizeof(temp), 1, file);
                    output[i][(n_rows*r)+c]= (double)temp;
                }
            }
        }
    }
    fclose(file);
    return output;
}

int *readLabelData(const char *filePath)
{
    FILE *file = fopen(filePath, "rb");
    int *output = NULL;
    if (file) {
        int magic_number=0;
        int number_of_items=0;
        fread((char*)&magic_number, sizeof(magic_number), 1, file);
        magic_number= reverseInt(magic_number);
        fread((char*)&number_of_items, sizeof(number_of_items), 1, file);
        number_of_items= reverseInt(number_of_items);
        output = (int *)malloc(sizeof(int) * number_of_items);
        for(int i=0;i<number_of_items;++i)
        {
            unsigned char temp=0;
            fread((char*)&temp, sizeof(temp), 1, file);
            output[i]= (int)temp;
        }
    }
    fclose(file);
    return output;
}

Softmax實現(xiàn)


  • 首先我這里選用的梯度下降算法去逼近最值,所以需要一個迭代次數(shù)挤牛,代碼中默認(rèn)的是500次种蘸。輸入訓(xùn)練圖片是60000竞膳,如果每次迭代都全部用上坦辟,訓(xùn)練會花去很多時間,所以每次迭代默認(rèn)隨機(jī)取100張圖片進(jìn)行訓(xùn)練滨彻。
  • 下降梯度的速率默認(rèn)是0.01挪蹭。
  • 代碼中實現(xiàn)兩種梯度下降逼近,一種是每次迭代依次使用每張圖片去更新所有分類變量辜羊,另一種是每次迭代順序更新每個分類變量,更新每個分類時候使用所有隨機(jī)圖片數(shù)據(jù)碱妆。兩種方法其實效率一樣昔驱,但是測試時候發(fā)現(xiàn)第二種方法的正確率略高。

代碼如下:

//
//  MLSoftMax.m
//  MNIST
//
//  Created by Jiao Liu on 9/26/16.
//  Copyright ? 2016 ChangHong. All rights reserved.
//

#import "MLSoftMax.h"

@implementation MLSoftMax

- (id)initWithLoopNum:(int)loopNum dim:(int)dim type:(int)type size:(int)size descentRate:(double)rate
{
    self = [super init];
    if (self) {
        _iterNum = loopNum == 0 ? 500 : loopNum;
        _dim = dim;
        _kType = type;
        _randSize = size == 0 ? 100 : size;
        _bias = malloc(sizeof(double) * type);
        _theta = malloc(sizeof(double) * type * dim);
        for (int i = 0; i < type; i++) {
            _bias[i] = 0;
            for (int j = 0; j < dim; j++) {
                _theta[i * dim +j] = 0.0f;
            }
        }
        
        _descentRate = rate == 0 ? 0.01 : rate;
    }
    return  self;
}

- (void)dealloc
{
    if (_bias != NULL) {
        free(_bias);
        _bias = NULL;
    }
    
    if (_theta != NULL) {
        free(_theta);
        _theta = NULL;
    }
    
    if (_randomX != NULL) {
        free(_randomX);
        _randomX = NULL;
    }
    
    if (_randomY != NULL) {
        free(_randomY);
        _randomY = NULL;
    }
}

#pragma mark - SoftMax Main

- (void)randomPick:(int)maxSize
{
    long rNum = random();
    for (int i = 0; i < _randSize; i++) {
        _randomX[i] = _image[(rNum+i) % maxSize];
        _randomY[i] = _label[(rNum+i) % maxSize];
    }
}
/*
- (double *)MaxPro:(double *)index
{
    long double maxNum = index[0];
    for (int i = 1; i < _kType; i++) {
        maxNum = MAX(maxNum, index[i]);
    }
    
    long double sum = 0;
    for (int i = 0; i < _kType; i++) {
        index[i] -= maxNum;
        index[i] = expl(index[i]);
        sum += index[i];
    }
    
    for (int i = 0; i < _kType; i++) {
        index[i] /= sum;
    }
    return index;
}

- (void)updateModel:(double *)index currentPos:(int)pos
{
    double *delta = malloc(sizeof(double) * _kType);
    for (int i = 0; i < _kType; i++) {
        if (i != _randomY[pos]) {
            delta[i] = 0.0 - index[i];
        }
        else
        {
            delta[i] = 1.0 - index[i];
        }
        
        _bias[i] -= _descentRate * delta[i];
        
        for (int j = 0; j < _dim; j++) {
            _theta[i * _dim +j] += _descentRate * delta[i] * _randomX[pos][j] / _randSize;
        }
    }
    
    if (delta != NULL) {
        free(delta);
        delta = NULL;
    }
}

- (void)train
{
    _randomX = malloc(sizeof(double) * _randSize);
    _randomY = malloc(sizeof(int) * _randSize);
    double *index = malloc(sizeof(double) * _kType);
    
    for (int i = 0; i < _iterNum; i++) {
        [self randomPick:_trainNum];
        for (int j = 0; j < _randSize; j++) {
            // calculate wx+b
            vDSP_mmulD(_theta, 1, _randomX[j], 1, index, 1, _kType, 1, _dim);
            vDSP_vaddD(index, 1, _bias, 1, index, 1, _kType);
            
            index = [self MaxPro:index];
            [self updateModel:index currentPos:j];
        }
    }
    if (index != NULL) {
        free(index);
        index = NULL;
    }
}
*/

- (int)indicator:(int)label var:(int)x
{
    if (label == x) {
        return 1;
    }
    return 0;
}

- (double)sigmod:(int)type index:(int) index
{
    double up = 0;
    for (int i = 0; i < _dim; i++) {
        up += _theta[type * _dim + i] * _randomX[index][i];
    }
    up += _bias[type];
    
    double *down = malloc(sizeof(double) * _kType);
    double maxNum = -0xfffffff;
    vDSP_mmulD(_theta, 1, _randomX[index], 1, down, 1, _kType, 1, _dim);
    vDSP_vaddD(down, 1, _bias, 1, down, 1, _kType);
    
    for (int i = 0; i < _kType; i++) {
        maxNum = MAX(maxNum, down[i]);
    }
    
    double sum = 0;
    for (int i = 0; i < _kType; i++) {
        down[i] -= maxNum;
        sum += exp(down[i]);
    }
    
    if (down != NULL) {
        free(down);
        down = NULL;
    }
    
    return exp(up - maxNum) / sum;
}

- (double *)fderivative:(int)type
{
    double *outP = malloc(sizeof(double) * _dim);
    for (int i = 0; i < _dim; i++) {
        outP[i] = 0;
    }
    
    double *inner = malloc(sizeof(double) * _dim);
    for (int i = 0; i < _randSize; i++) {
        long double sig = [self sigmod:type index:i];
        int ind = [self indicator:_randomY[i] var:type];
        double loss = -_descentRate * (ind - sig) / _randSize;
        _bias[type] += loss * _randSize;
        vDSP_vsmulD(_randomX[i], 1, &loss, inner, 1, _dim);
        vDSP_vaddD(outP, 1, inner, 1, outP, 1, _dim);
    }
    if (inner != NULL) {
        free(inner);
        inner = NULL;
    }
    
    return outP;
}

- (void)train
{
    _randomX = malloc(sizeof(double) * _randSize);
    _randomY = malloc(sizeof(int) * _randSize);
    for (int i = 0; i < _iterNum; i++) {
        [self randomPick:_trainNum];
        for (int j = 0; j < _kType; j++) {
            double *newTheta = [self fderivative:j];
            for (int m = 0; m < _dim; m++) {
                _theta[j * _dim + m] = _theta[j * _dim + m] - _descentRate * newTheta[m];
            }
            if (newTheta != NULL) {
                free(newTheta);
                newTheta = NULL;
            }
        }
    }
}

- (void)saveTrainDataToDisk
{
    NSFileManager *fileManager = [NSFileManager defaultManager];
    NSString *thetaPath = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingString:@"/Theta.txt"];
//    NSLog(@"%@",thetaPath);
    NSData *data = [NSData dataWithBytes:_theta length:sizeof(double) *  _dim * _kType];
    [fileManager createFileAtPath:thetaPath contents:data attributes:nil];
    
    NSString *biasPath = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingString:@"/bias.txt"];
    data = [NSData dataWithBytes:_bias length:sizeof(double) * _kType];
    [fileManager createFileAtPath:biasPath contents:data attributes:nil];
}

- (int)predict:(double *)image
{
    double maxNum = -0xffffff;
    int label = -1;
    double *index = malloc(sizeof(double) * _kType);
    vDSP_mmulD(_theta, 1, image, 1, index, 1, _kType, 1, _dim);
    vDSP_vaddD(index, 1, _bias, 1, index, 1, _kType);
    for (int i = 0; i < _kType; i++) {
        if (index[i] > maxNum) {
            maxNum = index[i];
            label = i;
        }
    }
    return label;
}

- (int)predict:(double *)image withOldTheta:(double *)theta andBias:(double *)bias
{
    double maxNum = -0xffffff;
    int label = -1;
    double *index = malloc(sizeof(double) * _kType);
    vDSP_mmulD(theta, 1, image, 1, index, 1, _kType, 1, _dim);
    vDSP_vaddD(index, 1, bias, 1, index, 1, _kType);
    for (int i = 0; i < _kType; i++) {
        if (index[i] > maxNum) {
            maxNum = index[i];
            label = i;
        }
    }
    return label;
}

@end

最后訓(xùn)練結(jié)果:

Simulator Screen Shot

不斷改變循環(huán)次數(shù),下降速率等參數(shù)朴艰,都會帶來正確率的變化混移。測試結(jié)果發(fā)現(xiàn)默認(rèn)參數(shù)能帶來接近最好的識別的正確率,90%左右??毁嗦。

結(jié)語


90%的正確率并非達(dá)到最優(yōu)回铛,因為這僅僅是個簡單的模型,可以加上卷積神經(jīng)網(wǎng)絡(luò)來改善效果腔长。
關(guān)于卷積神經(jīng)網(wǎng)絡(luò)實現(xiàn)验残,我也實現(xiàn)了一版,但由于效率與正確率不高鸟召,后面優(yōu)化后再分享??氨鹏。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末喻犁,一起剝皮案震驚了整個濱河市何缓,隨后出現(xiàn)的幾起案子还栓,更是在濱河造成了極大的恐慌,老刑警劉巖谷婆,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件辽聊,死亡現(xiàn)場離奇詭異,居然都是意外死亡异袄,警方通過查閱死者的電腦和手機(jī)玛臂,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進(jìn)店門迹冤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人橱鹏,你說我怎么就攤上這事堪藐。” “怎么了贮勃?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵苏章,是天一觀的道長奏瞬。 經(jīng)常有香客問我硼端,道長,這世上最難降的妖魔是什么珍昨? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮唾琼,結(jié)果婚禮上澎剥,老公的妹妹穿的比我還像新娘。我一直安慰自己祭饭,他們只是感情好叙量,可當(dāng)我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著寺鸥,像睡著了一般征炼。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上眼坏,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天酸些,我揣著相機(jī)與錄音魄懂,去河邊找鬼。 笑死市栗,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的蛛淋。 我是一名探鬼主播篡腌,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼嘹悼,長吁一口氣:“原來是場噩夢啊……” “哼层宫!你這毒婦竟也來了其监?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤哮奇,失蹤者是張志新(化名)和其女友劉穎鼎俘,沒想到半個月后辩涝,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡捉邢,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年商膊,在試婚紗的時候發(fā)現(xiàn)自己被綠了晕拆。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡吝镣,死狀恐怖昆庇,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情拱撵,我是刑警寧澤表蝙,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響欲诺,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜蛹含,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一浦箱、第九天 我趴在偏房一處隱蔽的房頂上張望祠锣。 院中可真熱鬧,春花似錦蓬推、人聲如沸澡腾。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽澜公。三九已至,卻和暖如春蜕青,著一層夾襖步出監(jiān)牢的瞬間糊渊,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工贺喝, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留宗兼,地道東北人。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓染苛,卻偏偏與公主長得像茶行,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子畔师,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,979評論 2 355

推薦閱讀更多精彩內(nèi)容