使用gensim和sklearn搭建一个文本分类器(一):文档向量化

总的来讲,一个完整的文本分类器主要由两个阶段,或者说两个部分组成:一是将文本向量化,将一个字符串转化成向量形式;二是传统的分类器,包括线性分类器,SVM, 神经网络分类器等等。

之前看的THUCTC的技术栈是使用 tf-idf 来进行文本向量化,使用卡方校验(chi-square)来降低向量维度,使用liblinear(采用线性核的svm) 来进行分类。而这里所述的文本分类器,使用lsi (latent semantic analysis, 隐性语义分析) 来进行向量化, 不需要降维, 因为可以直接指定维度, 然后使用线性核svm进行分类。lsi的部分主要使用gensim来进行, 分类主要由sklearn来完成。

1. 文档向量化

这部分的内容主要由gensim来完成。gensim库的一些基本用法在我之前的文章中已经有过介绍 点这里 这里就不再详述, 直接按照流程来写了。采用lsi进行向量化的流程主要有下面几步:

  1. 将各文档分词,从字符串转化为单词列表
  2. 统计各文档单词,生成词典(dictionary)
  3. 利用词典将文档转化成词频表示的向量,即指向量中的各值对应于词典中对应位置单词在该文档中出现次数
  4. 再进行进一步处理,将词频表示的向量转化成tf-idf表示的向量
  5. 由tf-idf表示的向量转化成lsi表示的向量

接下来按照上述流程来分别阐述

1.1 文档分词及预处理

分词有很多种方法,也有很多现成的库,这里仅介绍结巴的简单用法

import jieba

content = """面对当前挑战,我们应该落实2030年可持续发展议程,促进包容性发展"""
content = list(jieba.cut(content, cut_all=False))
print(content)
>>>[‘面对‘, ‘当前‘, ‘挑战‘, ‘,‘, ‘我们‘, ‘应该‘, ‘落实‘, ‘2030‘, ‘年‘, ‘可‘, ‘持续‘, ‘发展‘, ‘议程‘, ‘,‘, ‘促进‘, ‘包容性‘, ‘发展‘]

注意上面的cut_all选项,如果cut_all=False, 则会列出最优的分割选项; 如果cut_all=True, 则会列出所有可能出现的词

content = list(jieba.cut(content, cut_all=True))
print(content)
>>>[‘面对‘, ‘当前‘, ‘挑战‘, ‘‘, ‘‘, ‘我们‘, ‘应该‘, ‘落实‘, ‘2030‘, ‘年‘, ‘可‘, ‘持续‘, ‘发展‘, ‘议程‘, ‘‘, ‘‘, ‘促进‘, ‘包容‘, ‘包容性‘, ‘容性‘, ‘发展‘]

应该观察到,在分词后的直接结果中,有大量的无效项,例如空格,逗号等等。因此,一般在分词以后,还要进行预处理。例如去掉停用词(stop words, 指的是没什么意义的词,例如空格,逗号,句号,啊,呀, 等等), 去掉出现出现频率过低和过高的词等等。

我这一部分的程序是

def convert_doc_to_wordlist(str_doc,cut_all):
    # 分词的主要方法
    sent_list = str_doc.split(‘\n‘)
    sent_list = map(rm_char, sent_list) # 去掉一些字符,例如\u3000
    word_2dlist = [rm_tokens(jieba.cut(part,cut_all=cut_all)) for part in sent_list] # 分词
    word_list = sum(word_2dlist,[])
    return word_list

def rm_char(text):
    text = re.sub(‘\u3000‘,‘‘,text)
    return text

def get_stop_words(path=‘/home/multiangle/coding/python/PyNLP/static/stop_words.txt‘):
    # stop_words中,每行放一个停用词,以\n分隔
    file = open(path,‘rb‘).read().decode(‘utf8‘).split(‘\n‘)
    return set(file)

def rm_tokens(words): # 去掉一些停用次和数字
    words_list = list(words)
    stop_words = get_stop_words()
    for i in range(words_list.__len__())[::-1]:
        if words_list[i] in stop_words: # 去除停用词
            words_list.pop(i)
        elif words_list[i].isdigit():
            words_list.pop(i)
    return words_list

主程序是convert_doc_to_wordlist方法,拿到要分词的文本以后,首先去掉一些字符,例如\u3000等等。然后进行分词,再去掉其中的停用词和数字。 最后得到的单词,其顺序是打乱的,即单词间的相关信息已经丢失

1.2 统计单词,生成词典

一般来讲, 生成词典应该在将所有文档都分完词以后统一进行,不过对于规模特别大的数据,可以采用边分词边统计的方法。将文本分批读取分词,然后用之前生成的词典加入新内容的统计结果,如下面所示

from gensim import corpora,models
import jieba
import re
from pprint import pprint
import os

files = ["但是现在教育局非要治理这么一个情况",
         "然而又不搞明白为什么这些词会出现"]
dictionary = corpora.Dictionary()
for file in files:
    file = convert_doc_to_wordlist(file, cut_all=True)
    dictionary.add_documents([file])
pprint(sorted(list(dictionary.items()),key=lambda x:x[0]))
>>>[(0, ‘教育‘),
>>> (1, ‘治理‘),
>>> (2, ‘教育局‘),
>>> (3, ‘情况‘),
>>> (4, ‘非要‘),
>>> (5, ‘搞‘),
>>> (6, ‘明白‘),
>>> (7, ‘词‘)]

对于已经存在的词典,可以使用dictionary.add_documents来往其中增加新的内容。当生成词典以后,会发现词典中的词太多了,达到了几十万的数量级, 因此需要去掉出现次数过少的单词,因为这些代词没什么代表性。

small_freq_ids = [tokenid for tokenid, docfreq in dictionary.dfs.items() if docfreq < 5 ]
dictionary.filter_tokens(small_freq_ids)
dictionary.compactify()

1.3 将文档转化成按词频表示的向量

继续沿着之前的思路走,接下来要用dictionary把文档从词语列表转化成用词频表示的向量,也就是one-hot表示的向量。所谓one-hot,就是向量中的一维对应于词典中的一项。如果以词频表示,则向量中该维的值即为词典中该单词在文档中出现的频率。其实这个转化很简单,使用dictionray.doc2bow方法即可。

count = 0
bow  = []
for file in files:
    count += 1
    if count%100 == 0 :
        print(‘{c} at {t}‘.format(c=count, t=time.strftime(‘%Y-%m-%d %H:%M:%S‘,time.localtime())))
    word_list = convert_doc_to_wordlist(file, cut_all=False)
    word_bow = dictionary.doc2bow(word_list)
    bow.append(word_bow)
pprint(bow)
>>>[[(1, 1), (2, 1), (4, 1)], [(5, 1), (6, 1)]]

1.4 转化成tf-idf和lsi向量

之所以把这两部分放到一起,并不是因为这两者的计算方式或者说原理有多相似(实际上两者完全不同),而是说在gensim中计算这两者的调用方法比较类似,都需要调用gensim.models库。

tfidf_model = models.TfidfModel(corpus=corpus,
                                dictionary=dictionary)
corpus_tfidf = [tfidf_model[doc] for doc in corpus]
lsi_model = models.LsiModel(corpus = corpus_tfidf,
                            id2word = dictionary,
                            num_topics=50)
corpus_lsi = [lsi_model[doc] for doc in corpus]

可以看到gensim的方法还是比较简洁的。

1.5 实践中的一些问题

由于之前阅读THUCTC源码的时候下载了THUCTCNews文档集,大概1G多点,已经帮你分好类,放在各个文件夹下面了。为了便于分析,各个环节的中间结果(词频向量,tfidf向量等)也都会存放到本地。为了便于以后标注,各个类的中间结果也是按类别存储的。


2. 分类问题

在将文本向量化以后,就可以采用传统的分类方法了, 例如线性分类法,线性核的svm,rbf核的svm,神经网络分类等方法。我在这个分类器中尝试了前3种,都可以由sklearn库来完成

2.1 从gensim到sklearn的格式转换

一个很尴尬的问题是,gensim中的corpus数据格式,sklearn是无法识别的。即gensim中对向量的表示形式与sklearn要求的不符

在gensim中,向量是稀疏表示的。例如[(0,5),(6,3)] 意思就是说,该向量的第0个元素值为5,第6个元素值为3,其他为0.但是这种表示方式sklearn是无法识别的。sklearn的输入一般是与numpy或者scipy配套的。如果是密集矩阵,就需要输入numpy.array格式的; 如果是稀疏矩阵,则需要输入scipy.sparse.csr_matrix.由于后者可以转化成前者,而且gensim中向量本身就是稀疏表示,所以这边只讲如何将gensim中的corpus格式转化成csr_matrix.

scipy的官网去找相关文档,可以看到csr_matrix的构造有如下几种方法。

第一种是由现有的密集矩阵来构建稀疏矩阵,第二种不是很清楚,第三种构建一个空矩阵。第四种和第五种符合我们的要求。其中第四种最为直观,构建三个数组,分别存储每个元素的行,列和数值即可。

官网给出的示例代码如下,还是比较直观的。

row = np.array([0, 0, 1, 2, 2, 2])
col = np.array([0, 2, 2, 0, 1, 2])
data = np.array([1, 2, 3, 4, 5, 6])
print(csr_matrix((data, (row, col)), shape=(3, 3)).toarray())
>>>array([[1, 0, 2],
         [0, 0, 3],
         [4, 5, 6]])

依样画葫芦,gensim转化到csr_matrix的程序可以写成

data = []
rows = []
cols = []
line_count = 0
for line in lsi_corpus_total:  # lsi_corpus_total 是之前由gensim生成的lsi向量
    for elem in line:
        rows.append(line_count)
        cols.append(elem[0])
        data.append(elem[1])
    line_count += 1
lsi_sparse_matrix = csr_matrix((data,(rows,cols))) # 稀疏向量
lsi_matrix = lsi_sparse_matrix.toarray()  # 密集向量

2.2 线性分类器

sklearn中,可以使用sklearn.linear_model.SGDClassifier来进行线性分类。

import numpy as np
from sklearn import linear_model

X = np.array([[-1, -1], [-2, -1], [1, 1], [2, 1]])
Y = np.array([1, 1, 2, 2])
clf = linear_model.SGDClassifier()
clf.fit(X, Y)
print(clf.predict([[-0.8, -1]]))

在上面的例子中,X代表了训练集。上面的X是一个4*2的矩阵,代表训练集中含有4各样本,每个样本的维度是2维。而Y代表的是训练集中各样本所期望的分类结果。所以,回到文本分类的任务。假设在之前的lsi中向量维度为50,而训练集的规模是8.9W的话,我们应该输入一个8.9W*50的矩阵和一个长为8.9W的向量,代表各文本所属类别。

3. 具体实践和代码

所以在运行前,需要先修改存放数据集的位置path_doc_parent, 以及存放中间结果的位置path_tmp

path_root   = ‘/media/multiangle/F/DataSet/THUCNews‘
path_tmp    = path_root + ‘/tmp‘  # 存放中间结果的目录
path_doc_parent = os.path.join(path_root,‘THUCNewsTotal‘) # 存放文本数据集的目录
path_dict_folder = os.path.join(path_tmp, ‘THUNewsDict‘) # 存放词典的地方

将THUCTCNews文本集转化成lsi向量的完整代码如下:

import os
from gensim import corpora, models
import jieba
import re
from multiprocessing import Process,Queue
import time
import pickle as pkl

class loadFolders(object):
    def __init__(self,par_path):
        self.par_path = par_path
    def __iter__(self):
        for file in os.listdir(self.par_path):
            file_abspath = os.path.join(self.par_path, file)
            if os.path.isdir(file_abspath): # if file is a folder
                yield file_abspath

class loadFiles(object):
    def __init__(self,par_path):
        self.par_path = par_path
    def __iter__(self):
        folders = loadFolders(self.par_path)
        for folder in folders:              # level directory
            for file in os.listdir(folder):     # secondary directory
                file_path = os.path.join(folder,file)
                if os.path.isfile(file_path):
                    this_file = open(file_path,‘rb‘)
                    content = this_file.read().decode(‘utf8‘)
                    yield content
                    this_file.close()

def rm_char(text):
    text = re.sub(‘\u3000‘,‘‘,text)
    return text

def get_stop_words(path=‘/home/multiangle/coding/python/PyNLP/static/stop_words.txt‘):
    file = open(path,‘rb‘).read().decode(‘utf8‘).split(‘\n‘)
    return set(file)

def rm_tokens(words): # 去掉一些停用次和数字
    words_list = list(words)
    stop_words = get_stop_words()
    for i in range(words_list.__len__())[::-1]:
        if words_list[i] in stop_words: # 去除停用词
            words_list.pop(i)
        elif words_list[i].isdigit():
            words_list.pop(i)
    return words_list

def convert_doc_to_wordlist(str_doc,cut_all):
    sent_list = str_doc.split(‘\n‘)
    sent_list = map(rm_char, sent_list) # 去掉一些字符,例如\u3000
    word_2dlist = [rm_tokens(jieba.cut(part,cut_all=cut_all)) for part in sent_list] # 分词
    word_list = sum(word_2dlist,[])
    return word_list

def generate_dict_subprocess(id,
                             p_num,
                             dict_queue,
                             cut_all,
                             file_parent_path=‘/mnt/D/multiangle/DataSet/THUCNews‘
                             ):
    files = loadFiles(file_parent_path)
    dictionary = corpora.Dictionary()
    file_count = 0
    exe_count = 0
    for file in files:
        file_count += 1
        if file_count%p_num==id:
            exe_count += 1
            file = convert_doc_to_wordlist(file, cut_all)
            dictionary.add_documents([file])
            if exe_count%100==0:
                print(‘Process {i} has execute {c} at {t}‘.format(
                    c=exe_count,
                    t=time.strftime(‘%Y-%m-%d %H:%M:%S‘,time.localtime()),
                    i=id)
                )
                print(file[:min(file.__len__(),5)])
    dict_queue.put(dictionary)

def genDict(path_parent, path_dict_folder):
    # 第一次遍历,成立词典,获取词频,文频等信息
    p_pool = []
    dict_queue = Queue()
    p_num = 3
    for i in range(p_num):
        p = Process(target=generate_dict_subprocess,
                    args=(i,
                          p_num,
                          dict_queue,
                          cut_all,
                          # ‘/mnt/D/multiangle/DataSet/THUCNews‘
                          path_parent
                          ))
        p_pool.append(p)

    for p in p_pool: # 启动进程
        # p = Process(p)
        p.start()

    while True: # 检测是否全部完成
        if dict_queue.qsize() >= p_num:
            break
        time.sleep(1)

    dictionary = corpora.Dictionary()
    for i in range(p_num):
        q_dict = dict_queue.get()
        dictionary.merge_with(q_dict)

    for p in p_pool:
        p.terminate()

    dictionary.save(os.path.join(path_dict_folder,‘THUNews.dict‘))
    dictionary.save_as_text(os.path.join(path_dict_folder,‘THUNews.txt‘))

def convDoc2Vector(path_doc_parent,path_dict_folder,path_root,path_tmp):
    dictionary = corpora.Dictionary.load(os.path.join(path_dict_folder,‘THUNews_picked.dict‘))
    print(os.path.join(path_dict_folder,‘THUNews_picked.dict‘))
    for folder in loadFolders(path_doc_parent):
        folder_name = folder.split(‘/‘)[-1]
        print(folder_name)
        files = os.listdir(folder)
        cate_bow = []
        count = 0
        for file in files:
            count += 1
            if count%100 == 0 :
                print(‘{c} at {t}‘.format(c=count, t=time.strftime(‘%Y-%m-%d %H:%M:%S‘,time.localtime())))
            if count%10 > 0: # 抽样 n抽1
                continue
            file_path = os.path.join(folder,file)
            file = open(file_path,‘rb‘)
            doc = file.read().decode(‘utf8‘)
            word_list = convert_doc_to_wordlist(doc, cut_all)
            word_bow = dictionary.doc2bow(word_list)
            cate_bow.append(word_bow)
            file.close()

        tmp_path = os.path.join(path_tmp,‘bow_sampling‘)
        if not os.path.exists(tmp_path):
            os.mkdir(tmp_path)
        corpora.MmCorpus.serialize(tmp_path+‘/{x}.mm‘.format(x=folder_name),
                                   cate_bow,
                                   id2word=dictionary,
                                   # labels=folder_name
                                   )

if __name__==‘__main__‘:

    cut_all = True # 是否要把所有可能的单词都列出来? true 表示是 , false 表示否
    path_root   = ‘/media/multiangle/F/DataSet/THUCNews‘
    path_tmp    = path_root + ‘/tmp‘
    if not os.path.exists(path_tmp):
        os.mkdir(path_tmp)
    path_doc_parent = os.path.join(path_root,‘THUCNewsTotal‘)
    os.chdir(path_doc_parent)
    path_dict_folder = os.path.join(path_tmp, ‘THUNewsDict‘) # 存放词典的地方
    if not os.path.exists(path_dict_folder):
        os.mkdir(path_dict_folder)

    # # ===================================================================
    # # 第一次遍历,成立词典,获取词频,文频等信息
    genDict(path_doc_parent, path_dict_folder)

    # # ===================================================================
    # # 去掉词典中出现次数过少的
    dictionary = corpora.Dictionary.load(os.path.join(path_dict_folder, ‘THUNews.dict‘))
    small_freq_ids = [tokenid for tokenid, docfreq in dictionary.dfs.items() if docfreq < 5 ]
    dictionary.filter_tokens(small_freq_ids)
    dictionary.compactify()
    dictionary.save(os.path.join(path_dict_folder, ‘THUNews_picked.dict‘))

    # # ===================================================================
    # # 第二次遍历,开始将文档转化成id稀疏表示
    convDoc2Vector(path_doc_parent, path_dict_folder, path_root,path_tmp)

    # # ===================================================================
    # 第三次遍历,开始将文档转化成tf idf 表示
    dictionary = corpora.Dictionary.load(os.path.join(path_dict_folder,‘THUNews_picked.dict‘))
    bow_path = path_tmp + ‘/bow_sampling‘
    tfidf_path = path_tmp + ‘/tfidf_sampling‘
    if not os.path.exists(tfidf_path):
        os.mkdir(tfidf_path)
    files = os.listdir(bow_path)
    cate_set = set([x.split(‘.‘)[0] for x in files])
    for cat in cate_set:
        path = ‘{pp}/{cat}.mm‘.format(pp=bow_path, cat=cat)
        corpus = corpora.MmCorpus(path)
        tfidf_model = models.TfidfModel(corpus=corpus,
                                        dictionary=dictionary)

        tfidf_file = open(path_tmp+‘/tfidf_model.pkl‘,‘wb‘)
        pkl.dump(tfidf_model, tfidf_file)
        tfidf_file.close()

        corpus_tfidf = [tfidf_model[doc] for doc in corpus]
        corpora.MmCorpus.serialize(‘{f}/{c}.mm‘.format(f=tfidf_path,c=cat),
                                   corpus_tfidf,
                                   id2word=dictionary
                                   )
        print(‘{f}/{c}.mm‘.format(f=tfidf_path,c=cat))

    # # ===================================================================
    # # 第四次遍历,计算lsi
    dictionary  = corpora.Dictionary.load(os.path.join(path_dict_folder,‘THUNews_picked.dict‘))
    tfidf_path  = path_tmp + ‘/tfidf_sampling‘
    lsi_path    = path_tmp + ‘/lsi_sampling‘
    if not os.path.exists(lsi_path):
        os.mkdir(lsi_path)
    files = os.listdir(tfidf_path)
    cate_list = list(set([x.split(‘.‘)[0] for x in files]))
    doc_num_list = []
    tfidf_corpus_total = None
    for cat in cate_list:
        path = ‘{pp}/{cat}.mm‘.format(pp=tfidf_path, cat=cat)
        corpus = corpora.MmCorpus(path)
        doc_num_list.append(corpus.num_docs)
        if not tfidf_corpus_total:
            tfidf_corpus_total = [x for x in corpus]
        else:
            tfidf_corpus_total += [x for x in corpus]
        print(‘category {c} loaded,len {l} at {t}‘
              .format(c=cat,l=corpus.num_docs,t=time.strftime(‘%Y-%m-%d %H:%M:%S‘,time.localtime())))

    lsi_model = models.LsiModel(corpus = tfidf_corpus_total, id2word = dictionary, num_topics=50)
    lsi_file = open(path_tmp+‘/lsi_model.pkl‘,‘wb‘)
    pkl.dump(lsi_model, lsi_file)
    lsi_file.close()

    print(‘lsi model is generated at {t}‘.format(t=time.strftime(‘%Y-%m-%d %H:%M:%S‘,time.localtime())))
    del tfidf_corpus_total  # 总共的tfidf corpus已经用完,释放变量空间
    for cat in cate_list:
        path = ‘{pp}/{cat}.mm‘.format(pp=tfidf_path, cat=cat)
        corpus = corpora.MmCorpus(path)
        corpus_lsi = [lsi_model[doc] for doc in corpus]
        corpora.MmCorpus.serialize(‘{f}/{c}.mm‘.format(f=lsi_path,c=cat),
                                   corpus_lsi,
                                   id2word=dictionary
                                   )
        print(‘category {c} generate lsi vector, at {t}‘
              .format(c=cat,t=time.strftime(‘%Y-%m-%d %H:%M:%S‘,time.localtime())))
时间: 2024-12-20 19:25:32

使用gensim和sklearn搭建一个文本分类器(一):文档向量化的相关文章

用VC++MFC做文本编辑器(单文档模式)

用VC++MFC做文本编辑器(单文档模式) 原来做过一个用对话框实现的文本编辑器,其实用MFC模板里面的单文档模板也可以做,甚至更加方便,适合入门级的爱好者试试,现介绍方法如下: < xmlnamespace prefix ="o" ns ="urn:schemas-microsoft-com:office:office" /> 1,首先新建一个工程,选择MFC AppWizard(exe),定名字为:textview_1,程序类型选择单个文档,其他均默

[.NET] 打造一个很简单的文档转换器 - 使用组件 Spire.Office

打造一个很简单的文档转换器 - 使用组件 Spire.Office 目录 Spire.Office 介绍 库引用 界面预览 代码片段 Spire.Office 介绍 关于 Spire.Office,它是一个专门为开发人员创建,读取,写入设计的库,转换和从打印 word 文档文件.作为一个独立的 .NET组件,它不需要在机器上安装微软的 Word 等办公软件.然而,它可以将微软的“文档创建功能”集成到任何开发人员的网络应用程序中.它是一个可靠的 MS Word 的API,可以执行许多Word文档处

一个GO语言 的文档中文译本网站

一个GO语言 的文档中文译本网站: 1. <学习Go语言>中文版:http://mikespook.com/learning-go/ 2.GITHUB :https://github.com/mikespook/Learning-Go-zh-cn 一个GO语言 的文档中文译本网站,布布扣,bubuko.com

如何从一个对话框弹出单文档视图

如何从一个对话框弹出单文档视图 分类: Visual C++2006-06-01 20:02 9323人阅读 评论(19) 收藏 举报 文档initializationmfctemplatesvalidationcommand 朱金灿 相信不少人进行数据库编程都有这样的问题,如何设置一个登陆框,通过登陆框来进入单文档视图.我看到很多数据库编程方面的书,都是对话框之间的相互切换.而在对话框中添加菜单不少人都不太熟悉(当然这是可以办到的).我在想:为何不能在对话框中弹出单文档,这样添加菜单等工作就方

搭建Nginx负载均衡服务文档一

搭建负载均衡服务的实际需求: 1.把单台服务器无法承受的大规模并发访问或数据流量分担到多台节点设备上,分别进行处理,减少用户等待响应的时间,提升用户体验. 2.单个重负载的运算分担到多台节点设备上做并行处理,每个节点处理结束后,将结果汇总,返回给用户. 3.7*24小时的服务保证,任意一个或多个有限后面节点设备宕机,不能影响业务. 实现Nginx负载均衡需要两个组件: l  Ngx_http_proxy_module,用于把请求后抛给服务器节点或upstream服务器池: l  Ngx_http

一个在线编辑markdown文档的编辑器

![mahua](mahua-logo.jpg) ##MaHua是什么? 向Mac下优秀的markdown编辑器mou致敬 #MaHua有哪些功能? * 方便的`导入导出`功能 *  直接把一个markdown的文本文件拖放到当前这个页面就可以了 *  导出为一个html格式的文件,样式一点也不会丢失 * 编辑和预览`同步滚动`,所见即所得(右上角设置) * `VIM快捷键`支持,方便vim党们快速的操作 (右上角设置) * 强大的`自定义CSS`功能,方便定制自己的展示 * 有数量也有质量的`

做一个项目的基本文档结构

---恢复内容开始--- 1.首先是对psd文档进行分析,找出各个文档中的相同点,比如说相同的头部(header).尾部(footer).侧边(sidebar)等. 2.建立基本的文档结构,如: 3.切片:对于logo一般保存在imgs里面,我这里是保存在imgs/common里面,即保存在图片的公共部分.imgs里面一般保存的是临时图片,即网站上线之后随时需要换掉的图片.比如轮播图等一般都是临时图片.还有就是对于比较大的图片也是保存在imgs里面.如: 对于css里面的背景图片,一般保存在和c

apidoc,一个相当不错的文档生成器

http://apidocjs.com/ 例子:myapp目录下的MyCode.java /** * * @api {get} /company/list 获取公司信息 * @apiName 获取公司列表 * @apiGroup All * @apiVersion 0.1.0 * @apiDescription 接口详细描述 * * @apiParam {int} pageNum分页大小 * * @apiSuccess {String} code 结果码 * @apiSuccess {Strin

搭建个考试系统帮助文档

原文地址:https://www.cnblogs.com/028686a/p/8507297.html