Windows에서 Caffe 예제 돌리기 : MNIST(1) (๑•‿•๑)
이번 포스팅에서는 mnist 예제를 실행해 보려고 합니다.
(1)과 (2)로 나누어 포스팅 할 계획인데요,
(1)에서는 그냥 실행만 해보고 (2)에서는 좀 더 내부적으로 들어가 볼 생각입니다.
쓰다가 길어지면 더 나눌수도 있구요 o(๑•‿‿•๑)o~♪룰루
이번 포스팅의 목표는 바로..
빠★본인의 손글씨 숫자를 판별하기★밤
입니다.
Caffe에서 Learning을 하기 위해서는 아래와 같은 것들이 필요합니다.
- Training Data: 학습할 데이터
- Test Data: 테스트할 데이터
- Network(=net): layer들을 정의
- Solver: 학습 파라미터 설정
추가적으로 본인의 데이터를 가지고 테스트하기 위해서는 아래와 같은 파일들이 필요합니다.
- 알아보고자 하는 이미지 (본인의 이미지)
- label: '라벨'을 정의한 텍스트 파일
- 실행 파일: 명령어 실행을 위해서 필요함
사실 이 파일들만 필요한건 아니지만, 나머지는 차근차근 실행하다 보면 만들어지기 때문에 아무 문제 없습니다! (>_<)౨
각각의 내부적인 사항은 다음 포스팅에서 살펴보기로 하고, 일단은 실행을 해 볼까요!
저의 경우, 트레이닝 까지는 했는데 도대체 내 데이터로 검사를 어떻게 하는건지 몰라서 며칠을 헤맸습니다만 ;ㅅ; 여러분은 그런 일 없으셨으면 좋겠습니다. 생각보다 간단하고 쉬워요 ;ㅅ;
1. 데이터 준비하기
위 링크로 들어가시면 UI가 개똥같은; 내가 만들어도 저것보단 예쁘겠다 mnist 예제를 위한 데이터베이스를 다운로드 하실 수 있습니다.
그냥 맨 처음 보이는 파일 4개를 모두 다운로드 하시면 돼요. 각각 training을 위한 이미지와 라벨, test를 위한 이미지와 라벨 파일들입니다. 사이트에 의하면 브라우저가 멋대로 압축을풀어버리는(?) 경우가 있다고 하니 파일 크기를 한번쯤 체크해 주세요 (・ิω・ิ)/ \(・ิω・ิ)
압축을 다 풀고 Caffe/examples/mnist 안에 넣어주세요!
혹시나 해서 다시 말씀드리지만 경로 입력이 빡세서 저는 애초에 caffe 폴더를 C: 아래에 만들었습니다(디스트 파티션도 안하는 생초짜인증;). 그래서 제 기준 경로는:
C:/Caffe/examples/mnist
C:/Caffe/examples/mnist
위 경로에서 역사가 쓰여질 겁니다 헤헤 ٩(ˊᗜˋ*)و.
Caffe에서는 LevelDB, LMDB, hdf5, 일반 이미지 형식을 지원합니다. 이번에는 levelDB 형식으로 써보도록 하겠습니다!
2. Training Data 만들기
아래 명령을 실행합니다.
C:/Caffe/에서 실행하는 경우라면 :
convert_mnist_data -backend=“leveldb” examples/mnist/train-images.idx3-ubyte examples/mnist/train-labels.idx1-ubyte examples/mnist/mnist_train_leveldb
그 외의 경로에서 실행하는 경우라면 :
convert_mnist_data -backend="leveldb" C:/Caffe/examples/mnist/train-images.idx3-ubyte C:/Caffe/examples/mnist/train-labels.idx1-ubyte C:/Caffe/examples.mnist/mnist_train_leveldb
convert_mnist_data.exe 를 실행한다는 뜻입니다. 이전 포스팅에서 caffe 컴파일을 잘 따라 오셨다면 해당 폴더 아래에 이 실행 파일이 있을겁니다. "leveldb" 타입으로 출력하며 순서대로 이미지, 라벨의 주소고 맨 마지막 파라미터는 저장할 폴더입니다.
위 명령어를 잘 실행하고 나면
위와 같은 폴더가 생깁니다. 그 안에 LOG, LOCK 라는 이름의 파일이 있으면 성공입니다!
3. Test Data 만들기
convert_mnist_data -backend=“leveldb”examples/mnist/t10k-images.idx3-ubyte examples/mnist/t10k-labels.idx1-ubyte examples/mnist/mnist_test_leveldb
그 외의 경로에서 실행하는 경우라면 :
convert_mnist_data -backend=“leveldb” C:/Caffe/examples/mnist/t10k-images.idx3-ubyte C:/Caffe/examples/mnist/t10k-labels.idx1-ubyte C:/Caffe/examples/mnist/mnist_test_leveldb
역시 위와 같은 폴더가 생성됩니다.
4. Net, Solver 준비하기
다만 한 가지 수정해 주셔야 하는 부분이 있는데요, p(´⌒`q)
지금까지 data를 leveldb 형식으로 만들었기 때문에 해당 파일의 LMDB라고 되어있는 부분들을 LEVELDB로 바꾸어야 합니다.
lenet_train_test.prototxt의 data layer 아래 data_pram에서 source하고 backend부분이요!
아, 혹시 no module matched 에러가 뜬다면 경로가 정확하지 않아서 일 수 있습니다.
lenet_solver 에서는 net과 snapshot_profix를, lenet_train_test에서는 data layer들의 data_pram의 source를 절대 경로로 바꾸어 주면 해결됩니다(절대경로성애자) (^0^)//
파라미터 공부는 다음 포스팅이라고 하긴 했지만, lenet_solver.prototxt에 max_iter라는 파라미터가 있는데요. 이건 총 얼마나 학습을 시키는지에 대한 변수입니다. 초기값은 10000인데 만개를 다 하려면 하루 내내 돌려야 하더라구요 ㅇㅂㅇ; 이것만 살짝 바꿔볼게요. 저는 100개로 했습니다!
5. Training 하기
아래 명령어를 실행해 줍니다.
C:/Caffe/에서 실행하는 경우라면 :
caffe train -solver=examples/mnist/lenet_solver.prototxt
그 외의 경로에서 실행하는 경우라면 :
caffe train -solver=C:/Caffe/examples/mnist/lenet_solver.prototxt
이렇게 하면 solver 인 lenet_solver.prototxt에 정의된 대로 잘 실행됩니다. 아래와 같은 결과 화면이 나오면 성공입니다!
다 실행된 후 결과 파일은 아래와 같이 caffemodel이라는 확장자명을 가집니다. 저는 solver에서 max_iter를 100으로 지정해 주었기 때문에 파일 이름에 100 이 들어갔구요. 지정하는 max_iter 값에 따라 파일 이름이 바뀝니다. 이 caffemodel 파일에는 net들의 weight 값들이 저장된다고 하네요(^0^*)
결과 화면이 위와는 달리 아래 사진처럼 나오는 경우도 있을 수 있습니다. 하지만 걱정 마세요! 둘다 잘 된 화면이니까요 >.< 시간이 촤라락 찍히면서 로그를 많이 찍는건 Debug 모드, 아닌건 Release 모드로 실행해서 그렇대요!C:/Caffe/에서 실행하는 경우라면 :
caffe train -solver=examples/mnist/lenet_solver.prototxt
그 외의 경로에서 실행하는 경우라면 :
caffe train -solver=C:/Caffe/examples/mnist/lenet_solver.prototxt
이렇게 하면 solver 인 lenet_solver.prototxt에 정의된 대로 잘 실행됩니다. 아래와 같은 결과 화면이 나오면 성공입니다!
다 실행된 후 결과 파일은 아래와 같이 caffemodel이라는 확장자명을 가집니다. 저는 solver에서 max_iter를 100으로 지정해 주었기 때문에 파일 이름에 100 이 들어갔구요. 지정하는 max_iter 값에 따라 파일 이름이 바뀝니다. 이 caffemodel 파일에는 net들의 weight 값들이 저장된다고 하네요(^0^*)
6. 본인의 손글씨 이미지를 판독하기
다음으로는 label.txt 를 만들어 보겠습니다. 데이터는 0부터 9까지 총 10개가 학습되어 있기 때문에 해당 데이터를 나타낼 수 있는 라벨을 텍스트 파일로 정의해 줍니다. 아래와 같이 만들어 주세요.
0
1
2
3
4
5
6
7
8
9
이 두 개의 파일을 C:/Caffe/examples/mnist/아래에 잘 넣어주세요. ٩(๑`^´๑)۶
caffe 솔루션을 잘 빌드하셨다면 classification.exe 파일이 있을 겁니다. 이 명령어를 이용해 실행해 보겠습니다만, 소스코드를 보시면 알겠지만 mean 파일을 받는 부분이 있습니다. 하지만 우리는 mean file을 쓰지 않기 때문에 ;ㅅ;
**처음에는 주석처리만 하고 했는데, 제가 이것저것 건드리다가 성공한걸 다시 해보니 안되더라구요ㅎㅅㅎ; 그래서 그냥 제 소스 전문을 올릴테니 참고해 주세요!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
|
#include <caffe/caffe.hpp>
#ifdef USE_OPENCV
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#endif // USE_OPENCV
#include <algorithm>
#include <iosfwd>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#ifdef USE_OPENCV
using namespace caffe; // NOLINT(build/namespaces)
using std::string;
/* Pair (label, confidence) representing a prediction. */
typedef std::pair<string, float> Prediction;
class Classifier {
public:
Classifier(const string& model_file,
const string& trained_file,
const string& label_file);
std::vector<Prediction> Classify(const cv::Mat& img, int N = 5);
private:
std::vector<float> Predict(const cv::Mat& img);
void WrapInputLayer(std::vector<cv::Mat>* input_channels);
void Preprocess(const cv::Mat& img,
std::vector<cv::Mat>* input_channels);
private:
shared_ptr<Net<float> > net_;
cv::Size input_geometry_;
int num_channels_;
std::vector<string> labels_;
};
Classifier::Classifier(const string& model_file,
const string& trained_file,
const string& label_file) {
#ifdef CPU_ONLY
Caffe::set_mode(Caffe::CPU);
#else
Caffe::set_mode(Caffe::GPU);
#endif
/* Load the network. */
net_.reset(new Net<float>(model_file, TEST));
net_->CopyTrainedLayersFrom(trained_file);
CHECK_EQ(net_->num_inputs(), 1) << "Network should have exactly one input.";
CHECK_EQ(net_->num_outputs(), 1) << "Network should have exactly one output.";
Blob<float>* input_layer = net_->input_blobs()[0];
num_channels_ = input_layer->channels();
CHECK(num_channels_ == 3 || num_channels_ == 1)
<< "Input layer should have 1 or 3 channels.";
input_geometry_ = cv::Size(input_layer->width(), input_layer->height());
/* Load labels. */
std::ifstream labels(label_file.c_str());
CHECK(labels) << "Unable to open labels file " << label_file;
string line;
while (std::getline(labels, line))
labels_.push_back(string(line));
Blob<float>* output_layer = net_->output_blobs()[0];
CHECK_EQ(labels_.size(), output_layer->channels())
<< "Number of labels is different from the output layer dimension.";
}
static bool PairCompare(const std::pair<float, int>& lhs,
const std::pair<float, int>& rhs) {
return lhs.first > rhs.first;
}
/* Return the indices of the top N values of vector v. */
static std::vector<int> Argmax(const std::vector<float>& v, int N) {
std::vector<std::pair<float, int> > pairs;
for (size_t i = 0; i < v.size(); ++i)
pairs.push_back(std::make_pair(v[i], static_cast<int>(i)));
std::partial_sort(pairs.begin(), pairs.begin() + N, pairs.end(), PairCompare);
std::vector<int> result;
for (int i = 0; i < N; ++i)
result.push_back(pairs[i].second);
return result;
}
/* Return the top N predictions. */
std::vector<Prediction> Classifier::Classify(const cv::Mat& img, int N) {
std::vector<float> output = Predict(img);
N = std::min<int>(labels_.size(), N);
std::vector<int> maxN = Argmax(output, N);
std::vector<Prediction> predictions;
for (int i = 0; i < N; ++i) {
int idx = maxN[i];
predictions.push_back(std::make_pair(labels_[idx], output[idx]));
}
return predictions;
}
std::vector<float> Classifier::Predict(const cv::Mat& img) {
Blob<float>* input_layer = net_->input_blobs()[0];
input_layer->Reshape(1, num_channels_,
input_geometry_.height, input_geometry_.width);
/* Forward dimension change to all layers. */
net_->Reshape();
std::vector<cv::Mat> input_channels;
WrapInputLayer(&input_channels);
Preprocess(img, &input_channels);
net_->Forward();
/* Copy the output layer to a std::vector */
Blob<float>* output_layer = net_->output_blobs()[0];
const float* begin = output_layer->cpu_data();
const float* end = begin + output_layer->channels();
return std::vector<float>(begin, end);
}
/* Wrap the input layer of the network in separate cv::Mat objects
* (one per channel). This way we save one memcpy operation and we
* don't need to rely on cudaMemcpy2D. The last preprocessing
* operation will write the separate channels directly to the input
* layer. */
void Classifier::WrapInputLayer(std::vector<cv::Mat>* input_channels) {
Blob<float>* input_layer = net_->input_blobs()[0];
int width = input_layer->width();
int height = input_layer->height();
float* input_data = input_layer->mutable_cpu_data();
for (int i = 0; i < input_layer->channels(); ++i) {
cv::Mat channel(height, width, CV_32FC1, input_data);
input_channels->push_back(channel);
input_data += width * height;
}
}
void Classifier::Preprocess(const cv::Mat& img,
std::vector<cv::Mat>* input_channels) {
/* Convert the input image to the input image format of the network. */
cv::Mat sample;
if (img.channels() == 3 && num_channels_ == 1)
cv::cvtColor(img, sample, cv::COLOR_BGR2GRAY);
else if (img.channels() == 4 && num_channels_ == 1)
cv::cvtColor(img, sample, cv::COLOR_BGRA2GRAY);
else if (img.channels() == 4 && num_channels_ == 3)
cv::cvtColor(img, sample, cv::COLOR_BGRA2BGR);
else if (img.channels() == 1 && num_channels_ == 3)
cv::cvtColor(img, sample, cv::COLOR_GRAY2BGR);
else
sample = img;
cv::Mat sample_resized;
if (sample.size() != input_geometry_)
cv::resize(sample, sample_resized, input_geometry_);
else
sample_resized = sample;
cv::Mat sample_float;
if (num_channels_ == 3)
sample_resized.convertTo(sample_float, CV_32FC3);
else
sample_resized.convertTo(sample_float, CV_32FC1);
/* This operation will write the separate BGR planes directly to the
* input layer of the network because it is wrapped by the cv::Mat
* objects in input_channels. */
cv::split(sample_float, *input_channels);
CHECK(reinterpret_cast<float*>(input_channels->at(0).data)
== net_->input_blobs()[0]->cpu_data())
<< "Input channels are not wrapping the input layer of the network.";
}
int main(int argc, char** argv) {
clock_t begin, end;
begin = clock();
if (argc != 5) {
std::cerr << "Usage: " << argv[0]
<< " deploy.prototxt network.caffemodel"
<< " labels.txt img.jpg" << std::endl;
return 1;
}
::google::InitGoogleLogging(argv[0]);
string model_file = argv[1];
string trained_file = argv[2];
string label_file = argv[3];
Classifier classifier(model_file, trained_file, label_file);
string file = argv[4];
std::cout << "---------- Prediction for "
<< file << " ----------" << std::endl;
cv::Mat img = cv::imread(file, -1);
CHECK(!img.empty()) << "Unable to decode image " << file;
std::vector<Prediction> predictions = classifier.Classify(img);
/* Print the top N predictions. */
for (size_t i = 0; i < predictions.size(); ++i) {
Prediction p = predictions[i];
std::cout << std::fixed << std::setprecision(4) << p.second << " - \""
<< p.first << "\"" << std::endl;
}
end = clock();
std::cout<<"running time : " << ((end-begin)/CLOCKS_PER_SEC / 1000)<< "ms" << std::endl;
}
#else
int main(int argc, char** argv) {
LOG(FATAL) << "This example requires OpenCV; compile with USE_OPENCV.";
}
#endif // USE_OPENCV
| cs |
잘 바꾸어 주었다면 저장-빌드를 해주세요. 자, 그럼 드디어 명령어 실행을 해볼까요!
C:/Caffe/examples/mnist/에서 실행하는 경우라면 :
classification lenet.prototxt lenel_iter_100.caffemodel labels.txt a.jpg
그 외의 경로에서 실행하는 경우라면 :
classification C:/Caffe/examples/mnist/lenet.prototxt C:/Caffe/examples/mnist/lenel_iter_100.caffemodel C:/Caffe/examples/mnist/labels.txt C:/Caffe/examples/mnist/a.jpg
1의 확률로 8이라고 나왔네요. 잘 나왔습니다 (・ิω・ิ)
다른 사진으로 해도 꽤 잘 먹네요! 노이즈를 많이 끼면 잘 인식을 못하지만... 보통 그러진 않으니까요ㅎㅎ
여기까지 잘 따라와 주셨나요? 좀 길었나요? (저는 많이 길게 느껴졌는데요ㅠㅠ..)
다음 포스팅에서는 예고한 대로, 각 prototxt 파일이나 소스코드의 내부를 좀 더 살펴보려고 합니다! 그럼 다음에 뵈어요 ( ㅎㅅㅎ)//
좋은글 잘읽었습니다~
답글삭제감사합니다!! 많은 도움받았어요 ㅎㅎ
답글삭제2. Training data 만들기에서
답글삭제convert_mnist_data -backend="leveldb" C:/Caffe/examples/mnist/train-images.idx3-ubyte C:/Caffe/examples/mnist/train-labels.idx1-ubyte C:/Caffe/examples.mnist/mnist_train_leveldb
라고 되어있는데 C:/Caffe/examples.mnist/mnist_train_leveldb 의 examples.mnist 이부분은
examples/mnist 가 맞나요?
잘봤습니다.
답글삭제말투 너무 귀엽네요 ㅋㅋㅋㅋㅋㅋ
좋은 글 감사합니다^^
답글삭제실제로 내가 쓴 손글씨 그림파일가지고 실습할 때, classification lenet.prototxt lenel_iter_100.caffemodel labels.txt a.jpg 가 아니라 classification lenet.prototxt lene"t"_iter_100.caffemodel labels.txt a.jpg 아닌가요?
답글삭제작성자가 댓글을 삭제했습니다.
답글삭제