K折交叉验证Python实现

6

我正在尝试在Python中实现k折交叉验证算法。我知道SKLearn提供了一个实现,但仍然...目前这是我的代码。

from sklearn import metrics
import numpy as np

class Cross_Validation:

@staticmethod
def partition(vector, fold, k):
    size = vector.shape[0]
    start = (size/k)*fold
    end = (size/k)*(fold+1)
    validation = vector[start:end]
    if str(type(vector)) == "<class 'scipy.sparse.csr.csr_matrix'>":
        indices = range(start, end)
        mask = np.ones(vector.shape[0], dtype=bool)
        mask[indices] = False
        training = vector[mask]
    elif str(type(vector)) == "<type 'numpy.ndarray'>":
        training = np.concatenate((vector[:start], vector[end:]))
    return training, validation

@staticmethod
def Cross_Validation(learner, k, examples, labels):
    train_folds_score = []
    validation_folds_score = []
    for fold in range(0, k):
        training_set, validation_set = Cross_Validation.partition(examples, fold, k)
        training_labels, validation_labels = Cross_Validation.partition(labels, fold, k)
        learner.fit(training_set, training_labels)
        training_predicted = learner.predict(training_set)
        validation_predicted = learner.predict(validation_set)
        train_folds_score.append(metrics.accuracy_score(training_labels, training_predicted))
        validation_folds_score.append(metrics.accuracy_score(validation_labels, validation_predicted))
    return train_folds_score, validation_folds_score

learner参数是来自SKlearn库的分类器,k是交叉验证的折数,examples是由CountVectorizer(同样是SKlearn)生成的稀疏矩阵,它代表了词袋的表示方式。 例如:

from sklearn.feature_extraction.text import CountVectorizer
from sklearn.naive_bayes import MultinomialNB
from Cross_Validation import Cross_Validation as cv

vectorizer = CountVectorizer(stop_words='english', lowercase=True, min_df=2, analyzer="word")
data = vectorizer.fit_transform("""textual data""")
clfMNB = MultinomialNB(alpha=.0001)
score = cv.Cross_Validation(clfMNB, 10, data, labels)
print "Train score" + str(score[0])
print "Test score" + str(score[1])

我猜测由于某些逻辑错误导致模型在训练集上得分为95%(预期),但在测试集上几乎为0,但我找不到原因。
希望我表达清楚。 提前感谢。
这是加载文本并转换为可传递给向量化器的向量的代码。它还返回标签向量。
from nltk.tokenize import word_tokenize
from Categories_Data import categories
import numpy as np
import codecs
import glob
import os
import re

class Data_Preprocessor:

def tokenize(self, text):
    tokens = word_tokenize(text)
    alpha = [t for t in tokens if unicode(t).isalpha()]
    return alpha

def header_not_fully_removed(self, text):
    if ":" in text.splitlines()[0]:
        return len(text.splitlines()[0].split(":")[0].split()) == 1
    else:
        return False

def strip_newsgroup_header(self, text):
    _before, _blankline, after = text.partition('\n\n')
    if len(after) > 0 and self.header_not_fully_removed(after):
        after = self.strip_newsgroup_header(after)
    return after

def strip_newsgroup_quoting(self, text):
    _QUOTE_RE = re.compile(r'(writes in|writes:|wrote:|says:|said:'r'|^In article|^Quoted from|^\||^>)')
    good_lines = [line for line in text.split('\n')
        if not _QUOTE_RE.search(line)]
    return '\n'.join(good_lines)

def strip_newsgroup_footer(self, text):
    lines = text.strip().split('\n')
    for line_num in range(len(lines) - 1, -1, -1):
        line = lines[line_num]
        if line.strip().strip('-') == '':
            break
    if line_num > 0:
        return '\n'.join(lines[:line_num])
    else:
        return text

def raw_to_vector(self, path, to_be_stripped=["header", "footer", "quoting"], noise_threshold=-1):
    base_dir = os.getcwd()
    train_data = []
    label_data = []
    for category in categories:
        os.chdir(base_dir)
        os.chdir(path+"/"+category[0])
        for filename in glob.glob("*"):
            with codecs.open(filename, 'r', encoding='utf-8', errors='replace') as target:
                data = target.read()
                if "quoting" in to_be_stripped:
                    data = self.strip_newsgroup_quoting(data)
                if "header" in to_be_stripped:
                    data = self.strip_newsgroup_header(data)
                if "footer" in to_be_stripped:
                    data = self.strip_newsgroup_footer(data)
                if len(data) > noise_threshold:
                    train_data.append(data)
                    label_data.append(category[1])
    os.chdir(base_dir)
    return np.array(train_data), np.array(label_data)

这是"from Categories_Data import categories"导入的内容...
categories = [
    ('alt.atheism',0),
    ('comp.graphics',1),
    ('comp.os.ms-windows.misc',2),
    ('comp.sys.ibm.pc.hardware',3),
    ('comp.sys.mac.hardware',4),
    ('comp.windows.x',5),
    ('misc.forsale',6),
    ('rec.autos',7),
    ('rec.motorcycles',8),
    ('rec.sport.baseball',9),
    ('rec.sport.hockey',10),
    ('sci.crypt',11),
    ('sci.electronics',12),
    ('sci.med',13),
    ('sci.space',14),
    ('soc.religion.christian',15),
    ('talk.politics.guns',16),
    ('talk.politics.mideast',17),
    ('talk.politics.misc',18),
    ('talk.religion.misc',19)
 ]

2
抱歉,我认为在sklearn中轻松获取的内容去实现一遍有点浪费时间。唯一的意义可能是用于教学目的——如果你正在尝试自己编写代码或者遇到了一些语言问题无法解决。但在这些情况下,将这些大量的代码堆砌给别人,并让他们帮你调试,这样有什么意义呢?最好的结果是你又多了一个可以运行的k-fold实现,而已经有一个这样的实现了…… - Ami Tavory
1
当然,这只是为了理解我做错了什么。由于已经过去了几天,我还是无法弄清楚,所以我问是否存在明显的逻辑错误或者我不知道的scipy等问题...我不知道如何向其他人解释问题而不提供代码(特别是因为我不知道问题出在哪里)。 - Lorenzo Norcini
我编辑了问题。你可以在这里找到数据集 http://qwone.com/~jason/20Newsgroups/ 。我使用原始数据集(第一个)。谢谢。 - Lorenzo Norcini
太好了。让我们来看看 :) - Pankaj Daga
您是否也有将数据输入到CountVectorizer中的代码? - Pankaj Daga
显示剩余4条评论
1个回答

4
你的验证分数低的原因很微妙。
问题在于你如何划分数据集。记住,在进行交叉验证时,你应该随机划分数据集。你缺失的是随机性。
你的数据按类别加载,这意味着在你的输入数据集中,类标签和示例是一个接一个地排列的。如果不进行随机划分,则会完全删除一个在训练阶段中模型从未见过的类别,因此在测试/验证阶段会得到较差的结果。
你可以通过进行随机洗牌来解决这个问题。所以,请执行以下操作:
from sklearn.utils import shuffle    

processor = Data_Preprocessor()
td, tl = processor.raw_to_vector(path="C:/Users/Pankaj/Downloads/ng/")
vectorizer = CountVectorizer(stop_words='english', lowercase=True, min_df=2, analyzer="word")
data = vectorizer.fit_transform(td)
# Shuffle the data and labels
data, tl = shuffle(data, tl, random_state=0)
clfMNB = MultinomialNB(alpha=.0001)
score = Cross_Validation.Cross_Validation(clfMNB, 10, data, tl)

print("Train score" + str(score[0]))
print("Test score" + str(score[1]))

就是这样!非常感谢你! - Lorenzo Norcini
5
很高兴为您服务。我不同意这些评论认为这是毫无意义的活动。 - Pankaj Daga
1
交叉验证数据不应总是随机分割。时间序列数据有不同的分割方式,如滑动窗口。 - vipin bansal
1
@PankajDaga,浏览代码并弄清楚洗牌的事情肯定花费了很多时间。我很高兴有像你这样的人来帮忙。 :) - Tejas Anil Shah

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接