加速使用列表理解的简单Python函数

时间:2013-05-01 19:56:55

标签: python list loops python-2.7

我从导入的CSV文件中提取4列(~500MB),用于拟合scikit-learn回归模型。

似乎这个用于提取的函数非常慢。我今天刚刚学习了python,关于如何加速这个功能的任何建议?

可以使用多线程/核心吗?我的系统有4个核心。

def splitData(jobs):
    salaries = [jobs[i]['salaryNormalized'] for i, v in enumerate(jobs)]
    descriptions = [jobs[i]['description'] + jobs[i]['normalizedLocation'] + jobs[i]['category'] for i, v in enumerate(jobs)]
    titles = [jobs[i]['title'] for i, v in enumerate(jobs)]

    return salaries, descriptions, titles

打印类型(作业)

<type 'list'>

打印作业[:1]

[{'category': 'Engineering Jobs', 'salaryRaw': '20000 - 30000/annum 20-30K', 'rawLocation': 'Dorking, Surrey, Surrey', 'description': 'Engineering Systems Analyst Dorking Surrey Salary ****K Our client is located in Dorking, Surrey and are looking for Engineering Systems Analyst our client provides specialist software development Keywords Mathematical Modelling, Risk Analysis, System Modelling, Optimisation, MISER, PIONEEER Engineering Systems Analyst Dorking Surrey Salary ****K', 'title': 'Engineering Systems Analyst', 'sourceName': 'cv-library.co.uk', 'company': 'Gregory Martin International', 'contractTime': 'permanent', 'normalizedLocation': 'Dorking', 'contractType': '', 'id': '12612628', 'salaryNormalized': '25000'}]


def loadData(filePath):
    reader = csv.reader( open(filePath) )
    rows = []

    for i, row in enumerate(reader):
        categories = ["id", "title", "description", "rawLocation", "normalizedLocation",
                        "contractType", "contractTime", "company", "category",
                        "salaryRaw", "salaryNormalized","sourceName"]

        # Skip header row
        if i != 0: 
            rows.append( dict(zip(categories, row)) )

    return rows



def splitData(jobs):
    salaries = []
    descriptions = []
    titles = []

    for i in xrange(len(jobs)):
        salaries.append( jobs[i]['salaryNormalized'] )
        descriptions.append( jobs[i]['description'] + jobs[i]['normalizedLocation'] + jobs[i]['category'] )
        titles.append( jobs[i]['title'] )

    return salaries, descriptions, titles



def fit(salaries, descriptions, titles):
    #Vectorize
    vect = TfidfVectorizer()
    vect2 = TfidfVectorizer()
    descriptions = vect.fit_transform(descriptions)
    titles = vect2.fit_transform(titles)

    #Fit
    X = hstack((descriptions, titles))
    y = [ np.log(float(salaries[i])) for i, v in enumerate(salaries) ]

    rr = Ridge(alpha=0.035)
    rr.fit(X, y)

    return vect, vect2, rr, X, y



jobs = loadData( paths['train_data_path'] )
salaries, descriptions, titles = splitData(jobs)
vect, vect2, rr, X_train, y_train = fit(salaries, descriptions, titles)

4 个答案:

答案 0 :(得分:2)

我发现您的代码存在多个问题,直接影响其性能。

  1. 您多次enumerate个工作清单。您只能枚举一次,而是使用枚举列表(存储在变量中)。
  2. 您根本不使用枚举项中的值。您只需要索引,您可以使用内置的range函数轻松实现此目的。
  3. 每个列表都是以热切的方式生成的。会发生以下情况:第一个列表阻止程序的执行,需要一些时间才能完成。同样的事情发生在第二和第三个列表中,其中计算完全相同。
  4. 我将为您提供的是使用生成器,以便您以懒惰的方式处理数据。它性能更高,允许您随时随地提取数据。

    def splitData(jobs):
        for job in jobs:
            yield job['salaryNormalized'], job['description'] + job['normalizedLocation'] + job['category'], job['title']
    

答案 1 :(得分:1)

一个简单的加速就是减少列表遍历。您可以构建一个生成器或生成器表达式,它返回单个字典的元组,然后压缩生成的可迭代:

(salaries, descriptions, titles) = zip(*((j['salaryNormalized'], j['description'] + j['normalizedLocation'] + j['category'], j['title']) for j in jobs))

不幸的是,仍然会创建三个相当大的内存列表 - 使用生成器表达式而不是列表推导应该至少阻止它在压缩之前创建一个完整的三元素元组列表。

答案 2 :(得分:1)

如果我错了,请纠正我,但似乎TfidVectorizer也接受迭代器(例如生成器表达式)。这有助于防止在内存中存储这个相当大的数据的多个副本,这可能是导致它变慢的原因。或者,确保它可以直接使用文件。可以将csv转换为单独的文件,然后直接将这些文件提供给TfidVectorizer,而不会以任何方式将它们保存在内存中。

修改1

现在您提供了更多代码,我可以更具体一些。

首先,请注意loadData所做的工作超出了需要;它复制csv.DictReader中存在的功能。如果我们使用它,我们跳过类别名称列表。使用另一种打开文件的语法,因为这样它们会自动关闭。此外,一些名称被更改为更准确和Pythonic(下划线样式)。

def data_from_file(filename):
    rows = []
    with open(filename) as f:
        reader = csv.DictReader(f)
        for row in reader:
            rows.append(row)
    return rows

我们现在可以更改此内容,以便我们不会在内存中构建所有行的列表,而是在我们从文件中读取后立即返回一行。如果这看起来像魔术,只需阅读一些Python中的生成器。

def data_from_file(path):
    with open(filename) as f:
        reader = csv.DictReader(f)
        for row in reader:
            yield row

现在让我们来看看splitData。我们可以这样写得更干净:

def split_data(jobs):
    salaries = []
    descriptions = []
    titles = []

    for job in jobs:
        salaries.append(job['salaryNormalized'] )
        descriptions.append(job['description'] + job['normalizedLocation'] + 
                            job['category'])
        titles.append(job['title'])

    return salaries, descriptions, titles

但我们再次不想在内存中构建三个巨大的列表。一般来说,这个功能给我们三个不同的东西是不切实际的。所以要把它分开:

def extract_salaries(jobs):
    for job in jobs:
        yield job['salaryNormalized']

等等。这有助于我们建立某种处理流程;每次我们从extract_salaries(data_from_file(filename))请求一个值时,将读取csv的一行并提取salary。下一次,第二行回馈第二行salary。没有必要为这个简单的案例制作函数。相反,您可以使用生成器表达式:

salaries = (job['salaryNormalized'] for job in data_from_file(filename))
descriptions = (job['description'] + job['normalizedLocation'] +
                job['category'] for job in data_from_file(filename))
titles = (job['title'] for job in data_from_file(filename))

您现在可以将这些生成器传递给fit,其中最重要的修改是:

y = [np.log(float(salary)) for salary in salaries]

你不能索引到迭代器(一次只能给你一个值),所以你假设只要还有更多的salary就会得到salaries,并且有它的东西。

最后,您将多次读取整个csv文件,但我不认为这是瓶颈。否则,需要进行一些重组。

修改2

使用DictReader似乎有点慢。不知道为什么,但你可能会坚持使用自己的实现(修改为生成器),甚至更好,与namedtuple s一起使用:

def data_from_file(filename):
    with open(filename) as f:
        reader = csv.reader(f)
        header = reader.next()
        Job = namedtuple('Job', header)
        for row in reader:
            yield Job(*row)

然后使用点(job.salaryNormalized)访问属性。但无论如何请注意,您可以从文件中获取列名列表;不要在代码中复制它。

您当然可以决定将文件的单个副本保留在内存中。在这种情况下,做这样的事情:

data = list(data_from_file(filename))
salaries = (job['salaryNormalized'] for job in data)

功能保持不变。对list的调用会占用整个生成器并将所有值存储在list

答案 3 :(得分:0)

根本不需要索引。只需使用in即可。这节省了额外的元组列表的创建,并且它删除了一个间接级别;

salaries = [j['salaryNormalized'] for j in jobs]
descriptions = [j['description'] + j['normalizedLocation'] + j['category'] for j in jobs]
titles = [j['title'] for j in jobs]

这仍然会对数据进行三次迭代。

或者你可以在列表中理解所有内容,将一个作业中的相关数据分组在一个元组中;

data = [(j['salaryNormalized'], 
         j['description'] + j['normalizedLocation'] + j['category'],
         j['title']) for j in jobs]

为最后保存最好的;为什么不直接从CSV文件填写列表而不是先写一个字典?

import csv

with open('data.csv', 'r') as df:
    reader = csv.reader(df)
    # I made up the row indices...
    data = [(row[1], row[3]+row[7]+row[6], row[2]) for row in reader]